clay-server 5.2.0-beta.8 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/lib/clay-product-control-tools.js +1 -0
- package/lib/clay-product-control.js +14 -11
- package/lib/clay-vendor-usage.js +44 -0
- package/lib/github-star-count.js +147 -0
- package/lib/notes-checklist.js +43 -1
- package/lib/notes.js +2 -1
- package/lib/pane-pending-interactions.js +20 -0
- package/lib/pane-session-activation.js +114 -0
- package/lib/pane-session-transport.js +26 -0
- package/lib/project-session-notes.js +33 -8
- package/lib/project-sessions.js +78 -29
- package/lib/project-start-protocol.js +29 -0
- package/lib/project-vendor-login.js +2 -0
- package/lib/project.js +16 -4
- package/lib/public/app.js +41 -18
- package/lib/public/css/contextual-clay.css +2 -2
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/input.css +6 -0
- package/lib/public/css/mate-loading-scene.css +21 -0
- package/lib/public/css/overlays.css +64 -256
- package/lib/public/css/project-mate-creation.css +5 -6
- package/lib/public/css/project-start.css +108 -0
- package/lib/public/css/projects-hub.css +53 -14
- package/lib/public/css/settings-shell.css +103 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +37 -11
- package/lib/public/css/title-bar.css +83 -0
- package/lib/public/index.html +50 -63
- package/lib/public/modules/account-transport-settings.js +8 -2
- package/lib/public/modules/admin-settings-email.js +7 -2
- package/lib/public/modules/admin-settings-invitations.js +2 -2
- package/lib/public/modules/admin-settings-projects.js +2 -2
- package/lib/public/modules/admin-settings-ui.js +20 -4
- package/lib/public/modules/admin-settings-users.js +4 -4
- package/lib/public/modules/app-connection.js +3 -0
- package/lib/public/modules/app-home-hub.js +2 -1
- package/lib/public/modules/app-message-router.js +13 -0
- package/lib/public/modules/app-messages.js +35 -7
- package/lib/public/modules/app-misc.js +3 -1
- package/lib/public/modules/app-projects.js +6 -407
- package/lib/public/modules/app-rate-limit.js +12 -1
- package/lib/public/modules/clay-owner-navigation.js +0 -3
- package/lib/public/modules/clay-studio-render.js +3 -1
- package/lib/public/modules/clay-tool-presentation.js +1 -0
- package/lib/public/modules/command-palette.js +52 -14
- package/lib/public/modules/contextual-clay-workspace.js +14 -2
- package/lib/public/modules/contextual-clay.js +19 -4
- package/lib/public/modules/contextual-mate-context.js +6 -4
- package/lib/public/modules/elicitation-form.js +5 -0
- package/lib/public/modules/github-star.js +66 -0
- package/lib/public/modules/history-replay-batch.js +8 -0
- package/lib/public/modules/home-surface-boot.js +0 -1
- package/lib/public/modules/input.js +55 -11
- package/lib/public/modules/mate-loading-scene.js +17 -0
- package/lib/public/modules/message-delivery.js +10 -2
- package/lib/public/modules/model-picker.js +2 -0
- package/lib/public/modules/pane-activation.js +214 -0
- package/lib/public/modules/pane-appearance-bridge.js +59 -0
- package/lib/public/modules/pane-binding.js +82 -0
- package/lib/public/modules/pane-bridge.js +5 -2
- package/lib/public/modules/pane-browser-bridge.js +5 -3
- package/lib/public/modules/pane-file-bridge.js +4 -0
- package/lib/public/modules/pane-issue-bridge.js +4 -1
- package/lib/public/modules/profile.js +11 -3
- package/lib/public/modules/project-mate-creation-workspace.js +13 -4
- package/lib/public/modules/project-session-visibility-settings.js +19 -16
- package/lib/public/modules/project-settings-environment.js +12 -1
- package/lib/public/modules/project-settings.js +119 -129
- package/lib/public/modules/project-start-dialog.js +130 -0
- package/lib/public/modules/project-start-folder-state.js +78 -0
- package/lib/public/modules/project-start-folder-view.js +190 -0
- package/lib/public/modules/project-start-forms.js +66 -0
- package/lib/public/modules/project-start-path.js +60 -0
- package/lib/public/modules/project-start-state.js +54 -0
- package/lib/public/modules/project-start-submit.js +67 -0
- package/lib/public/modules/projects-hub-view.js +32 -44
- package/lib/public/modules/projects-hub.js +39 -28
- package/lib/public/modules/rewind.js +5 -0
- package/lib/public/modules/server-settings.js +36 -91
- package/lib/public/modules/session-actions.js +5 -2
- package/lib/public/modules/session-context-menu.js +5 -0
- package/lib/public/modules/session-rich-drafts.js +21 -0
- package/lib/public/modules/session-switch-defer.js +67 -0
- package/lib/public/modules/settings-project-target.js +56 -0
- package/lib/public/modules/settings-select.js +1 -1
- package/lib/public/modules/settings-shell-registry.js +88 -0
- package/lib/public/modules/settings-shell-view.js +104 -0
- package/lib/public/modules/settings-shell.js +342 -0
- package/lib/public/modules/sidebar-mobile.js +8 -30
- package/lib/public/modules/sidebar-projects.js +2 -1
- package/lib/public/modules/split-group-restore.js +68 -0
- package/lib/public/modules/split-pair-ui.js +1 -0
- package/lib/public/modules/split-pane-reconciler.js +15 -0
- package/lib/public/modules/split-pane-renderer.js +87 -6
- package/lib/public/modules/split-session-boundary.js +1 -1
- package/lib/public/modules/split-view.js +51 -52
- package/lib/public/modules/sticky-notes-checklist.js +51 -6
- package/lib/public/modules/stt-controller.js +4 -1
- package/lib/public/modules/stt-recording.js +1 -1
- package/lib/public/modules/theme.js +31 -3
- package/lib/public/modules/tools.js +18 -2
- package/lib/public/modules/user-settings-integrations.js +2 -1
- package/lib/public/modules/user-settings-layout.js +12 -34
- package/lib/public/modules/user-settings-profile.js +1 -1
- package/lib/public/modules/user-settings.js +18 -85
- package/lib/public/modules/vendor-usage-format.js +93 -0
- package/lib/public/modules/vendor-usage-list.js +45 -0
- package/lib/public/modules/vendor-usage-model.js +215 -0
- package/lib/public/modules/vendor-usage-overview.js +323 -0
- package/lib/public/style.css +10 -7
- package/lib/public/welcome-art/conversation-desk.png +0 -0
- package/lib/public/welcome-art/mate-clay.png +0 -0
- package/lib/public/welcome-art/project-clay.png +0 -0
- package/lib/sdk-bridge.js +6 -1
- package/lib/sdk-message-processor.js +9 -1
- package/lib/sdk-quota-snapshot.js +20 -2
- package/lib/server-github-stars.js +39 -0
- package/lib/server-global-ws.js +21 -17
- package/lib/server.js +11 -0
- package/lib/session-notes-mcp-server.js +26 -4
- package/lib/session-notes-writes.js +88 -0
- package/lib/session-visibility.js +20 -8
- package/lib/sessions.js +27 -10
- package/lib/vendor-usage-acp-readers.js +97 -0
- package/lib/vendor-usage-capabilities.js +88 -0
- package/lib/vendor-usage-http-readers.js +214 -0
- package/lib/vendor-usage-metrics.js +47 -0
- package/lib/vendor-usage-native-readers.js +106 -0
- package/lib/vendor-usage-overview.js +406 -0
- package/lib/vendor-usage-service.js +281 -0
- package/lib/ws-schema.js +4 -2
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex-query-contract.js +1 -0
- package/lib/yoke/adapters/codex.js +45 -12
- package/lib/yoke/claude-hook-installer.js +1 -0
- package/lib/yoke/codex-dynamic-tool-names.js +80 -0
- package/lib/yoke/codex-model-catalog.js +37 -3
- package/lib/yoke/muse-services.js +16 -3
- package/lib/yoke/providers/claude-model-descriptions.js +22 -2
- package/lib/yoke/vendor-registry.js +6 -2
- package/package.json +5 -3
- package/lib/public/modules/server-settings-navigation.js +0 -39
|
@@ -8,6 +8,7 @@ import { setRewindMode, isRewindMode } from './rewind.js';
|
|
|
8
8
|
import { renderPicker as renderContextPicker } from './context-sources.js';
|
|
9
9
|
import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible, mentionMenuKeydown, setMentionAtIdx, parseMentionFromInput, clearMentionState, stickyReapplyMention, sendMention, sendUserMention, renderMentionUser, renderUserMention, removeMentionChip } from './mention.js';
|
|
10
10
|
import { store } from './store.js';
|
|
11
|
+
import { sessionCallback, paneMessageBinding, samePaneBinding } from './pane-binding.js';
|
|
11
12
|
import { sendAcknowledgedMessage } from './message-delivery.js';
|
|
12
13
|
import { mateAvatarUrl } from './avatar.js';
|
|
13
14
|
import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
|
|
@@ -122,6 +123,9 @@ function showPreThinkingForTurn() {
|
|
|
122
123
|
|
|
123
124
|
// --- Send ---
|
|
124
125
|
export function sendMessage() {
|
|
126
|
+
if (store.get('deferredSessionSwitch') || store.get('panePendingBinding') || store.get('paneSuspended')) return;
|
|
127
|
+
if (store.get('paneCommittedBinding') && !store.get('paneReady')) return;
|
|
128
|
+
var actionBinding = paneMessageBinding();
|
|
125
129
|
if (store.get('contextualWriterSuspended')) return;
|
|
126
130
|
if (ctx && requestRecordedSend(ctx.inputEl, sendMessage)) return;
|
|
127
131
|
stopSTT();
|
|
@@ -346,6 +350,7 @@ export function sendMessage() {
|
|
|
346
350
|
store.set({ pendingScheduleAdmissions: admissions, pendingScheduleAdmission: admissions[scheduleRequestId] });
|
|
347
351
|
ctx.ws.send(JSON.stringify({
|
|
348
352
|
type: "schedule_message", requestId: scheduleRequestId,
|
|
353
|
+
paneBinding: actionBinding || undefined,
|
|
349
354
|
projectSlug: scheduleContext.projectSlug, sessionId: scheduleContext.sessionId, accountId: scheduleContext.accountId,
|
|
350
355
|
text: text || "", images: images, pastes: pastes, resetsAt: resetsAt,
|
|
351
356
|
}));
|
|
@@ -354,6 +359,7 @@ export function sendMessage() {
|
|
|
354
359
|
|
|
355
360
|
var screenReference = contextualScreenReference();
|
|
356
361
|
var payload = { type: "message", text: text || "" };
|
|
362
|
+
if (actionBinding) payload.paneBinding = actionBinding;
|
|
357
363
|
if (screenReference) payload.contextualSnapshot = screenReference;
|
|
358
364
|
if (images.length > 0) {
|
|
359
365
|
payload.images = images;
|
|
@@ -440,6 +446,7 @@ export function sendTextMessage(text) {
|
|
|
440
446
|
export function sendShellResultToAgent(msg) {
|
|
441
447
|
if (!ctx || !ctx.connected || !msg || msg.error || !msg.command) return false;
|
|
442
448
|
if (String(msg.sessionId) !== String(store.get("activeSessionId"))) return false;
|
|
449
|
+
if (store.get('paneCommittedBinding') && !samePaneBinding(msg.paneActivation, store.get('paneCommittedBinding'))) return false;
|
|
443
450
|
var payload = {
|
|
444
451
|
type: "message",
|
|
445
452
|
text: "$ " + msg.command,
|
|
@@ -447,7 +454,8 @@ export function sendShellResultToAgent(msg) {
|
|
|
447
454
|
};
|
|
448
455
|
var selectedVendor = store.get("currentVendor") || null;
|
|
449
456
|
if (selectedVendor) payload.vendor = selectedVendor;
|
|
450
|
-
|
|
457
|
+
if (msg.paneActivation) payload.paneBinding = msg.paneActivation;
|
|
458
|
+
if (ctx.ws.send(JSON.stringify(payload)) === false) return false;
|
|
451
459
|
commitVendorForTurn();
|
|
452
460
|
showPreThinkingForTurn();
|
|
453
461
|
return true;
|
|
@@ -543,9 +551,17 @@ function removePendingImage(idx) {
|
|
|
543
551
|
}
|
|
544
552
|
|
|
545
553
|
// Move composer ownership between two authenticated views of the same session.
|
|
554
|
+
export function composerDepartureReason() {
|
|
555
|
+
if (uploadingCount) return 'Wait for attachments to finish uploading before changing views.';
|
|
556
|
+
if (store.get('speechActive') || ctx && hasRecordedSpeechFor(ctx.inputEl)) return 'Finish or discard the voice recording before changing views.';
|
|
557
|
+
var retry = store.get('speechRetry');
|
|
558
|
+
if (retry && ctx && retry.input === ctx.inputEl) return 'Retry or discard the voice recording before changing views.';
|
|
559
|
+
return '';
|
|
560
|
+
}
|
|
546
561
|
export function takeComposerDraft() {
|
|
547
|
-
|
|
548
|
-
if (
|
|
562
|
+
var reason = composerDepartureReason();
|
|
563
|
+
if (reason) throw new Error(reason);
|
|
564
|
+
if (document.dispatchEvent) document.dispatchEvent(new CustomEvent('clay-composer-handoff'));
|
|
549
565
|
var input = ctx.inputEl;
|
|
550
566
|
var draft = { text: input.value, start: input.selectionStart, end: input.selectionEnd, direction: input.selectionDirection, images: pendingImages, pastes: pendingPastes, files: pendingFiles, delay: scheduleDelayMs };
|
|
551
567
|
input.value = '';
|
|
@@ -585,14 +601,14 @@ function pastePreview(text) {
|
|
|
585
601
|
}
|
|
586
602
|
|
|
587
603
|
function editPendingPaste(idx) {
|
|
588
|
-
showPasteModal(pendingPastes[idx].text, function (text) {
|
|
604
|
+
showPasteModal(pendingPastes[idx].text, sessionCallback(function (text) {
|
|
589
605
|
if (!text) {
|
|
590
606
|
removePendingPaste(idx);
|
|
591
607
|
return;
|
|
592
608
|
}
|
|
593
609
|
pendingPastes[idx] = { text: text, preview: pastePreview(text) };
|
|
594
610
|
renderInputPreviews();
|
|
595
|
-
});
|
|
611
|
+
}));
|
|
596
612
|
}
|
|
597
613
|
|
|
598
614
|
function removePendingFile(idx) {
|
|
@@ -726,6 +742,7 @@ function uploadFile(file) {
|
|
|
726
742
|
return;
|
|
727
743
|
}
|
|
728
744
|
uploadingCount++;
|
|
745
|
+
store.set({ composerUploadingCount: uploadingCount });
|
|
729
746
|
renderInputPreviews();
|
|
730
747
|
var reader = new FileReader();
|
|
731
748
|
reader.onload = function (ev) {
|
|
@@ -738,12 +755,14 @@ function uploadFile(file) {
|
|
|
738
755
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
739
756
|
xhr.onload = function () {
|
|
740
757
|
uploadingCount--;
|
|
758
|
+
store.set({ composerUploadingCount: uploadingCount });
|
|
741
759
|
if (xhr.status === 200) {
|
|
742
760
|
try {
|
|
743
761
|
var resp = JSON.parse(xhr.responseText);
|
|
744
762
|
pendingFiles.push({ name: resp.name || uploadName, path: resp.path });
|
|
745
|
-
} catch (e) {}
|
|
763
|
+
} catch (e) { showToast('Upload response could not be read: ' + uploadName + '. Your draft is preserved.', 'error'); }
|
|
746
764
|
} else {
|
|
765
|
+
showToast('Upload failed: ' + uploadName + '. Your draft is preserved.', 'error');
|
|
747
766
|
if (ctx.addSystemMessage) ctx.addSystemMessage("Upload failed: " + uploadName, true);
|
|
748
767
|
}
|
|
749
768
|
renderInputPreviews();
|
|
@@ -753,6 +772,8 @@ function uploadFile(file) {
|
|
|
753
772
|
};
|
|
754
773
|
xhr.onerror = function () {
|
|
755
774
|
uploadingCount--;
|
|
775
|
+
store.set({ composerUploadingCount: uploadingCount });
|
|
776
|
+
showToast('Upload failed: ' + uploadName + '. Your draft is preserved.', 'error');
|
|
756
777
|
if (ctx.addSystemMessage) ctx.addSystemMessage("Upload failed: " + uploadName, true);
|
|
757
778
|
renderInputPreviews();
|
|
758
779
|
if (ctx.processing && ctx.setSendBtnMode) {
|
|
@@ -761,6 +782,7 @@ function uploadFile(file) {
|
|
|
761
782
|
};
|
|
762
783
|
xhr.send(JSON.stringify({ name: uploadName, data: b64 }));
|
|
763
784
|
};
|
|
785
|
+
reader.onerror = function () { uploadingCount--; store.set({ composerUploadingCount: uploadingCount }); renderInputPreviews(); showToast('Attachment could not be read. Your draft is preserved.', 'error'); };
|
|
764
786
|
reader.readAsDataURL(file);
|
|
765
787
|
}
|
|
766
788
|
|
|
@@ -772,7 +794,14 @@ function readImageBlob(blob) {
|
|
|
772
794
|
uploadFile(blob);
|
|
773
795
|
return;
|
|
774
796
|
}
|
|
797
|
+
uploadingCount++; store.set({ composerUploadingCount: uploadingCount });
|
|
798
|
+
var completed = false;
|
|
799
|
+
function finish() {
|
|
800
|
+
if (completed) return; completed = true; uploadingCount--;
|
|
801
|
+
store.set({ composerUploadingCount: uploadingCount }); renderInputPreviews();
|
|
802
|
+
}
|
|
775
803
|
var reader = new FileReader();
|
|
804
|
+
reader.onerror = function () { finish(); showToast('Image could not be read. Your draft is preserved.', 'error'); };
|
|
776
805
|
reader.onload = function (ev) {
|
|
777
806
|
var dataUrl = ev.target.result;
|
|
778
807
|
// Check base64 payload size (~3/4 of base64 length)
|
|
@@ -781,7 +810,7 @@ function readImageBlob(blob) {
|
|
|
781
810
|
var estimatedBytes = b64.length * 0.75;
|
|
782
811
|
|
|
783
812
|
if (estimatedBytes <= MAX_IMAGE_BYTES) {
|
|
784
|
-
addPendingImage(dataUrl);
|
|
813
|
+
addPendingImage(dataUrl); finish();
|
|
785
814
|
return;
|
|
786
815
|
}
|
|
787
816
|
|
|
@@ -799,8 +828,9 @@ function readImageBlob(blob) {
|
|
|
799
828
|
var cx = canvas.getContext("2d");
|
|
800
829
|
cx.drawImage(img, 0, 0, nw, nh);
|
|
801
830
|
var resized = canvas.toDataURL("image/jpeg", RESIZE_QUALITY);
|
|
802
|
-
addPendingImage(resized);
|
|
831
|
+
addPendingImage(resized); finish();
|
|
803
832
|
};
|
|
833
|
+
img.onerror = function () { finish(); showToast('Image could not be loaded. Your draft is preserved.', 'error'); };
|
|
804
834
|
img.src = dataUrl;
|
|
805
835
|
};
|
|
806
836
|
reader.readAsDataURL(blob);
|
|
@@ -869,7 +899,9 @@ function sendInputSync() {
|
|
|
869
899
|
if (dk) ctx.ws.send(JSON.stringify({ type: "dm_typing", dmKey: dk, typing: hasText }));
|
|
870
900
|
return;
|
|
871
901
|
}
|
|
872
|
-
|
|
902
|
+
var speech = store.get('speechActive');
|
|
903
|
+
ctx.ws.send(JSON.stringify({ type: "input_sync", text: ctx.inputEl.value,
|
|
904
|
+
paneBinding: speech && speech.writing ? speech.paneBinding || undefined : undefined }));
|
|
873
905
|
}
|
|
874
906
|
|
|
875
907
|
export function handleInputSync(text) {
|
|
@@ -893,7 +925,7 @@ function createFileInput(accept, capture, multiple) {
|
|
|
893
925
|
input.style.display = "none";
|
|
894
926
|
document.body.appendChild(input);
|
|
895
927
|
|
|
896
|
-
|
|
928
|
+
var selectFiles = sessionCallback(function () {
|
|
897
929
|
if (input.files) {
|
|
898
930
|
for (var i = 0; i < input.files.length; i++) {
|
|
899
931
|
if (input.files[i].type.indexOf("image/") === 0) {
|
|
@@ -903,6 +935,9 @@ function createFileInput(accept, capture, multiple) {
|
|
|
903
935
|
}
|
|
904
936
|
}
|
|
905
937
|
}
|
|
938
|
+
});
|
|
939
|
+
input.addEventListener("change", function () {
|
|
940
|
+
selectFiles();
|
|
906
941
|
document.body.removeChild(input);
|
|
907
942
|
});
|
|
908
943
|
|
|
@@ -991,6 +1026,14 @@ export function initInput(_ctx) {
|
|
|
991
1026
|
}
|
|
992
1027
|
}
|
|
993
1028
|
|
|
1029
|
+
document.addEventListener('clay-composer-handoff', function () {
|
|
1030
|
+
if (scheduleInlineInput) {
|
|
1031
|
+
var minutes = parseInt(scheduleInlineInput.value, 10);
|
|
1032
|
+
if (minutes >= 1 && minutes <= 1440) setScheduleDelayMs(minutes * 60000);
|
|
1033
|
+
}
|
|
1034
|
+
collapseScheduleBtn();
|
|
1035
|
+
});
|
|
1036
|
+
|
|
994
1037
|
function setScheduleDelay(ms) {
|
|
995
1038
|
scheduleDelayMs = ms;
|
|
996
1039
|
if (!scheduleBtn) return;
|
|
@@ -1020,7 +1063,7 @@ export function initInput(_ctx) {
|
|
|
1020
1063
|
scheduleInlineInput.className = "schedule-inline-input";
|
|
1021
1064
|
scheduleBtn.appendChild(scheduleInlineInput);
|
|
1022
1065
|
|
|
1023
|
-
setTimeout(function () { scheduleInlineInput.focus(); }, 0);
|
|
1066
|
+
setTimeout(sessionCallback(function () { if (scheduleInlineInput) scheduleInlineInput.focus(); }), 0);
|
|
1024
1067
|
|
|
1025
1068
|
scheduleInlineInput.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
1026
1069
|
scheduleInlineInput.addEventListener("keydown", function (e) {
|
|
@@ -1039,6 +1082,7 @@ export function initInput(_ctx) {
|
|
|
1039
1082
|
|
|
1040
1083
|
// Close on outside click
|
|
1041
1084
|
setTimeout(function () {
|
|
1085
|
+
if (!scheduleInlineInput) return;
|
|
1042
1086
|
scheduleOutsideHandler = function (ev) {
|
|
1043
1087
|
if (!scheduleBtn.contains(ev.target)) {
|
|
1044
1088
|
if (scheduleInlineInput) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Decorative opening state; conversation readiness remains with its host.
|
|
2
|
+
export function mateLoadingMarkup() {
|
|
3
|
+
return '<div data-clay-loading class="mate-loading" role="status" aria-live="polite">' +
|
|
4
|
+
'<div class="mate-loading-scene" aria-hidden="true">' +
|
|
5
|
+
'<img class="mate-loading-avatar" alt="">' +
|
|
6
|
+
'<img class="mate-loading-desk" src="/welcome-art/conversation-desk.png" alt="" decoding="async">' +
|
|
7
|
+
'</div><p class="mate-loading-label"></p>' +
|
|
8
|
+
'<div class="mate-loading-dots" aria-hidden="true"><span></span><span></span><span></span></div></div>';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function updateMateLoadingScene(element, name, avatar) {
|
|
12
|
+
var image = element.querySelector('.mate-loading-avatar');
|
|
13
|
+
if (image.getAttribute('src') !== avatar) image.setAttribute('src', avatar);
|
|
14
|
+
var label = element.querySelector('.mate-loading-label');
|
|
15
|
+
var text = 'Getting ready with ' + name;
|
|
16
|
+
if (label.textContent !== text) label.textContent = text;
|
|
17
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { store } from './store.js';
|
|
4
4
|
import { getWs } from './ws-ref.js';
|
|
5
|
+
import { paneMessageBinding } from './pane-binding.js';
|
|
5
6
|
|
|
6
7
|
var ACK_TIMEOUT_MS = 5000;
|
|
7
8
|
var ACK_RETRY_LIMIT = 3;
|
|
@@ -152,7 +153,7 @@ function transmit(entry, automatic) {
|
|
|
152
153
|
}
|
|
153
154
|
socketRefs[entry.payload.clientMessageId] = socket;
|
|
154
155
|
try {
|
|
155
|
-
socket.send(JSON.stringify(entry.payload));
|
|
156
|
+
if (socket.send(JSON.stringify(entry.payload)) === false) { markUnconfirmed(entry); return false; }
|
|
156
157
|
armAckTimer(entry.payload.clientMessageId, socket);
|
|
157
158
|
return true;
|
|
158
159
|
} catch (e) {
|
|
@@ -163,6 +164,8 @@ function transmit(entry, automatic) {
|
|
|
163
164
|
|
|
164
165
|
export function sendAcknowledgedMessage(payload) {
|
|
165
166
|
var nextPayload = Object.assign({}, payload, { clientMessageId: createClientMessageId() });
|
|
167
|
+
var binding = paneMessageBinding();
|
|
168
|
+
if (binding && !nextPayload.paneBinding) nextPayload.paneBinding = binding;
|
|
166
169
|
var entry = {
|
|
167
170
|
projectSlug: store.get('currentSlug'),
|
|
168
171
|
sessionId: store.get('activeSessionId'),
|
|
@@ -193,7 +196,12 @@ export function sendAcknowledgedMessage(payload) {
|
|
|
193
196
|
export function retryPendingMessage(clientMessageId) {
|
|
194
197
|
var entry = pendingEntry(clientMessageId);
|
|
195
198
|
if (!matchesActivatedContext(entry) || hasKnownCliMismatch(entry)) return false;
|
|
196
|
-
|
|
199
|
+
// An explicit Retry in the same durable conversation may use its new live
|
|
200
|
+
// binding. Automatic retry retains the original action binding and refuses.
|
|
201
|
+
var payload = Object.assign({}, entry.payload);
|
|
202
|
+
var binding = paneMessageBinding();
|
|
203
|
+
if (binding) payload.paneBinding = binding;
|
|
204
|
+
var retryEntry = Object.assign({}, entry, { payload: payload, attempts: 0, status: "pending" });
|
|
197
205
|
replacePending(pendingMessages().map(function (candidate) {
|
|
198
206
|
return candidate.payload.clientMessageId === clientMessageId ? retryEntry : candidate;
|
|
199
207
|
}));
|
|
@@ -50,6 +50,8 @@ export function setupModelPicker() {
|
|
|
50
50
|
|
|
51
51
|
export function resetModelPickerState() {
|
|
52
52
|
clearRequestTimer();
|
|
53
|
+
if (configPopover) configPopover.classList.add('hidden');
|
|
54
|
+
if (configChip) configChip.classList.remove('active');
|
|
53
55
|
store.set({
|
|
54
56
|
modelListVendor: "",
|
|
55
57
|
modelListStatus: "idle",
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
4
|
+
import { takeComposerDraft, restoreComposerDraft } from './input.js';
|
|
5
|
+
import { resetHistoryReplayBatch } from './history-replay-batch.js';
|
|
6
|
+
import { updateContextPanel } from './app-panels.js';
|
|
7
|
+
import { paneTarget, samePaneBinding, samePaneTarget } from './pane-binding.js';
|
|
8
|
+
|
|
9
|
+
function enabled() {
|
|
10
|
+
return store.get('paneMode') && !store.get('paneActivationUnsupported') && !store.get('activeProjectMateId') && !/^\/p\/mate-/.test(location.pathname) && !new URLSearchParams(location.search).has('contextual') && window.parent !== window;
|
|
11
|
+
}
|
|
12
|
+
function block(value) {
|
|
13
|
+
var app = document.getElementById('app');
|
|
14
|
+
if (app) app.inert = value;
|
|
15
|
+
document.documentElement.dataset.paneReady = value ? 'false' : 'true';
|
|
16
|
+
}
|
|
17
|
+
function draftKey() { return String(store.get('myUserId')) + ':' + store.get('sessionOriginId'); }
|
|
18
|
+
function clearDeadline() {
|
|
19
|
+
if (store.get('paneActivationTimer')) clearTimeout(store.get('paneActivationTimer'));
|
|
20
|
+
store.set({ paneActivationTimer: null });
|
|
21
|
+
}
|
|
22
|
+
function failed(requestId) {
|
|
23
|
+
var pending = store.get('panePendingBinding');
|
|
24
|
+
if (!pending || requestId !== pending.requestId) return;
|
|
25
|
+
clearDeadline();
|
|
26
|
+
store.set({ paneReady: false, panePendingBinding: null });
|
|
27
|
+
block(true);
|
|
28
|
+
var frame = window.frameElement;
|
|
29
|
+
if (frame) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-activation-failed'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Claim ordinary initial pins before the legacy session-list handler runs.
|
|
33
|
+
// Authentication, list and group metadata can arrive in either order; the
|
|
34
|
+
// subscription retries when those authoritative prerequisites become ready.
|
|
35
|
+
export function primePaneSession() {
|
|
36
|
+
if (!enabled() || !window.frameElement || !window.frameElement.clayPane) return false;
|
|
37
|
+
var id = store.get('paneSessionId');
|
|
38
|
+
var session = (getCachedSessions() || []).find(function (item) { return item.id === id; });
|
|
39
|
+
if (!session || session.isMate || (session.runtimeMode || session.mode || 'gui') !== 'gui' || session.tuiSuspended) return false;
|
|
40
|
+
activatePaneSession(id);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function activatePaneSession(id, force, expectedTarget) {
|
|
45
|
+
var state = store.snap(), ws = getWs();
|
|
46
|
+
if (state.authIdentityLoaded === false) return false;
|
|
47
|
+
if (state.isMultiUserMode && !state.myUserId) return false;
|
|
48
|
+
if (!enabled() || !state.connected || !ws || ws.readyState !== 1 || state.splitGroupsProjectSlug !== state.currentSlug) return false;
|
|
49
|
+
var target = paneTarget(state, getCachedSessions(), id);
|
|
50
|
+
if (!target) return false;
|
|
51
|
+
if (expectedTarget && !samePaneTarget(target, expectedTarget)) return false;
|
|
52
|
+
var sourceFrame = window.frameElement;
|
|
53
|
+
if (!expectedTarget && sourceFrame && Number(sourceFrame.dataset && sourceFrame.dataset.sessionId) === id && sourceFrame.dataset.sessionOriginId && sourceFrame.dataset.sessionOriginId !== target.sessionOriginId) return false;
|
|
54
|
+
var old = state.paneCommittedBinding, pending = state.panePendingBinding;
|
|
55
|
+
if (!force && !state.paneSuspended && samePaneTarget(pending || old, target)) return true;
|
|
56
|
+
if (!pending && state.sessionOriginId) {
|
|
57
|
+
var draft;
|
|
58
|
+
try { draft = takeComposerDraft(); } catch (error) { return false; }
|
|
59
|
+
var drafts = Object.assign({}, state.paneRichDrafts || {});
|
|
60
|
+
drafts[draftKey()] = draft;
|
|
61
|
+
store.set({ paneRichDrafts: drafts });
|
|
62
|
+
}
|
|
63
|
+
clearDeadline();
|
|
64
|
+
resetHistoryReplayBatch();
|
|
65
|
+
var generation = (store.get('paneActivationGeneration') || 0) + 1;
|
|
66
|
+
target = Object.assign({}, target, { generation: generation, requestId: 'pane-' + generation + '-' + Math.random().toString(36).slice(2, 12) });
|
|
67
|
+
var path = new URL(store.get('wsPath'), location.origin);
|
|
68
|
+
path.searchParams.set('session', String(id));
|
|
69
|
+
store.set({ paneActivationGeneration: generation, panePendingBinding: target, paneReady: false, paneSuspended: false,
|
|
70
|
+
paneSessionId: id, panePinPending: false, wsPath: path.pathname + path.search });
|
|
71
|
+
block(true);
|
|
72
|
+
store.set({ paneActivationTimer: setTimeout(function () { failed(target.requestId); }, 12000) });
|
|
73
|
+
ws.send(JSON.stringify({ type: 'switch_session', id: id, paneActivation: target }));
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Runs before replay batching, so an obsolete history_meta cannot open a batch
|
|
78
|
+
// that later consumes destination messages.
|
|
79
|
+
export function filterPaneActivation(message) {
|
|
80
|
+
if (!enabled()) return false;
|
|
81
|
+
var pending = store.get('panePendingBinding'), committed = store.get('paneCommittedBinding');
|
|
82
|
+
if (message.type === 'pane_activation_rejected') { failed(message.requestId); return true; }
|
|
83
|
+
if (!pending && !committed) return !!message.paneActivation;
|
|
84
|
+
if (['session_list', 'split_groups', 'session_presence', 'pong', 'account_auth_required', 'project_removed', 'project_access_revoked'].indexOf(message.type) >= 0 && !message.paneActivation) return false;
|
|
85
|
+
var expected = pending || committed;
|
|
86
|
+
if (!samePaneBinding(expected, message.paneActivation)) return true;
|
|
87
|
+
if (message.type === 'pane_activation_started') return true;
|
|
88
|
+
if (message.type === 'pane_activation_snapshot_done') {
|
|
89
|
+
if (!pending) return true;
|
|
90
|
+
var draft = (store.get('paneRichDrafts') || {})[String(store.get('myUserId')) + ':' + expected.sessionOriginId];
|
|
91
|
+
restoreComposerDraft(draft || { text: '', start: 0, end: 0 });
|
|
92
|
+
function paint() {
|
|
93
|
+
if (store.get('panePendingBinding') !== expected || !store.get('connected') || store.get('paneSuspended')) return;
|
|
94
|
+
var current = paneTarget(store.snap(), getCachedSessions(), expected.sessionId);
|
|
95
|
+
var messages = document.getElementById('messages');
|
|
96
|
+
if (!samePaneTarget(current, expected) ||
|
|
97
|
+
store.get('activeSessionId') !== expected.sessionId || store.get('sessionOriginId') !== expected.sessionOriginId || store.get('replayingHistory') ||
|
|
98
|
+
!messages || messages.dataset.controlSessionId !== String(expected.sessionId) || messages.dataset.controlSessionOrigin !== expected.sessionOriginId) { failed(expected.requestId); return; }
|
|
99
|
+
clearDeadline();
|
|
100
|
+
store.set({ paneCommittedBinding: expected, panePendingBinding: null, paneReady: true });
|
|
101
|
+
block(false);
|
|
102
|
+
var frame = window.frameElement;
|
|
103
|
+
if (frame) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-ready'));
|
|
104
|
+
updateContextPanel();
|
|
105
|
+
}
|
|
106
|
+
requestAnimationFrame(function () { requestAnimationFrame(paint); });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function paneOutgoing(data) {
|
|
113
|
+
var message;
|
|
114
|
+
try { message = JSON.parse(data); } catch (error) { return null; }
|
|
115
|
+
if (['ping', 'pong', 'clay_ui_ready'].indexOf(message.type) >= 0) return data;
|
|
116
|
+
if (store.get('deferredSessionSwitch') && store.get('deferredSessionSwitch').draining) return null;
|
|
117
|
+
if (message.type === 'switch_session') {
|
|
118
|
+
var entry = store.get('pendingSessionEntryNavigation');
|
|
119
|
+
if (entry && entry.projectSlug === store.get('currentSlug') && String(entry.sessionId) !== String(message.id)) store.set({ pendingSessionEntryNavigation: null });
|
|
120
|
+
if (enabled() && (store.get('panePendingBinding') || store.get('paneCommittedBinding')) && !message.paneActivation) return null;
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
if (store.get('deferredSessionSwitch')) {
|
|
124
|
+
var source = store.get('deferredSessionSwitch');
|
|
125
|
+
if (source.draining) return null;
|
|
126
|
+
if (message.type === 'stop') return JSON.stringify(Object.assign({}, message, { sessionId: source.sessionId, sessionOriginId: source.origin }));
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (!enabled()) return data;
|
|
130
|
+
var binding = store.get('paneCommittedBinding');
|
|
131
|
+
if (store.get('panePendingBinding') || store.get('paneSuspended') || binding && !store.get('paneReady')) return null;
|
|
132
|
+
if (!binding) return ['stop', 'user_message', 'permission_response', 'ask_user_response', 'elicitation_response', 'user_dialog_response', 'input_sync'].indexOf(message.type) >= 0 ? null : data;
|
|
133
|
+
if (message.paneBinding && !samePaneBinding(binding, message.paneBinding)) return null;
|
|
134
|
+
var gesture = (store.get('paneActionEvents') || []).find(function (entry) { return entry.event.isTrusted && entry.event.eventPhase && samePaneBinding(entry.binding, binding); });
|
|
135
|
+
var action = store.get('paneCallbackBinding') || gesture && gesture.binding;
|
|
136
|
+
var read = /^(get_|list_)|(_get|_list|_options)$/.test(message.type) || message.type === 'load_more_history';
|
|
137
|
+
if (!message.paneBinding && !read && !samePaneBinding(action, binding)) return null;
|
|
138
|
+
return JSON.stringify(Object.assign({}, message, { paneBinding: message.paneBinding || action || binding }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function refreshPaneActivation() {
|
|
142
|
+
if (!enabled()) return;
|
|
143
|
+
var binding = store.get('panePendingBinding') || store.get('paneCommittedBinding');
|
|
144
|
+
if (!binding) return;
|
|
145
|
+
var current = paneTarget(store.snap(), getCachedSessions(), binding.sessionId);
|
|
146
|
+
if (samePaneTarget(current, binding)) return;
|
|
147
|
+
if (store.get('panePendingBinding')) failed(binding.requestId);
|
|
148
|
+
else { store.set({ paneReady: false }); block(true); }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function initPaneActivation() {
|
|
152
|
+
if (!enabled() || !window.frameElement) return;
|
|
153
|
+
var frame = window.frameElement;
|
|
154
|
+
var latestId = Number(frame.dataset && (frame.dataset.requestedSessionId || frame.dataset.sessionId));
|
|
155
|
+
if (Number.isSafeInteger(latestId) && latestId > 0 && frame.dataset.projectSlug === store.get('currentSlug')) {
|
|
156
|
+
var latestPath = new URL(store.get('wsPath'), location.origin);
|
|
157
|
+
latestPath.searchParams.set('session', String(latestId));
|
|
158
|
+
store.set({ paneSessionId: latestId, panePinPending: true, wsPath: latestPath.pathname + latestPath.search });
|
|
159
|
+
}
|
|
160
|
+
block(true);
|
|
161
|
+
frame.clayPane = {
|
|
162
|
+
source: window, document: document,
|
|
163
|
+
identity: function () {
|
|
164
|
+
var binding = store.get('paneCommittedBinding');
|
|
165
|
+
return binding && { ownerId: binding.viewerId, projectSlug: binding.projectSlug, sessionId: binding.sessionId,
|
|
166
|
+
binding: binding, ready: store.get('connected') && store.get('paneReady') && !store.get('paneSuspended') };
|
|
167
|
+
},
|
|
168
|
+
activate: activatePaneSession,
|
|
169
|
+
checkDraft: function () { if (!store.get('panePendingBinding')) { var draft = takeComposerDraft(); restoreComposerDraft(draft); } },
|
|
170
|
+
suspend: function () { store.set({ paneSuspended: true, paneReady: false }); block(true); },
|
|
171
|
+
};
|
|
172
|
+
// A pointer that began on the previous conversation cannot become a Stop or
|
|
173
|
+
// approval click on the replacement conversation after hydration.
|
|
174
|
+
document.addEventListener('pointerdown', function () { store.set({ panePointerBinding: store.get('paneCommittedBinding') }); }, true);
|
|
175
|
+
document.addEventListener('click', function (event) {
|
|
176
|
+
if (store.get('paneSuspended') || !store.get('paneReady') && store.get('panePendingBinding') || event.detail && store.get('panePointerBinding') !== store.get('paneCommittedBinding')) {
|
|
177
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
178
|
+
}
|
|
179
|
+
}, true);
|
|
180
|
+
['click', 'input', 'change', 'keydown', 'keyup', 'submit', 'blur'].forEach(function (name) {
|
|
181
|
+
document.addEventListener(name, function (event) {
|
|
182
|
+
var keys = store.get('paneHeldKeys') || {};
|
|
183
|
+
var binding = store.get('paneCommittedBinding');
|
|
184
|
+
var previousKey = keys[event.code || event.key];
|
|
185
|
+
var staleKey = (name === 'keyup' || name === 'keydown' && event.repeat) && previousKey && !samePaneBinding(previousKey, binding);
|
|
186
|
+
if (name === 'keydown' && !event.repeat) keys[event.code || event.key] = binding;
|
|
187
|
+
if (name === 'keyup') delete keys[event.code || event.key];
|
|
188
|
+
if (staleKey || store.get('panePendingBinding') || store.get('paneSuspended') || !store.get('paneReady')) {
|
|
189
|
+
event.preventDefault(); event.stopImmediatePropagation(); return;
|
|
190
|
+
}
|
|
191
|
+
var events = (store.get('paneActionEvents') || []).filter(function (entry) { return entry.event.eventPhase; });
|
|
192
|
+
events.push({ event: event, binding: binding });
|
|
193
|
+
store.set({ paneHeldKeys: keys, paneActionEvents: events });
|
|
194
|
+
}, true);
|
|
195
|
+
});
|
|
196
|
+
store.subscribe(function (state, previous) {
|
|
197
|
+
if (state.activeSessionMode === 'tui' && previous.activeSessionMode !== 'tui') {
|
|
198
|
+
delete frame.clayPane; store.set({ paneActivationUnsupported: true }); block(false); return;
|
|
199
|
+
}
|
|
200
|
+
if (!state.connected && previous.connected) {
|
|
201
|
+
clearDeadline();
|
|
202
|
+
store.set({ paneReady: false, panePendingBinding: null, paneCommittedBinding: null }); block(true);
|
|
203
|
+
}
|
|
204
|
+
if (state.connected && (state.splitGroups !== previous.splitGroups || state.splitGroupsProjectSlug !== previous.splitGroupsProjectSlug || state.paneBindingSessions !== previous.paneBindingSessions || state.authIdentityLoaded !== previous.authIdentityLoaded || state.myUserId !== previous.myUserId || state.activeSessionId !== previous.activeSessionId || state.connected !== previous.connected)) {
|
|
205
|
+
queueMicrotask(function () {
|
|
206
|
+
refreshPaneActivation();
|
|
207
|
+
if (!store.get('panePendingBinding') && !store.get('paneSuspended')) {
|
|
208
|
+
if (store.get('panePinPending')) primePaneSession();
|
|
209
|
+
else if (store.get('activeSessionId') === store.get('paneSessionId')) activatePaneSession(store.get('paneSessionId'));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Live appearance sync between the app shell and its pane iframes (split
|
|
2
|
+
// Driver/Worker panes and the contextual Mate window). Each iframe runs its own
|
|
3
|
+
// theme module and only reads the persisted preference at load, so the shell
|
|
4
|
+
// pushes the applied appearance down on every change and when a frame loads.
|
|
5
|
+
// Same-origin only, shell to child only; nothing is saved from either side.
|
|
6
|
+
|
|
7
|
+
import { store } from './store.js';
|
|
8
|
+
import { onThemeChange, getAppearanceSnapshot, applyExternalAppearance } from './theme.js';
|
|
9
|
+
|
|
10
|
+
var ID = /^[a-zA-Z0-9_-]{1,120}$/;
|
|
11
|
+
|
|
12
|
+
function paneFrames() {
|
|
13
|
+
return Array.prototype.filter.call(document.querySelectorAll('iframe'), function (frame) {
|
|
14
|
+
try { var url = new URL(frame.src, window.location.href); return url.origin === window.location.origin && url.searchParams.get('pane') === '1'; }
|
|
15
|
+
catch (error) { return false; }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function push(frame) {
|
|
20
|
+
var snapshot = getAppearanceSnapshot();
|
|
21
|
+
try { if (frame.contentWindow) frame.contentWindow.postMessage({ type: 'clay-appearance', snapshot: snapshot }, window.location.origin); } catch (error) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isId(value) { return typeof value === 'string' && ID.test(value); }
|
|
25
|
+
function isIdOrNull(value) { return value === null || isId(value); }
|
|
26
|
+
|
|
27
|
+
// Exact shape only: strings or null, never coerced values.
|
|
28
|
+
function validSnapshot(value) {
|
|
29
|
+
var pref = value && typeof value === 'object' ? value.preference : null;
|
|
30
|
+
if (!pref || typeof pref !== 'object' || !isId(value.themeId)) return false;
|
|
31
|
+
if (value.userId !== null && (typeof value.userId !== 'string' || !value.userId || value.userId.length > 200)) return false;
|
|
32
|
+
if (typeof pref.configured !== 'boolean') return false;
|
|
33
|
+
if (pref.mode !== null && pref.mode !== 'light' && pref.mode !== 'dark') return false;
|
|
34
|
+
return isIdOrNull(pref.theme) && isIdOrNull(pref.lightTheme) && isIdOrNull(pref.darkTheme);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function initPaneAppearanceBridge() {
|
|
38
|
+
if (store.get('paneMode')) {
|
|
39
|
+
if (window.parent === window) return;
|
|
40
|
+
window.addEventListener('message', function (event) {
|
|
41
|
+
if (event.origin !== window.location.origin || event.source !== window.parent) return;
|
|
42
|
+
var msg = event.data;
|
|
43
|
+
if (!msg || msg.type !== 'clay-appearance' || !validSnapshot(msg.snapshot)) return;
|
|
44
|
+
// Once this frame knows its owner the snapshot must name exactly that owner (null is a mismatch).
|
|
45
|
+
// Before identity is known, or in solo mode without one, any well-formed snapshot applies.
|
|
46
|
+
var owner = store.get('myUserId') || null;
|
|
47
|
+
if (owner !== null && msg.snapshot.userId !== owner) return;
|
|
48
|
+
applyExternalAppearance(msg.snapshot);
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
onThemeChange(function () { paneFrames().forEach(push); });
|
|
53
|
+
// Frames that rejected an early snapshot (identity not yet known here) get it again once it is.
|
|
54
|
+
store.subscribe(function (state, previous) { if (state.myUserId !== previous.myUserId) paneFrames().forEach(push); });
|
|
55
|
+
// A frame that finished loading may have been seeded before a pending save.
|
|
56
|
+
document.addEventListener('load', function (event) {
|
|
57
|
+
if (event.target && event.target.tagName === 'IFRAME' && paneFrames().indexOf(event.target) >= 0) push(event.target);
|
|
58
|
+
}, true);
|
|
59
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
|
|
4
|
+
export function captureSessionAction() {
|
|
5
|
+
var state = store.snap();
|
|
6
|
+
return { projectSlug: state.currentSlug, ownerId: state.myUserId, sessionId: state.activeSessionId,
|
|
7
|
+
origin: state.sessionOriginId, socket: getWs(), binding: state.paneCommittedBinding || null,
|
|
8
|
+
epoch: state.sessionActionEpoch || 0 };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function sessionActionCurrent(action, draftCompletion) {
|
|
12
|
+
var state = store.snap();
|
|
13
|
+
return !!action && action.socket === getWs() && state.connected &&
|
|
14
|
+
action.projectSlug === state.currentSlug && action.ownerId === state.myUserId &&
|
|
15
|
+
action.sessionId === state.activeSessionId && action.origin === state.sessionOriginId &&
|
|
16
|
+
action.epoch === (state.sessionActionEpoch || 0) &&
|
|
17
|
+
(draftCompletion || !state.deferredSessionSwitch) && !state.panePendingBinding && !state.paneSuspended &&
|
|
18
|
+
(action.binding ? state.paneReady && samePaneBinding(action.binding, state.paneCommittedBinding) : !state.paneCommittedBinding);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Capture at callback creation, never when an old callback eventually sends.
|
|
22
|
+
export function sessionCallback(callback, draftCompletion) {
|
|
23
|
+
var action = captureSessionAction();
|
|
24
|
+
return function () {
|
|
25
|
+
if (!sessionActionCurrent(action, draftCompletion)) return;
|
|
26
|
+
var previous = store.get('paneCallbackBinding');
|
|
27
|
+
store.set({ paneCallbackBinding: action.binding });
|
|
28
|
+
try { return callback.apply(this, arguments); }
|
|
29
|
+
finally { store.set({ paneCallbackBinding: previous }); }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function samePaneBinding(a, b) {
|
|
34
|
+
return !!a && !!b && ['requestId', 'generation', 'projectSlug', 'viewerId', 'ownerId', 'sessionId', 'sessionOriginId', 'role', 'workerGeneration', 'groupStamp', 'parentSessionOriginId'].every(function (key) { return a[key] === b[key]; });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function samePaneTarget(a, b) {
|
|
38
|
+
return !!a && !!b && ['projectSlug', 'viewerId', 'ownerId', 'sessionId', 'sessionOriginId', 'role', 'workerGeneration', 'groupStamp', 'parentSessionOriginId'].every(function (key) { return a[key] === b[key]; });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function paneTarget(state, sessions, id) {
|
|
42
|
+
var session = (sessions || []).find(function (item) { return item.id === id; });
|
|
43
|
+
if (!session || session.isMate || !session.sessionOriginId || (session.runtimeMode || session.mode || 'gui') !== 'gui' || session.tuiSuspended) return null;
|
|
44
|
+
var group = (state.splitGroups || []).find(function (item) { return item.members.indexOf(id) >= 0; });
|
|
45
|
+
var parent = session.sessionRole === 'worker' && sessions.find(function (item) { return item.id === session.parentSessionId; });
|
|
46
|
+
if (session.sessionRole === 'worker' && (!parent || !session.parentAvailable || !Number.isSafeInteger(session.workerGeneration) || !parent.sessionOriginId || parent.ownerId !== session.ownerId || !group || !group.pair || group.pair.driverId !== parent.id)) return null;
|
|
47
|
+
return {
|
|
48
|
+
projectSlug: state.currentSlug, viewerId: state.myUserId || null, ownerId: session.ownerId || null,
|
|
49
|
+
sessionId: session.id, sessionOriginId: session.sessionOriginId,
|
|
50
|
+
role: session.sessionRole === 'worker' ? 'worker' : 'driver', workerGeneration: session.workerGeneration || null,
|
|
51
|
+
parentSessionOriginId: parent ? parent.sessionOriginId : null,
|
|
52
|
+
groupStamp: group ? JSON.stringify({ id: group.id, members: group.members, pair: group.pair || null }) : null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Ordinary reusable panes have a live document binding; legacy/canonical frames
|
|
57
|
+
// retain their existing URL and receipt checks in their own bridge paths.
|
|
58
|
+
export function livePaneBinding(frame, message) {
|
|
59
|
+
if (!frame || !frame.clayPane) return false;
|
|
60
|
+
var api = frame.clayPane, live = api.identity();
|
|
61
|
+
var state = store.snap(), split = state.splitPanes;
|
|
62
|
+
var target = live && paneTarget(state, state.paneBindingSessions || [], live.sessionId);
|
|
63
|
+
return api.source === frame.contentWindow && api.document === frame.contentDocument && live && live.ready &&
|
|
64
|
+
samePaneTarget(target, live.binding) &&
|
|
65
|
+
live.ownerId === (state.myUserId || null) && live.projectSlug === state.currentSlug &&
|
|
66
|
+
split && split.panes.some(function (pane) { return pane.slug === live.projectSlug && pane.sessionId === live.sessionId; }) &&
|
|
67
|
+
(!message || samePaneBinding(live.binding, message.paneBinding));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function paneMessageBinding() {
|
|
71
|
+
return store.get('paneReady') && !store.get('paneSuspended') ? store.get('paneCommittedBinding') : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function currentPaneReview(container) {
|
|
75
|
+
if (!container || !container.paneBinding) return true;
|
|
76
|
+
return container.isConnected !== false && store.get('paneReady') && !store.get('paneSuspended') && samePaneBinding(container.paneBinding, store.get('paneCommittedBinding'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function paneCallbackCurrent(binding) {
|
|
80
|
+
var current = store.get('panePendingBinding') || store.get('paneCommittedBinding');
|
|
81
|
+
return current ? samePaneBinding(current, binding) : !binding;
|
|
82
|
+
}
|