clay-server 5.2.0-beta.7 → 5.2.0-beta.9
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/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-sessions.js +15 -5
- package/lib/project.js +11 -3
- package/lib/public/app.js +14 -0
- package/lib/public/css/contextual-clay.css +1 -1
- package/lib/public/css/input.css +6 -0
- package/lib/public/css/sticky-notes.css +37 -11
- package/lib/public/modules/app-connection.js +3 -0
- package/lib/public/modules/app-message-router.js +13 -0
- package/lib/public/modules/app-messages.js +25 -5
- package/lib/public/modules/app-misc.js +3 -1
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/clay-owner-navigation.js +0 -3
- package/lib/public/modules/clay-studio-render.js +3 -1
- 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 +16 -3
- package/lib/public/modules/contextual-mate-context.js +6 -4
- package/lib/public/modules/elicitation-form.js +5 -0
- package/lib/public/modules/history-replay-batch.js +8 -0
- package/lib/public/modules/input.js +55 -11
- 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-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/rewind.js +5 -0
- 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/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/stt.js +7 -2
- package/lib/public/modules/tools.js +18 -2
- package/lib/sessions.js +17 -10
- package/package.json +4 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Pending requests can precede the initial history window. Restate their exact
|
|
2
|
+
// display payloads before snapshot completion, without resolving or replaying
|
|
3
|
+
// any human decision. Client renderers deduplicate by their original IDs.
|
|
4
|
+
function send(session, emit) {
|
|
5
|
+
Object.keys(session.pendingAskUser || {}).forEach(function (id) {
|
|
6
|
+
var pending = session.pendingAskUser[id];
|
|
7
|
+
var input = pending.input || { questions: pending.request && pending.request.questions || [] };
|
|
8
|
+
emit({ type: 'ask_user_pending', toolId: id, input: input });
|
|
9
|
+
});
|
|
10
|
+
Object.keys(session.pendingElicitations || {}).forEach(function (id) {
|
|
11
|
+
var request = session.pendingElicitations[id].request || {};
|
|
12
|
+
emit({ type: 'elicitation_request', requestId: id, serverName: request.serverName, message: request.message,
|
|
13
|
+
mode: request.mode || 'form', url: request.url || null, elicitationId: request.elicitationId || null, requestedSchema: request.requestedSchema || null });
|
|
14
|
+
});
|
|
15
|
+
Object.keys(session.pendingUserDialogs || {}).forEach(function (id) {
|
|
16
|
+
var request = session.pendingUserDialogs[id].request || {};
|
|
17
|
+
emit({ type: 'user_dialog_request', requestId: id, dialogKind: request.dialogKind, payload: request.payload || {}, toolUseId: request.toolUseID || null });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
module.exports = { send: send };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
var provenance = require('./session-provenance');
|
|
2
|
+
|
|
3
|
+
// This opt-in protocol only describes an authenticated ordinary pane snapshot.
|
|
4
|
+
// It grants no action authority and is never a visible-render acknowledgement.
|
|
5
|
+
function begin(ctx, ws, msg) {
|
|
6
|
+
if (!Object.prototype.hasOwnProperty.call(msg, 'paneActivation')) {
|
|
7
|
+
ws._clayPaneActivation = null;
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
var request = msg.paneActivation;
|
|
11
|
+
var session = ctx.sm.sessions.get(msg.id);
|
|
12
|
+
var userId = ws._clayUser && ws._clayUser.id || null;
|
|
13
|
+
var worker = provenance.isWorker(session);
|
|
14
|
+
var parentSession = worker && Array.from(ctx.sm.sessions.values()).find(function (parent) { return parent.sessionOriginId === session.sessionProvenance.parentSessionOriginId && !provenance.isWorker(parent); });
|
|
15
|
+
var identity = session && {
|
|
16
|
+
requestId: request && request.requestId,
|
|
17
|
+
generation: request && request.generation,
|
|
18
|
+
projectSlug: ctx.slug,
|
|
19
|
+
viewerId: userId,
|
|
20
|
+
ownerId: session.ownerId || null,
|
|
21
|
+
sessionId: session.localId,
|
|
22
|
+
sessionOriginId: session.sessionOriginId,
|
|
23
|
+
role: worker ? 'worker' : 'driver',
|
|
24
|
+
workerGeneration: worker ? session.sessionProvenance.generation : null,
|
|
25
|
+
parentSessionOriginId: worker ? session.sessionProvenance.parentSessionOriginId : null,
|
|
26
|
+
groupStamp: groupStamp(),
|
|
27
|
+
};
|
|
28
|
+
function groupStamp() {
|
|
29
|
+
var group = ctx.splitStore && ctx.splitStore.groupForMember(msg.id);
|
|
30
|
+
return group ? JSON.stringify({ id: group.id, members: group.members, pair: group.pair || null }) : null;
|
|
31
|
+
}
|
|
32
|
+
function allowed() {
|
|
33
|
+
if (typeof ws._clayAuthCheck === 'function' && !ws._clayAuthCheck()) return false;
|
|
34
|
+
var access = ctx.getProjectAccess ? ctx.getProjectAccess() : { visibility: 'public' };
|
|
35
|
+
if (!ctx.usersModule.isMultiUser()) return !session.ownerId;
|
|
36
|
+
return !!userId &&
|
|
37
|
+
!!ctx.usersModule.findUserById(userId) &&
|
|
38
|
+
ctx.usersModule.canAccessProject(userId, access) &&
|
|
39
|
+
ctx.usersModule.canAccessSession(userId, session, access);
|
|
40
|
+
}
|
|
41
|
+
var valid = request && typeof request === 'object' &&
|
|
42
|
+
typeof request.requestId === 'string' && /^[a-zA-Z0-9_-]{1,96}$/.test(request.requestId) &&
|
|
43
|
+
Number.isSafeInteger(request.generation) && request.generation > (ws._clayPaneActivationGeneration || 0) &&
|
|
44
|
+
ws._clayPane === true && !ws._clayContextual && !ctx.isMate && ws.readyState === 1 &&
|
|
45
|
+
session && session.sessionOriginId && !session.isMate &&
|
|
46
|
+
(session.runtimeMode || session.mode || 'gui') === 'gui' && !session.tuiSuspended &&
|
|
47
|
+
request.projectSlug === identity.projectSlug && request.viewerId === identity.viewerId &&
|
|
48
|
+
request.ownerId === identity.ownerId && request.sessionOriginId === identity.sessionOriginId &&
|
|
49
|
+
request.role === identity.role && request.workerGeneration === identity.workerGeneration &&
|
|
50
|
+
(!ctx.splitStore || request.groupStamp === identity.groupStamp && request.parentSessionOriginId === identity.parentSessionOriginId) &&
|
|
51
|
+
(!worker || Number.isSafeInteger(identity.workerGeneration) && identity.workerGeneration > 0) && allowed();
|
|
52
|
+
if (!valid) {
|
|
53
|
+
// Do not disclose whether a foreign or removed session exists.
|
|
54
|
+
ctx.sendTo(ws, { type: 'pane_activation_rejected', requestId: request && typeof request.requestId === 'string' ? request.requestId.slice(0, 96) : null });
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
ws._clayPaneActivationGeneration = identity.generation;
|
|
58
|
+
ws._clayPaneActivation = identity;
|
|
59
|
+
function current() {
|
|
60
|
+
return ws.readyState === 1 && ws._clayPane === true && !ws._clayContextual && !ctx.isMate && !session.isMate &&
|
|
61
|
+
ws._clayPaneActivation === identity &&
|
|
62
|
+
ctx.sm.sessions.get(msg.id) === session && session.sessionOriginId === identity.sessionOriginId &&
|
|
63
|
+
(ws._clayUser && ws._clayUser.id || null) === userId &&
|
|
64
|
+
(session.ownerId || null) === identity.ownerId &&
|
|
65
|
+
(session.runtimeMode || session.mode || 'gui') === 'gui' && !session.tuiSuspended &&
|
|
66
|
+
(provenance.isWorker(session) ? 'worker' : 'driver') === identity.role &&
|
|
67
|
+
(provenance.isWorker(session) ? session.sessionProvenance.generation : null) === identity.workerGeneration &&
|
|
68
|
+
groupStamp() === identity.groupStamp &&
|
|
69
|
+
(!worker || parentSession && ctx.sm.sessions.get(parentSession.localId) === parentSession &&
|
|
70
|
+
session.sessionProvenance.parentSessionOriginId === identity.parentSessionOriginId && parentSession.sessionOriginId === identity.parentSessionOriginId &&
|
|
71
|
+
!provenance.isWorker(parentSession) && (parentSession.ownerId || null) === identity.ownerId) &&
|
|
72
|
+
allowed();
|
|
73
|
+
}
|
|
74
|
+
function send(message) {
|
|
75
|
+
if (current() && ws._clayActiveSession === session.localId) ctx.sendTo(ws, Object.assign({}, message, { paneActivation: identity }));
|
|
76
|
+
}
|
|
77
|
+
var historyItems = [], historyText = 0;
|
|
78
|
+
// Only synchronous replay uses this bounded envelope. Each actual disclosure
|
|
79
|
+
// rechecks live authorization; nothing is cached across a send or async work.
|
|
80
|
+
send.history = function (message) {
|
|
81
|
+
if (message.type === 'history_done') {
|
|
82
|
+
flushHistory();
|
|
83
|
+
send(message);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
historyItems.push(message);
|
|
87
|
+
historyText += typeof message.text === 'string' ? message.text.length : 0;
|
|
88
|
+
if (historyItems.length >= 100 || historyText >= 65536) flushHistory();
|
|
89
|
+
};
|
|
90
|
+
function flushHistory() {
|
|
91
|
+
if (!historyItems.length) return;
|
|
92
|
+
var items = historyItems;
|
|
93
|
+
historyItems = []; historyText = 0;
|
|
94
|
+
send({ type: 'pane_history_batch', items: items });
|
|
95
|
+
}
|
|
96
|
+
if (current()) ctx.sendTo(ws, { type: 'pane_activation_started', paneActivation: identity });
|
|
97
|
+
ws._clayPaneActivationValid = function () { return current() && ws._clayActiveSession === identity.sessionId; };
|
|
98
|
+
return {
|
|
99
|
+
validate: function () {
|
|
100
|
+
if (current()) return true;
|
|
101
|
+
ctx.sendTo(ws, { type: 'pane_activation_rejected', requestId: identity.requestId });
|
|
102
|
+
ws._clayPaneActivation = null;
|
|
103
|
+
return false;
|
|
104
|
+
},
|
|
105
|
+
send: send,
|
|
106
|
+
finish: function () {
|
|
107
|
+
// Includes history listeners, status, permissions and context sources.
|
|
108
|
+
// Consumers still need to observe their own DOM/composer readiness.
|
|
109
|
+
if (ws._clayActiveSession === session.localId) send({ type: 'pane_activation_snapshot_done' });
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { begin: begin };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage;
|
|
2
|
+
var requests = new AsyncLocalStorage();
|
|
3
|
+
|
|
4
|
+
function same(a, b) {
|
|
5
|
+
return !!a && !!b && ['requestId', 'generation', 'projectSlug', 'viewerId', 'ownerId', 'sessionId', 'sessionOriginId', 'role', 'workerGeneration', 'groupStamp', 'parentSessionOriginId'].every(function (key) { return a[key] === b[key]; });
|
|
6
|
+
}
|
|
7
|
+
function run(ws, msg, dispatch) {
|
|
8
|
+
var binding = ws._clayPaneActivation;
|
|
9
|
+
if (!binding && msg.paneBinding || binding && msg.type === 'switch_session' && !msg.paneActivation) return;
|
|
10
|
+
if (binding && ['switch_session', 'ping', 'pong', 'clay_ui_ready'].indexOf(msg.type) < 0) {
|
|
11
|
+
if (!same(binding, msg.paneBinding) || !ws._clayPaneActivationValid || !ws._clayPaneActivationValid()) return;
|
|
12
|
+
}
|
|
13
|
+
return requests.run({ ws: ws, binding: binding || null }, function () { return dispatch(ws, msg); });
|
|
14
|
+
}
|
|
15
|
+
// A direct asynchronous reply retains the request's original binding. Live
|
|
16
|
+
// provider events instead supply their authoritative source session explicitly.
|
|
17
|
+
function outbound(ws, message, sessionId) {
|
|
18
|
+
if (!message || message.paneActivation || /^pane_activation_/.test(message.type)) return message;
|
|
19
|
+
var scope = requests.getStore();
|
|
20
|
+
var binding = sessionId === undefined ? scope && scope.ws === ws && scope.binding : ws._clayPaneActivation;
|
|
21
|
+
if (!binding) return message;
|
|
22
|
+
if (sessionId !== undefined && binding.sessionId !== sessionId) return null;
|
|
23
|
+
if (!same(binding, ws._clayPaneActivation) || !ws._clayPaneActivationValid || !ws._clayPaneActivationValid()) return null;
|
|
24
|
+
return Object.assign({}, message, { paneActivation: binding });
|
|
25
|
+
}
|
|
26
|
+
module.exports = { run: run, outbound: outbound, same: same };
|
package/lib/project-sessions.js
CHANGED
|
@@ -804,6 +804,8 @@ function attachSessions(ctx) {
|
|
|
804
804
|
// adoptOrphanedCliSessions). Click a session in the sidebar instead.
|
|
805
805
|
|
|
806
806
|
if (msg.type === "switch_session") {
|
|
807
|
+
var paneActivation = require('./pane-session-activation').begin(ctx, ws, msg);
|
|
808
|
+
if (paneActivation === false) return true;
|
|
807
809
|
if (msg.id && sm.sessions.has(msg.id)) {
|
|
808
810
|
// resolveSessionForView sets runtimeMode / runtimeTerminalId /
|
|
809
811
|
// tuiSuspended (and hydrates the transcript) before sm.switchSession
|
|
@@ -817,6 +819,7 @@ function attachSessions(ctx) {
|
|
|
817
819
|
resolveSessionForView(xmTarget, ws);
|
|
818
820
|
}
|
|
819
821
|
var switchTargetSess = sm.sessions.get(msg.id);
|
|
822
|
+
if (paneActivation && !paneActivation.validate()) return true;
|
|
820
823
|
var switchTargetVendor = switchTargetSess && (switchTargetSess.vendor || sm.defaultVendor || "claude");
|
|
821
824
|
if (switchTargetSess && !switchTargetSess.effort) {
|
|
822
825
|
switchTargetSess.effort = yoke.clampEffort(
|
|
@@ -829,20 +832,20 @@ function attachSessions(ctx) {
|
|
|
829
832
|
if (usersModule.isMultiUser() && ws._clayUser) {
|
|
830
833
|
var switchTarget = sm.sessions.get(msg.id);
|
|
831
834
|
if (!usersModule.canAccessSession(ws._clayUser.id, switchTarget, { visibility: "public" })) return true;
|
|
832
|
-
ws.
|
|
833
|
-
sm.switchSession(msg.id, ws, hydrateImageRefs);
|
|
835
|
+
sm.switchSession(msg.id, ws, hydrateImageRefs, paneActivation && paneActivation.send, paneActivation && paneActivation.validate);
|
|
834
836
|
if (!ws._clayPane) broadcastPresence();
|
|
835
837
|
} else {
|
|
836
|
-
ws.
|
|
837
|
-
sm.switchSession(msg.id, ws, hydrateImageRefs);
|
|
838
|
+
sm.switchSession(msg.id, ws, hydrateImageRefs, paneActivation && paneActivation.send, paneActivation && paneActivation.validate);
|
|
838
839
|
}
|
|
839
840
|
// Send per-session context sources
|
|
840
841
|
if (typeof loadContextSources === "function") {
|
|
841
842
|
var switchedSources = loadContextSources(slug, msg.id);
|
|
842
|
-
|
|
843
|
+
if (paneActivation) paneActivation.send({ type: "context_sources_state", active: switchedSources });
|
|
844
|
+
else sendTo(ws, { type: "context_sources_state", active: switchedSources });
|
|
843
845
|
}
|
|
844
846
|
var swPresKey = ws._clayUser ? ws._clayUser.id : "_default";
|
|
845
847
|
if (!ws._clayPane) userPresence.setPresence(slug, swPresKey, userPresence.sessionIdForPersistence(switchTargetSess), null);
|
|
848
|
+
if (paneActivation) paneActivation.finish();
|
|
846
849
|
}
|
|
847
850
|
return true;
|
|
848
851
|
}
|
|
@@ -1084,6 +1087,12 @@ function attachSessions(ctx) {
|
|
|
1084
1087
|
|
|
1085
1088
|
if (msg.type === "stop") {
|
|
1086
1089
|
var session = getSessionForWs(ws);
|
|
1090
|
+
if (msg.sessionId != null || msg.sessionOriginId != null) {
|
|
1091
|
+
session = sm.sessions.get(msg.sessionId);
|
|
1092
|
+
if (!session || session.sessionOriginId !== msg.sessionOriginId ||
|
|
1093
|
+
!usersModule.isMultiUser() && session.ownerId ||
|
|
1094
|
+
usersModule.isMultiUser() && (!ws._clayUser || !usersModule.canAccessSession(ws._clayUser.id, session, getProjectAccess()))) return true;
|
|
1095
|
+
}
|
|
1087
1096
|
stopSession(session, ws && ws._clayUser);
|
|
1088
1097
|
return true;
|
|
1089
1098
|
}
|
|
@@ -1465,6 +1474,7 @@ function attachSessions(ctx) {
|
|
|
1465
1474
|
var sessionId = sm.permissionRequestIndex[requestId];
|
|
1466
1475
|
var session = sessionId ? sm.sessions.get(sessionId) : getSessionForWs(ws);
|
|
1467
1476
|
if (!session) return true;
|
|
1477
|
+
if (ws._clayPaneActivation && session.localId !== ws._clayPaneActivation.sessionId) return true;
|
|
1468
1478
|
var pending = session.pendingPermissions[requestId];
|
|
1469
1479
|
if (!pending) return true;
|
|
1470
1480
|
delete sm.permissionRequestIndex[requestId];
|
package/lib/project.js
CHANGED
|
@@ -459,7 +459,8 @@ function createProjectContext(opts) {
|
|
|
459
459
|
if (obj.type === "loop_registry_updated") payload.records = getHubSchedules(userId);
|
|
460
460
|
else payload.schedules = getHubSchedules(userId);
|
|
461
461
|
}
|
|
462
|
-
|
|
462
|
+
payload = require('./pane-session-transport').outbound(ws, payload);
|
|
463
|
+
if (payload) ws.send(JSON.stringify(payload));
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
function sendToAdmins(obj) {
|
|
@@ -555,7 +556,8 @@ function createProjectContext(opts) {
|
|
|
555
556
|
var data = JSON.stringify(obj), delivered = 0;
|
|
556
557
|
for (var ws of clients) {
|
|
557
558
|
if (ws.readyState === 1 && canReceiveMate(ws) && canReceiveYou(ws, sessionId) && ws._clayActiveSession === sessionId) {
|
|
558
|
-
|
|
559
|
+
var panePayload = require('./pane-session-transport').outbound(ws, obj, sessionId);
|
|
560
|
+
if (panePayload) { ws.send(JSON.stringify(panePayload)); delivered++; if (onSent) onSent(ws); }
|
|
559
561
|
}
|
|
560
562
|
}
|
|
561
563
|
homeDebateClients.forEach(function (ws) {
|
|
@@ -569,7 +571,8 @@ function createProjectContext(opts) {
|
|
|
569
571
|
var data = JSON.stringify(obj);
|
|
570
572
|
for (var ws of clients) {
|
|
571
573
|
if (ws !== sender && ws.readyState === 1 && canReceiveMate(ws) && canReceiveYou(ws, sessionId) && ws._clayActiveSession === sessionId) {
|
|
572
|
-
|
|
574
|
+
var panePayload = require('./pane-session-transport').outbound(ws, obj, sessionId);
|
|
575
|
+
if (panePayload) ws.send(JSON.stringify(panePayload));
|
|
573
576
|
}
|
|
574
577
|
}
|
|
575
578
|
}
|
|
@@ -1827,6 +1830,10 @@ function createProjectContext(opts) {
|
|
|
1827
1830
|
}
|
|
1828
1831
|
|
|
1829
1832
|
function handleMessage(ws, msg) {
|
|
1833
|
+
return require('./pane-session-transport').run(ws, msg, dispatchMessage);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
function dispatchMessage(ws, msg) {
|
|
1830
1837
|
if (!_requestAccess.permitMessage(ws, msg)) return;
|
|
1831
1838
|
// --- Keep-alive (delegated to project-connection.js) ---
|
|
1832
1839
|
// Answered before any routing so a ping can never be forwarded elsewhere.
|
|
@@ -2147,6 +2154,7 @@ function createProjectContext(opts) {
|
|
|
2147
2154
|
});
|
|
2148
2155
|
sm.setSessionHydrator(_sessionSkillCatalog.hydrate);
|
|
2149
2156
|
var _sessions = attachSessions({
|
|
2157
|
+
splitStore: _splitGroups.store,
|
|
2150
2158
|
adapters: adapters,
|
|
2151
2159
|
isPendingQueuePaused: function (session) { return _pendingMessageQueue.isPaused(session); },
|
|
2152
2160
|
cwd: cwd,
|
package/lib/public/app.js
CHANGED
|
@@ -35,6 +35,7 @@ import { initQrCode, triggerShare } from './modules/qrcode.js';
|
|
|
35
35
|
import { initFileBrowser, loadRootDirectory, reopenFileViewer, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
|
|
36
36
|
import { initGitPanel } from './modules/git-panel.js';
|
|
37
37
|
import { initWorkerPaneLock } from './modules/worker-pane-lock.js';
|
|
38
|
+
import { initPaneActivation } from './modules/pane-activation.js';
|
|
38
39
|
import { initAutonomousRun } from './modules/autonomous-run.js';
|
|
39
40
|
import { initLoopInterview } from './modules/loop-interview.js';
|
|
40
41
|
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
@@ -295,6 +296,16 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
295
296
|
paneMode: paneMode,
|
|
296
297
|
paneSessionId: paneSessionId,
|
|
297
298
|
panePinPending: paneMode && !!paneSessionId,
|
|
299
|
+
paneCommittedBinding: null, panePendingBinding: null, paneReady: false, paneSuspended: false,
|
|
300
|
+
paneActivationGeneration: 0, paneActivationTimer: null, panePointerBinding: null, paneRichDrafts: {},
|
|
301
|
+
sessionRichDrafts: {},
|
|
302
|
+
sessionActionEpoch: 0, composerUploadingCount: 0, deferredSessionSwitch: null,
|
|
303
|
+
paneCallbackBinding: null, paneActionEvents: [], paneHeldKeys: {}, rewindAction: null, handoffAction: null,
|
|
304
|
+
authIdentityLoaded: false,
|
|
305
|
+
splitIdentityBlockedSocket: null,
|
|
306
|
+
splitIdentityHydrated: false,
|
|
307
|
+
paneActivationUnsupported: false,
|
|
308
|
+
paneBindingSessions: [],
|
|
298
309
|
splitPanes: null,
|
|
299
310
|
splitGroups: [],
|
|
300
311
|
sessionFolders: null,
|
|
@@ -320,6 +331,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
320
331
|
replayingHistory: false,
|
|
321
332
|
historyReplayBatch: null,
|
|
322
333
|
pendingSessionEntryNavigation: null,
|
|
334
|
+
sessionEntryHighlight: null,
|
|
323
335
|
sessionNavigationRequestId: null,
|
|
324
336
|
projectName: projectName,
|
|
325
337
|
cwd: "",
|
|
@@ -1169,6 +1181,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1169
1181
|
if (document.body) document.body.classList.add('is-multi-user');
|
|
1170
1182
|
}
|
|
1171
1183
|
if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); requestDefaultVendor(); }
|
|
1184
|
+
store.set({ authIdentityLoaded: true });
|
|
1172
1185
|
store.set({ permissions: d.permissions || null, projectsAccessLoaded: true });
|
|
1173
1186
|
if (d.mustChangePin) showForceChangePinOverlay();
|
|
1174
1187
|
// Single-user mode: clear user strip skeletons immediately (no presence message will arrive)
|
|
@@ -1266,6 +1279,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1266
1279
|
initSharedBrowser();
|
|
1267
1280
|
initStudioBridge();
|
|
1268
1281
|
initWorkerPaneLock();
|
|
1282
|
+
initPaneActivation();
|
|
1269
1283
|
|
|
1270
1284
|
// --- Terminal ---
|
|
1271
1285
|
initTerminal({
|
|
@@ -50,7 +50,7 @@ html.contextual-pane-boot #connect-overlay { display: none !important; }
|
|
|
50
50
|
#contextual-clay [data-conversation-notice]:not([hidden]) { display: flex; align-items: center; gap: 8px; flex-shrink: 0; padding: 6px 12px; border-bottom: 1px solid var(--border-subtle); font-size: 12px; color: var(--text-secondary); }
|
|
51
51
|
#contextual-clay [data-conversation-notice] span { flex: 1; }
|
|
52
52
|
#contextual-clay [data-conversation-notice] button { color: var(--accent); white-space: nowrap; }
|
|
53
|
-
#contextual-clay [data-clay-expand] span
|
|
53
|
+
#contextual-clay [data-clay-expand] span { display: none; }
|
|
54
54
|
@media (pointer: coarse) { #contextual-clay [data-mate-history] summary, #contextual-clay [data-mate-session-list] button { min-width: 44px; min-height: 44px; } }
|
|
55
55
|
@starting-style {
|
|
56
56
|
#contextual-clay[data-mode="popover"]:not([hidden]) { opacity: 0; transform: translateX(-4px); }
|
package/lib/public/css/input.css
CHANGED
|
@@ -1754,3 +1754,9 @@ body:not(.worker-pane-locked) #input-bottom-right:has(#send-btn.stop:not(:disabl
|
|
|
1754
1754
|
margin: 8px auto 0;
|
|
1755
1755
|
}
|
|
1756
1756
|
#input-area > #generation-stop.hidden { display: none; }
|
|
1757
|
+
|
|
1758
|
+
.session-switch-wait {
|
|
1759
|
+
padding: 6px 8px;
|
|
1760
|
+
font-size: 12px;
|
|
1761
|
+
color: var(--text-secondary);
|
|
1762
|
+
}
|
|
@@ -927,15 +927,41 @@
|
|
|
927
927
|
.sticky-note:not(.sticky-note-flight-away) { transition-duration: 0ms; }
|
|
928
928
|
}
|
|
929
929
|
|
|
930
|
-
/* Checklist
|
|
931
|
-
.sn-task-form { display:
|
|
932
|
-
.sn-task-
|
|
933
|
-
.sn-task-form
|
|
934
|
-
.sn-task-form
|
|
935
|
-
.sn-task-
|
|
936
|
-
.sn-task-form
|
|
937
|
-
.sn-task-
|
|
938
|
-
.sn-task-
|
|
939
|
-
.sn-task-form button:
|
|
930
|
+
/* Checklist writing stays on the stationery surface. */
|
|
931
|
+
.sn-task-form { display: flex; flex-direction: column; gap: 2px; min-height: 100%; box-sizing: border-box; padding: 2px 2px 0; }
|
|
932
|
+
.sn-task-form textarea { display: block; min-width: 0; width: 100%; box-sizing: border-box; border: 1px solid transparent; border-radius: 4px; background: transparent; color: inherit; font: inherit; line-height: 1.5; padding: 4px; margin: 0; resize: none; field-sizing: content; overflow: hidden; overflow-wrap: anywhere; }
|
|
933
|
+
.sn-task-form textarea:hover { background: color-mix(in srgb, var(--note-ink) 4%, transparent); }
|
|
934
|
+
.sn-task-form textarea:focus { background: color-mix(in srgb, var(--note-ink) 5%, transparent); }
|
|
935
|
+
.sn-task-form textarea::placeholder { color: inherit; opacity: .58; }
|
|
936
|
+
.sn-task-form .sn-task-title { font-size: 16px; font-weight: 650; line-height: 1.35; letter-spacing: -.02em; padding: 2px 4px; }
|
|
937
|
+
.sn-task-summary { font-size: 11px; opacity: .65; padding: 0 4px 8px; }
|
|
938
|
+
.sn-task-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) 26px; align-items: start; gap: 1px; }
|
|
939
|
+
.sn-task-form button { color: inherit; background: transparent; border: 0; font: inherit; cursor: pointer; border-radius: 4px; }
|
|
940
|
+
.sn-task-form .sn-check { width: 26px; height: 29px; margin: 0; padding: 0; opacity: 1; transform: none; display: grid; place-items: center; }
|
|
941
|
+
.sn-task-box { display: grid; place-items: center; width: 15px; height: 15px; box-sizing: border-box; border: 1.25px solid color-mix(in srgb, var(--note-ink) 55%, transparent); border-radius: 4px; }
|
|
942
|
+
.sn-task-box svg { width: 13px; height: 13px; visibility: hidden; }
|
|
943
|
+
.sn-task-form .checked .sn-task-box { background: var(--note-ink); border-color: var(--note-ink); color: var(--note-surface); }
|
|
944
|
+
.sn-task-form .checked .sn-task-box svg { visibility: visible; }
|
|
945
|
+
.sn-task-row.is-complete textarea:not(:focus) { text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--note-ink) 45%, transparent); color: color-mix(in srgb, var(--note-ink) 68%, transparent); }
|
|
946
|
+
.sn-task-remove { width: 26px; height: 29px; padding: 0; display: grid; place-items: center; opacity: 0; }
|
|
947
|
+
.sn-task-remove .lucide, .sn-task-plus .lucide { width: 14px; height: 14px; }
|
|
948
|
+
.sn-task-row:hover .sn-task-remove, .sn-task-row:focus-within .sn-task-remove { opacity: .65; }
|
|
949
|
+
.sn-task-form button:hover { background: color-mix(in srgb, var(--note-ink) 8%, transparent); }
|
|
950
|
+
.sn-task-form button:active:not(:disabled) { transform: translateY(1px); }
|
|
951
|
+
.sn-task-add { display: grid; grid-template-columns: 26px minmax(0, 1fr); align-items: start; gap: 1px; margin-top: 4px; }
|
|
952
|
+
.sn-task-plus { display: grid; place-items: center; height: 29px; opacity: .55; }
|
|
953
|
+
.sn-task-footer { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; gap: 4px 8px; margin-top: auto; padding: 10px 4px 2px; }
|
|
954
|
+
.sn-task-feedback { flex: 1 1 100px; font-size: 11px; line-height: 1.4; opacity: .75; overflow-wrap: anywhere; }
|
|
955
|
+
.sn-task-form .sn-task-save { font-size: 11px; font-weight: 550; min-height: 28px; padding: 3px 8px; border: 1px solid color-mix(in srgb, var(--note-ink) 20%, transparent); }
|
|
956
|
+
.sn-task-form button:disabled { cursor: default; opacity: .45; }
|
|
957
|
+
.sn-task-form button:focus-visible, .sn-task-form textarea:focus-visible { outline: 2px solid var(--note-ink); outline-offset: -1px; }
|
|
940
958
|
.sticky-note-header .sticky-note-md-btn[hidden] { display: none !important; }
|
|
941
|
-
|
|
959
|
+
@media (hover: none), (pointer: coarse) {
|
|
960
|
+
.sn-task-row { grid-template-columns: 36px minmax(0, 1fr) 36px; }
|
|
961
|
+
.sn-task-form .sn-check, .sn-task-remove { width: 36px; height: 36px; }
|
|
962
|
+
.sn-task-remove { opacity: .6; }
|
|
963
|
+
.sn-task-add { grid-template-columns: 36px minmax(0, 1fr); }
|
|
964
|
+
.sn-task-plus { height: 36px; }
|
|
965
|
+
.sn-task-form textarea { padding-top: 7px; padding-bottom: 7px; }
|
|
966
|
+
.sn-task-form .sn-task-save { min-height: 36px; }
|
|
967
|
+
}
|
|
@@ -26,6 +26,7 @@ import { clearMcpPermissionModePending } from './mcp-ui.js';
|
|
|
26
26
|
import { createWebSocketLifecycle } from './websocket-lifecycle.js';
|
|
27
27
|
import { createWebSocketWatchdog } from './websocket-watchdog.js';
|
|
28
28
|
import { resetHistoryReplayBatch } from './history-replay-batch.js';
|
|
29
|
+
import { paneOutgoing } from './pane-activation.js';
|
|
29
30
|
|
|
30
31
|
var connectOverlay = null;
|
|
31
32
|
var hasConnectedOnce = false;
|
|
@@ -371,6 +372,8 @@ export function connect() {
|
|
|
371
372
|
var currentWs = getWs();
|
|
372
373
|
var _origSend = currentWs.send.bind(currentWs);
|
|
373
374
|
currentWs.send = function (data) {
|
|
375
|
+
data = paneOutgoing(data);
|
|
376
|
+
if (data === null) return false;
|
|
374
377
|
blinkIO();
|
|
375
378
|
return _origSend(data);
|
|
376
379
|
};
|
|
@@ -17,6 +17,8 @@ import { handleDefaultAiMessage } from './default-ai.js';
|
|
|
17
17
|
import { handleCursorSharingMessage } from './cursor-sharing-preference.js';
|
|
18
18
|
import { handlePendingMessageQueueMessage } from './pending-message-queue.js';
|
|
19
19
|
import { receiveHistoryReplay } from './history-replay-batch.js';
|
|
20
|
+
import { filterPaneActivation } from './pane-activation.js';
|
|
21
|
+
import { deferSessionSwitch } from './session-switch-defer.js';
|
|
20
22
|
import { handleProjectMateCreationMessage } from './project-mate-creation-workspace.js';
|
|
21
23
|
import { handleProjectDebatePlanningMessage } from './project-debate-planning-workspace.js';
|
|
22
24
|
|
|
@@ -94,6 +96,17 @@ export function handleHomeProtocolMessage(msg) {
|
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
export function processMessage(msg) {
|
|
99
|
+
if (filterPaneActivation(msg)) return;
|
|
100
|
+
if (deferSessionSwitch(msg)) return;
|
|
101
|
+
if (msg.type === 'pane_history_batch') {
|
|
102
|
+
if (!msg.paneActivation || !Array.isArray(msg.items) || msg.items.length > 100) return;
|
|
103
|
+
msg.items.forEach(function (item) {
|
|
104
|
+
// Every original event keeps its order/index and goes through the same
|
|
105
|
+
// stale-binding filter and replay handler as an individual socket event.
|
|
106
|
+
if (item && item.type !== 'pane_history_batch') processMessage(Object.assign({}, item, { paneActivation: msg.paneActivation }));
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
97
110
|
receiveHistoryReplay(msg, dispatchMessage);
|
|
98
111
|
}
|
|
99
112
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { handleStudioDocumentMessage } from './clay-studio-document.js';
|
|
2
|
+
import { saveSessionRichDraft, restoreSessionRichDraft } from './session-rich-drafts.js';
|
|
3
|
+
import { refreshPaneActivation, primePaneSession } from './pane-activation.js';
|
|
4
|
+
import { paneCallbackCurrent } from './pane-binding.js';
|
|
2
5
|
import { handleClayControlActivity } from './clay-control-activity.js';
|
|
3
6
|
import { handleContextualWorkspaceMessage } from './contextual-clay-workspace.js';
|
|
4
7
|
import { handleContextualSessionMessage } from './contextual-clay-session.js';
|
|
@@ -268,6 +271,7 @@ export function processMessage(msg) {
|
|
|
268
271
|
break;
|
|
269
272
|
|
|
270
273
|
case "history_done":
|
|
274
|
+
var historyPaintEpoch = store.get('sessionActionEpoch') || 0;
|
|
271
275
|
if (messagesEl && msg.sessionId === store.get("activeSessionId") && msg.sessionOriginId === store.get("sessionOriginId")) { messagesEl.dataset.controlSessionId = String(msg.sessionId); messagesEl.dataset.controlSessionOrigin = msg.sessionOriginId; messagesEl.dataset.controlHistoryRevision = msg.controlHistoryRevision || ""; }
|
|
272
276
|
store.set({ replayingHistory: false });
|
|
273
277
|
resumeThinkingAfterReplay(store.get('sessionIsProcessing'));
|
|
@@ -350,6 +354,7 @@ export function processMessage(msg) {
|
|
|
350
354
|
// Scroll to tool element if navigating from file edit history
|
|
351
355
|
if (hasNavTarget) {
|
|
352
356
|
requestAnimationFrame(function() {
|
|
357
|
+
if (historyPaintEpoch !== (store.get('sessionActionEpoch') || 0) || !paneCallbackCurrent(msg.paneActivation)) return;
|
|
353
358
|
// Prefer scrolling to the exact tool element
|
|
354
359
|
var target = nav.toolId ? messagesEl.querySelector('[data-tool-id="' + nav.toolId + '"]') : null;
|
|
355
360
|
if (!target && nav.assistantUuid) {
|
|
@@ -365,7 +370,7 @@ export function processMessage(msg) {
|
|
|
365
370
|
}
|
|
366
371
|
});
|
|
367
372
|
}
|
|
368
|
-
requestAnimationFrame(function () { handlePaletteHistoryDone(); });
|
|
373
|
+
requestAnimationFrame(function () { if (historyPaintEpoch === (store.get('sessionActionEpoch') || 0) && paneCallbackCurrent(msg.paneActivation)) handlePaletteHistoryDone(); });
|
|
369
374
|
break;
|
|
370
375
|
|
|
371
376
|
case "restore_mate_dm":
|
|
@@ -777,9 +782,11 @@ export function processMessage(msg) {
|
|
|
777
782
|
markProjectSessionListHydrated();
|
|
778
783
|
renderMateSessionList(msg.sessions || []);
|
|
779
784
|
renderSessionList(msg.sessions || []);
|
|
785
|
+
store.set({ paneBindingSessions: msg.sessions || [] });
|
|
786
|
+
refreshPaneActivation();
|
|
780
787
|
syncGithubSessions(msg.sessions || []);
|
|
781
788
|
syncPaneTitles();
|
|
782
|
-
var panePin = resolvePaneSession(store.get('paneMode'), store.get('panePinPending'), store.get('paneSessionId'), msg.sessions);
|
|
789
|
+
var panePin = resolvePaneSession(store.get('paneMode') && !primePaneSession(), store.get('panePinPending'), store.get('paneSessionId'), msg.sessions);
|
|
783
790
|
if (panePin.consumed) {
|
|
784
791
|
store.set({ panePinPending: false });
|
|
785
792
|
if (panePin.sessionId && getWs() && getWs().readyState === 1) {
|
|
@@ -906,6 +913,8 @@ export function processMessage(msg) {
|
|
|
906
913
|
break;
|
|
907
914
|
|
|
908
915
|
case "session_switched":
|
|
916
|
+
saveSessionRichDraft();
|
|
917
|
+
store.set({ sessionActionEpoch: (store.get('sessionActionEpoch') || 0) + 1 });
|
|
909
918
|
clearPairResultStatus();
|
|
910
919
|
resetStreamBuffer();
|
|
911
920
|
syncAutonomousRunForSession(msg.autonomousRun || null);
|
|
@@ -923,7 +932,7 @@ export function processMessage(msg) {
|
|
|
923
932
|
? msg.runtimeTerminalId
|
|
924
933
|
: (typeof msg.terminalId === "number" ? msg.terminalId : null);
|
|
925
934
|
var _sessionRole = msg.sessionRole === "worker" ? "worker" : "driver";
|
|
926
|
-
store.set({ activeSessionId: msg.id, sessionOriginId: msg.sessionOriginId || null, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort ||
|
|
935
|
+
store.set({ activeSessionId: msg.id, sessionOriginId: msg.sessionOriginId || null, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort || '', vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, turnActivity: null, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || 'default', effectivePermissionMode: msg.effectivePermissionMode || null, permissionCapabilities: msg.permissionCapabilities || { auto: false, mcpOverride: false }, mcpPermissionModeOverrides: msg.mcpPermissionModeOverrides || {}, activeSessionMateId: _sessionRole === "worker" ? null : (store.get('activeProjectMateId') || null), activeSessionMateName: _sessionRole === "worker" ? "" : (store.get('activeProjectMateName') || "") });
|
|
927
936
|
handleScheduledTaskSessionSwitched(msg);
|
|
928
937
|
requestLoopInterviewState();
|
|
929
938
|
activateDeliverySession(msg.id, msg.cliSessionId || null, msg.sessionOriginId || null);
|
|
@@ -998,14 +1007,19 @@ export function processMessage(msg) {
|
|
|
998
1007
|
// Restore draft for incoming session
|
|
999
1008
|
var draft = store.get('sessionDrafts')[store.get('activeSessionId')] || "";
|
|
1000
1009
|
inputEl.value = draft;
|
|
1010
|
+
restoreSessionRichDraft();
|
|
1001
1011
|
autoResize();
|
|
1002
1012
|
if (!("ontouchstart" in window)) {
|
|
1003
1013
|
inputEl.focus();
|
|
1004
1014
|
}
|
|
1005
1015
|
// Reload survival: a restored active session that is a split-group
|
|
1006
1016
|
// member reopens its group (no-op when a split is already open).
|
|
1007
|
-
|
|
1008
|
-
|
|
1017
|
+
var deferredSwitch = store.get('deferredSessionSwitch');
|
|
1018
|
+
if (!deferredSwitch || !deferredSwitch.draining || deferredSwitch.finalSwitch === msg) {
|
|
1019
|
+
finishProjectSessionActivation();
|
|
1020
|
+
maybeRestoreSplitGroup();
|
|
1021
|
+
handlePaletteSessionSwitch(true);
|
|
1022
|
+
}
|
|
1009
1023
|
break;
|
|
1010
1024
|
|
|
1011
1025
|
case "session_full_access_changed":
|
|
@@ -1350,6 +1364,11 @@ export function processMessage(msg) {
|
|
|
1350
1364
|
stopUrgentBlink();
|
|
1351
1365
|
break;
|
|
1352
1366
|
|
|
1367
|
+
case "ask_user_pending":
|
|
1368
|
+
renderAskUserQuestion(msg.toolId, msg.input);
|
|
1369
|
+
startUrgentBlink();
|
|
1370
|
+
break;
|
|
1371
|
+
|
|
1353
1372
|
case "permission_request_pending":
|
|
1354
1373
|
store.set({ turnActivity: null });
|
|
1355
1374
|
setActivity(null);
|
|
@@ -1770,6 +1789,7 @@ export function processMessage(msg) {
|
|
|
1770
1789
|
store.set({ pendingTermCommand: null });
|
|
1771
1790
|
// Small delay to let terminal initialize
|
|
1772
1791
|
setTimeout(function() {
|
|
1792
|
+
if (!paneCallbackCurrent(msg.paneActivation)) return;
|
|
1773
1793
|
sendTerminalCommand(cmd);
|
|
1774
1794
|
}, 300);
|
|
1775
1795
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
5
5
|
import { getWs } from './ws-ref.js';
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
import { sessionCallback } from './pane-binding.js';
|
|
6
8
|
import { updateBrowserTabList } from './context-sources.js';
|
|
7
9
|
import { setExtensionConnected } from './mcp-ui.js';
|
|
8
10
|
import { initPasteModal } from './paste-modal.js';
|
|
@@ -270,7 +272,7 @@ export function showConfirm(text, onConfirm, okLabel, destructive, onCancel) {
|
|
|
270
272
|
confirmCancelCallback = null;
|
|
271
273
|
if (replaced) replaced();
|
|
272
274
|
confirmText.textContent = text;
|
|
273
|
-
confirmCallback = onConfirm;
|
|
275
|
+
confirmCallback = store.get('paneCommittedBinding') ? sessionCallback(onConfirm) : onConfirm;
|
|
274
276
|
confirmCancelCallback = onCancel || null;
|
|
275
277
|
confirmOk.textContent = okLabel || "Delete";
|
|
276
278
|
confirmOk.className = "confirm-btn " + (destructive === false ? "confirm-ok" : "confirm-delete");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareContextualWorkspaceDeparture } from './contextual-clay-workspace.js';
|
|
2
|
+
import { checkComposerDeparture, saveSessionRichDraft } from './session-rich-drafts.js';
|
|
2
3
|
// app-projects.js - Project list, switching, add/remove project modals
|
|
3
4
|
// Extracted from app.js (PR-29)
|
|
4
5
|
|
|
@@ -391,6 +392,10 @@ export function resetClientState() {
|
|
|
391
392
|
|
|
392
393
|
export function switchProject(slug) {
|
|
393
394
|
if (!slug) return;
|
|
395
|
+
if (slug !== store.get('currentSlug')) {
|
|
396
|
+
try { checkComposerDeparture(); saveSessionRichDraft(); }
|
|
397
|
+
catch (error) { showToast(error.message, 'warn'); return; }
|
|
398
|
+
}
|
|
394
399
|
if (prepareContextualWorkspaceDeparture(slug) === false) return;
|
|
395
400
|
var target = cachedProjects.find(function (project) { return project.slug === slug; });
|
|
396
401
|
if (target && target.contentAccess === false && target.administrativeAccess === true) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { observeNavigationRendering } from './clay-navigation-render.js';
|
|
2
|
-
import { dismissContextualClay } from './contextual-clay.js';
|
|
3
2
|
import { showClayNavigationActivity, clearClayControlActivity } from './clay-control-activity.js';
|
|
4
3
|
import { store } from './store.js';
|
|
5
4
|
import { getWs } from './ws-ref.js';
|
|
@@ -23,8 +22,6 @@ export function handleOwnerNavigation(msg) {
|
|
|
23
22
|
var status = 'refused';
|
|
24
23
|
if (accepted && msg.target && typeof msg.target.projectSlug === 'string' && Number.isSafeInteger(msg.target.sessionId)) {
|
|
25
24
|
try {
|
|
26
|
-
var contextual = store.get('contextualClay') || {};
|
|
27
|
-
if (msg.target.contextualSourceMateId && contextual.mateId === msg.target.contextualSourceMateId && contextual.mode === 'popover') dismissContextualClay(false);
|
|
28
25
|
showClayNavigationActivity(msg.target, false); navigateToSession(msg.target, null, msg.target.entryIndex); status = 'accepted';
|
|
29
26
|
} catch (error) { showClayNavigationActivity(msg.target, true); status = 'failed'; }
|
|
30
27
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forwardDocumentSource } from './clay-studio-document.js';
|
|
2
2
|
import { store } from './store.js';
|
|
3
|
+
import { paneMessageBinding, livePaneBinding } from './pane-binding.js';
|
|
3
4
|
import { getWs } from './ws-ref.js';
|
|
4
5
|
import { contextualMateStamp } from './contextual-mate-identity.js';
|
|
5
6
|
import { visibleTarget } from './clay-control-target-visibility.js';
|
|
@@ -34,7 +35,7 @@ export function forwardStudioPresentation(message, type) {
|
|
|
34
35
|
if (Object.keys(receipts).length >= 100) return true;
|
|
35
36
|
if (evidence) receipts[evidence.id] = { evidence: evidence, socket: getWs() };
|
|
36
37
|
store.set({ clayStudioRelays: receipts });
|
|
37
|
-
window.parent.postMessage({ type: type, message: message, studioSource: frame.clayStudioSource.identity() }, location.origin);
|
|
38
|
+
window.parent.postMessage({ type: type, message: message, paneBinding: paneMessageBinding(), studioSource: frame.clayStudioSource.identity() }, location.origin);
|
|
38
39
|
return true;
|
|
39
40
|
}
|
|
40
41
|
export function trustedStudioFrame(event, data) {
|
|
@@ -45,6 +46,7 @@ export function trustedStudioFrame(event, data) {
|
|
|
45
46
|
if (!live || !live.connected || live.ownerId !== (store.get('myUserId') || null) || live.ownerId !== expected.ownerId || live.slug !== expected.slug || live.sessionId !== expected.sessionId || live.origin !== expected.origin || live.runtime !== expected.runtime) return null;
|
|
46
47
|
if (new URL(frame.src, location.href).pathname !== '/p/' + encodeURIComponent(live.slug) + '/') return null;
|
|
47
48
|
if (frame.classList.contains('split-pane-frame')) {
|
|
49
|
+
if (frame.clayPane && !livePaneBinding(frame, data)) return null;
|
|
48
50
|
var split = store.get('splitPanes');
|
|
49
51
|
if (!split || !split.panes.some(function (pane) { return pane.slug === live.slug && String(pane.sessionId) === String(live.sessionId); })) return null;
|
|
50
52
|
} else {
|