clay-server 5.3.0-beta.1 → 5.3.0-beta.3
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/project-worker-conversation.js +67 -3
- package/lib/public/css/pane.css +0 -36
- package/lib/public/css/session-folders.css +1 -1
- package/lib/public/css/split-view-layout.css +7 -3
- package/lib/public/modules/app-favicon.js +1 -1
- package/lib/public/modules/app-message-cards.js +1 -1
- package/lib/public/modules/app-messages.js +9 -3
- package/lib/public/modules/app-projects.js +2 -1
- package/lib/public/modules/app-rendering.js +2 -2
- package/lib/public/modules/chat-render-runtime.js +54 -5
- package/lib/public/modules/filebrowser.js +6 -2
- package/lib/public/modules/markdown-view-mode.js +34 -0
- package/lib/public/modules/pane-file-bridge.js +8 -3
- package/lib/public/modules/session-folder-collapse.js +2 -1
- package/lib/public/modules/session-folder-dnd.js +4 -1
- package/lib/public/modules/split-view-presentation-drag.js +24 -0
- package/lib/public/modules/split-view-presentations.js +5 -2
- package/lib/public/modules/split-view-worker-activity.js +0 -56
- package/lib/public/modules/split-view-worker-transcript.js +19 -11
- package/lib/public/modules/split-view-workers.js +78 -12
- package/lib/public/modules/worker-created-presentation.js +37 -0
- package/lib/public/pane-assets/{app-OJEQXBBS.js → app-K4LGSKX5.js} +1 -1
- package/lib/public/pane-assets/{chunk-4SMIVAB4.js → chunk-6AJEGTWL.js} +1 -1
- package/lib/public/pane-assets/{chunk-I5P3MWQG.js → chunk-AILOLDZA.js} +1 -1
- package/lib/public/pane-assets/{chunk-UVGOPFEX.js → chunk-BYSBUZDZ.js} +1 -1
- package/lib/public/pane-assets/{chunk-623V5CEO.js → chunk-QJPBAN5H.js} +1 -1
- package/lib/public/pane-assets/{chunk-GML2R5JE.js → chunk-W34XSARI.js} +98 -98
- package/lib/public/pane-assets/manifest.json +330 -329
- package/lib/public/pane-assets/style-JVCOCG3W.css +1 -0
- package/lib/public/style.css +0 -1
- package/lib/ws-schema.js +1 -0
- package/package.json +1 -1
- package/lib/public/css/split-view-worker-activity.css +0 -97
- package/lib/public/pane-assets/style-GN7R6XKV.css +0 -1
|
@@ -20,71 +20,15 @@ export function workerActivityLabel(message, names) {
|
|
|
20
20
|
WebSearch: 'Search the web', WebFetch: 'Read web page' };
|
|
21
21
|
return definition ? definition.noun : (Object.prototype.hasOwnProperty.call(generic, name) ? generic[name] : null) || (typeof name === 'string' ? name.slice(0, 120) : 'Tool activity');
|
|
22
22
|
}
|
|
23
|
-
function workerActivityActionIcon(message, names) {
|
|
24
|
-
var match = message.toolId && names[message.toolId];
|
|
25
|
-
var name = message.toolName || match && match.name;
|
|
26
|
-
var definition = clayToolDefinition(name);
|
|
27
|
-
if (definition && typeof definition.icon === 'string' && definition.icon) return definition.icon;
|
|
28
|
-
var generic = { Read: 'file-text', read_file: 'file-text', Edit: 'pencil', Write: 'file-plus-2',
|
|
29
|
-
Bash: 'square-terminal', exec_command: 'square-terminal', Grep: 'search', Glob: 'search', WebSearch: 'globe', WebFetch: 'globe' };
|
|
30
|
-
return Object.prototype.hasOwnProperty.call(generic, name) ? generic[name] : null;
|
|
31
|
-
}
|
|
32
|
-
export function appendWorkerActivity(body, messages, names) {
|
|
33
|
-
var group = document.createElement('section'); group.className = 'worker-activity-group';
|
|
34
|
-
var key = String(messages[messages.length - 1].recordIndex);
|
|
35
|
-
group.dataset.activityKey = key;
|
|
36
|
-
var toggle = document.createElement('button'); toggle.type = 'button'; toggle.className = 'worker-activity-summary';
|
|
37
|
-
toggle.dataset.disclosureKey = 'activity-' + key;
|
|
38
|
-
var activityIcon = document.createElement('i'); activityIcon.setAttribute('data-lucide', 'activity'); activityIcon.setAttribute('aria-hidden', 'true');
|
|
39
|
-
var heading = document.createElement('span'); heading.className = 'worker-activity-heading'; heading.textContent = 'Activity';
|
|
40
|
-
var count = document.createElement('span'); count.className = 'worker-activity-count'; count.textContent = messages.length + (messages.length === 1 ? ' recorded step' : ' recorded steps');
|
|
41
|
-
var chevron = document.createElement('i'); chevron.className = 'worker-activity-chevron';
|
|
42
|
-
chevron.setAttribute('data-lucide', 'chevron-down'); chevron.setAttribute('aria-hidden', 'true');
|
|
43
|
-
toggle.append(activityIcon, heading, count, chevron);
|
|
44
|
-
toggle.setAttribute('aria-expanded', 'false'); toggle.disabled = messages.length <= 2;
|
|
45
|
-
var list = document.createElement('div'); list.className = 'worker-activity-list';
|
|
46
|
-
var items = [];
|
|
47
|
-
messages.forEach(function (message) {
|
|
48
|
-
var item = document.createElement('article'); item.className = 'worker-activity-item worker-tool-message';
|
|
49
|
-
item.dataset.recordIndex = String(message.recordIndex);
|
|
50
|
-
var phase = message.isError ? 'failed' : message.recordType === 'tool_result' ? 'returned' : 'recorded';
|
|
51
|
-
item.dataset.phase = phase;
|
|
52
|
-
var row = document.createElement('div'); row.className = 'worker-activity-row';
|
|
53
|
-
var actionIcon = workerActivityActionIcon(message, names);
|
|
54
|
-
var statusIcon = phase === 'failed' ? 'circle-alert' : phase === 'returned' ? 'corner-down-left' : 'circle-dot';
|
|
55
|
-
var glyph = document.createElement('i'); glyph.className = actionIcon ? 'worker-activity-glyph' : 'worker-activity-glyph worker-activity-status'; glyph.setAttribute('aria-hidden', 'true');
|
|
56
|
-
glyph.setAttribute('data-lucide', actionIcon || statusIcon);
|
|
57
|
-
var label = document.createElement('span'); label.className = 'worker-activity-label'; label.textContent = workerActivityLabel(message, names);
|
|
58
|
-
var detail = document.createElement('span'); detail.className = 'worker-activity-detail';
|
|
59
|
-
detail.textContent = phase === 'failed' ? 'Failed' : phase === 'returned' ? 'Result returned' : 'Call recorded';
|
|
60
|
-
row.append(glyph, label, detail);
|
|
61
|
-
if (actionIcon) {
|
|
62
|
-
var status = document.createElement('i'); status.className = 'worker-activity-status';
|
|
63
|
-
status.setAttribute('data-lucide', statusIcon); status.setAttribute('aria-hidden', 'true'); row.appendChild(status);
|
|
64
|
-
}
|
|
65
|
-
var evidence = document.createElement('details'); evidence.className = 'worker-activity-evidence'; evidence.dataset.disclosureKey = 'record-' + message.recordIndex;
|
|
66
|
-
var summary = document.createElement('summary'); summary.textContent = 'Technical details';
|
|
67
|
-
var output = document.createElement('pre'); output.textContent = message.text;
|
|
68
|
-
evidence.append(summary, output); item.append(row, evidence); items.push(item); list.appendChild(item);
|
|
69
|
-
});
|
|
70
|
-
function expand(open) {
|
|
71
|
-
toggle.setAttribute('aria-expanded', String(open));
|
|
72
|
-
items.forEach(function (item, index) { item.hidden = !open && index < items.length - 2; item.inert = item.hidden; });
|
|
73
|
-
}
|
|
74
|
-
toggle.addEventListener('click', function () { expand(toggle.getAttribute('aria-expanded') !== 'true'); });
|
|
75
|
-
expand(false); group.append(toggle, list); body.appendChild(group);
|
|
76
|
-
}
|
|
77
23
|
export function captureWorkerDisclosures(body) {
|
|
78
24
|
return {
|
|
79
25
|
open: Array.from(body.querySelectorAll('details[open]')).map(function (node) { return node.dataset.disclosureKey; }),
|
|
80
|
-
groups: Array.from(body.querySelectorAll('.worker-activity-summary[aria-expanded="true"]')).map(function (node) { return node.dataset.disclosureKey; }),
|
|
81
26
|
focus: document.activeElement && body.contains(document.activeElement) && (document.activeElement.dataset.disclosureKey || document.activeElement.parentElement && document.activeElement.parentElement.dataset.disclosureKey)
|
|
82
27
|
};
|
|
83
28
|
}
|
|
84
29
|
export function restoreWorkerDisclosures(body, saved) {
|
|
85
30
|
Array.from(body.querySelectorAll('[data-disclosure-key]')).forEach(function (node) {
|
|
86
31
|
var key = node.dataset.disclosureKey;
|
|
87
|
-
if (saved.groups.indexOf(key) >= 0 && node.getAttribute('aria-expanded') === 'false') node.click();
|
|
88
32
|
if (saved.open.indexOf(key) >= 0) node.open = true;
|
|
89
33
|
if (saved.focus === key) {
|
|
90
34
|
var target = node.tagName === 'DETAILS' ? node.querySelector('summary') : node;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { workerActivityLabel, workerActivityNames } from './split-view-worker-activity.js';
|
|
2
2
|
import { renderMarkdown } from './markdown.js';
|
|
3
3
|
|
|
4
4
|
// Reassemble bounded record fragments and contiguous streaming deltas before
|
|
@@ -17,13 +17,26 @@ export function workerTranscriptRows(messages) {
|
|
|
17
17
|
return rows;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function appendWorkerMessage(body, message) {
|
|
21
|
-
if (message.role === 'tool') {
|
|
20
|
+
export function appendWorkerMessage(body, message, names) {
|
|
21
|
+
if (message.role === 'tool') {
|
|
22
|
+
var article = document.createElement('article');
|
|
23
|
+
article.className = 'message worker-readonly-message worker-tool-message';
|
|
24
|
+
article.dataset.recordIndex = String(message.recordIndex);
|
|
25
|
+
article.dataset.phase = message.isError ? 'failed' : message.recordType === 'tool_result' ? 'returned' : 'recorded';
|
|
26
|
+
var details = document.createElement('details');
|
|
27
|
+
details.dataset.disclosureKey = 'record-' + message.recordIndex;
|
|
28
|
+
var summary = document.createElement('summary');
|
|
29
|
+
summary.textContent = workerActivityLabel(message, names || workerActivityNames([message])) +
|
|
30
|
+
(message.isError ? ' · Failed' : message.recordType === 'tool_result' ? ' · Result returned' : ' · Call recorded');
|
|
31
|
+
var output = document.createElement('pre'); output.textContent = message.text;
|
|
32
|
+
details.append(summary, output); article.appendChild(details); body.appendChild(article);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
22
35
|
var article = document.createElement('article');
|
|
23
36
|
article.className = 'message worker-readonly-message ' + (message.role === 'user' ? 'msg-user' : 'msg-assistant');
|
|
24
37
|
if (Number.isInteger(message.recordIndex)) article.dataset.recordIndex = String(message.recordIndex);
|
|
25
38
|
var author = document.createElement('strong'); author.className = 'author';
|
|
26
|
-
author.textContent = message.role === 'user' ? '
|
|
39
|
+
author.textContent = message.role === 'user' ? 'Driver' : 'Worker';
|
|
27
40
|
var content = document.createElement('div'); content.className = 'message-body md-content';
|
|
28
41
|
// Recorded HTML must not create controls, scoped overlays or active embeds.
|
|
29
42
|
// Keep the shared Markdown parser and its sanitization, with a narrower
|
|
@@ -36,11 +49,6 @@ export function appendWorkerMessage(body, message) {
|
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
export function appendWorkerTranscript(body, messages) {
|
|
39
|
-
var rows = workerTranscriptRows(messages), names = workerActivityNames(rows)
|
|
40
|
-
function
|
|
41
|
-
rows.forEach(function (message) {
|
|
42
|
-
if (message.role === 'tool') pending.push(message);
|
|
43
|
-
else { flush(); appendWorkerMessage(body, message); }
|
|
44
|
-
});
|
|
45
|
-
flush();
|
|
52
|
+
var rows = workerTranscriptRows(messages), names = workerActivityNames(rows);
|
|
53
|
+
rows.forEach(function (message) { appendWorkerMessage(body, message, names); });
|
|
46
54
|
}
|
|
@@ -75,6 +75,12 @@ function popover() { return document.getElementById("split-view-worker-popover")
|
|
|
75
75
|
function close(returnFocus) {
|
|
76
76
|
var state = store.get('splitViewWorkerConversation');
|
|
77
77
|
if (!state) return;
|
|
78
|
+
var socket = getWs();
|
|
79
|
+
if (state.subscriptionId && socket && socket === state.socket && socket.readyState === 1) {
|
|
80
|
+
try { socket.send(JSON.stringify({ type: 'split_worker_conversation_request', watch: false,
|
|
81
|
+
requestId: state.subscriptionId, parentSessionOriginId: state.driverOriginId,
|
|
82
|
+
workerSessionOriginId: state.workerOriginId, generation: state.generation })); } catch (_error) {}
|
|
83
|
+
}
|
|
78
84
|
if (state.fillTimer) clearTimeout(state.fillTimer);
|
|
79
85
|
if (state.requestTimer) clearTimeout(state.requestTimer);
|
|
80
86
|
store.set({ splitViewWorkerConversation: null });
|
|
@@ -91,7 +97,7 @@ function position(element, anchor) {
|
|
|
91
97
|
element.style.left = Math.max(10, Math.min(rect.left, window.innerWidth - width - 10)) + "px";
|
|
92
98
|
element.style.top = Math.max(10, Math.min(rect.bottom + 6, window.innerHeight - height - 10)) + "px";
|
|
93
99
|
}
|
|
94
|
-
function requestPage(state, before, append) {
|
|
100
|
+
function requestPage(state, before, append, refresh) {
|
|
95
101
|
var current = store.get('splitViewWorkerConversation');
|
|
96
102
|
if (!state || !current || state.requestId !== current.requestId || current.inFlight || workerCursorKey(before) === null || !ensurePopover()) return;
|
|
97
103
|
state = store.get('splitViewWorkerConversation');
|
|
@@ -103,8 +109,9 @@ function requestPage(state, before, append) {
|
|
|
103
109
|
renderContent(); return;
|
|
104
110
|
}
|
|
105
111
|
var requestId = "worker-read-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 9);
|
|
106
|
-
var
|
|
107
|
-
|
|
112
|
+
var subscribe = !state.subscriptionId || !state.subscriptionConfirmed;
|
|
113
|
+
var pending = Object.assign({}, state, { subscriptionId: subscribe ? requestId : state.subscriptionId, requestId: requestId, socket: ws, projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId') || null,
|
|
114
|
+
socketGeneration: store.get('projectSplitViewSocketGeneration') || 0, status: "loading", error: null, inFlight: true, fillTimer: null, requestedBefore: before == null ? null : before, append: append === true, refreshing: refresh === true, refreshPending: false });
|
|
108
115
|
pending.requestTimer = setTimeout(function () {
|
|
109
116
|
var current = store.get('splitViewWorkerConversation');
|
|
110
117
|
if (!current || current.requestId !== requestId || !current.inFlight) return;
|
|
@@ -113,10 +120,12 @@ function requestPage(state, before, append) {
|
|
|
113
120
|
renderContent();
|
|
114
121
|
}, 15000);
|
|
115
122
|
store.set({ splitViewWorkerConversation: pending }); renderContent();
|
|
123
|
+
var last = refresh && state.messages[state.messages.length - 1];
|
|
124
|
+
var after = last && Number.isInteger(last.recordIndex) && Number.isInteger(last.end) ? { index: last.recordIndex, offset: last.end } : undefined;
|
|
116
125
|
try {
|
|
117
126
|
ws.send(JSON.stringify({ type: "split_worker_conversation_request", requestId: requestId,
|
|
118
127
|
parentSessionOriginId: state.driverOriginId, workerSessionOriginId: state.workerOriginId,
|
|
119
|
-
generation: state.generation, before: before }));
|
|
128
|
+
generation: state.generation, before: before, after: after, watch: subscribe ? true : undefined }));
|
|
120
129
|
} catch (_error) {
|
|
121
130
|
var current = store.get('splitViewWorkerConversation');
|
|
122
131
|
if (current && current.requestId === requestId) {
|
|
@@ -176,6 +185,7 @@ function renderContent() {
|
|
|
176
185
|
if (body && (state.renderedMessages !== state.messages || !state.messages.length)) {
|
|
177
186
|
var anchor = state.prepend ? captureWorkerAnchor(body) : null;
|
|
178
187
|
var disclosures = captureWorkerDisclosures(body);
|
|
188
|
+
var followTail = body.scrollHeight - body.clientHeight - body.scrollTop < 48;
|
|
179
189
|
var oldTop = body.scrollTop, wasFocused = document.activeElement === body;
|
|
180
190
|
body.textContent = "";
|
|
181
191
|
if (state.status === "loading" && !state.messages.length) body.textContent = "Loading conversation…";
|
|
@@ -183,18 +193,18 @@ function renderContent() {
|
|
|
183
193
|
else if (!state.messages.length) body.textContent = "No recorded conversation.";
|
|
184
194
|
appendWorkerTranscript(body, state.messages);
|
|
185
195
|
restoreWorkerDisclosures(body, disclosures);
|
|
186
|
-
if (state.initialPage && state.status === 'ready') body.scrollTop = body.scrollHeight;
|
|
196
|
+
if ((state.initialPage || state.liveRefresh && followTail) && state.status === 'ready') body.scrollTop = body.scrollHeight;
|
|
187
197
|
else if (anchor) restoreWorkerAnchor(body, anchor);
|
|
188
198
|
else body.scrollTop = oldTop;
|
|
189
199
|
if (wasFocused) body.focus({ preventScroll: true });
|
|
190
200
|
}
|
|
191
|
-
if (body) store.set({ splitViewWorkerConversation: Object.assign({}, state, { renderedMessages: state.messages, prepend: false,
|
|
201
|
+
if (body) store.set({ splitViewWorkerConversation: Object.assign({}, state, { renderedMessages: state.messages, prepend: false, liveRefresh: false,
|
|
192
202
|
initialPage: state.status === 'ready' ? false : state.initialPage }) });
|
|
193
203
|
if (footer) {
|
|
194
|
-
footer.textContent =
|
|
204
|
+
footer.textContent = state.status === 'error' ? 'Read-only conversation' : state.refreshing ? 'Updating live conversation…' : state.subscriptionConfirmed ? 'Read-only · Live updates' : 'Read-only conversation';
|
|
195
205
|
if (state.status === "error") { var status = document.createElement('span'); status.setAttribute('role', 'status'); status.textContent = state.error || "Conversation is unavailable."; footer.appendChild(status); }
|
|
196
206
|
var status = document.createElement('span'); status.className = 'split-view-worker-page-status'; status.setAttribute('role', 'status');
|
|
197
|
-
status.textContent = state.inFlight ? (state.messages.length ? 'Loading earlier messages…' : 'Loading conversation…') : state.hasMore ? 'Scroll up for earlier messages' : state.messages.length ? 'Beginning of conversation' : '';
|
|
207
|
+
status.textContent = state.inFlight ? (state.refreshing ? 'Receiving Worker activity…' : state.messages.length ? 'Loading earlier messages…' : 'Loading conversation…') : state.hasMore ? 'Scroll up for earlier messages' : state.messages.length ? 'Beginning of conversation' : '';
|
|
198
208
|
footer.appendChild(status);
|
|
199
209
|
if (state.status === 'error') {
|
|
200
210
|
var retry = document.createElement('button'); retry.type = 'button'; retry.textContent = 'Retry loading conversation';
|
|
@@ -245,10 +255,56 @@ export function handleSplitWorkerConversation(message) {
|
|
|
245
255
|
if (!error && (!Array.isArray(message.messages) || message.hasMore && !workerCursorAdvances(state.requestedBefore, message.nextBefore))) error = 'Worker history page did not advance. Retry loading conversation.';
|
|
246
256
|
var incoming = error ? [] : message.messages;
|
|
247
257
|
var messages = error ? state.messages : state.append ? incoming.concat(state.messages || []) : incoming.slice();
|
|
258
|
+
if (!error && state.refreshing && incoming.length) {
|
|
259
|
+
var first = incoming[0];
|
|
260
|
+
messages = state.messages.filter(function (item) {
|
|
261
|
+
return item.recordIndex < first.recordIndex || item.recordIndex === first.recordIndex && item.end <= first.start;
|
|
262
|
+
}).concat(incoming);
|
|
263
|
+
}
|
|
264
|
+
if (!error && state.refreshing && !incoming.length) messages = state.messages;
|
|
265
|
+
var refreshAgain = state.refreshPending || message.moreRecent === true;
|
|
248
266
|
store.set({ splitViewWorkerConversation: Object.assign({}, state, { status: error ? 'error' : 'ready', error: error,
|
|
249
|
-
messages: messages, inFlight: false, requestTimer: null,
|
|
250
|
-
|
|
251
|
-
|
|
267
|
+
messages: messages, subscriptionConfirmed: !error || state.subscriptionConfirmed, inFlight: false, requestTimer: null, nextBefore: error || state.refreshing && state.messages.length ? state.nextBefore : message.nextBefore,
|
|
268
|
+
hasMore: error || state.refreshing && state.messages.length ? state.hasMore : message.hasMore === true,
|
|
269
|
+
liveRefresh: !error && state.refreshing, refreshing: false, refreshPending: false, prepend: !error && state.append, append: false }) });
|
|
270
|
+
renderContent();
|
|
271
|
+
if (refreshAgain && !error) refreshWorkerConversation();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
function refreshWorkerConversation() {
|
|
275
|
+
var state = store.get('splitViewWorkerConversation');
|
|
276
|
+
if (!state || !store.get('connected') || !ensurePopover()) return;
|
|
277
|
+
if (state.inFlight) {
|
|
278
|
+
if (!state.refreshPending) store.set({ splitViewWorkerConversation: Object.assign({}, state, { refreshPending: true }) });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
requestPage(state, undefined, false, true);
|
|
282
|
+
}
|
|
283
|
+
export function handleWorkerConversationChanged(message) {
|
|
284
|
+
var state = store.get('splitViewWorkerConversation');
|
|
285
|
+
if (!state || !store.get('connected') || state.socket !== getWs() || state.projectSlug !== store.get('currentSlug') ||
|
|
286
|
+
state.ownerId !== (store.get('myUserId') || null) || state.socketGeneration !== (store.get('projectSplitViewSocketGeneration') || 0) ||
|
|
287
|
+
message.subscriptionId !== state.subscriptionId || message.parentSessionOriginId !== state.driverOriginId ||
|
|
288
|
+
message.workerSessionOriginId !== state.workerOriginId || message.generation !== state.generation || !ensurePopover()) return false;
|
|
289
|
+
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
290
|
+
if (chip.dataset.workerOrigin === state.workerOriginId) {
|
|
291
|
+
var dot = chip.querySelector('.split-view-worker-dot');
|
|
292
|
+
if (dot) dot.dataset.state = message.isProcessing ? 'working' : 'ready';
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
refreshWorkerConversation();
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
export function handleWorkerSessionActivity(message) {
|
|
299
|
+
var worker = sessions().find(function (item) { return item.id === message.id && item.sessionRole === 'worker'; });
|
|
300
|
+
if (!worker || !store.get('connected')) return;
|
|
301
|
+
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
302
|
+
if (chip.dataset.workerOrigin !== worker.sessionOriginId) return;
|
|
303
|
+
var dot = chip.querySelector('.split-view-worker-dot');
|
|
304
|
+
if (dot) { dot.dataset.state = 'working'; dot.setAttribute('aria-label', 'Worker activity received'); }
|
|
305
|
+
});
|
|
306
|
+
var state = store.get('splitViewWorkerConversation');
|
|
307
|
+
if (state && state.workerOriginId === worker.sessionOriginId && state.generation === worker.workerGeneration) refreshWorkerConversation();
|
|
252
308
|
}
|
|
253
309
|
export function renderPaneWorkers(container, driver) {
|
|
254
310
|
if (!container || !driver) return;
|
|
@@ -270,7 +326,8 @@ export function renderPaneWorkers(container, driver) {
|
|
|
270
326
|
button.title = (VENDOR_NAMES[worker.vendor] || worker.vendor || "Unknown vendor") + " · " + modelLabel + " · Split Worker #" + worker.id +
|
|
271
327
|
(current ? "" : " · Archived · Gen " + worker.workerGeneration) + " · " + (worker.title || "Worker");
|
|
272
328
|
button.setAttribute("aria-label", button.title + " conversation");
|
|
273
|
-
var dot = document.createElement("span"); dot.className = "split-view-worker-dot"; dot.dataset.state =
|
|
329
|
+
var dot = document.createElement("span"); dot.className = "split-view-worker-dot"; dot.dataset.state = !store.get('connected') ? 'disconnected' : worker.isProcessing ? 'working' : 'ready';
|
|
330
|
+
dot.setAttribute('aria-label', !store.get('connected') ? 'Disconnected' : worker.isProcessing ? 'Worker is working' : 'Worker is idle');
|
|
274
331
|
if (VENDOR_AVATARS[worker.vendor]) {
|
|
275
332
|
var vendorIcon = document.createElement("img"); vendorIcon.className = "split-view-worker-vendor";
|
|
276
333
|
vendorIcon.src = VENDOR_AVATARS[worker.vendor]; vendorIcon.alt = ""; vendorIcon.setAttribute("aria-hidden", "true"); button.appendChild(vendorIcon);
|
|
@@ -282,6 +339,7 @@ export function renderPaneWorkers(container, driver) {
|
|
|
282
339
|
var generation = document.createElement("span"); generation.className = "split-view-worker-generation";
|
|
283
340
|
generation.textContent = "Gen " + worker.workerGeneration; button.appendChild(generation);
|
|
284
341
|
}
|
|
342
|
+
button.dataset.workerSessionId = String(worker.id);
|
|
285
343
|
button.appendChild(dot);
|
|
286
344
|
var state = store.get('splitViewWorkerConversation');
|
|
287
345
|
button.setAttribute("aria-expanded", state && state.driverOriginId === driver.sessionOriginId && state.workerOriginId === worker.sessionOriginId ? "true" : "false");
|
|
@@ -328,6 +386,14 @@ window.addEventListener("scroll", function (event) {
|
|
|
328
386
|
reanchorPopover();
|
|
329
387
|
}, true);
|
|
330
388
|
store.subscribe(function (state, previous) {
|
|
389
|
+
if (state.paneBindingSessions !== previous.paneBindingSessions || state.connected !== previous.connected) {
|
|
390
|
+
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
391
|
+
var worker = sessions().find(function (item) { return item.sessionOriginId === chip.dataset.workerOrigin; });
|
|
392
|
+
var dot = chip.querySelector('.split-view-worker-dot');
|
|
393
|
+
if (dot) dot.dataset.state = !state.connected ? 'disconnected' : worker && worker.isProcessing ? 'working' : 'ready';
|
|
394
|
+
});
|
|
395
|
+
if (state.connected && state.splitViewWorkerConversation) refreshWorkerConversation();
|
|
396
|
+
}
|
|
331
397
|
if (state.splitViewWorkerConversation && (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId ||
|
|
332
398
|
state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration)) {
|
|
333
399
|
close(false);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// New execution pairs keep the Driver's current presentation; Workers live in pills.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
5
|
+
import { splitGroupRoles } from './split-group-helpers.js';
|
|
6
|
+
import { setActiveView, switchToStandalone } from './split-view-presentations.js';
|
|
7
|
+
|
|
8
|
+
export function showCreatedWorkerPresentation(group) {
|
|
9
|
+
var roles = splitGroupRoles(group), state = store.snap();
|
|
10
|
+
if (!roles || state.paneMode || state.dmMode) return false;
|
|
11
|
+
store.set({ createdWorkerPresentation: { group: group, slug: state.currentSlug, ownerId: state.myUserId,
|
|
12
|
+
activeSessionId: state.activeSessionId, socketGeneration: state.projectSplitViewSocketGeneration } });
|
|
13
|
+
return reconcileCreatedWorkerPresentation();
|
|
14
|
+
}
|
|
15
|
+
function reconcileCreatedWorkerPresentation() {
|
|
16
|
+
var pending = store.get('createdWorkerPresentation'), state = store.snap();
|
|
17
|
+
if (!pending) return false;
|
|
18
|
+
if (pending.slug !== state.currentSlug || pending.ownerId !== state.myUserId || pending.activeSessionId !== state.activeSessionId ||
|
|
19
|
+
pending.socketGeneration !== state.projectSplitViewSocketGeneration || !state.connected) {
|
|
20
|
+
store.set({ createdWorkerPresentation: null }); return false;
|
|
21
|
+
}
|
|
22
|
+
var roles = splitGroupRoles(pending.group), sessions = getCachedSessions() || [];
|
|
23
|
+
var driver = sessions.find(function (session) { return session.id === roles.driverId && session.sessionRole === 'driver' &&
|
|
24
|
+
(session.ownerId || null) === (state.myUserId || null); });
|
|
25
|
+
if (state.sessionListProjectSlug !== state.currentSlug || !driver || !driver.sessionOriginId || !getWs() || getWs().readyState !== 1) return false;
|
|
26
|
+
store.set({ createdWorkerPresentation: null });
|
|
27
|
+
if (state.activeSessionId === driver.id && !state.splitPanes) return true;
|
|
28
|
+
var view = (state.projectSplitViews || []).find(function (item) {
|
|
29
|
+
return item.sessions.some(function (ref) { return ref.id === driver.id && ref.sessionOriginId === driver.sessionOriginId; });
|
|
30
|
+
});
|
|
31
|
+
return view ? setActiveView(view.id, driver.sessionOriginId) : switchToStandalone(driver.id);
|
|
32
|
+
}
|
|
33
|
+
store.subscribe(function (state, previous) {
|
|
34
|
+
if (state.createdWorkerPresentation && (state.paneBindingSessions !== previous.paneBindingSessions ||
|
|
35
|
+
state.activeSessionId !== previous.activeSessionId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration ||
|
|
36
|
+
state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.connected !== previous.connected)) reconcileCreatedWorkerPresentation();
|
|
37
|
+
});
|