clay-server 5.2.1-beta.2 → 5.3.0-beta.2
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/clay-control-presentation-binding.js +5 -3
- package/lib/clay-product-control-tools.js +3 -3
- package/lib/clay-product-control.js +4 -2
- package/lib/clay-screen-controls.js +18 -3
- package/lib/clay-split-view-awareness.js +35 -0
- package/lib/clay-split-view-controls.js +122 -0
- package/lib/clay-split-view-input.js +20 -0
- package/lib/clay-split-view-render-proof.js +22 -0
- package/lib/pane-asset-manifest.js +63 -0
- package/lib/project-connection.js +19 -10
- package/lib/project-session-actions.js +1 -0
- package/lib/project-session-delete.js +2 -2
- package/lib/project-session-folders.js +1 -0
- package/lib/project-sessions.js +6 -1
- package/lib/project-split-view-document.js +33 -0
- package/lib/project-split-view-names.js +67 -0
- package/lib/project-split-view-organization.js +23 -0
- package/lib/project-split-views.js +479 -0
- package/lib/project-worker-conversation.js +222 -0
- package/lib/project-worker-creation-proposal.js +3 -4
- package/lib/project-worker-proposal.js +1 -1
- package/lib/project-worker-removal.js +101 -0
- package/lib/project.js +28 -1
- package/lib/public/app.js +32 -13
- package/lib/public/css/base.css +9 -0
- package/lib/public/css/filebrowser.css +0 -1
- package/lib/public/css/pane-header-controls.css +100 -0
- package/lib/public/css/pane.css +24 -41
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/session-folders.css +1 -1
- package/lib/public/css/split-view-layout.css +463 -0
- package/lib/public/css/split-view-worker-activity.css +97 -0
- package/lib/public/css/sticky-notes.css +13 -0
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-connection.js +4 -1
- package/lib/public/modules/app-favicon.js +1 -1
- package/lib/public/modules/app-messages.js +31 -9
- package/lib/public/modules/chat-render-runtime.js +54 -0
- package/lib/public/modules/clay-control-copy.js +3 -0
- package/lib/public/modules/clay-navigation-render.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +15 -5
- package/lib/public/modules/clay-split-view-screen.js +74 -0
- package/lib/public/modules/clay-tool-activity.js +5 -0
- package/lib/public/modules/command-palette.js +30 -28
- package/lib/public/modules/contextual-clay-session.js +26 -2
- package/lib/public/modules/contextual-clay.js +5 -2
- package/lib/public/modules/contextual-mate-sessions.js +2 -0
- package/lib/public/modules/filebrowser.js +6 -2
- package/lib/public/modules/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -0
- package/lib/public/modules/markdown-view-mode.js +34 -0
- package/lib/public/modules/markdown.js +11 -0
- package/lib/public/modules/mate-knowledge-workbench.js +2 -7
- package/lib/public/modules/mate-sidebar.js +1 -1
- package/lib/public/modules/palette-clay-conversation.js +87 -0
- package/lib/public/modules/palette-render-helpers.js +25 -0
- package/lib/public/modules/pane-activation.js +46 -7
- package/lib/public/modules/pane-binding.js +12 -2
- package/lib/public/modules/pane-bridge.js +12 -3
- package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
- package/lib/public/modules/pane-feature-assets.js +35 -0
- package/lib/public/modules/pane-file-bridge.js +8 -3
- package/lib/public/modules/pane-search-receipt.js +12 -0
- package/lib/public/modules/permission-control.js +6 -6
- package/lib/public/modules/popup-chat-rendering.js +244 -0
- package/lib/public/modules/project-activation.js +2 -2
- package/lib/public/modules/qrcode.js +12 -1
- package/lib/public/modules/right-workbench.js +68 -0
- package/lib/public/modules/search-hints.js +79 -0
- package/lib/public/modules/session-context-menu.js +35 -2
- package/lib/public/modules/session-folder-collapse.js +2 -1
- package/lib/public/modules/session-folder-context.js +24 -13
- package/lib/public/modules/session-folder-delete.js +18 -1
- package/lib/public/modules/session-folder-dnd.js +4 -1
- package/lib/public/modules/session-folder-layout.js +7 -6
- package/lib/public/modules/session-search-pane-state.js +30 -0
- package/lib/public/modules/session-search.js +53 -13
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
- package/lib/public/modules/sidebar-sessions.js +57 -24
- package/lib/public/modules/sidebar.js +20 -3
- package/lib/public/modules/split-group-helpers.js +1 -1
- package/lib/public/modules/split-group-restore.js +8 -11
- package/lib/public/modules/split-pair-ui.js +18 -0
- package/lib/public/modules/split-pane-header-state.js +45 -0
- package/lib/public/modules/split-pane-reconciler.js +23 -4
- package/lib/public/modules/split-pane-renderer.js +330 -23
- package/lib/public/modules/split-pane-retention.js +157 -0
- package/lib/public/modules/split-session-boundary.js +21 -0
- package/lib/public/modules/split-view-create-action.js +15 -0
- package/lib/public/modules/split-view-create-menu.js +83 -0
- package/lib/public/modules/split-view-create-request.js +23 -0
- package/lib/public/modules/split-view-layout-client.js +198 -0
- package/lib/public/modules/split-view-layout-icons.js +37 -0
- package/lib/public/modules/split-view-layout-scope.js +38 -0
- package/lib/public/modules/split-view-organization.js +73 -0
- package/lib/public/modules/split-view-presentation-close.js +35 -0
- package/lib/public/modules/split-view-presentation-drag.js +278 -0
- package/lib/public/modules/split-view-presentation-drop.js +57 -0
- package/lib/public/modules/split-view-presentation-lookup.js +44 -0
- package/lib/public/modules/split-view-presentation-renderer.js +494 -0
- package/lib/public/modules/split-view-presentations.js +498 -0
- package/lib/public/modules/split-view-shell-controls.js +28 -0
- package/lib/public/modules/split-view-sidebar-menu.js +42 -0
- package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
- package/lib/public/modules/split-view-singleton.js +21 -0
- package/lib/public/modules/split-view-worker-activity.js +94 -0
- package/lib/public/modules/split-view-worker-controls.js +30 -0
- package/lib/public/modules/split-view-worker-removal.js +137 -0
- package/lib/public/modules/split-view-worker-scroll.js +57 -0
- package/lib/public/modules/split-view-worker-transcript.js +46 -0
- package/lib/public/modules/split-view-workers.js +418 -0
- package/lib/public/modules/split-view.js +61 -22
- package/lib/public/modules/sticky-notes.js +9 -2
- package/lib/public/modules/tools.js +25 -5
- package/lib/public/modules/workbench-layout-preference.js +76 -0
- package/lib/public/modules/worker-created-presentation.js +37 -0
- package/lib/public/pane-assets/app-2DRBYY3L.js +1 -0
- package/lib/public/pane-assets/chunk-4BM7UVNG.js +2 -0
- package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
- package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
- package/lib/public/pane-assets/chunk-C4RVVYNT.js +1 -0
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-O6MQL6MR.js +167 -0
- package/lib/public/pane-assets/chunk-PO2JOHHW.js +1 -0
- package/lib/public/pane-assets/chunk-UMGHINOT.js +1 -0
- package/lib/public/pane-assets/manifest.json +553 -0
- package/lib/public/pane-assets/style-63EZCREE.css +1 -0
- package/lib/public/style.css +5 -1
- package/lib/server-owner-ui.js +6 -2
- package/lib/server-owner-view-observation.js +34 -0
- package/lib/server-palette.js +1 -0
- package/lib/server-pane-bootstrap.js +24 -0
- package/lib/server-settings.js +2 -0
- package/lib/server-static-assets.js +52 -0
- package/lib/server-workbench-layout-settings.js +56 -0
- package/lib/server.js +6 -45
- package/lib/session-activity-state.js +28 -0
- package/lib/session-list-delivery.js +17 -0
- package/lib/session-pair-turn-control.js +1 -6
- package/lib/session-search.js +3 -0
- package/lib/session-split-group-anchors.js +2 -2
- package/lib/session-split-group-roles.js +5 -8
- package/lib/session-split-group-v2-store.js +0 -3
- package/lib/sessions.js +16 -6
- package/lib/split-view-layout.js +132 -0
- package/lib/users-workbench-layout-preferences.js +33 -0
- package/lib/users.js +3 -0
- package/lib/workspace-query-service.js +1 -5
- package/lib/ws-schema.js +16 -0
- package/package.json +6 -3
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Exact, read-only Split Worker transcript snapshots for an owning Driver.
|
|
2
|
+
// This does not switch sessions or expose Worker controls.
|
|
3
|
+
var provenance = require("./session-provenance");
|
|
4
|
+
|
|
5
|
+
function attachProjectWorkerConversation(ctx) {
|
|
6
|
+
var sm = ctx.sm;
|
|
7
|
+
var users = ctx.usersModule;
|
|
8
|
+
var watches = new WeakMap();
|
|
9
|
+
var MAX_MESSAGES = 120;
|
|
10
|
+
var MAX_TEXT = 8000;
|
|
11
|
+
var MAX_PAGE_TEXT = 64000;
|
|
12
|
+
|
|
13
|
+
function send(ws, value) { ctx.sendTo(ws, value); }
|
|
14
|
+
function sessionForOrigin(origin) {
|
|
15
|
+
var found = null;
|
|
16
|
+
var ambiguous = false;
|
|
17
|
+
sm.sessions.forEach(function (session) {
|
|
18
|
+
if (session.sessionOriginId !== origin) return;
|
|
19
|
+
if (found && found !== session) ambiguous = true;
|
|
20
|
+
else if (!found) found = session;
|
|
21
|
+
});
|
|
22
|
+
return ambiguous ? null : found;
|
|
23
|
+
}
|
|
24
|
+
function actor(ws) {
|
|
25
|
+
if (!ws || !ctx.clients || !ctx.clients.has(ws) || ws._clayPaneActivation || ctx.isMate || !ctx.canAccessProject(ws)) return null;
|
|
26
|
+
var user = ws._clayUser && users.findUserById(ws._clayUser.id);
|
|
27
|
+
if (users.isMultiUser() && !user) return null;
|
|
28
|
+
if (ctx.osUsers && (!user || !user.linuxUser || !ctx.getOsUserInfoForWs({ _clayUser: user }))) return null;
|
|
29
|
+
return { id: user && user.id || null, linuxUser: ctx.osUsers && user ? user.linuxUser : null };
|
|
30
|
+
}
|
|
31
|
+
function exactPair(ws, msg, currentActor) {
|
|
32
|
+
if (!msg || typeof msg.parentSessionOriginId !== "string" || typeof msg.workerSessionOriginId !== "string" ||
|
|
33
|
+
!Number.isInteger(msg.generation) || msg.generation < 1) return null;
|
|
34
|
+
var driver = sessionForOrigin(msg.parentSessionOriginId), worker = sessionForOrigin(msg.workerSessionOriginId);
|
|
35
|
+
if (!driver || !worker || driver === worker || sm.sessions.get(driver.localId) !== driver || sm.sessions.get(worker.localId) !== worker) return null;
|
|
36
|
+
if (!ctx.canAccessSession(ws, driver) || !ctx.canAccessSession(ws, worker)) return null;
|
|
37
|
+
if ((driver.ownerId || null) !== (worker.ownerId || null) || currentActor.id !== (worker.ownerId || null)) return null;
|
|
38
|
+
var record = worker.sessionProvenance;
|
|
39
|
+
if (!record || record.kind !== "worker" || record.parentSessionOriginId !== driver.sessionOriginId || record.generation !== msg.generation) return null;
|
|
40
|
+
if (provenance.isWorker(driver) || !driver.sessionOriginId || !worker.sessionOriginId) return null;
|
|
41
|
+
if (ctx.osUsers) {
|
|
42
|
+
var driverLinux = ctx.getLinuxUserForSession(driver), workerLinux = ctx.getLinuxUserForSession(worker);
|
|
43
|
+
if (!driverLinux || driverLinux !== workerLinux || driverLinux !== currentActor.linuxUser) return null;
|
|
44
|
+
}
|
|
45
|
+
return { driver: driver, worker: worker };
|
|
46
|
+
}
|
|
47
|
+
// Share the exact owner/OS/provenance checks used by the human Worker reader.
|
|
48
|
+
function supervision(ws, driverOrigin) {
|
|
49
|
+
var user = actor(ws), driver = sessionForOrigin(driverOrigin);
|
|
50
|
+
if (!user || !driver || provenance.isWorker(driver) || (driver.ownerId || null) !== user.id || !ctx.canAccessSession(ws, driver)) throw new Error('Driver supervision access changed.');
|
|
51
|
+
if (ctx.osUsers && ctx.getLinuxUserForSession(driver) !== user.linuxUser) throw new Error('Driver OS identity changed.');
|
|
52
|
+
var linked = provenance.ownedWorkersByDriver(Array.from(sm.sessions.values()))[driver.localId] || [];
|
|
53
|
+
var group = ctx.getWorkerGroup && ctx.getWorkerGroup(driver.localId);
|
|
54
|
+
var splitRoles = require('./session-split-group-roles');
|
|
55
|
+
var roles = splitRoles.normalizePair(group && group.pair, group && group.members);
|
|
56
|
+
if (group && !roles.ok) throw new Error('Worker configuration is unavailable.');
|
|
57
|
+
var groupIdentity = JSON.stringify(group && [group.id, group.members, group.pair]);
|
|
58
|
+
var configured = roles.ok && roles.driverId === driver.localId ? roles.workerIds : [];
|
|
59
|
+
var bindings = linked.slice().sort(function (a, b) { return Number(configured.indexOf(b.localId) >= 0) - Number(configured.indexOf(a.localId) >= 0); }).slice(0, 100).map(function (worker) {
|
|
60
|
+
var input = { parentSessionOriginId: driverOrigin, workerSessionOriginId: worker.sessionOriginId, generation: worker.sessionProvenance.generation };
|
|
61
|
+
var pair = exactPair(ws, input, user);
|
|
62
|
+
if (!pair || pair.driver !== driver || pair.worker !== worker || worker.destroying || worker._pairGeneration != null && worker._pairGeneration !== input.generation) throw new Error('Exact Worker supervision identity is unavailable.');
|
|
63
|
+
return { input: input, worker: worker };
|
|
64
|
+
});
|
|
65
|
+
function revalidate() {
|
|
66
|
+
var latest = actor(ws);
|
|
67
|
+
if (!latest || latest.id !== user.id || latest.linuxUser !== user.linuxUser || sessionForOrigin(driverOrigin) !== driver) throw new Error('Driver supervision identity changed.');
|
|
68
|
+
if (!ctx.canAccessSession(ws, driver) || ctx.osUsers && ctx.getLinuxUserForSession(driver) !== user.linuxUser) throw new Error('Driver access or OS identity changed.');
|
|
69
|
+
var nextGroup = ctx.getWorkerGroup && ctx.getWorkerGroup(driver.localId);
|
|
70
|
+
if (JSON.stringify(nextGroup && [nextGroup.id, nextGroup.members, nextGroup.pair]) !== groupIdentity) throw new Error('Worker configuration changed.');
|
|
71
|
+
var nextLinked = provenance.ownedWorkersByDriver(Array.from(sm.sessions.values()))[driver.localId] || [];
|
|
72
|
+
if (linked.length !== nextLinked.length || linked.some(function (worker, i) { return worker !== nextLinked[i]; })) throw new Error('Linked Worker membership changed.');
|
|
73
|
+
bindings.forEach(function (binding) {
|
|
74
|
+
var pair = exactPair(ws, binding.input, latest);
|
|
75
|
+
if (!pair || pair.driver !== driver || pair.worker !== binding.worker || pair.worker.destroying || pair.worker._pairGeneration != null && pair.worker._pairGeneration !== binding.input.generation) throw new Error('Exact Worker generation changed.');
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return { revalidate: revalidate, read: function () {
|
|
79
|
+
revalidate();
|
|
80
|
+
return { observedAt: Date.now(), limit: 100, truncated: linked.length > bindings.length, workers: bindings.map(function (binding) {
|
|
81
|
+
var worker = binding.worker;
|
|
82
|
+
if (worker._pairDelegation && !splitRoles.tokenMatchesWorker(worker, worker._pairDelegation)) throw new Error('Delegated Worker generation changed.');
|
|
83
|
+
var activity = require('./project-pair-lifecycle-status').activityStatus(worker);
|
|
84
|
+
return { id: worker.localId, sessionOriginId: worker.sessionOriginId, parentSessionOriginId: driverOrigin, generation: binding.input.generation,
|
|
85
|
+
association: configured.indexOf(worker.localId) >= 0 ? 'current' : 'archived',
|
|
86
|
+
runtime: require('./session-activity-state').currentState(worker), delegated: activity.delegated,
|
|
87
|
+
currentTaskId: activity.currentTaskId, interrupted: !!activity.interruption, lastTurnInterrupted: activity.lastTurnInterrupted };
|
|
88
|
+
}) };
|
|
89
|
+
} };
|
|
90
|
+
}
|
|
91
|
+
function textContent(value) {
|
|
92
|
+
if (typeof value === "string") return value;
|
|
93
|
+
if (!Array.isArray(value)) return "";
|
|
94
|
+
return value.map(function (part) { return part && typeof part.text === "string" ? part.text : ""; }).join("\n");
|
|
95
|
+
}
|
|
96
|
+
function toolIdentity(entry) {
|
|
97
|
+
var id = entry.id || entry.tool_use_id || entry.toolId;
|
|
98
|
+
var name = entry.toolName || entry.name;
|
|
99
|
+
return { toolId: typeof id === "string" && id.length <= 160 ? id : null,
|
|
100
|
+
toolName: typeof name === "string" ? name.slice(0, 120) : null };
|
|
101
|
+
}
|
|
102
|
+
function record(history, index) {
|
|
103
|
+
var entry = history[index] || {}, type = entry.type;
|
|
104
|
+
if (type === "user_message") return { role: "user", text: textContent(entry.text || entry.content) };
|
|
105
|
+
if (type === "delta" || type === "result" || type === "assistant_message") return { role: "assistant", text: textContent(entry.text || entry.content) };
|
|
106
|
+
if (type === "tool_start" || type === "tool_use") return Object.assign({ role: "tool", text: "Used " + (toolIdentity(entry).toolName || "tool") }, toolIdentity(entry));
|
|
107
|
+
if (type === "tool_result") return Object.assign({ role: "tool", text: textContent(entry.content) || "No recorded tool output.", isError: entry.is_error === true }, toolIdentity(entry));
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function snapshot(history, before) {
|
|
111
|
+
var index, offset;
|
|
112
|
+
if (before && typeof before === "object" && Number.isInteger(before.index) && Number.isInteger(before.offset)) {
|
|
113
|
+
index = Math.min(before.index, history.length - 1); offset = Math.max(0, before.offset);
|
|
114
|
+
} else {
|
|
115
|
+
var end = Number.isInteger(before) && before >= 0 ? Math.min(before, history.length) : history.length;
|
|
116
|
+
index = end - 1;
|
|
117
|
+
var latest = index >= 0 ? record(history, index) : null;
|
|
118
|
+
offset = latest ? latest.text.length : 0;
|
|
119
|
+
}
|
|
120
|
+
var rows = [], budget = MAX_PAGE_TEXT, nextBefore = null;
|
|
121
|
+
while (index >= 0 && rows.length < MAX_MESSAGES && budget > 0) {
|
|
122
|
+
var item = record(history, index);
|
|
123
|
+
if (!item) { index -= 1; offset = index >= 0 && record(history, index) ? record(history, index).text.length : 0; continue; }
|
|
124
|
+
offset = Math.min(offset, item.text.length);
|
|
125
|
+
if (!item.text.length || !offset) { index -= 1; offset = index >= 0 && record(history, index) ? record(history, index).text.length : 0; continue; }
|
|
126
|
+
var metadataSize = (item.toolName || "").length + (item.toolId || "").length;
|
|
127
|
+
if (budget <= metadataSize) { nextBefore = { index: index, offset: offset }; break; }
|
|
128
|
+
var start = Math.max(0, offset - Math.min(MAX_TEXT, budget - metadataSize));
|
|
129
|
+
rows.push({ role: item.role, text: item.text.slice(start, offset), recordIndex: index, recordType: history[index].type,
|
|
130
|
+
start: start, end: offset, toolName: item.toolName || null, toolId: item.toolId || null, isError: item.isError === true });
|
|
131
|
+
budget -= offset - start + metadataSize;
|
|
132
|
+
if (start > 0) { nextBefore = { index: index, offset: start }; break; }
|
|
133
|
+
index -= 1;
|
|
134
|
+
if (index >= 0) { var prior = record(history, index); offset = prior ? prior.text.length : 0; }
|
|
135
|
+
}
|
|
136
|
+
rows.reverse();
|
|
137
|
+
if (!nextBefore && index >= 0) nextBefore = { index: index, offset: offset };
|
|
138
|
+
return { messages: rows, nextBefore: nextBefore, hasMore: nextBefore !== null };
|
|
139
|
+
}
|
|
140
|
+
// Follow recorded events forward without dropping an intervening busy burst.
|
|
141
|
+
function snapshotAfter(history, after) {
|
|
142
|
+
var rows = [], index = after.index, offset = after.offset, budget = MAX_PAGE_TEXT;
|
|
143
|
+
while (index < history.length && rows.length < MAX_MESSAGES && budget > 0) {
|
|
144
|
+
var item = record(history, index);
|
|
145
|
+
if (!item || offset >= item.text.length) { index++; offset = 0; continue; }
|
|
146
|
+
var metadataSize = (item.toolName || '').length + (item.toolId || '').length;
|
|
147
|
+
if (budget <= metadataSize) break;
|
|
148
|
+
var end = Math.min(item.text.length, offset + Math.min(MAX_TEXT, budget - metadataSize));
|
|
149
|
+
rows.push({ role: item.role, text: item.text.slice(offset, end), recordIndex: index, recordType: history[index].type,
|
|
150
|
+
start: offset, end: end, toolName: item.toolName || null, toolId: item.toolId || null, isError: item.isError === true });
|
|
151
|
+
budget -= end - offset + metadataSize;
|
|
152
|
+
offset = end;
|
|
153
|
+
}
|
|
154
|
+
while (index < history.length) {
|
|
155
|
+
var remaining = record(history, index);
|
|
156
|
+
if (remaining && offset < remaining.text.length) break;
|
|
157
|
+
index++; offset = 0;
|
|
158
|
+
}
|
|
159
|
+
return { messages: rows, hasMore: false, nextBefore: null, moreRecent: index < history.length };
|
|
160
|
+
}
|
|
161
|
+
function stopWatching(ws) {
|
|
162
|
+
var watch = watches.get(ws);
|
|
163
|
+
if (!watch) return;
|
|
164
|
+
watches.delete(ws);
|
|
165
|
+
clearTimeout(watch.timer);
|
|
166
|
+
if (watch.release) watch.release();
|
|
167
|
+
if (ws.removeListener) ws.removeListener('close', watch.close);
|
|
168
|
+
}
|
|
169
|
+
function watchWorker(ws, msg, pair) {
|
|
170
|
+
stopWatching(ws);
|
|
171
|
+
msg = { requestId: msg.requestId.slice(0, 160), parentSessionOriginId: pair.driver.sessionOriginId,
|
|
172
|
+
workerSessionOriginId: pair.worker.sessionOriginId, generation: msg.generation };
|
|
173
|
+
var watch = { worker: pair.worker, requestId: msg.requestId, timer: null };
|
|
174
|
+
watch.close = function () { stopWatching(ws); };
|
|
175
|
+
watch.notify = function () {
|
|
176
|
+
if (watch.timer) return;
|
|
177
|
+
watch.timer = setTimeout(function () {
|
|
178
|
+
watch.timer = null;
|
|
179
|
+
var user = actor(ws), latest = user && exactPair(ws, msg, user);
|
|
180
|
+
if (ws.readyState !== 1 || !latest || latest.worker !== pair.worker || latest.driver !== pair.driver) { stopWatching(ws); return; }
|
|
181
|
+
send(ws, { type: 'split_worker_conversation_changed', subscriptionId: msg.requestId,
|
|
182
|
+
parentSessionOriginId: msg.parentSessionOriginId, workerSessionOriginId: msg.workerSessionOriginId,
|
|
183
|
+
generation: msg.generation, isProcessing: pair.worker.isProcessing === true });
|
|
184
|
+
}, 32);
|
|
185
|
+
};
|
|
186
|
+
watch.release = sm.subscribeSession(pair.worker.localId, watch.notify);
|
|
187
|
+
watches.set(ws, watch);
|
|
188
|
+
if (ws.once) ws.once('close', watch.close);
|
|
189
|
+
}
|
|
190
|
+
function handleMessage(ws, msg) {
|
|
191
|
+
if (!msg || msg.type !== "split_worker_conversation_request") return false;
|
|
192
|
+
var requestId = typeof msg.requestId === "string" ? msg.requestId.slice(0, 160) : "";
|
|
193
|
+
var response = { type: "split_worker_conversation", requestId: requestId, pageBefore: msg.before == null ? null : msg.before,
|
|
194
|
+
parentSessionOriginId: msg.parentSessionOriginId || null, workerSessionOriginId: msg.workerSessionOriginId || null,
|
|
195
|
+
generation: msg.generation || null, messages: [], hasMore: false, nextBefore: null };
|
|
196
|
+
if (msg.watch === false) {
|
|
197
|
+
var watch = watches.get(ws);
|
|
198
|
+
if (watch && watch.requestId === msg.requestId && watch.worker.sessionOriginId === msg.workerSessionOriginId) stopWatching(ws);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
var current = actor(ws), pair = current && exactPair(ws, msg, current);
|
|
202
|
+
if (!requestId || !pair) response.error = "This Worker conversation is no longer available.";
|
|
203
|
+
else {
|
|
204
|
+
if (msg.after != null) {
|
|
205
|
+
var after = msg.after;
|
|
206
|
+
if (msg.before != null || !after || !Number.isInteger(after.index) || after.index < 0 || after.index >= pair.worker.history.length ||
|
|
207
|
+
!Number.isInteger(after.offset) || after.offset < 0) response.error = 'Worker activity cursor is invalid.';
|
|
208
|
+
else Object.assign(response, snapshotAfter(pair.worker.history || [], after));
|
|
209
|
+
} else {
|
|
210
|
+
var before = msg.before == null ? pair.worker.history.length : msg.before;
|
|
211
|
+
if (!(Number.isInteger(before) && before >= 0) && !(before && typeof before === "object" && Number.isInteger(before.index) && Number.isInteger(before.offset) && before.index >= 0 && before.offset >= 0)) response.error = "Worker conversation page is invalid.";
|
|
212
|
+
else Object.assign(response, snapshot(pair.worker.history || [], before));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!response.error && msg.watch === true) watchWorker(ws, msg, pair);
|
|
216
|
+
send(ws, response);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
return { handleMessage: handleMessage, supervision: supervision };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = { attachProjectWorkerConversation: attachProjectWorkerConversation };
|
|
@@ -50,9 +50,8 @@ function attachWorkerCreationProposal(ctx) {
|
|
|
50
50
|
if (!roles || roles.driver !== session || roles.role.kind === "adhoc") {
|
|
51
51
|
return { error: "The exact Driver/Split Worker membership is not eligible for another Worker." };
|
|
52
52
|
}
|
|
53
|
-
if (roles.workers.length
|
|
54
|
-
if (roles.workers.
|
|
55
|
-
if (!session.sessionOriginId || !roles.workers[0].sessionOriginId || !Number.isInteger(generationOf(roles.workers[0]))) {
|
|
53
|
+
if (roles.workers.length < 1) return { error: "The existing Split Worker membership is invalid." };
|
|
54
|
+
if (!session.sessionOriginId || roles.workers.some(function (worker) { return !worker.sessionOriginId || !Number.isInteger(generationOf(worker)); })) {
|
|
56
55
|
return { error: "The exact Driver and Split Worker identities are not durable enough to add another Worker." };
|
|
57
56
|
}
|
|
58
57
|
return {
|
|
@@ -79,7 +78,7 @@ function attachWorkerCreationProposal(ctx) {
|
|
|
79
78
|
var group = store.groupForMember(session.localId);
|
|
80
79
|
if (!group || group.id !== snapshot.groupId || requireSameObject && group !== snapshot.group) return { ok: false };
|
|
81
80
|
var roles = splitRoles.resolveLiveRoles(sm, group);
|
|
82
|
-
if (!roles || roles.role.kind === "adhoc" || roles.driver !== session || roles.role.workerIds.length
|
|
81
|
+
if (!roles || roles.role.kind === "adhoc" || roles.driver !== session || roles.role.workerIds.length < 1 ||
|
|
83
82
|
roles.role.workerIds.join(",") !== snapshot.workerIds.join(",")) return { ok: false };
|
|
84
83
|
for (var i = 0; i < snapshot.workers.length; i++) {
|
|
85
84
|
var expected = snapshot.workers[i];
|
|
@@ -463,7 +463,7 @@ function attachWorkerProposal(ctx) {
|
|
|
463
463
|
|
|
464
464
|
function getSystemPrompt(session) {
|
|
465
465
|
if (creationProposal.canMountProposal(session) && !isEligible(session)) {
|
|
466
|
-
return "This Driver may configure
|
|
466
|
+
return "This Driver may configure additional Split Workers through propose_worker. With multiple Workers, pass workerId to every targeted partner, queue, interrupt, replacement, close, permission, and targeted status operation. Call partner_status without workerId to discover current Worker handles. Give each Worker independent file ownership and bounded work. Do not assign simultaneous overlapping file edits unless the work is explicitly separated, such as by distinct paths or worktrees.";
|
|
467
467
|
}
|
|
468
468
|
if (!isEligible(session) || !driverOrchestration.isHighTierDriverSession(session, sm)) return "";
|
|
469
469
|
return "For implementation-heavy work, use propose_worker before substantial execution. Work is implementation-heavy when it spans multiple modules, crosses client/server/data boundaries, requires a migration, or contains independent investigation and execution. Do not skip delegation merely because you can implement it yourself; preserve the Driver for decomposition, runtime selection, judgment, review, and integration. The user's explicit request to work directly takes precedence, and work that must remain sequential in this session stays here. Recommend an exact available vendor, model, and effort, and give a concise recommendationRationale explaining why all three fit the task. Clay always records and shows the runtime configuration card. In full-access mode Clay may auto-accept that exact validated recommendation; otherwise it waits for the user's explicit choice. After posting, end the turn. If accepted, Clay creates the exact Driver/Split Worker pair and delegates the proposed task once. If declined, continue in this Driver session. Do not call send_to_partner while unpaired.";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
var crypto = require('crypto');
|
|
2
|
+
var provenance = require('./session-provenance');
|
|
3
|
+
|
|
4
|
+
function active(worker) {
|
|
5
|
+
return !!(worker.isProcessing || worker._queryStarting || worker._responseRecoveryStopPending);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Human socket endpoint only. Receipts expire after one minute and are never replayed.
|
|
9
|
+
function attachWorkerRemoval(ctx) {
|
|
10
|
+
var receipts = new WeakMap();
|
|
11
|
+
function exact(ws, msg) {
|
|
12
|
+
if (ctx.isMate || !ctx.clients.has(ws) || ws.readyState !== 1 || ws._clayPaneActivation || msg.projectSlug !== ctx.slug) throw new Error('The project connection changed.');
|
|
13
|
+
var user = ws._clayUser && ctx.usersModule.findUserById(ws._clayUser.id);
|
|
14
|
+
if (ctx.usersModule.isMultiUser() && (!user || !ctx.usersModule.canAccessProject(user.id, ctx.getProjectAccess()))) throw new Error('Project access is unavailable.');
|
|
15
|
+
var owner = user && user.id || null;
|
|
16
|
+
if (msg.ownerId !== owner || !ctx.sessionDelete.hasDeletePermission(ws)) throw new Error('Worker removal is not permitted.');
|
|
17
|
+
var driver = ctx.sm.sessions.get(msg.driverId), worker = ctx.sm.sessions.get(msg.workerId);
|
|
18
|
+
if (!driver || !worker || driver === worker || driver.destroying || worker.destroying || driver.hidden || worker.hidden || provenance.isWorker(driver) ||
|
|
19
|
+
driver.sessionOriginId !== msg.driverOriginId || worker.sessionOriginId !== msg.workerOriginId ||
|
|
20
|
+
!msg.driverOriginId || !msg.workerOriginId || (driver.ownerId || null) !== owner || (worker.ownerId || null) !== owner) throw new Error('The exact Driver or Worker changed.');
|
|
21
|
+
var matches = 0;
|
|
22
|
+
ctx.sm.sessions.forEach(function (session) { if (session.sessionOriginId === msg.driverOriginId || session.sessionOriginId === msg.workerOriginId) matches++; });
|
|
23
|
+
var record = worker.sessionProvenance;
|
|
24
|
+
if (matches !== 2 || !record || record.kind !== 'worker' || record.parentSessionOriginId !== driver.sessionOriginId ||
|
|
25
|
+
!Number.isInteger(msg.generation) || msg.generation < 1 || record.generation !== msg.generation ||
|
|
26
|
+
worker._pairGeneration && worker._pairGeneration !== msg.generation) throw new Error('The Worker generation changed.');
|
|
27
|
+
if (ctx.osUsers) {
|
|
28
|
+
var linux = user && user.linuxUser;
|
|
29
|
+
if (!linux || !ctx.getOsUserInfoForWs(ws) || ctx.getLinuxUserForSession(driver) !== linux || ctx.getLinuxUserForSession(worker) !== linux) throw new Error('The OS identity changed.');
|
|
30
|
+
}
|
|
31
|
+
if (user && (!ctx.usersModule.getEffectivePermissions(user, ctx.osUsers).sessionDelete ||
|
|
32
|
+
!ctx.usersModule.canAccessSession(user.id, driver, ctx.getProjectAccess()))) throw new Error('Driver access or removal permission changed.');
|
|
33
|
+
var plan = ctx.sessionDelete.planDeletion(ws, [worker], ctx.getProjectAccess());
|
|
34
|
+
if (!plan.ok || plan.ids.length !== 1 || plan.ids[0] !== worker.localId) throw new Error(plan.error || 'The exact removal scope changed.');
|
|
35
|
+
return { driver: driver, worker: worker, plan: plan };
|
|
36
|
+
}
|
|
37
|
+
function respond(ws, msg, status, error, token) {
|
|
38
|
+
ctx.sendTo(ws, { type: 'split_worker_remove_result', requestId: msg.requestId, projectSlug: ctx.slug,
|
|
39
|
+
workerOriginId: msg.workerOriginId, generation: msg.generation, status: status, error: error || null, token: token || null });
|
|
40
|
+
}
|
|
41
|
+
async function perform(ws, msg, receipt) {
|
|
42
|
+
try {
|
|
43
|
+
var pair = exact(ws, msg), worker = pair.worker, driver = pair.driver;
|
|
44
|
+
if (receipt.driver && (receipt.driver !== driver || receipt.worker !== worker)) throw new Error('The reviewed sessions were replaced.');
|
|
45
|
+
if (receipt.confirmed && (receipt.query !== worker.queryInstance || receipt.delegation !== worker._pairDelegation ||
|
|
46
|
+
receipt.stopRevision !== worker._humanStopRevision || receipt.driverStopRevision !== driver._humanStopRevision)) throw new Error('The reviewed Worker execution changed.');
|
|
47
|
+
var sourceSession = ws._clayActiveSession;
|
|
48
|
+
if (receipt.sourceSession !== sourceSession) throw new Error('The source conversation changed.');
|
|
49
|
+
if (active(worker)) {
|
|
50
|
+
if (!receipt.confirmed) {
|
|
51
|
+
receipt.driver = driver; receipt.worker = worker; receipt.query = worker.queryInstance; receipt.delegation = worker._pairDelegation;
|
|
52
|
+
receipt.stopRevision = worker._humanStopRevision; receipt.driverStopRevision = driver._humanStopRevision;
|
|
53
|
+
receipt.token = crypto.randomUUID(); receipt.status = 'confirmation_required';
|
|
54
|
+
respond(ws, msg, receipt.status, null, receipt.token); return;
|
|
55
|
+
}
|
|
56
|
+
var runtime = worker.queryInstance, process = worker.worker;
|
|
57
|
+
ctx.actions.stop(worker, ws._clayUser || null);
|
|
58
|
+
var stopRevision = worker._humanStopRevision, driverRevision = driver._humanStopRevision;
|
|
59
|
+
var deadline = Date.now() + (ctx.stopTimeoutMs || 5000);
|
|
60
|
+
while (active(worker)) {
|
|
61
|
+
if (Date.now() >= deadline) throw new Error('Stop completion is unknown. The Worker session was retained.');
|
|
62
|
+
await new Promise(function (resolve) { setTimeout(resolve, 25); });
|
|
63
|
+
var current = exact(ws, msg);
|
|
64
|
+
if (receipt.cancelled || current.worker !== worker || current.driver !== driver || ws._clayActiveSession !== sourceSession ||
|
|
65
|
+
worker._humanStopRevision !== stopRevision || driver._humanStopRevision !== driverRevision ||
|
|
66
|
+
worker.queryInstance && worker.queryInstance !== runtime || worker.worker && worker.worker !== process) throw new Error('Removal interrupted by a changed execution or source. The session was retained.');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var finalPair = exact(ws, msg);
|
|
70
|
+
if (receipt.cancelled || finalPair.worker !== worker || finalPair.driver !== driver || ws._clayActiveSession !== sourceSession) throw new Error('Removal was cancelled or its target changed.');
|
|
71
|
+
ctx.sessionDelete.executeOwnedDeletion(finalPair.plan, { requireDurableRemoval: true });
|
|
72
|
+
if (ctx.sm.sessions.has(worker.localId)) throw new Error('Worker removal was not observed.');
|
|
73
|
+
receipt.status = 'removed'; respond(ws, msg, 'removed');
|
|
74
|
+
} catch (error) { receipt.status = 'failed'; respond(ws, msg, 'failed', error.message || String(error)); }
|
|
75
|
+
}
|
|
76
|
+
function handleMessage(ws, msg) {
|
|
77
|
+
if (!msg || msg.type !== 'split_worker_remove') return false;
|
|
78
|
+
if (typeof msg.requestId !== 'string' || !/^[a-zA-Z0-9_-]{1,100}$/.test(msg.requestId)) return true;
|
|
79
|
+
var records = receipts.get(ws);
|
|
80
|
+
if (!records) { records = new Map(); receipts.set(ws, records); }
|
|
81
|
+
records.forEach(function (value, key) { if (value.expires < Date.now() && value.status !== 'removing') records.delete(key); });
|
|
82
|
+
var receipt = records.get(msg.requestId);
|
|
83
|
+
var fingerprint = JSON.stringify([msg.projectSlug, msg.ownerId, msg.driverId, msg.driverOriginId, msg.workerId, msg.workerOriginId, msg.generation]);
|
|
84
|
+
if (msg.action === 'cancel') { if (receipt && receipt.fingerprint === fingerprint) receipt.cancelled = true; return true; }
|
|
85
|
+
if (receipt) {
|
|
86
|
+
if (receipt.status === 'removing' && receipt.fingerprint === fingerprint) return true;
|
|
87
|
+
if (msg.action !== 'confirm' || receipt.status !== 'confirmation_required' || receipt.expires < Date.now() || receipt.cancelled ||
|
|
88
|
+
receipt.fingerprint !== fingerprint || receipt.token !== msg.token) { respond(ws, msg, 'failed', 'This removal request is stale or already used.'); return true; }
|
|
89
|
+
receipt.confirmed = true;
|
|
90
|
+
} else {
|
|
91
|
+
if (msg.action !== 'request' || records.size >= 32) { respond(ws, msg, 'failed', 'Start a fresh removal request.'); return true; }
|
|
92
|
+
receipt = { fingerprint: fingerprint, sourceSession: ws._clayActiveSession, expires: Date.now() + 60000, confirmed: false };
|
|
93
|
+
records.set(msg.requestId, receipt);
|
|
94
|
+
}
|
|
95
|
+
receipt.status = 'removing';
|
|
96
|
+
receipt.promise = perform(ws, msg, receipt);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return { handleMessage: handleMessage, exact: exact };
|
|
100
|
+
}
|
|
101
|
+
module.exports = { attachWorkerRemoval: attachWorkerRemoval, active: active };
|
package/lib/project.js
CHANGED
|
@@ -71,6 +71,8 @@ var { attachProjectLogs } = require("./project-logs");
|
|
|
71
71
|
var { attachProjectLogFeedbackDelivery } = require("./project-log-feedback-delivery");
|
|
72
72
|
var { attachProjectMateKnowledge } = require("./project-mate-knowledge");
|
|
73
73
|
var { attachSplitGroups } = require("./session-split-groups");
|
|
74
|
+
var { attachProjectSplitViews } = require("./project-split-views");
|
|
75
|
+
var { attachProjectWorkerConversation } = require("./project-worker-conversation");
|
|
74
76
|
var multiWorkerFeature = require("./multi-worker-feature");
|
|
75
77
|
var toolControlMcp = require("./tool-control-mcp-server");
|
|
76
78
|
var capsuleFloor = require("./capsule-display-floor");
|
|
@@ -696,6 +698,24 @@ function createProjectContext(opts) {
|
|
|
696
698
|
}
|
|
697
699
|
},
|
|
698
700
|
});
|
|
701
|
+
var _splitViews = attachProjectSplitViews({
|
|
702
|
+
sm: sm, slug: slug, clients: clients, sendTo: sendTo, usersModule: usersModule,
|
|
703
|
+
isMate: isMate, osUsers: osUsers,
|
|
704
|
+
canAccessProject: function (ws) { return _requestAccess.canAccessProject(ws, slug); },
|
|
705
|
+
canAccessSession: function (ws, session) { return _requestAccess.canReadSession(ws, session); },
|
|
706
|
+
getLinuxUserForSession: getLinuxUserForSession,
|
|
707
|
+
getOsUserInfoForWs: getOsUserInfoForWs,
|
|
708
|
+
});
|
|
709
|
+
var _workerConversation = attachProjectWorkerConversation({
|
|
710
|
+
getWorkerGroup: function (id) { return _splitGroups.store.groupForMember(id); },
|
|
711
|
+
sm: sm, slug: slug, clients: clients, sendTo: sendTo, usersModule: usersModule,
|
|
712
|
+
isMate: isMate,
|
|
713
|
+
canAccessProject: function (ws) { return _requestAccess.canAccessProject(ws, slug); },
|
|
714
|
+
canAccessSession: function (ws, session) { return _requestAccess.canReadSession(ws, session); },
|
|
715
|
+
osUsers: osUsers,
|
|
716
|
+
getLinuxUserForSession: getLinuxUserForSession,
|
|
717
|
+
getOsUserInfoForWs: getOsUserInfoForWs,
|
|
718
|
+
});
|
|
699
719
|
|
|
700
720
|
var _projMode = typeof opts.onGetProjectDefaultMode === "function" ? opts.onGetProjectDefaultMode(slug) : null;
|
|
701
721
|
var _srvMode = typeof opts.onGetServerDefaultMode === "function" ? opts.onGetServerDefaultMode() : null;
|
|
@@ -1859,7 +1879,7 @@ function createProjectContext(opts) {
|
|
|
1859
1879
|
if (_studioPresentation.handleMessage(ws, msg)) return;
|
|
1860
1880
|
|
|
1861
1881
|
// --- DM messages (delegated to server-level handler) ---
|
|
1862
|
-
if (msg.type === "home_mate_question_response" || msg.type === "mate_context_prepare" || msg.type === "clay_ui_ready" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "clay_navigation_rendered" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
|
|
1882
|
+
if (msg.type === "home_mate_question_response" || msg.type === "mate_context_prepare" || msg.type === "clay_ui_ready" || msg.type === "clay_view_observed" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "clay_navigation_rendered" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
|
|
1863
1883
|
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1864
1884
|
return;
|
|
1865
1885
|
}
|
|
@@ -1968,6 +1988,8 @@ function createProjectContext(opts) {
|
|
|
1968
1988
|
if (_sessionHandoff.handleMessage(ws, msg)) return;
|
|
1969
1989
|
if (_driverContinuation.handleMessage(ws, msg)) return;
|
|
1970
1990
|
if (_splitGroups.handleMessage(ws, msg)) return;
|
|
1991
|
+
if (_splitViews.handleMessage(ws, msg)) return;
|
|
1992
|
+
if (_workerConversation.handleMessage(ws, msg)) return;
|
|
1971
1993
|
if (_models.handleMessage(ws, msg)) return;
|
|
1972
1994
|
if (_scheduledTasks.handleMessage(ws, msg)) return;
|
|
1973
1995
|
if (_loopInterview.handleMessage(ws, msg)) return;
|
|
@@ -2155,6 +2177,7 @@ function createProjectContext(opts) {
|
|
|
2155
2177
|
});
|
|
2156
2178
|
sm.setSessionHydrator(_sessionSkillCatalog.hydrate);
|
|
2157
2179
|
var _sessions = attachSessions({
|
|
2180
|
+
onSessionFoldersChanged: function (accountId) { _splitViews.onFoldersChanged(accountId); },
|
|
2158
2181
|
splitStore: _splitGroups.store,
|
|
2159
2182
|
adapters: adapters,
|
|
2160
2183
|
isPendingQueuePaused: function (session) { return _pendingMessageQueue.isPaused(session); },
|
|
@@ -2767,6 +2790,8 @@ function createProjectContext(opts) {
|
|
|
2767
2790
|
_mcp: _mcp,
|
|
2768
2791
|
_notifications: _notifications,
|
|
2769
2792
|
_splitGroups: _splitGroups,
|
|
2793
|
+
_splitViews: _splitViews,
|
|
2794
|
+
_workerConversation: _workerConversation,
|
|
2770
2795
|
_vendorLogin: _vendorLogin,
|
|
2771
2796
|
autonomousRun: _autonomousRun,
|
|
2772
2797
|
resolveSessionForView: _sessions.resolveSessionForView,
|
|
@@ -3031,6 +3056,8 @@ function createProjectContext(opts) {
|
|
|
3031
3056
|
},
|
|
3032
3057
|
cancelMateSchedule: function (session, record) { return _mateLifecycle.cancel(session, record); },
|
|
3033
3058
|
getSessionManager: function () { return sm; },
|
|
3059
|
+
getSplitViewControl: _splitViews.forConnection,
|
|
3060
|
+
getViewWorkerSupervision: _workerConversation.supervision,
|
|
3034
3061
|
createDelegatedSession: _delegatedSession.createDelegatedSession,
|
|
3035
3062
|
refreshEnvironmentRuntime: function () { return sdk.refreshEnvironmentRuntime(); },
|
|
3036
3063
|
getWorkspaceProductOperations: function () { return isMate ? null : _workspaceOperations; },
|
package/lib/public/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { initializeAfterPaneReady } from './modules/pane-deferred-bootstrap.js';
|
|
1
2
|
import { initStudioBridge } from './modules/clay-studio-bridge.js';
|
|
2
3
|
import { initSharedBrowser } from './modules/shared-browser.js';
|
|
3
4
|
import { initTopbarWorkbenchControls } from './modules/topbar-workbench-controls.js';
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
} from './modules/sidebar-mobile.js';
|
|
28
29
|
import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
|
|
29
30
|
import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
|
|
30
|
-
import { initMateKnowledgeWorkbench } from './modules/
|
|
31
|
+
import { initMateKnowledgeWorkbench } from './modules/knowledge-workbench-entry.js';
|
|
31
32
|
import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
|
|
32
33
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton, onRewindComplete, onRewindError } from './modules/rewind.js';
|
|
33
34
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
@@ -114,6 +115,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
114
115
|
// --- Base path for multi-project routing ---
|
|
115
116
|
var bootParams = new URLSearchParams(location.search);
|
|
116
117
|
var paneMode = bootParams.get("pane") === "1";
|
|
118
|
+
if (paneMode && typeof performance !== "undefined" && performance.mark) performance.mark("clay:app-evaluated");
|
|
117
119
|
var paneSessionId = Number(bootParams.get("session"));
|
|
118
120
|
if (!Number.isSafeInteger(paneSessionId) || paneSessionId < 1) paneSessionId = null;
|
|
119
121
|
if (paneMode) document.body.classList.add("pane-mode");
|
|
@@ -307,6 +309,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
307
309
|
panePinPending: paneMode && !!paneSessionId,
|
|
308
310
|
paneCommittedBinding: null, panePendingBinding: null, paneReady: false, paneSuspended: false,
|
|
309
311
|
paneActivationGeneration: 0, paneActivationTimer: null, panePointerBinding: null, paneRichDrafts: {},
|
|
312
|
+
paneLayoutScrollLock: null,
|
|
313
|
+
paneLastUserScrollTop: null,
|
|
314
|
+
paneUserScrollIntent: null,
|
|
315
|
+
paneUserScrollRevision: 0,
|
|
310
316
|
sessionRichDrafts: {},
|
|
311
317
|
sessionActionEpoch: 0, composerUploadingCount: 0, deferredSessionSwitch: null,
|
|
312
318
|
paneCallbackBinding: null, paneActionEvents: [], paneHeldKeys: {}, rewindAction: null, handoffAction: null,
|
|
@@ -317,6 +323,27 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
317
323
|
paneBindingSessions: [],
|
|
318
324
|
splitPanes: null,
|
|
319
325
|
splitGroups: [],
|
|
326
|
+
splitViewWorkerConversation: null,
|
|
327
|
+
splitViewWorkerRemoval: null,
|
|
328
|
+
projectSplitViews: [],
|
|
329
|
+
projectSplitViewRevision: 0,
|
|
330
|
+
projectSplitViewContextKey: null,
|
|
331
|
+
projectSplitViewHydrated: false,
|
|
332
|
+
projectSplitViewPending: null,
|
|
333
|
+
projectSplitViewError: "",
|
|
334
|
+
projectSplitViewActiveId: null,
|
|
335
|
+
projectSplitViewFocusedOrigin: null,
|
|
336
|
+
projectSplitViewPreviewTree: null,
|
|
337
|
+
projectSplitViewDrag: null,
|
|
338
|
+
splitViewOrganizationDrag: null,
|
|
339
|
+
projectSplitViewDividerDrag: null,
|
|
340
|
+
projectSplitViewTranscriptScroll: {},
|
|
341
|
+
projectSplitViewSuppressClick: null,
|
|
342
|
+
projectSplitViewSocketGeneration: 0,
|
|
343
|
+
projectSplitViewRequestSocket: null,
|
|
344
|
+
projectSplitViewRequestGeneration: null,
|
|
345
|
+
projectSplitViewRequestOwnerId: null,
|
|
346
|
+
projectSplitViewRequestSlug: null,
|
|
320
347
|
sessionFolders: null,
|
|
321
348
|
sessionFoldersSlug: null,
|
|
322
349
|
sessionFolderCreate: null,
|
|
@@ -763,19 +790,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
763
790
|
// buttons exist in the DOM when sidebar.js (and later terminal.js /
|
|
764
791
|
// mcp-ui.js / etc.) attach click handlers by ID.
|
|
765
792
|
initToolPalettes();
|
|
766
|
-
initProjectLogs
|
|
767
|
-
initYou();
|
|
768
|
-
initYouActivity();
|
|
769
|
-
initIssues();
|
|
770
|
-
initIssuesContext();
|
|
771
|
-
initIssueLinks();
|
|
772
|
-
initScheduledTasks();
|
|
773
|
-
initDebatesWorkbench();
|
|
793
|
+
initializeAfterPaneReady([initProjectLogs, initYou, initYouActivity, initIssues, initIssuesContext, initIssueLinks, initScheduledTasks, initDebatesWorkbench]);
|
|
774
794
|
initSidebar(sidebarCtx);
|
|
775
795
|
var wsGetter = function () { return _getWsRef(); };
|
|
776
796
|
initMateSidebar(wsGetter);
|
|
777
797
|
initMateKnowledge(wsGetter);
|
|
778
|
-
initMateKnowledgeWorkbench
|
|
798
|
+
initializeAfterPaneReady([initMateKnowledgeWorkbench]);
|
|
779
799
|
initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
|
|
780
800
|
initCommandPalette({
|
|
781
801
|
switchProject: function (slug) { switchProject(slug); },
|
|
@@ -1289,9 +1309,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1289
1309
|
fileTreeEl: $("file-tree"),
|
|
1290
1310
|
fileViewerEl: $("file-viewer"),
|
|
1291
1311
|
});
|
|
1292
|
-
initGitPanel
|
|
1293
|
-
initTopbarWorkbenchControls();
|
|
1294
|
-
initSharedBrowser();
|
|
1312
|
+
initializeAfterPaneReady([initGitPanel, initTopbarWorkbenchControls, initSharedBrowser]);
|
|
1295
1313
|
initStudioBridge();
|
|
1296
1314
|
initWorkerPaneLock();
|
|
1297
1315
|
initPaneActivation();
|
|
@@ -1502,6 +1520,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1502
1520
|
|
|
1503
1521
|
// --- Init ---
|
|
1504
1522
|
lucide.createIcons();
|
|
1523
|
+
if (paneMode && typeof performance !== "undefined" && performance.mark) performance.mark("clay:bootstrap-complete");
|
|
1505
1524
|
connect();
|
|
1506
1525
|
if (slugMatch && location.hash === "#scheduler") {
|
|
1507
1526
|
// Restore scheduler view after refresh
|
package/lib/public/css/base.css
CHANGED
|
@@ -278,6 +278,15 @@ html, body {
|
|
|
278
278
|
overflow: hidden;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/* Seat the content corner inside the connected navigation/sidebar surround. */
|
|
282
|
+
@media (min-width: 769px) {
|
|
283
|
+
body:not(.pane-mode) #main-area { background: var(--sidebar-bg); }
|
|
284
|
+
body:not(.pane-mode) #main-column {
|
|
285
|
+
background: var(--bg);
|
|
286
|
+
border-top-left-radius: 12px;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
281
290
|
/* --- Main Panels (app + file-viewer + terminal side-by-side) --- */
|
|
282
291
|
#main-panels {
|
|
283
292
|
flex: 1;
|
|
@@ -391,7 +391,6 @@
|
|
|
391
391
|
/* --- Panel fullscreen --- */
|
|
392
392
|
@media (min-width: 1024px) {
|
|
393
393
|
/* Browser, Terminal and Files expand below the session title; see right-workbench.css. */
|
|
394
|
-
#main-column:has(.panel-fullscreen:not(.hidden):not(#shared-browser-panel, #terminal-container, #file-viewer)) > .title-bar-content { display: none; }
|
|
395
394
|
#main-panels:has(.panel-fullscreen:not(.hidden)) > #app { display: none; }
|
|
396
395
|
}
|
|
397
396
|
@media (max-width: 1023px) {
|