clay-server 5.3.0-beta.1 → 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.
Files changed (27) hide show
  1. package/lib/project-worker-conversation.js +67 -3
  2. package/lib/public/css/pane.css +0 -36
  3. package/lib/public/css/session-folders.css +1 -1
  4. package/lib/public/css/split-view-layout.css +4 -1
  5. package/lib/public/modules/app-favicon.js +1 -1
  6. package/lib/public/modules/app-messages.js +7 -2
  7. package/lib/public/modules/chat-render-runtime.js +9 -0
  8. package/lib/public/modules/filebrowser.js +6 -2
  9. package/lib/public/modules/markdown-view-mode.js +34 -0
  10. package/lib/public/modules/pane-file-bridge.js +8 -3
  11. package/lib/public/modules/session-folder-collapse.js +2 -1
  12. package/lib/public/modules/session-folder-dnd.js +4 -1
  13. package/lib/public/modules/split-view-presentation-drag.js +24 -0
  14. package/lib/public/modules/split-view-presentations.js +5 -2
  15. package/lib/public/modules/split-view-workers.js +78 -12
  16. package/lib/public/modules/worker-created-presentation.js +37 -0
  17. package/lib/public/pane-assets/{app-OJEQXBBS.js → app-2DRBYY3L.js} +1 -1
  18. package/lib/public/pane-assets/{chunk-UVGOPFEX.js → chunk-4BM7UVNG.js} +1 -1
  19. package/lib/public/pane-assets/{chunk-4SMIVAB4.js → chunk-C4RVVYNT.js} +1 -1
  20. package/lib/public/pane-assets/{chunk-GML2R5JE.js → chunk-O6MQL6MR.js} +94 -94
  21. package/lib/public/pane-assets/{chunk-623V5CEO.js → chunk-PO2JOHHW.js} +1 -1
  22. package/lib/public/pane-assets/{chunk-I5P3MWQG.js → chunk-UMGHINOT.js} +1 -1
  23. package/lib/public/pane-assets/manifest.json +322 -320
  24. package/lib/public/pane-assets/style-63EZCREE.css +1 -0
  25. package/lib/ws-schema.js +1 -0
  26. package/package.json +1 -1
  27. package/lib/public/pane-assets/style-GN7R6XKV.css +0 -1
@@ -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 pending = Object.assign({}, state, { requestId: requestId, socket: ws, projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId') || null,
107
- socketGeneration: store.get('projectSplitViewSocketGeneration') || 0, status: "loading", error: null, inFlight: true, fillTimer: null, requestedBefore: before == null ? null : before, append: append === true });
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 = "Read-only conversation";
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, hasMore: error ? state.hasMore : message.hasMore === true,
250
- nextBefore: error ? state.nextBefore : message.nextBefore, prepend: !error && state.append, append: false }) });
251
- renderContent(); return true;
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 = current && worker.isProcessing ? "working" : "ready";
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
+ });