clay-server 5.3.0-beta.6 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/project-admin-overview.js +22 -11
- package/lib/project-background-tasks.js +76 -0
- package/lib/project-connection.js +1 -1
- package/lib/project-sessions.js +12 -5
- package/lib/project-split-views.js +12 -3
- package/lib/project-view-store.js +7 -5
- package/lib/project-worker-conversation.js +5 -0
- package/lib/public/app.js +7 -0
- package/lib/public/css/contextual-clay.css +2 -1
- package/lib/public/css/mate-conversation.css +1 -1
- package/lib/public/css/pending-message-queue.css +24 -19
- package/lib/public/css/project-admin-overview.css +42 -13
- package/lib/public/css/shared-browser.css +31 -8
- package/lib/public/css/split-view-layout.css +92 -7
- package/lib/public/css/tool-pane.css +4 -7
- package/lib/public/index.html +5 -5
- package/lib/public/modules/agent-message-bubbles.js +96 -0
- package/lib/public/modules/app-header.js +6 -0
- package/lib/public/modules/app-messages.js +26 -9
- package/lib/public/modules/background-task-previews.js +8 -0
- package/lib/public/modules/background-task-viewer.js +97 -0
- package/lib/public/modules/background-tasks-ui.js +34 -125
- package/lib/public/modules/chat-bubble-renderer.js +11 -5
- package/lib/public/modules/contextual-clay-geometry.js +7 -3
- package/lib/public/modules/pane-activation.js +5 -3
- package/lib/public/modules/pane-header-tooltips.js +32 -0
- package/lib/public/modules/pending-message-queue-drag.js +69 -0
- package/lib/public/modules/pending-message-queue.js +3 -33
- package/lib/public/modules/permission-control.js +6 -5
- package/lib/public/modules/project-admin-overview.js +125 -10
- package/lib/public/modules/project-grouping.js +9 -0
- package/lib/public/modules/server-settings-controls.js +33 -4
- package/lib/public/modules/session-folder-toolbar.js +1 -1
- package/lib/public/modules/session-tool-item.js +86 -0
- package/lib/public/modules/settings-select.js +2 -1
- package/lib/public/modules/shared-browser-tabs.js +37 -21
- package/lib/public/modules/sidebar-projects.js +1 -10
- package/lib/public/modules/split-session-empty-pane.js +47 -0
- package/lib/public/modules/split-view-create-menu.js +74 -65
- package/lib/public/modules/split-view-layout-scope.js +4 -0
- package/lib/public/modules/split-view-picker-actions.js +71 -0
- package/lib/public/modules/split-view-picker-render.js +81 -0
- package/lib/public/modules/split-view-presentation-renderer.js +8 -8
- package/lib/public/modules/split-view-worker-activity.js +2 -2
- package/lib/public/modules/split-view-worker-transcript.js +70 -33
- package/lib/public/modules/split-view-workers.js +6 -4
- package/lib/public/modules/terminal-viewport.js +14 -0
- package/lib/public/modules/terminal.js +3 -4
- package/lib/public/modules/theme-palette.js +177 -0
- package/lib/public/modules/theme.js +1 -174
- package/lib/public/modules/tool-pane-chrome.js +7 -1
- package/lib/public/modules/tools.js +5 -89
- package/lib/public/modules/tooltip.js +32 -6
- package/lib/public/modules/wireframe-workbench.js +2 -2
- package/lib/public/modules/worker-message-previews.js +39 -0
- package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
- package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
- package/lib/public/pane-assets/{chunk-BMHTBRMO.js → chunk-7OVNC5HT.js} +1 -1
- package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
- package/lib/public/pane-assets/{chunk-2AIWCI6Y.js → chunk-EMLN2Y7L.js} +1 -1
- package/lib/public/pane-assets/{chunk-VKYBAVWT.js → chunk-ENPAHXLB.js} +1 -1
- package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
- package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
- package/lib/public/pane-assets/{chunk-6NYCNWVF.js → chunk-FQTNR4ZS.js} +1 -1
- package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
- package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
- package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
- package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
- package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
- package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
- package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
- package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
- package/lib/public/pane-assets/{chunk-PV4PCH2V.js → chunk-SJOT74VW.js} +1 -1
- package/lib/public/pane-assets/{chunk-LDIZLKB2.js → chunk-SJTGHIN5.js} +1 -1
- package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
- package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
- package/lib/public/pane-assets/{chunk-VNAV23CM.js → chunk-UPXU2IPC.js} +1 -1
- package/lib/public/pane-assets/{chunk-TMGSDFJI.js → chunk-VEBYD7TL.js} +1 -1
- package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
- package/lib/public/pane-assets/manifest.json +411 -399
- package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
- package/lib/sdk-message-processor.js +5 -2
- package/lib/server-storage.js +27 -3
- package/lib/server.js +4 -2
- package/lib/sessions.js +1 -1
- package/lib/storage-usage.js +29 -7
- package/lib/terminal-manager.js +2 -2
- package/lib/worker-message-previews.js +50 -0
- package/lib/ws-schema.js +1 -1
- package/lib/yoke/adapters/claude-worker.js +8 -0
- package/lib/yoke/adapters/claude.js +17 -2
- package/lib/yoke/claude-task-reader.js +94 -0
- package/lib/yoke/interface.js +1 -0
- package/package.json +1 -1
- package/lib/public/pane-assets/app-7UZ727NA.js +0 -1
- package/lib/public/pane-assets/chunk-6MIHIQ2W.js +0 -1
- package/lib/public/pane-assets/chunk-6UJ2ALOB.js +0 -1
- package/lib/public/pane-assets/chunk-6XK2OATZ.js +0 -1
- package/lib/public/pane-assets/chunk-BKIDR2ME.js +0 -1
- package/lib/public/pane-assets/chunk-GEFCFZEN.js +0 -1
- package/lib/public/pane-assets/chunk-GN2NQEVC.js +0 -2
- package/lib/public/pane-assets/chunk-IMLMBPT4.js +0 -166
- package/lib/public/pane-assets/chunk-K5S6AILM.js +0 -1
- package/lib/public/pane-assets/chunk-OWNNLZ36.js +0 -1
- package/lib/public/pane-assets/chunk-P2C3LOWL.js +0 -3
- package/lib/public/pane-assets/chunk-R4LMPTKU.js +0 -1
- package/lib/public/pane-assets/chunk-VWTHIQCE.js +0 -1
- package/lib/public/pane-assets/chunk-WM3K5AFH.js +0 -1
- package/lib/public/pane-assets/chunk-XOB3O327.js +0 -1
- package/lib/public/pane-assets/chunk-XOOUCFYT.js +0 -1
- package/lib/public/pane-assets/style-ZM7OOU3M.css +0 -1
|
@@ -1,83 +1,92 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Direction menu, then a centered session picker inside the reserved empty pane.
|
|
2
2
|
import { store } from './store.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { retryOptions } from './session-create.js';
|
|
4
|
+
import { pickerReceipt, pickerCurrent, insertPickerSession, createPickerSession } from './split-view-picker-actions.js';
|
|
5
|
+
import { renderPicker, renderPickerResults } from './split-view-picker-render.js';
|
|
6
|
+
import { showToast } from './utils.js';
|
|
6
7
|
|
|
7
8
|
function menuElement() { return document.getElementById('split-view-create-menu'); }
|
|
8
9
|
function close(returnFocus) {
|
|
9
10
|
var menu = menuElement(); if (!menu) return;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
var state = store.get('splitSessionPicker');
|
|
12
|
+
var update = { splitSessionPicker: null, projectSplitViewCreateSelection: [] };
|
|
13
|
+
if (returnFocus && state && pickerCurrent(state.receipt) && !store.get('projectSplitViewPending')) update.projectSplitViewFocusedOrigin = state.priorFocus || null;
|
|
14
|
+
var anchor = menu.clayCreateAnchor; menu.clayCreateAnchor = null;
|
|
15
|
+
menu.hidden = true; store.set(update);
|
|
16
|
+
if (menu.contains(document.activeElement)) document.activeElement.blur();
|
|
17
|
+
if (menu.parentNode !== document.body) document.body.appendChild(menu);
|
|
18
|
+
if (anchor) anchor.setAttribute('aria-expanded', 'false');
|
|
19
|
+
document.dispatchEvent(new CustomEvent('clay-split-picker-layout'));
|
|
20
|
+
if (returnFocus && anchor && anchor.isConnected) anchor.focus();
|
|
13
21
|
}
|
|
14
|
-
function
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
function position() {
|
|
23
|
+
var menu = menuElement(), anchor = menu && menu.clayCreateAnchor;
|
|
24
|
+
if (!menu || menu.hidden) return;
|
|
25
|
+
if (!anchor || !anchor.isConnected) { close(false); return; }
|
|
26
|
+
var state = store.get('splitSessionPicker');
|
|
27
|
+
if (state && state.side) { menu.style.left = ''; menu.style.top = ''; return; }
|
|
28
|
+
var rect = anchor.getBoundingClientRect(), width = menu.offsetWidth;
|
|
29
|
+
menu.style.left = Math.max(8, Math.min(rect.right - width, innerWidth - width - 8)) + 'px';
|
|
30
|
+
menu.style.top = Math.max(8, Math.min(rect.bottom + 5, innerHeight - menu.offsetHeight - 8)) + 'px';
|
|
18
31
|
}
|
|
19
32
|
function open(anchor) {
|
|
33
|
+
var receipt = pickerReceipt(anchor);
|
|
34
|
+
if (!receipt || !pickerCurrent(receipt)) { showToast('Wait for this View to finish loading, then try again.', 'warn'); return; }
|
|
20
35
|
var menu = menuElement();
|
|
36
|
+
if (menu && !menu.hidden && menu.clayCreateAnchor === anchor) { close(true); return; }
|
|
37
|
+
if (menu && !menu.hidden) close(false);
|
|
21
38
|
if (!menu) {
|
|
22
|
-
menu = document.createElement('section'); menu.id = 'split-view-create-menu'; menu.className = 'split-view-arrange';
|
|
23
|
-
menu.
|
|
24
|
-
var header = document.createElement('div'); header.className = 'split-view-arrange-header';
|
|
25
|
-
var title = document.createElement('strong'); title.textContent = 'Arrange sessions'; header.appendChild(title);
|
|
26
|
-
var closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.setAttribute('aria-label', 'Close arrangement picker'); closeButton.innerHTML = iconHtml('x'); header.appendChild(closeButton); menu.appendChild(header);
|
|
27
|
-
var list = document.createElement('div'); list.className = 'split-view-arrange-grid'; list.setAttribute('role', 'group'); list.setAttribute('aria-label', 'Choose sessions');
|
|
28
|
-
list.style.cssText = 'display:flex;flex-direction:column;max-height:230px;overflow:auto'; menu.appendChild(list);
|
|
29
|
-
var create = document.createElement('button'); create.type = 'button'; create.dataset.createSplitView = 'true'; menu.appendChild(create);
|
|
30
|
-
document.body.appendChild(menu);
|
|
39
|
+
menu = document.createElement('section'); menu.id = 'split-view-create-menu'; menu.className = 'split-view-arrange split-session-picker'; menu.hidden = true;
|
|
40
|
+
menu.setAttribute('role', 'dialog'); menu.setAttribute('aria-label', 'Add a session split'); document.body.appendChild(menu);
|
|
31
41
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
var current = (getCachedSessions() || []).find(function (session) { return session.id === currentId; });
|
|
37
|
-
var selected = activeView ? activeView.sessions.map(function (item) { return item.sessionOriginId; }) :
|
|
38
|
-
current && current.sessionRole !== 'worker' && !current.isMate && !current.isTui ? [current.sessionOriginId] : [];
|
|
39
|
-
var candidates = (getCachedSessions() || []).filter(function (session) {
|
|
40
|
-
if (!session.sessionOriginId || session.sessionRole === 'worker' || session.isMate || session.isTui) return false;
|
|
41
|
-
var owner = viewForOrigin(session.sessionOriginId); return !owner || activeView && owner.id === activeView.id;
|
|
42
|
-
});
|
|
43
|
-
candidates.forEach(function (session) {
|
|
44
|
-
var choice = document.createElement('button'); choice.type = 'button'; choice.dataset.createSessionOrigin = session.sessionOriginId;
|
|
45
|
-
choice.setAttribute('aria-pressed', selected.indexOf(session.sessionOriginId) >= 0 ? 'true' : 'false');
|
|
46
|
-
choice.textContent = session.title || 'Session'; list.appendChild(choice);
|
|
47
|
-
});
|
|
48
|
-
store.set({ projectSplitViewCreateSelection: selected }); refreshCreateButton(menu); menu.hidden = false;
|
|
49
|
-
var rect = anchor.getBoundingClientRect(), width = Math.min(280, window.innerWidth - 16);
|
|
50
|
-
menu.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - width - 8)) + 'px';
|
|
51
|
-
menu.style.top = Math.max(8, Math.min(rect.bottom + 4, window.innerHeight - menu.offsetHeight - 8)) + 'px';
|
|
52
|
-
var first = menu.querySelector('[data-create-session-origin]'); if (first) first.focus();
|
|
42
|
+
receipt.reservationId = 'split-picker-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);
|
|
43
|
+
menu.clayCreateAnchor = anchor; anchor.setAttribute('aria-expanded', 'true');
|
|
44
|
+
store.set({ splitSessionPicker: { receipt: receipt, side: null, query: '', error: '', priorFocus: store.get('projectSplitViewFocusedOrigin') } });
|
|
45
|
+
renderPicker(menu); menu.hidden = false; position(); menu.querySelector('[data-split-side]').focus();
|
|
53
46
|
}
|
|
54
47
|
function onClick(event) {
|
|
55
|
-
var
|
|
56
|
-
var
|
|
57
|
-
if (menu.contains(target)) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (index === -1) selected.push(origin); else selected.splice(index, 1);
|
|
65
|
-
store.set({ projectSplitViewCreateSelection: selected }); choice.setAttribute('aria-pressed', selected.indexOf(origin) >= 0 ? 'true' : 'false'); refreshCreateButton(menu); return;
|
|
66
|
-
}
|
|
67
|
-
if (target.closest('[data-create-split-view]') && !target.closest('[data-create-split-view]').disabled) {
|
|
68
|
-
event.preventDefault(); document.dispatchEvent(new CustomEvent('clay-split-view-create', { detail: { origins: (store.get('projectSplitViewCreateSelection') || []).slice() } })); close(true);
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
48
|
+
var menu = menuElement(), state = store.get('splitSessionPicker'); if (!menu || menu.hidden || !state) return;
|
|
49
|
+
var button = event.target.closest && event.target.closest('button');
|
|
50
|
+
if (!menu.contains(event.target)) { if (!state.side && (!event.target.closest || !event.target.closest('.split-view-arrange-trigger'))) close(false); return; }
|
|
51
|
+
if (!button || button.disabled) return;
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
if (button.dataset.splitClose) { close(true); return; }
|
|
54
|
+
if (!pickerCurrent(state.receipt)) { close(true); return; }
|
|
55
|
+
if (button.dataset.splitSide) {
|
|
56
|
+
store.set({ splitSessionPicker: Object.assign({}, state, { side: button.dataset.splitSide, query: '' }), projectSplitViewFocusedOrigin: null }); renderPicker(menu); document.dispatchEvent(new CustomEvent('clay-split-picker-layout')); position(); menu.querySelector('input').focus(); retryOptions(); return;
|
|
71
57
|
}
|
|
72
|
-
if (
|
|
73
|
-
|
|
58
|
+
if (button.dataset.splitRetry) { retryOptions(); return; }
|
|
59
|
+
var accepted = button.dataset.splitSession ? insertPickerSession(state.receipt, state.side, Number(button.dataset.splitSession)) : button.dataset.splitVendor ? createPickerSession(state.receipt, state.side, button.dataset.splitVendor) : false;
|
|
60
|
+
if (accepted) renderPickerResults(menu);
|
|
61
|
+
else { store.set({ splitSessionPicker: Object.assign({}, state, { error: 'The split could not be added. Check the View and try again.' }) }); renderPickerResults(menu); }
|
|
62
|
+
}
|
|
63
|
+
function onKey(event) {
|
|
64
|
+
var menu = menuElement(), state = store.get('splitSessionPicker'); if (!menu || menu.hidden || !state) return;
|
|
65
|
+
if (event.key === 'Escape') { event.preventDefault(); event.stopImmediatePropagation(); close(true); return; }
|
|
66
|
+
if (!menu.contains(event.target)) return;
|
|
67
|
+
var keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
68
|
+
if (keys.indexOf(event.key) < 0 && !(event.key === 'Enter' && event.target.tagName === 'INPUT')) return;
|
|
69
|
+
var items = Array.from(menu.querySelectorAll('[data-split-side], [data-split-session], [data-split-vendor]')).filter(function (button) { return !button.disabled; });
|
|
70
|
+
if (!items.length) return;
|
|
71
|
+
if (event.target.tagName === 'INPUT' && (event.key === 'Home' || event.key === 'End')) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
var index = items.indexOf(document.activeElement);
|
|
74
|
+
if (event.key === 'Enter') { items[0].click(); return; }
|
|
75
|
+
var next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 : index < 0 ? event.key === 'ArrowUp' ? items.length - 1 : 0 : (index + (event.key === 'ArrowUp' ? -1 : 1) + items.length) % items.length;
|
|
76
|
+
items[next].focus();
|
|
74
77
|
}
|
|
75
78
|
export function initSplitViewCreateMenu() {
|
|
76
79
|
document.addEventListener('clay-split-view-open-create', function (event) { if (event.detail && event.detail.anchor) open(event.detail.anchor); });
|
|
77
|
-
document.addEventListener('click', onClick, true);
|
|
78
|
-
document.addEventListener('
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
document.addEventListener('click', onClick, true); document.addEventListener('keydown', onKey, true);
|
|
81
|
+
document.addEventListener('input', function (event) {
|
|
82
|
+
var state = store.get('splitSessionPicker'); if (!state || !event.target.matches('#split-view-create-menu input')) return;
|
|
83
|
+
store.set({ splitSessionPicker: Object.assign({}, state, { query: event.target.value }) });
|
|
84
|
+
var label = menuElement().querySelector('.split-session-section'); label.textContent = event.target.value ? 'Search results' : 'Recent sessions'; renderPickerResults(menuElement()); position();
|
|
85
|
+
});
|
|
86
|
+
window.addEventListener('resize', position);
|
|
87
|
+
store.subscribe(function (state, previous) {
|
|
88
|
+
if (!state.splitSessionPicker) return;
|
|
89
|
+
if (!pickerCurrent(state.splitSessionPicker.receipt)) { close(false); return; }
|
|
90
|
+
if (state.sessionCreateOptions !== previous.sessionCreateOptions || state.projectSplitViewPending !== previous.projectSplitViewPending || state.paneBindingSessions !== previous.paneBindingSessions || state.splitSessionCreation !== previous.splitSessionCreation) { renderPickerResults(menuElement()); position(); }
|
|
91
|
+
});
|
|
83
92
|
}
|
|
@@ -36,3 +36,7 @@ export function presentationTree(view) {
|
|
|
36
36
|
JSON.stringify(receipt) === JSON.stringify(layoutScope(view)) ?
|
|
37
37
|
store.get('projectSplitViewPreviewTree') : view && view.tree;
|
|
38
38
|
}
|
|
39
|
+
export function bindingMatches(left, right) {
|
|
40
|
+
return !!left && !!right && left.sessionId === right.sessionId && left.sessionOriginId === right.sessionOriginId &&
|
|
41
|
+
left.generation === right.generation && left.requestId === right.requestId;
|
|
42
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Exact-context session insertion through the existing View and creation services.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
5
|
+
import { activeSavedView } from './view-workspace-state.js';
|
|
6
|
+
import { layoutScope, currentLayoutScope } from './split-view-layout-scope.js';
|
|
7
|
+
import { viewForOrigin } from './split-view-presentation-lookup.js';
|
|
8
|
+
import { applyPresentationDrop } from './split-view-presentation-drop.js';
|
|
9
|
+
import { mutation } from './view-mutations.js';
|
|
10
|
+
import { checkComposerDeparture } from './session-rich-drafts.js';
|
|
11
|
+
import { createOptions } from './session-create.js';
|
|
12
|
+
import { showToast } from './utils.js';
|
|
13
|
+
|
|
14
|
+
export function pickerReceipt(anchor) {
|
|
15
|
+
var view = activeSavedView(), pane = anchor && anchor.closest('.split-pane'), frame = pane && pane.querySelector('.split-pane-frame');
|
|
16
|
+
var origin = pane && pane.dataset.presentationOrigin || frame && frame.dataset.sessionOriginId || store.get('sessionOriginId');
|
|
17
|
+
return view && view.sessions.some(function (session) { return session.sessionOriginId === origin; }) ? { scope: layoutScope(view), origin: origin } : null;
|
|
18
|
+
}
|
|
19
|
+
export function pickerCurrent(receipt) {
|
|
20
|
+
var picker = store.get('splitSessionPicker');
|
|
21
|
+
return receipt && (!receipt.reservationId || picker && picker.receipt.reservationId === receipt.reservationId) && !store.get('paneMode') && currentLayoutScope(receipt.scope) && activeSavedView().sessions.some(function (session) { return session.sessionOriginId === receipt.origin; });
|
|
22
|
+
}
|
|
23
|
+
export function pickerCapacity() {
|
|
24
|
+
var view = activeSavedView();
|
|
25
|
+
return !!view && view.sessions.length + (view.workspace.linkedPair ? view.workspace.linkedPair.workerOrigins.length : 0) < 8;
|
|
26
|
+
}
|
|
27
|
+
function checkDrafts() {
|
|
28
|
+
checkComposerDeparture();
|
|
29
|
+
document.querySelectorAll('#split-host .split-pane:not(.split-pane-cached) .split-pane-frame').forEach(function (frame) { if (frame.clayPane) frame.clayPane.checkDraft(); });
|
|
30
|
+
}
|
|
31
|
+
export function insertPickerSession(receipt, side, id) {
|
|
32
|
+
if (['left', 'right', 'top', 'bottom'].indexOf(side) < 0 || !pickerCurrent(receipt) || !pickerCapacity() || store.get('projectSplitViewPending') || store.get('projectSplitViewError')) return false;
|
|
33
|
+
var view = activeSavedView(), session = (getCachedSessions() || []).find(function (item) { return item.id === id; });
|
|
34
|
+
if (!session || session.sessionRole === 'worker' || session.isMate || session.isTui || (session.ownerId || null) !== (store.get('myUserId') || null) || view.sessions.some(function (ref) { return ref.sessionOriginId === session.sessionOriginId; })) return false;
|
|
35
|
+
var source = viewForOrigin(session.sessionOriginId);
|
|
36
|
+
return applyPresentationDrop({ origin: session.sessionOriginId, sessionId: id, sourceViewId: source && source.id },
|
|
37
|
+
{ origin: receipt.origin, viewId: view.id, side: side }, mutation, checkDrafts) === true;
|
|
38
|
+
}
|
|
39
|
+
export function createPickerSession(receipt, side, vendor) {
|
|
40
|
+
if (['left', 'right', 'top', 'bottom'].indexOf(side) < 0 || !pickerCurrent(receipt) || !pickerCapacity() || store.get('splitSessionCreation') || store.get('projectSplitViewPending')) return false;
|
|
41
|
+
if (!createOptions().vendors.some(function (item) { return item.id === vendor && item.installed; })) return false;
|
|
42
|
+
try { checkDrafts(); } catch (error) { showToast(error.message, 'warn'); return false; }
|
|
43
|
+
var id = 'split-create-' + (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : Date.now().toString(36) + '-' + Math.random().toString(36).slice(2));
|
|
44
|
+
store.set({ splitSessionCreation: { receipt: receipt, side: side, requestId: id, createdId: null } });
|
|
45
|
+
try { getWs().send(JSON.stringify({ type: 'new_session', slug: store.get('currentSlug'), requestId: id, vendor: vendor, forceNew: true, activate: false })); }
|
|
46
|
+
catch (error) { store.set({ splitSessionCreation: null }); return false; }
|
|
47
|
+
setTimeout(function () {
|
|
48
|
+
var pending = store.get('splitSessionCreation');
|
|
49
|
+
if (pending && pending.requestId === id) {
|
|
50
|
+
store.set({ splitSessionCreation: null });
|
|
51
|
+
showToast('Creation was not confirmed. Check recent sessions before trying again.', 'warn');
|
|
52
|
+
}
|
|
53
|
+
}, 30000);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
export function handleSplitSessionCreated(message) {
|
|
57
|
+
var pending = store.get('splitSessionCreation');
|
|
58
|
+
if (message.type !== 'new_session_result' || !pending || message.requestId !== pending.requestId) return false;
|
|
59
|
+
if (!message.ok) { store.set({ splitSessionCreation: null }); showToast(message.error || 'The session could not be created.', 'error'); return true; }
|
|
60
|
+
store.set({ splitSessionCreation: Object.assign({}, pending, { createdId: message.sessionId }) });
|
|
61
|
+
finishPickerCreation(); return true;
|
|
62
|
+
}
|
|
63
|
+
export function finishPickerCreation() {
|
|
64
|
+
var pending = store.get('splitSessionCreation');
|
|
65
|
+
if (!pending || !pending.createdId || !getCachedSessions().some(function (session) { return session.id === pending.createdId; })) return;
|
|
66
|
+
store.set({ splitSessionCreation: null });
|
|
67
|
+
if (!insertPickerSession(pending.receipt, pending.side, pending.createdId)) showToast('The session was created in Unfiled. The View changed, so it was not added to the split.', 'warn');
|
|
68
|
+
}
|
|
69
|
+
store.subscribe(function (state, previous) {
|
|
70
|
+
if (state.splitSessionCreation && (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration || !state.connected)) store.set({ splitSessionCreation: null });
|
|
71
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
3
|
+
import { activeSavedView } from './view-workspace-state.js';
|
|
4
|
+
import { viewForOrigin } from './split-view-presentation-lookup.js';
|
|
5
|
+
import { createOptions } from './session-create.js';
|
|
6
|
+
import { sortVendorEntries } from './vendor-priority.js';
|
|
7
|
+
import { VENDOR_AVATARS, VENDOR_NAMES } from './chat-render-runtime.js';
|
|
8
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
9
|
+
import { pickerCapacity } from './split-view-picker-actions.js';
|
|
10
|
+
|
|
11
|
+
export var SPLIT_SIDES = [['right', 'Right', 'panel-right-open'], ['left', 'Left', 'panel-left-open'], ['top', 'Top', 'panel-top-open'], ['bottom', 'Bottom', 'panel-bottom-open']];
|
|
12
|
+
function button(label, action, value) {
|
|
13
|
+
var el = document.createElement('button'); el.type = 'button'; el.dataset[action] = value; el.setAttribute('aria-label', label); el.title = label; return el;
|
|
14
|
+
}
|
|
15
|
+
function vendorIcon(vendor) {
|
|
16
|
+
var image = document.createElement('img'); image.src = VENDOR_AVATARS[vendor] || '/icon-192.png'; image.alt = ''; return image;
|
|
17
|
+
}
|
|
18
|
+
function age(time) {
|
|
19
|
+
var minutes = Math.max(0, Math.floor((Date.now() - (time || 0)) / 60000));
|
|
20
|
+
return minutes < 1 ? 'Now' : minutes < 60 ? minutes + 'm' : minutes < 1440 ? Math.floor(minutes / 60) + 'h' : Math.floor(minutes / 1440) + 'd';
|
|
21
|
+
}
|
|
22
|
+
export function pickerCandidates(query) {
|
|
23
|
+
var view = activeSavedView(), text = (query || '').trim().toLocaleLowerCase();
|
|
24
|
+
return (getCachedSessions() || []).filter(function (session) {
|
|
25
|
+
return view && session.sessionOriginId && !session.hidden && session.sessionRole !== 'worker' && !session.isMate && !session.isTui &&
|
|
26
|
+
(session.ownerId || null) === (store.get('myUserId') || null) && !view.sessions.some(function (ref) { return ref.sessionOriginId === session.sessionOriginId; }) &&
|
|
27
|
+
(!text || (session.title || 'New session').toLocaleLowerCase().indexOf(text) >= 0);
|
|
28
|
+
}).sort(function (a, b) { return (b.lastActivity || b.createdAt || 0) - (a.lastActivity || a.createdAt || 0); }).slice(0, text ? 40 : 6);
|
|
29
|
+
}
|
|
30
|
+
export function renderPickerResults(menu) {
|
|
31
|
+
var state = store.get('splitSessionPicker'); if (!state) return;
|
|
32
|
+
var list = menu.querySelector('.split-session-results'); if (!list) return;
|
|
33
|
+
var focused = menu.contains(document.activeElement) && document.activeElement.dataset, focusKey = focused && (focused.splitSession ? ['splitSession', focused.splitSession] : focused.splitVendor ? ['splitVendor', focused.splitVendor] : null);
|
|
34
|
+
list.replaceChildren();
|
|
35
|
+
var candidates = pickerCandidates(state.query), blocked = !pickerCapacity() || !!store.get('projectSplitViewPending') || !!store.get('splitSessionCreation');
|
|
36
|
+
candidates.forEach(function (session) {
|
|
37
|
+
var title = session.title || 'New session', owner = viewForOrigin(session.sessionOriginId);
|
|
38
|
+
var choice = button('Add ' + title, 'splitSession', String(session.id)); choice.className = 'split-session-result'; choice.disabled = blocked;
|
|
39
|
+
choice.appendChild(vendorIcon(session.vendor));
|
|
40
|
+
var content = document.createElement('span'); content.className = 'split-session-result-title'; content.textContent = title; choice.appendChild(content);
|
|
41
|
+
var time = document.createElement('small'); time.textContent = age(session.lastActivity || session.createdAt); choice.appendChild(time);
|
|
42
|
+
choice.title = title + (owner ? ' · Move from ' + owner.name : '');
|
|
43
|
+
if (owner && owner.sessions.length > 1) { var note = document.createElement('small'); note.className = 'split-session-move'; note.textContent = 'Move from ' + owner.name; content.appendChild(note); }
|
|
44
|
+
list.appendChild(choice);
|
|
45
|
+
});
|
|
46
|
+
if (!candidates.length) { var empty = document.createElement('p'); empty.className = 'split-session-empty'; empty.textContent = state.query ? 'No matching sessions. Start a new one below.' : 'No other sessions yet. Start a new one below.'; list.appendChild(empty); }
|
|
47
|
+
var vendors = menu.querySelector('.split-session-vendors'); vendors.replaceChildren();
|
|
48
|
+
var options = createOptions(), installed = sortVendorEntries(options.vendors.filter(function (vendor) { return vendor.installed; }));
|
|
49
|
+
installed.forEach(function (vendor) {
|
|
50
|
+
var label = 'New ' + (VENDOR_NAMES[vendor.id] || vendor.displayName || vendor.id) + ' session';
|
|
51
|
+
var control = button(label, 'splitVendor', vendor.id); control.disabled = blocked; control.appendChild(vendorIcon(vendor.id)); vendors.appendChild(control);
|
|
52
|
+
});
|
|
53
|
+
if (options.error || !installed.length) {
|
|
54
|
+
var hint = document.createElement('span'); hint.className = 'split-session-empty'; hint.textContent = options.error || (options.loaded ? 'No installed vendors available.' : 'Loading vendors…'); vendors.appendChild(hint);
|
|
55
|
+
if (options.error) { var retry = button('Retry loading vendors', 'splitRetry', 'true'); retry.textContent = 'Retry'; vendors.appendChild(retry); }
|
|
56
|
+
}
|
|
57
|
+
var status = menu.querySelector('[role="status"]');
|
|
58
|
+
if (focusKey) { var restored = Array.from(menu.querySelectorAll('button')).find(function (item) { return item.dataset[focusKey[0]] === focusKey[1] && !item.disabled; }); if (restored) restored.focus({ preventScroll: true }); }
|
|
59
|
+
status.textContent = !pickerCapacity() ? 'This View already has eight session panes.' : store.get('projectSplitViewPending') ? 'Saving the View…' : store.get('splitSessionCreation') ? 'Creating session…' : state.error || '';
|
|
60
|
+
}
|
|
61
|
+
export function renderPicker(menu) {
|
|
62
|
+
var state = store.get('splitSessionPicker'); if (!state) return;
|
|
63
|
+
menu.replaceChildren(); menu.dataset.stage = state.side ? 'sessions' : 'direction';
|
|
64
|
+
if (!state.side) {
|
|
65
|
+
var header = document.createElement('div'); header.className = 'split-view-arrange-header';
|
|
66
|
+
var title = document.createElement('strong'); title.textContent = 'Add split'; header.appendChild(title);
|
|
67
|
+
var close = button('Close split picker', 'splitClose', 'true'); close.innerHTML = iconHtml('x'); header.appendChild(close); menu.appendChild(header);
|
|
68
|
+
var directions = document.createElement('div'); directions.className = 'split-session-directions';
|
|
69
|
+
SPLIT_SIDES.forEach(function (side) { var choice = button('Add ' + side[1] + ' Split', 'splitSide', side[0]); choice.disabled = !pickerCapacity() || !!store.get('projectSplitViewPending'); choice.innerHTML = iconHtml(side[2]); var label = document.createElement('span'); label.textContent = 'Add ' + side[1] + ' Split'; choice.appendChild(label); directions.appendChild(choice); });
|
|
70
|
+
menu.appendChild(directions); refreshIcons(); return;
|
|
71
|
+
}
|
|
72
|
+
var search = document.createElement('label'); search.className = 'split-session-search'; search.innerHTML = iconHtml('search');
|
|
73
|
+
var input = document.createElement('input'); input.type = 'search'; input.placeholder = 'Search sessions…'; input.setAttribute('aria-label', 'Search sessions'); input.value = state.query || ''; search.appendChild(input); menu.appendChild(search);
|
|
74
|
+
var recent = document.createElement('div'); recent.className = 'split-session-section'; recent.textContent = state.query ? 'Search results' : 'Recent sessions'; menu.appendChild(recent);
|
|
75
|
+
var list = document.createElement('div'); list.className = 'split-session-results'; list.setAttribute('aria-label', 'Available sessions'); menu.appendChild(list);
|
|
76
|
+
var heading = document.createElement('div'); heading.className = 'split-session-section split-session-new'; heading.textContent = 'New session'; menu.appendChild(heading);
|
|
77
|
+
var vendors = document.createElement('div'); vendors.className = 'split-session-vendors'; vendors.setAttribute('aria-label', 'Installed vendors'); menu.appendChild(vendors);
|
|
78
|
+
var status = document.createElement('p'); status.className = 'split-session-status'; status.setAttribute('role', 'status'); menu.appendChild(status);
|
|
79
|
+
var footer = document.createElement('small'); footer.className = 'split-session-hint'; footer.textContent = '↑↓ Choose · Enter Open · Esc Cancel'; menu.appendChild(footer);
|
|
80
|
+
renderPickerResults(menu); refreshIcons();
|
|
81
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { paintLegacyToolPanes, clearToolPaneWindows } from './tool-pane-legacy.js';
|
|
2
2
|
import { toolPaneLayoutTree, paintToolPaneWindows, bindWorkspaceDivider, dockedToolPanes } from './tool-pane-layout.js';
|
|
3
3
|
import { afterPaneMotion } from './split-view-drag-preview.js';
|
|
4
|
-
import { layoutScope, currentLayoutScope, presentationTree } from './split-view-layout-scope.js';
|
|
4
|
+
import { layoutScope, currentLayoutScope, presentationTree, bindingMatches } from './split-view-layout-scope.js';
|
|
5
5
|
// Flat, stable pane presentation for persisted binary trees. Geometry is
|
|
6
6
|
// applied to existing direct-child .split-pane nodes; iframes are never moved
|
|
7
7
|
// below a nested layout wrapper.
|
|
@@ -17,6 +17,7 @@ import { syncPaneHeaderState } from './split-pane-header-state.js';
|
|
|
17
17
|
import { rectangles, resize, validateTree, listIds } from './split-view-layout-client.js';
|
|
18
18
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
19
19
|
import { appendLayoutOption } from './split-view-layout-icons.js';
|
|
20
|
+
import { reserveEmptySplit, clearEmptySplit } from './split-session-empty-pane.js';
|
|
20
21
|
|
|
21
22
|
var host = null;
|
|
22
23
|
var app = null;
|
|
@@ -24,7 +25,6 @@ var menuTrigger = null;
|
|
|
24
25
|
var resizeObserver = null;
|
|
25
26
|
var geometryToken = 0;
|
|
26
27
|
var stickyStatusOrigins = null;
|
|
27
|
-
|
|
28
28
|
function relocateStickyStatus(enabled) {
|
|
29
29
|
var ids = ['todo-sticky', 'ralph-sticky', 'debate-sticky'];
|
|
30
30
|
var main = document.getElementById('main-panels'), appElement = document.getElementById('app');
|
|
@@ -52,6 +52,7 @@ export function initPresentationRenderer(hostElement) {
|
|
|
52
52
|
host = hostElement;
|
|
53
53
|
app = document.getElementById("app");
|
|
54
54
|
ensureArrangeMenu();
|
|
55
|
+
document.addEventListener('clay-split-picker-layout', renderCurrentGeometry);
|
|
55
56
|
document.addEventListener("click", onClick, true);
|
|
56
57
|
document.addEventListener("keydown", onKeyDown, true);
|
|
57
58
|
document.addEventListener("pointermove", onPointerMove, true);
|
|
@@ -69,6 +70,7 @@ export function initPresentationRenderer(hostElement) {
|
|
|
69
70
|
|
|
70
71
|
export function hidePresentationView() {
|
|
71
72
|
if (!host) return;
|
|
73
|
+
clearEmptySplit(host);
|
|
72
74
|
var legacySplit = store.get('splitPanes');
|
|
73
75
|
if (legacySplit) releaseRetainedPanes(host);
|
|
74
76
|
else clearRetainedPanes(host);
|
|
@@ -218,11 +220,12 @@ function applyGeometry(view, tree, stillCurrent) {
|
|
|
218
220
|
var windowTree = toolPaneLayoutTree(view, tree);
|
|
219
221
|
var geometry = rectangles(windowTree, Math.max(0, host.clientWidth - left - right), Math.max(0, host.clientHeight - top - bottom), 7);
|
|
220
222
|
paintToolPaneWindows(host, view, tree, windowTree, geometry, { left: left, top: top });
|
|
223
|
+
var reserving = reserveEmptySplit(host, geometry, { left: left, top: top });
|
|
221
224
|
var focused = store.get('projectSplitViewFocusedOrigin');
|
|
222
225
|
var panes = host.querySelectorAll(':scope > .split-pane');
|
|
223
226
|
for (var i = 0; i < panes.length; i++) {
|
|
224
227
|
var frame = panes[i].querySelector('.split-pane-frame'), origin = panes[i].dataset.presentationOrigin || frame && frame.dataset.sessionOriginId;
|
|
225
|
-
var rect = host.dataset.singlePane === 'true' && panes.length === 1 && !dockedToolPanes().length ?
|
|
228
|
+
var rect = !reserving && host.dataset.singlePane === 'true' && panes.length === 1 && !dockedToolPanes().length ?
|
|
226
229
|
{ x: 0, y: 0, width: Math.max(0, host.clientWidth - left - right), height: Math.max(0, host.clientHeight - top - bottom) } : geometry.panes[origin];
|
|
227
230
|
if (!rect) continue;
|
|
228
231
|
if (focused && geometry.panes[focused]) {
|
|
@@ -245,6 +248,7 @@ function applyGeometry(view, tree, stillCurrent) {
|
|
|
245
248
|
divider.setAttribute("role", "separator"); divider.setAttribute("tabindex", "0");
|
|
246
249
|
divider.setAttribute("aria-orientation", entry.axis === "horizontal" ? "vertical" : "horizontal");
|
|
247
250
|
divider.setAttribute("aria-label", "Resize Split view panels");
|
|
251
|
+
if (reserving) { divider.inert = true; divider.tabIndex = -1; divider.setAttribute('aria-disabled', 'true'); }
|
|
248
252
|
divider.style.left = (left + entry.rect.x) + "px"; divider.style.top = (top + entry.rect.y) + "px";
|
|
249
253
|
divider.style.width = entry.rect.width + "px"; divider.style.height = entry.rect.height + "px";
|
|
250
254
|
if (dockedToolPanes().length) bindWorkspaceDivider(divider, entry, windowTree);
|
|
@@ -261,15 +265,11 @@ function applyGeometry(view, tree, stillCurrent) {
|
|
|
261
265
|
});
|
|
262
266
|
});
|
|
263
267
|
}
|
|
264
|
-
function bindingMatches(left, right) {
|
|
265
|
-
return !!left && !!right && left.sessionId === right.sessionId && left.sessionOriginId === right.sessionOriginId &&
|
|
266
|
-
left.generation === right.generation && left.requestId === right.requestId;
|
|
267
|
-
}
|
|
268
268
|
export function ensureArrangeTrigger(header) {
|
|
269
269
|
if (!header) return;
|
|
270
270
|
if (!header.querySelector('[data-action="arrange"]')) {
|
|
271
271
|
var trigger = document.createElement("button"); trigger.type = "button"; trigger.className = "split-view-arrange-trigger";
|
|
272
|
-
trigger.dataset.action = "arrange"; trigger.setAttribute("aria-label", "
|
|
272
|
+
trigger.dataset.action = "arrange"; trigger.setAttribute("aria-label", "Split view options");
|
|
273
273
|
trigger.setAttribute("aria-expanded", "false"); trigger.setAttribute("aria-controls", "split-view-arrange-menu");
|
|
274
274
|
trigger.innerHTML = iconHtml("columns-2");
|
|
275
275
|
var close = header.querySelector(".split-pane-close"); header.insertBefore(trigger, close || null);
|
|
@@ -22,14 +22,14 @@ export function workerActivityLabel(message, names) {
|
|
|
22
22
|
}
|
|
23
23
|
export function captureWorkerDisclosures(body) {
|
|
24
24
|
return {
|
|
25
|
-
open: Array.from(body.querySelectorAll('details[open]')).map(function (node) { return node.dataset.disclosureKey; }),
|
|
25
|
+
open: Array.from(body.querySelectorAll('details[open], [data-disclosure-key][aria-expanded="true"]')).map(function (node) { return node.dataset.disclosureKey; }),
|
|
26
26
|
focus: document.activeElement && body.contains(document.activeElement) && (document.activeElement.dataset.disclosureKey || document.activeElement.parentElement && document.activeElement.parentElement.dataset.disclosureKey)
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
export function restoreWorkerDisclosures(body, saved) {
|
|
30
30
|
Array.from(body.querySelectorAll('[data-disclosure-key]')).forEach(function (node) {
|
|
31
31
|
var key = node.dataset.disclosureKey;
|
|
32
|
-
if (saved.open.indexOf(key) >= 0) node.open = true;
|
|
32
|
+
if (saved.open.indexOf(key) >= 0) { if (node.tagName === 'DETAILS') node.open = true; else if (node.getAttribute('aria-expanded') !== 'true') node.click(); }
|
|
33
33
|
if (saved.focus === key) {
|
|
34
34
|
var target = node.tagName === 'DETAILS' ? node.querySelector('summary') : node;
|
|
35
35
|
if (target) target.focus({ preventScroll: true });
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { workerActivityNames } from './split-view-worker-activity.js';
|
|
2
|
+
import { createAssistantBubble, createUserBubble, renderAssistantBubbleText } from './chat-bubble-renderer.js';
|
|
3
|
+
import { createSessionToolItem, createSessionToolGroup, sessionToolGroupSummary } from './session-tool-item.js';
|
|
4
|
+
import { applyClayToolItem, bindToolGroupDisclosure } from './clay-tool-activity.js';
|
|
5
|
+
import { iconHtml } from './icons.js';
|
|
6
|
+
import { VENDOR_AVATARS } from './chat-render-runtime.js';
|
|
3
7
|
|
|
4
8
|
// Reassemble bounded record fragments and contiguous streaming deltas before
|
|
5
9
|
// parsing Markdown, including fences and lists that cross a page boundary.
|
|
@@ -17,38 +21,71 @@ export function workerTranscriptRows(messages) {
|
|
|
17
21
|
return rows;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
function inputFor(message) {
|
|
25
|
+
if (message.recordType !== 'tool_use' && message.recordType !== 'tool_executing') return null;
|
|
26
|
+
try { var start = message.text.indexOf('{'); return start >= 0 ? JSON.parse(message.text.slice(start)) : null; } catch (error) { return null; }
|
|
27
|
+
}
|
|
28
|
+
export function appendWorkerMessage(body, message, names, options) {
|
|
29
|
+
options = options || {};
|
|
21
30
|
if (message.role === 'tool') {
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
var
|
|
27
|
-
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
var name = message.toolName || names && names[message.toolId] && names[message.toolId].name || 'Tool activity';
|
|
32
|
+
var el = createSessionToolItem(message.toolId, name);
|
|
33
|
+
el.classList.add('worker-readonly-message', 'worker-tool-message'); el.dataset.recordIndex = String(message.recordIndex);
|
|
34
|
+
el.dataset.phase = message.isError ? 'failed' : message.recordType === 'tool_result' ? 'returned' : 'recorded';
|
|
35
|
+
var input = inputFor(message), output = message.resultText;
|
|
36
|
+
var tool = { el: el, name: name, input: input, clayRawResult: output, clayOutcome: output !== undefined ? { phase: message.isError ? 'error' : 'done' } : { phase: 'returned', status: 'result_unavailable' } };
|
|
37
|
+
var header = el.querySelector('.tool-header'); header.dataset.disclosureKey = 'record-' + message.recordIndex;
|
|
38
|
+
if (!applyClayToolItem(tool)) {
|
|
39
|
+
var terminal = message.isError ? 'Failed' : output !== undefined || message.recordType === 'tool_result' ? 'Result returned' : 'Call recorded';
|
|
40
|
+
el.querySelector('.tool-desc').textContent = input && (input.file_path || input.command || input.pattern) || '';
|
|
41
|
+
el.querySelector('.tool-subtitle-text').textContent = terminal;
|
|
42
|
+
el.querySelector('.tool-status-icon').innerHTML = iconHtml(message.isError ? 'alert-triangle' : output !== undefined ? 'check' : 'circle-help');
|
|
43
|
+
el.classList.toggle('done', output !== undefined); el.classList.toggle('error', !!message.isError);
|
|
44
|
+
var block = document.createElement('div'); block.className = 'tool-result-block collapsed';
|
|
45
|
+
var pre = document.createElement('pre'); pre.textContent = output !== undefined ? output : message.text; block.appendChild(pre); el.appendChild(block);
|
|
46
|
+
header.setAttribute('role', 'button'); header.tabIndex = 0; header.setAttribute('aria-expanded', 'false');
|
|
47
|
+
function toggle() { var expanded = el.classList.toggle('expanded'); block.classList.toggle('collapsed', !expanded); header.setAttribute('aria-expanded', String(expanded)); }
|
|
48
|
+
header.addEventListener('click', toggle); header.addEventListener('keydown', function (event) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); toggle(); } });
|
|
49
|
+
}
|
|
50
|
+
body.appendChild(el); return;
|
|
34
51
|
}
|
|
35
|
-
var
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
var
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Keep the shared Markdown parser and its sanitization, with a narrower
|
|
43
|
-
// read-only prose vocabulary for this secondary history surface.
|
|
44
|
-
content.innerHTML = DOMPurify.sanitize(renderMarkdown(message.text), {
|
|
45
|
-
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'del', 's', 'blockquote', 'ul', 'ol', 'li', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'a', 'wbr'],
|
|
46
|
-
ALLOWED_ATTR: ['href', 'title', 'target', 'rel', 'start'], ALLOW_DATA_ATTR: false
|
|
47
|
-
});
|
|
48
|
-
article.append(author, content); body.appendChild(article);
|
|
52
|
+
var user = message.role === 'user';
|
|
53
|
+
var label = message.authorLabel || (user ? options.userName || 'Driver' : options.assistantName || 'Worker');
|
|
54
|
+
var config = { tagName: 'article', name: label, avatarUrl: user ? options.userAvatar || '/icon-192.png' : VENDOR_AVATARS[options.vendor || 'claude'], text: message.text };
|
|
55
|
+
var article = user ? createUserBubble(config) : createAssistantBubble(config);
|
|
56
|
+
article.classList.add('worker-readonly-message'); article.dataset.recordIndex = String(message.recordIndex);
|
|
57
|
+
if (!user) renderAssistantBubbleText(article, message.text, true, { readOnly: true });
|
|
58
|
+
body.appendChild(article);
|
|
49
59
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
export function appendWorkerTranscript(body, messages, options) {
|
|
61
|
+
var rows = workerTranscriptRows(messages), names = workerActivityNames(rows), consumed = new Set(), visible = [];
|
|
62
|
+
rows.forEach(function (message, index) {
|
|
63
|
+
if (consumed.has(index)) return;
|
|
64
|
+
var row = message;
|
|
65
|
+
if (message.role === 'tool' && names[message.toolId] && names[message.toolId].index === message.recordIndex) {
|
|
66
|
+
var later = rows.slice(index + 1), executing = later.find(function (item) { return item.toolId === message.toolId && item.recordType === 'tool_executing'; });
|
|
67
|
+
var result = later.find(function (item) { return item.toolId === message.toolId && item.recordType === 'tool_result'; });
|
|
68
|
+
row = Object.assign({}, message, executing ? { text: executing.text, recordType: executing.recordType } : {}, result ? { resultText: result.text, isError: result.isError } : {});
|
|
69
|
+
[executing, result].forEach(function (item) { if (item) consumed.add(rows.indexOf(item)); });
|
|
70
|
+
}
|
|
71
|
+
visible.push(row);
|
|
72
|
+
});
|
|
73
|
+
var group = null, groupRows = [];
|
|
74
|
+
function finish() {
|
|
75
|
+
if (!group) return;
|
|
76
|
+
var header = group.querySelector('.tool-group-header'), done = groupRows.every(function (row) { return row.resultText !== undefined || row.recordType === 'tool_result'; });
|
|
77
|
+
var meta = { toolNames: groupRows.map(function (row) { return row.toolName || names[row.toolId] && names[row.toolId].name || 'Tool'; }), doneCount: done ? groupRows.length : 0 };
|
|
78
|
+
header.style.display = groupRows.length > 1 ? '' : 'none';
|
|
79
|
+
header.dataset.disclosureKey = 'group-' + groupRows[0].recordIndex;
|
|
80
|
+
group.querySelector('.tool-group-label').textContent = done ? sessionToolGroupSummary(meta, {}) : groupRows.length + ' tool calls recorded';
|
|
81
|
+
group.classList.toggle('done', done); group.classList.toggle('collapsed', groupRows.length > 1);
|
|
82
|
+
group.querySelector('.tool-group-status-icon').innerHTML = iconHtml(done ? 'check' : 'circle-help');
|
|
83
|
+
bindToolGroupDisclosure(group, function () {}); group = null; groupRows = [];
|
|
84
|
+
}
|
|
85
|
+
visible.forEach(function (row) {
|
|
86
|
+
if (row.role !== 'tool') { finish(); appendWorkerMessage(body, row, names, options); return; }
|
|
87
|
+
if (!group) { group = createSessionToolGroup(); body.appendChild(group); }
|
|
88
|
+
groupRows.push(row); appendWorkerMessage(group.querySelector('.tool-group-items'), row, names, options);
|
|
89
|
+
});
|
|
90
|
+
finish();
|
|
54
91
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { syncWorkerMessagePreviews } from './worker-message-previews.js';
|
|
1
2
|
import { appendWorkerRemoval, reconcileWorkerRemoval } from './split-view-worker-removal.js';
|
|
2
3
|
import { workerCursorKey, workerCursorAdvances, shouldLoadWorkerPage, captureWorkerAnchor, restoreWorkerAnchor } from './split-view-worker-scroll.js';
|
|
3
4
|
import { appendWorkerTranscript } from './split-view-worker-transcript.js';
|
|
@@ -189,7 +190,7 @@ function open(chip, driver, worker, takeFocus) {
|
|
|
189
190
|
if (existing && existing.driverOriginId === driver.sessionOriginId && existing.workerOriginId === worker.sessionOriginId && existing.generation === worker.workerGeneration) { close(true); return; }
|
|
190
191
|
if (existing) close(false);
|
|
191
192
|
var state = { requestId: null, driverOriginId: driver.sessionOriginId, workerOriginId: worker.sessionOriginId,
|
|
192
|
-
generation: worker.workerGeneration, driverTitle: driver.title || "Driver session", workerTitle: worker.title || "Worker",
|
|
193
|
+
generation: worker.workerGeneration, workerVendor: worker.vendor, driverTitle: driver.title || "Driver session", workerTitle: worker.title || "Worker",
|
|
193
194
|
sourceBinding: chip.closest('.split-pane').querySelector('.split-pane-frame').clayPane.identity().binding,
|
|
194
195
|
status: "loading", messages: [], hasMore: false, nextBefore: null, append: false, inFlight: false, initialPage: true };
|
|
195
196
|
store.set({ splitViewWorkerConversation: state }); ensurePopover(); renderContent();
|
|
@@ -217,7 +218,7 @@ function ensurePopover() {
|
|
|
217
218
|
var closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.setAttribute("aria-label", "Close Worker conversation"); closeButton.innerHTML = iconHtml("x");
|
|
218
219
|
closeButton.addEventListener("click", function () { close(true); }); header.appendChild(closeButton); element.appendChild(header);
|
|
219
220
|
var source = document.createElement("div"); source.className = "split-view-worker-popover-source"; element.appendChild(source);
|
|
220
|
-
var body = document.createElement("div"); body.className = "split-view-worker-popover-body"; body.tabIndex = 0; body.setAttribute("aria-label", "Read-only Worker transcript"); element.appendChild(body);
|
|
221
|
+
var body = document.createElement("div"); body.className = "split-view-worker-popover-body readonly-session-transcript"; body.tabIndex = 0; body.setAttribute("aria-label", "Read-only Worker transcript"); element.appendChild(body);
|
|
221
222
|
body.addEventListener('scroll', function () { loadEarlier(body, true); }, { passive: true });
|
|
222
223
|
body.addEventListener('wheel', function (event) { if (event.deltaY < 0) loadEarlier(body, true); }, { passive: true });
|
|
223
224
|
var footer = document.createElement("footer"); footer.className = "split-view-worker-popover-footer"; element.appendChild(footer);
|
|
@@ -241,7 +242,7 @@ function renderContent() {
|
|
|
241
242
|
if (state.status === "loading" && !state.messages.length) body.textContent = "Loading conversation…";
|
|
242
243
|
else if (state.status === "error" && !state.messages.length) body.textContent = state.error || "Conversation is unavailable.";
|
|
243
244
|
else if (!state.messages.length) body.textContent = "No recorded conversation.";
|
|
244
|
-
appendWorkerTranscript(body, state.messages);
|
|
245
|
+
appendWorkerTranscript(body, state.messages, { vendor: state.workerVendor, assistantName: state.workerTitle });
|
|
245
246
|
restoreWorkerDisclosures(body, disclosures);
|
|
246
247
|
if ((state.initialPage || state.liveRefresh && followTail) && state.status === 'ready') body.scrollTop = body.scrollHeight;
|
|
247
248
|
else if (anchor) restoreWorkerAnchor(body, anchor);
|
|
@@ -370,7 +371,7 @@ export function renderPaneWorkers(container, driver) {
|
|
|
370
371
|
data.items.forEach(function (worker) {
|
|
371
372
|
var pill = document.createElement("span"); pill.className = "split-view-worker-pill";
|
|
372
373
|
var button = document.createElement("button"); button.type = "button"; button.className = "split-view-worker-chip";
|
|
373
|
-
button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
|
|
374
|
+
button.dataset.workerParent = driver.sessionOriginId; button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
|
|
374
375
|
var current = data.currentIds.has(worker.id);
|
|
375
376
|
var modelLabel = worker.model || "Default model";
|
|
376
377
|
button.title = (VENDOR_NAMES[worker.vendor] || worker.vendor || "Unknown vendor") + " · " + modelLabel + " · Split Worker #" + worker.id +
|
|
@@ -419,6 +420,7 @@ export function renderPaneWorkers(container, driver) {
|
|
|
419
420
|
root.appendChild(controls);
|
|
420
421
|
}
|
|
421
422
|
introduceWorkerViewer(container, driver);
|
|
423
|
+
syncWorkerMessagePreviews();
|
|
422
424
|
var conversation = store.get('splitViewWorkerConversation');
|
|
423
425
|
if (conversation) ensurePopover();
|
|
424
426
|
}
|