clay-server 5.3.0-beta.5 → 5.3.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/clay-product-control-tools.js +2 -2
- package/lib/clay-product-control.js +1 -1
- package/lib/clay-screen-controls.js +1 -0
- package/lib/clay-split-view-awareness.js +5 -2
- package/lib/clay-split-view-controls.js +14 -7
- package/lib/clay-split-view-input.js +5 -4
- package/lib/clay-split-view-render-proof.js +14 -4
- package/lib/clay-view-workspace-input.js +71 -0
- 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-file-history.js +11 -9
- package/lib/project-file-watch.js +26 -7
- package/lib/project-filesystem.js +5 -4
- package/lib/project-sessions.js +12 -5
- package/lib/project-shared-browser.js +7 -3
- package/lib/project-split-view-document.js +16 -2
- package/lib/project-split-views.js +27 -297
- package/lib/project-user-message.js +4 -3
- package/lib/project-vendor-login.js +2 -2
- package/lib/project-view-input.js +11 -0
- package/lib/project-view-pair.js +26 -0
- package/lib/project-view-store.js +348 -0
- package/lib/project-view-terminal-binding.js +16 -0
- package/lib/project-view-workspace.js +126 -0
- package/lib/project-worker-conversation.js +5 -0
- package/lib/project.js +1 -0
- package/lib/public/app.js +9 -0
- package/lib/public/css/contextual-clay.css +2 -1
- package/lib/public/css/issues.css +3 -0
- package/lib/public/css/mate-conversation.css +1 -1
- package/lib/public/css/pane.css +5 -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/right-workbench.css +0 -1
- package/lib/public/css/scheduled-tasks.css +7 -0
- package/lib/public/css/shared-browser.css +31 -8
- package/lib/public/css/split-view-layout.css +129 -10
- package/lib/public/css/tool-pane.css +124 -0
- package/lib/public/index.html +6 -6
- 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/clay-file-links.js +2 -1
- package/lib/public/modules/clay-split-view-screen.js +6 -3
- package/lib/public/modules/clay-tool-pane-screen.js +35 -0
- package/lib/public/modules/contextual-clay-geometry.js +7 -3
- package/lib/public/modules/file-history-snapshot.js +12 -0
- package/lib/public/modules/file-pane-entrypoints.js +55 -0
- package/lib/public/modules/file-pane-history.js +78 -0
- package/lib/public/modules/file-pane-instances.js +140 -0
- package/lib/public/modules/file-pane-links.js +41 -0
- package/lib/public/modules/file-pane-render.js +48 -0
- package/lib/public/modules/file-pane-review.js +26 -0
- package/lib/public/modules/file-pane-toolbar.js +54 -0
- package/lib/public/modules/file-viewer-source.js +70 -0
- package/lib/public/modules/filebrowser.js +43 -82
- package/lib/public/modules/git-panel.js +5 -0
- package/lib/public/modules/issues.js +3 -4
- package/lib/public/modules/linear-panel.js +2 -2
- package/lib/public/modules/mate-knowledge-workbench.js +10 -6
- package/lib/public/modules/notifications.js +4 -1
- package/lib/public/modules/pane-activation.js +7 -3
- package/lib/public/modules/pane-binding.js +5 -1
- package/lib/public/modules/pane-browser-bridge.js +6 -2
- package/lib/public/modules/pane-browser-transport.js +110 -0
- 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/plantuml-preview.js +12 -12
- package/lib/public/modules/project-admin-overview.js +125 -10
- package/lib/public/modules/project-grouping.js +9 -0
- package/lib/public/modules/right-workbench.js +34 -15
- package/lib/public/modules/scheduled-tasks.js +7 -6
- 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/shared-browser.js +69 -9
- package/lib/public/modules/sidebar-projects.js +1 -10
- package/lib/public/modules/split-pane-renderer.js +1 -0
- 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-drag-preview.js +90 -0
- package/lib/public/modules/split-view-layout-client.js +1 -1
- 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-drag.js +36 -13
- package/lib/public/modules/split-view-presentation-drop.js +21 -18
- package/lib/public/modules/split-view-presentation-renderer.js +22 -17
- package/lib/public/modules/split-view-presentations.js +25 -33
- 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/split-view.js +9 -2
- package/lib/public/modules/sticky-notes-browser.js +2 -2
- package/lib/public/modules/terminal-viewport.js +14 -0
- package/lib/public/modules/terminal.js +49 -23
- package/lib/public/modules/theme-palette.js +177 -0
- package/lib/public/modules/theme.js +2 -180
- package/lib/public/modules/tool-pane-chrome.js +88 -0
- package/lib/public/modules/tool-pane-drag.js +134 -0
- package/lib/public/modules/tool-pane-layout.js +95 -0
- package/lib/public/modules/tool-pane-legacy.js +60 -0
- package/lib/public/modules/tool-pane-tree.js +51 -0
- package/lib/public/modules/tools.js +5 -89
- package/lib/public/modules/tooltip.js +32 -6
- package/lib/public/modules/view-legacy-coordinates.js +16 -0
- package/lib/public/modules/view-mutations.js +24 -0
- package/lib/public/modules/view-pair-presentation.js +74 -0
- package/lib/public/modules/view-pair-validation.js +12 -0
- package/lib/public/modules/view-presentation-state.js +37 -0
- package/lib/public/modules/view-workspace-controller.js +210 -0
- package/lib/public/modules/view-workspace-state.js +103 -0
- package/lib/public/modules/view-workspace-validation.js +47 -0
- package/lib/public/modules/wireframe-workbench.js +13 -2
- package/lib/public/modules/workbench-layout-preference.js +12 -5
- package/lib/public/modules/workbench-panels.js +21 -0
- 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-6QFLYOVY.js +1 -0
- package/lib/public/pane-assets/chunk-7OVNC5HT.js +1 -0
- package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
- package/lib/public/pane-assets/chunk-EMLN2Y7L.js +1 -0
- package/lib/public/pane-assets/chunk-ENPAHXLB.js +1 -0
- 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-FQTNR4ZS.js +1 -0
- package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
- 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-R7QVVFUG.js +1 -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-SJOT74VW.js +2 -0
- package/lib/public/pane-assets/chunk-SJTGHIN5.js +1 -0
- 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-UPXU2IPC.js +1 -0
- package/lib/public/pane-assets/chunk-VEBYD7TL.js +1 -0
- package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
- package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
- package/lib/public/pane-assets/manifest.json +438 -381
- package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-message-processor.js +5 -2
- package/lib/server-storage.js +27 -3
- package/lib/server-workbench-layout-settings.js +3 -3
- package/lib/server.js +4 -2
- package/lib/sessions.js +1 -1
- package/lib/split-view-layout.js +11 -7
- package/lib/storage-usage.js +29 -7
- package/lib/terminal-manager.js +2 -2
- package/lib/users-workbench-layout-preferences.js +14 -4
- package/lib/workbench-window-layout.js +30 -0
- 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-6H5S2Y3Z.js +0 -1
- package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
- package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
- package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
- package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
- package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
- package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
- package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
- package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
- package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { dismissTooltip } from './tooltip.js';
|
|
2
|
+
import { renderMarkdown } from './markdown.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
|
|
5
|
+
var timer = null;
|
|
6
|
+
function context() { return JSON.stringify([store.get('myUserId') || null, store.get('currentSlug'), store.get('projectSplitViewSocketGeneration') || 0]); }
|
|
7
|
+
export function messagePreviewKey(kind, identities) { return JSON.stringify([context(), kind].concat(identities)); }
|
|
8
|
+
function element() { return document.getElementById('agent-message-bubble'); }
|
|
9
|
+
function anchor(key) { return Array.from(document.querySelectorAll('[data-message-preview]')).find(function (node) { return node.dataset.messagePreview === key && node.isConnected && node.getClientRects().length && !node.closest('[hidden], .split-pane-cached'); }); }
|
|
10
|
+
function remove() {
|
|
11
|
+
if (timer) clearTimeout(timer); timer = null;
|
|
12
|
+
var node = element(); if (node) node.remove();
|
|
13
|
+
document.querySelectorAll('[aria-describedby="agent-message-bubble"]').forEach(function (chip) { chip.removeAttribute('aria-describedby'); });
|
|
14
|
+
if (store.get('agentMessageBubble')) store.set({ agentMessageBubble: null });
|
|
15
|
+
}
|
|
16
|
+
function position() {
|
|
17
|
+
var state = store.get('agentMessageBubble'), node = element(), chip = state && anchor(state.key);
|
|
18
|
+
if (!chip || !node) { remove(); return; }
|
|
19
|
+
var rect = chip.getBoundingClientRect(), width = Math.min(360, innerWidth - 20);
|
|
20
|
+
var left = Math.max(10, Math.min(rect.left, innerWidth - width - 10));
|
|
21
|
+
node.style.width = width + 'px'; node.style.left = left + 'px'; node.style.top = Math.min(rect.bottom + 8, innerHeight - 70) + 'px';
|
|
22
|
+
node.style.setProperty('--bubble-tip-x', Math.max(16, Math.min(width - 16, rect.left + Math.min(rect.width / 2, 40) - left)) + 'px');
|
|
23
|
+
}
|
|
24
|
+
function renderMetadata(node, latest) {
|
|
25
|
+
var meta = node.querySelector('.agent-message-bubble-meta');
|
|
26
|
+
if (!meta) { meta = document.createElement('div'); meta.className = 'agent-message-bubble-meta'; node.prepend(meta); }
|
|
27
|
+
meta.textContent = '';
|
|
28
|
+
var vendor = (store.get('vendorInfo') || {})[latest.vendor] || {};
|
|
29
|
+
if (vendor.avatar) {
|
|
30
|
+
var avatar = document.createElement('img'); avatar.className = 'agent-message-bubble-avatar';
|
|
31
|
+
avatar.src = vendor.avatar; avatar.alt = ''; meta.appendChild(avatar);
|
|
32
|
+
}
|
|
33
|
+
var name = document.createElement('span'); name.className = 'agent-message-bubble-vendor';
|
|
34
|
+
name.textContent = vendor.displayName || 'Assistant'; meta.appendChild(name);
|
|
35
|
+
var time = document.createElement('time'); time.className = 'agent-message-bubble-time';
|
|
36
|
+
if (latest.timestamp && Number.isFinite(new Date(latest.timestamp).getTime())) {
|
|
37
|
+
var date = new Date(latest.timestamp); time.dateTime = date.toISOString();
|
|
38
|
+
time.textContent = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
39
|
+
time.setAttribute('aria-label', 'Message time: ' + date.toLocaleString());
|
|
40
|
+
} else time.textContent = 'Time unavailable';
|
|
41
|
+
meta.appendChild(time);
|
|
42
|
+
}
|
|
43
|
+
function show(key, held, fresh) {
|
|
44
|
+
var latest = (store.get('agentMessagePreviews') || {})[key], chip = anchor(key);
|
|
45
|
+
if (!latest || !chip || store.get('splitViewWorkerConversation') || store.get('backgroundTaskViewer') || store.get('paneSuspended') || document.hidden || !store.get('connected')) return;
|
|
46
|
+
var old = store.get('agentMessageBubble'), expires = fresh ? Date.now() + 7000 : old && old.key === key ? old.expiresAt : 0;
|
|
47
|
+
var node = element();
|
|
48
|
+
if (!node || !old || old.key !== key || fresh) {
|
|
49
|
+
if (node) node.remove();
|
|
50
|
+
document.querySelectorAll('[aria-describedby="agent-message-bubble"]').forEach(function (other) { other.removeAttribute('aria-describedby'); });
|
|
51
|
+
node = document.createElement('div'); node.id = 'agent-message-bubble'; node.className = 'agent-message-bubble';
|
|
52
|
+
node.setAttribute('role', 'tooltip'); var text = document.createElement('div'); text.className = 'agent-message-bubble-text'; node.appendChild(text); document.body.appendChild(node);
|
|
53
|
+
}
|
|
54
|
+
renderMetadata(node, latest);
|
|
55
|
+
node.querySelector('.agent-message-bubble-text').innerHTML = DOMPurify.sanitize(renderMarkdown(latest.text), { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'blockquote', 'del', 's'], ALLOWED_ATTR: [], ALLOW_DATA_ATTR: false }); node.dataset.held = String(!!held); node.dataset.live = String(expires > Date.now());
|
|
56
|
+
dismissTooltip();
|
|
57
|
+
chip.setAttribute('aria-describedby', node.id);
|
|
58
|
+
store.set({ agentMessageBubble: { key: key, expiresAt: expires, held: !!held } }); position();
|
|
59
|
+
if (timer) clearTimeout(timer);
|
|
60
|
+
if (expires > Date.now()) timer = setTimeout(function () {
|
|
61
|
+
timer = null; var state = store.get('agentMessageBubble'); if (state && state.key === key && !state.held) remove();
|
|
62
|
+
}, Math.max(0, expires - Date.now()));
|
|
63
|
+
}
|
|
64
|
+
export function rememberAgentMessage(key, preview, live) {
|
|
65
|
+
if (!preview || typeof preview.id !== 'string' || typeof preview.text !== 'string' || !preview.text.trim()) return;
|
|
66
|
+
var all = Object.assign({}, store.get('agentMessagePreviews') || {}), previous = all[key];
|
|
67
|
+
var fresh = !previous || previous.id !== preview.id;
|
|
68
|
+
all[key] = { id: preview.id, text: preview.text.slice(0, 1200), vendor: preview.vendor || previous && previous.vendor || null, timestamp: typeof preview.timestamp === 'number' && Number.isFinite(preview.timestamp) && preview.timestamp > 0 ? preview.timestamp : previous && previous.id === preview.id ? previous.timestamp : null };
|
|
69
|
+
var keys = Object.keys(all); while (keys.length > 128) delete all[keys.shift()];
|
|
70
|
+
store.set({ agentMessagePreviews: all });
|
|
71
|
+
var state = store.get('agentMessageBubble'), chip = anchor(key);
|
|
72
|
+
var held = !!(chip && (chip.matches(':hover') || chip.contains(document.activeElement)));
|
|
73
|
+
if (live && fresh || held || state && state.key === key) show(key, held || !!(state && state.key === key && state.held), !!live && fresh);
|
|
74
|
+
}
|
|
75
|
+
export function bindAgentMessageBubble(chip, key) {
|
|
76
|
+
chip.dataset.messagePreview = key;
|
|
77
|
+
if (chip.dataset.previewBound) return;
|
|
78
|
+
chip.dataset.previewBound = 'true';
|
|
79
|
+
function enter() { show(chip.dataset.messagePreview, true, false); }
|
|
80
|
+
function leave() {
|
|
81
|
+
var state = store.get('agentMessageBubble'); if (!state || state.key !== chip.dataset.messagePreview || chip.matches(':hover') || chip.contains(document.activeElement)) return;
|
|
82
|
+
if (state.expiresAt <= Date.now()) remove(); else show(state.key, false, false);
|
|
83
|
+
}
|
|
84
|
+
chip.addEventListener('pointerenter', enter); chip.addEventListener('pointerleave', leave);
|
|
85
|
+
chip.addEventListener('focusin', enter); chip.addEventListener('focusout', leave);
|
|
86
|
+
var state = store.get('agentMessageBubble'); if (state && state.key === key) requestAnimationFrame(position);
|
|
87
|
+
}
|
|
88
|
+
store.subscribe(function (state, previous) {
|
|
89
|
+
if (state.splitViewWorkerConversation && !previous.splitViewWorkerConversation || state.backgroundTaskViewer && !previous.backgroundTaskViewer) remove();
|
|
90
|
+
if (state.sessionOriginId !== previous.sessionOriginId || state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration || state.connected === false && previous.connected !== false || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.paneSuspended === true && previous.paneSuspended !== true) {
|
|
91
|
+
remove(); if (Object.keys(state.agentMessagePreviews || {}).length) store.set({ agentMessagePreviews: {} });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
document.addEventListener('keydown', function (event) { if (event.key === 'Escape') remove(); });
|
|
95
|
+
document.addEventListener('visibilitychange', function () { if (document.hidden) remove(); });
|
|
96
|
+
window.addEventListener('resize', position); window.addEventListener('scroll', function (event) { if (!element() || event.target === element()) return; position(); }, true);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
1
2
|
import { beginProjectDebatePrepend, endProjectDebatePrepend } from './project-debate-workspace.js';
|
|
2
3
|
// app-header.js - Session rename, session info popover, progressive history loading
|
|
3
4
|
// Extracted from app.js (PR-34)
|
|
@@ -422,6 +423,11 @@ export function prependOlderHistory(items, meta) {
|
|
|
422
423
|
updateContextPanel();
|
|
423
424
|
updateUsagePanel();
|
|
424
425
|
|
|
426
|
+
// History prepend deliberately skipped per-message enhancements during replay.
|
|
427
|
+
// Run the same completed-replay pass after restoring the live render state.
|
|
428
|
+
highlightCodeBlocks(messagesEl);
|
|
429
|
+
renderMermaidBlocks(messagesEl);
|
|
430
|
+
|
|
425
431
|
// Fix scroll: restore anchor element to same visual position
|
|
426
432
|
if (anchorEl) {
|
|
427
433
|
var newTop = anchorEl.getBoundingClientRect().top;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { handleSplitSessionCreated, finishPickerCreation } from './split-view-picker-actions.js';
|
|
2
|
+
import { handleWorkerMessagePreview } from './worker-message-previews.js';
|
|
3
|
+
import { handleBackgroundTaskPreview } from './background-task-previews.js';
|
|
4
|
+
import { handleBackgroundTaskRead } from './background-task-viewer.js';
|
|
1
5
|
import { showCreatedWorkerPresentation } from './worker-created-presentation.js';
|
|
2
6
|
import { handleWorkerRemovalResult } from './split-view-worker-removal.js';
|
|
3
7
|
import { handleStudioDocumentMessage } from './clay-studio-document.js';
|
|
@@ -152,6 +156,7 @@ export function processMessage(msg) {
|
|
|
152
156
|
if (handleScheduledTaskMessage(msg)) return;
|
|
153
157
|
if (handleKnowledgeWorkbenchMessage(msg)) return;
|
|
154
158
|
if (handleSessionCreateMessage(msg)) return;
|
|
159
|
+
if (handleSplitSessionCreated(msg)) return;
|
|
155
160
|
if (msg && msg.type === "new_session_result" && handleNewSessionResult(msg)) return;
|
|
156
161
|
if (handleDefaultVendorMessage(msg)) return;
|
|
157
162
|
if (handleContextViewMessage(msg)) return;
|
|
@@ -281,14 +286,6 @@ export function processMessage(msg) {
|
|
|
281
286
|
if (messagesEl && msg.sessionId === store.get("activeSessionId") && msg.sessionOriginId === store.get("sessionOriginId")) { messagesEl.dataset.controlSessionId = String(msg.sessionId); messagesEl.dataset.controlSessionOrigin = msg.sessionOriginId; messagesEl.dataset.controlHistoryRevision = msg.controlHistoryRevision || ""; }
|
|
282
287
|
store.set({ replayingHistory: false });
|
|
283
288
|
resumeThinkingAfterReplay(store.get('sessionIsProcessing'));
|
|
284
|
-
// Batched syntax highlight + mermaid pass for the entire replayed
|
|
285
|
-
// transcript. Per-message highlights are skipped during replay
|
|
286
|
-
// (see markdown.js) to avoid cascading reflows that the sticky-
|
|
287
|
-
// bottom observer chases for several seconds on long sessions.
|
|
288
|
-
if (messagesEl) {
|
|
289
|
-
highlightCodeBlocks(messagesEl);
|
|
290
|
-
renderMermaidBlocks(messagesEl);
|
|
291
|
-
}
|
|
292
289
|
// Compact dead-session todo widgets (unfinished items will never
|
|
293
290
|
// resolve — the agent isn't coming back) so they don't anchor
|
|
294
291
|
// visual position mid-page on resume.
|
|
@@ -314,6 +311,15 @@ export function processMessage(msg) {
|
|
|
314
311
|
markAllToolsDone();
|
|
315
312
|
finalizeAssistantBlock();
|
|
316
313
|
}
|
|
314
|
+
// Enhance only after the final replay flush has finished writing Markdown.
|
|
315
|
+
// Batched syntax highlight + mermaid pass for the entire replayed
|
|
316
|
+
// transcript. Per-message highlights are skipped during replay
|
|
317
|
+
// (see markdown.js) to avoid cascading reflows that the sticky-
|
|
318
|
+
// bottom observer chases for several seconds on long sessions.
|
|
319
|
+
if (messagesEl) {
|
|
320
|
+
highlightCodeBlocks(messagesEl);
|
|
321
|
+
renderMermaidBlocks(messagesEl);
|
|
322
|
+
}
|
|
317
323
|
stopUrgentBlink();
|
|
318
324
|
// Clean up debate UI if debate is not active after replay
|
|
319
325
|
if (!isDebateActive()) {
|
|
@@ -790,6 +796,7 @@ export function processMessage(msg) {
|
|
|
790
796
|
renderMateSessionList(msg.sessions || []);
|
|
791
797
|
renderSessionList(msg.sessions || []);
|
|
792
798
|
store.set({ paneBindingSessions: msg.sessions || [] });
|
|
799
|
+
finishPickerCreation();
|
|
793
800
|
requestProjectSplitViews();
|
|
794
801
|
refreshPaneActivation();
|
|
795
802
|
syncGithubSessions(msg.sessions || []);
|
|
@@ -1467,8 +1474,18 @@ export function processMessage(msg) {
|
|
|
1467
1474
|
updateSubagentTaskStatus(msg.parentToolId, msg.patch);
|
|
1468
1475
|
break;
|
|
1469
1476
|
|
|
1477
|
+
case "split_worker_message_preview":
|
|
1478
|
+
handleWorkerMessagePreview(msg);
|
|
1479
|
+
break;
|
|
1480
|
+
case "background_task_preview":
|
|
1481
|
+
handleBackgroundTaskPreview(msg);
|
|
1482
|
+
break;
|
|
1483
|
+
case "background_task_read":
|
|
1484
|
+
handleBackgroundTaskRead(msg);
|
|
1485
|
+
break;
|
|
1486
|
+
|
|
1470
1487
|
case "active_background_tasks":
|
|
1471
|
-
store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [] });
|
|
1488
|
+
store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [], backgroundTaskCatalog: Array.isArray(msg.catalog) ? msg.catalog : msg.tasks || [] });
|
|
1472
1489
|
break;
|
|
1473
1490
|
|
|
1474
1491
|
case "result":
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { messagePreviewKey, rememberAgentMessage } from './agent-message-bubbles.js';
|
|
3
|
+
export function backgroundPreviewKey(task) { return messagePreviewKey('background', [store.get('sessionOriginId'), task.task_id, task.run_id || null]); }
|
|
4
|
+
export function handleBackgroundTaskPreview(message) {
|
|
5
|
+
if (!store.get('connected') || message.sessionOriginId !== store.get('sessionOriginId')) return;
|
|
6
|
+
var task = (store.get('backgroundTaskCatalog') || []).find(function (task) { return task.task_id === message.taskId && (task.run_id || null) === (message.runId || null); });
|
|
7
|
+
if (task) rememberAgentMessage(backgroundPreviewKey(task), message.preview, message.live === true);
|
|
8
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { appendWorkerTranscript } from './split-view-worker-transcript.js';
|
|
4
|
+
import { captureWorkerDisclosures, restoreWorkerDisclosures, workerActivityNames } from './split-view-worker-activity.js';
|
|
5
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
|
+
|
|
7
|
+
function panel() { return document.getElementById('background-task-viewer'); }
|
|
8
|
+
function current(state) {
|
|
9
|
+
return state && store.get('connected') && state.socketGeneration === store.get('projectSplitViewSocketGeneration') && state.origin === store.get('sessionOriginId') &&
|
|
10
|
+
state.slug === store.get('currentSlug') && state.owner === store.get('myUserId') &&
|
|
11
|
+
(store.get('backgroundTaskCatalog') || []).some(function (task) { return task.task_id === state.taskId && (task.run_id || null) === state.runId; });
|
|
12
|
+
}
|
|
13
|
+
export function closeBackgroundTask(focus) {
|
|
14
|
+
var state = store.get('backgroundTaskViewer');
|
|
15
|
+
if (state && state.timer) clearTimeout(state.timer);
|
|
16
|
+
if (state && state.timeout) clearTimeout(state.timeout);
|
|
17
|
+
store.set({ backgroundTaskViewer: null });
|
|
18
|
+
var element = panel(); if (element) element.remove();
|
|
19
|
+
if (focus && state) {
|
|
20
|
+
var chip = document.querySelector('[data-background-task="' + CSS.escape(state.taskId) + '"]'); if (chip) chip.focus();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function openBackgroundTask(task) {
|
|
24
|
+
closeBackgroundTask(false);
|
|
25
|
+
var state = { taskId: task.task_id, runId: task.run_id || null, title: task.description || 'Background task',
|
|
26
|
+
origin: store.get('sessionOriginId'), slug: store.get('currentSlug'), owner: store.get('myUserId'), socketGeneration: store.get('projectSplitViewSocketGeneration'), offset: 0 };
|
|
27
|
+
if (!current(state)) return;
|
|
28
|
+
store.set({ backgroundTaskViewer: state });
|
|
29
|
+
var element = document.createElement('section'); element.id = 'background-task-viewer'; element.className = 'split-view-worker-popover background-task-viewer';
|
|
30
|
+
element.setAttribute('role', 'dialog'); element.setAttribute('aria-label', 'Background task conversation');
|
|
31
|
+
element.innerHTML = '<header class="split-view-worker-popover-header"><strong></strong><button type="button" aria-label="Close background task">' + iconHtml('x') + '</button></header>' +
|
|
32
|
+
'<div class="split-view-worker-popover-source">Read-only · Native background task</div><div class="split-view-worker-popover-body readonly-session-transcript" tabindex="0"></div>' +
|
|
33
|
+
'<footer class="split-view-worker-popover-footer"><span role="status"></span><button type="button" data-page="previous">Previous</button><button type="button" data-page="next">Next</button><button type="button" data-retry>Refresh</button></footer>';
|
|
34
|
+
element.querySelector('strong').textContent = state.title;
|
|
35
|
+
element.querySelector('[aria-label="Close background task"]').addEventListener('click', function () { closeBackgroundTask(true); });
|
|
36
|
+
element.querySelector('[data-retry]').addEventListener('click', request);
|
|
37
|
+
element.querySelectorAll('[data-page]').forEach(function (button) { button.addEventListener('click', function () {
|
|
38
|
+
var live = store.get('backgroundTaskViewer'); if (!current(live) || live.pending) return;
|
|
39
|
+
store.set({ backgroundTaskViewer: Object.assign({}, live, { offset: button.dataset.page === 'next' ? live.offset + 100 : Math.max(0, live.offset - 100) }) }); request();
|
|
40
|
+
}); });
|
|
41
|
+
document.body.appendChild(element); refreshIcons(); request();
|
|
42
|
+
element.querySelector('button').focus();
|
|
43
|
+
}
|
|
44
|
+
function request() {
|
|
45
|
+
var state = store.get('backgroundTaskViewer');
|
|
46
|
+
if (!current(state)) { closeBackgroundTask(false); return; }
|
|
47
|
+
if (state.pending) return;
|
|
48
|
+
if (state.timer) clearTimeout(state.timer);
|
|
49
|
+
var id = crypto.randomUUID();
|
|
50
|
+
var timeout = setTimeout(function () {
|
|
51
|
+
var live = store.get('backgroundTaskViewer'); if (!live || live.requestId !== id) return;
|
|
52
|
+
store.set({ backgroundTaskViewer: Object.assign({}, live, { pending: false, timeout: null }) });
|
|
53
|
+
var element = panel(); if (element) element.querySelector('[role="status"]').textContent = 'Task read timed out. Use Refresh to retry.';
|
|
54
|
+
}, 18000);
|
|
55
|
+
store.set({ backgroundTaskViewer: Object.assign({}, state, { requestId: id, pending: true, timeout: timeout, timer: null }) });
|
|
56
|
+
var sent = getWs().send(JSON.stringify({ type: 'background_task_read', requestId: id, taskId: state.taskId, runId: state.runId, sessionOriginId: state.origin, offset: state.offset }));
|
|
57
|
+
if (sent === false) { clearTimeout(timeout); var live = store.get('backgroundTaskViewer'); if (live && live.requestId === id) { store.set({ backgroundTaskViewer: Object.assign({}, live, { pending: false, timeout: null }) }); panel().querySelector('[role="status"]').textContent = 'Task pane is not ready. Use Refresh to retry.'; } }
|
|
58
|
+
}
|
|
59
|
+
export function handleBackgroundTaskRead(message) {
|
|
60
|
+
var state = store.get('backgroundTaskViewer'), element = panel();
|
|
61
|
+
if (!element || !current(state) || message.requestId !== state.requestId || message.sessionOriginId !== state.origin ||
|
|
62
|
+
message.taskId !== state.taskId || (message.runId || null) !== state.runId) return;
|
|
63
|
+
clearTimeout(state.timeout);
|
|
64
|
+
var body = element.querySelector('.split-view-worker-popover-body'), footer = element.querySelector('[role="status"]');
|
|
65
|
+
var data = message.data;
|
|
66
|
+
if (message.error) { footer.textContent = message.error; state = Object.assign({}, state, { failures: (state.failures || 0) + 1 }); }
|
|
67
|
+
else if (data) {
|
|
68
|
+
var signature = JSON.stringify(data), changed = signature !== state.signature;
|
|
69
|
+
if (changed) {
|
|
70
|
+
var follow = body.scrollHeight - body.scrollTop - body.clientHeight < 48, top = body.scrollTop;
|
|
71
|
+
var disclosures = captureWorkerDisclosures(body); body.textContent = '';
|
|
72
|
+
if (data.kind === 'shell') { var pre = document.createElement('pre'); pre.textContent = data.output || 'No output yet.'; body.appendChild(pre); }
|
|
73
|
+
else appendWorkerTranscript(body, data.messages || [], { vendor: 'claude', userName: 'Parent agent', assistantName: 'Background agent' });
|
|
74
|
+
restoreWorkerDisclosures(body, disclosures);
|
|
75
|
+
body.scrollTop = follow ? body.scrollHeight : top;
|
|
76
|
+
}
|
|
77
|
+
footer.textContent = (data.active ? 'Running' : 'Finished') + (data.truncated ? ' · Last 8 KiB of output' : '') +
|
|
78
|
+
(data.kind === 'agent' && !data.messages.length ? ' · No transcript messages yet' : '');
|
|
79
|
+
element.querySelector('[data-page="previous"]').hidden = data.kind !== 'agent' || state.offset === 0;
|
|
80
|
+
element.querySelector('[data-page="next"]').hidden = !data.hasMore;
|
|
81
|
+
state = Object.assign({}, state, { signature: signature, failures: 0 });
|
|
82
|
+
}
|
|
83
|
+
var task = (store.get('backgroundTaskCatalog') || []).find(function (item) { return item.task_id === state.taskId; });
|
|
84
|
+
var timer = task && task.active !== false && (state.failures || 0) < 3 ? setTimeout(function () { if (!document.hidden) request(); else schedule(); }, 2000) : null;
|
|
85
|
+
store.set({ backgroundTaskViewer: Object.assign({}, state, { pending: false, timeout: null, timer: timer }) });
|
|
86
|
+
}
|
|
87
|
+
function schedule() {
|
|
88
|
+
var state = store.get('backgroundTaskViewer'); if (!current(state)) return;
|
|
89
|
+
var timer = setTimeout(function () { if (!document.hidden) request(); else schedule(); }, 2000);
|
|
90
|
+
store.set({ backgroundTaskViewer: Object.assign({}, state, { timer: timer }) });
|
|
91
|
+
}
|
|
92
|
+
store.subscribe(function (state, prev) {
|
|
93
|
+
if (state.backgroundTaskViewer && (state.sessionOriginId !== prev.sessionOriginId || state.currentSlug !== prev.currentSlug ||
|
|
94
|
+
state.myUserId !== prev.myUserId || state.projectSplitViewSocketGeneration !== prev.projectSplitViewSocketGeneration || !state.connected || state.backgroundTaskCatalog !== prev.backgroundTaskCatalog && !current(state.backgroundTaskViewer))) closeBackgroundTask(false);
|
|
95
|
+
});
|
|
96
|
+
document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && panel()) { event.preventDefault(); closeBackgroundTask(true); } });
|
|
97
|
+
document.addEventListener('click', function (event) { var element = panel(); if (element && !element.contains(event.target) && !event.target.closest('[data-background-task]')) closeBackgroundTask(false); });
|
|
@@ -1,28 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
// a muted activity indicator, a count, and a chevron. Expanding lists each task
|
|
5
|
-
// with its elapsed time and a Stop control.
|
|
6
|
-
//
|
|
7
|
-
// Elapsed time comes from `started_at` (epoch ms) which the server stamps in
|
|
8
|
-
// lib/background-task-timing.js. The ticker only runs while the list is
|
|
9
|
-
// expanded, so a collapsed bar costs nothing.
|
|
1
|
+
import { bindAgentMessageBubble, rememberAgentMessage } from './agent-message-bubbles.js';
|
|
2
|
+
import { backgroundPreviewKey } from './background-task-previews.js';
|
|
3
|
+
// Native background work uses the same compact conversation chips as Workers.
|
|
10
4
|
|
|
5
|
+
import { openBackgroundTask } from './background-task-viewer.js';
|
|
11
6
|
import { store } from './store.js';
|
|
12
7
|
import { getWs } from './ws-ref.js';
|
|
13
|
-
import { escapeHtml } from './utils.js';
|
|
14
8
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
15
9
|
|
|
16
|
-
var expanded = false;
|
|
17
|
-
var elapsedTimer = null;
|
|
18
|
-
|
|
19
|
-
// Lucide icon per vendor-neutral task type (see normalizeBackgroundTasks in
|
|
20
|
-
// yoke/adapters/claude.js for where these types are assigned).
|
|
21
|
-
var TASK_TYPE_ICONS = { shell: "terminal", agent: "sparkles", other: "circle-dot" };
|
|
22
|
-
|
|
23
10
|
export function initBackgroundTasksUi() {
|
|
24
11
|
store.subscribe(function (state, prev) {
|
|
25
|
-
if (state.
|
|
12
|
+
if (state.sessionOriginId !== prev.sessionOriginId || state.currentSlug !== prev.currentSlug) { store.set({ activeBackgroundTasks: [], backgroundTaskCatalog: [] }); return; }
|
|
13
|
+
if (state.backgroundTaskViewer !== prev.backgroundTaskViewer) document.querySelectorAll('[data-background-task]').forEach(function (chip) { var view = state.backgroundTaskViewer; chip.setAttribute('aria-expanded', String(!!view && chip.dataset.backgroundTask === view.taskId)); });
|
|
14
|
+
if (state.activeBackgroundTasks !== prev.activeBackgroundTasks || state.backgroundTaskCatalog !== prev.backgroundTaskCatalog) renderBackgroundTasks();
|
|
26
15
|
});
|
|
27
16
|
renderBackgroundTasks();
|
|
28
17
|
}
|
|
@@ -45,37 +34,6 @@ function pad(value) {
|
|
|
45
34
|
return value < 10 ? "0" + value : String(value);
|
|
46
35
|
}
|
|
47
36
|
|
|
48
|
-
function stopElapsedTimer() {
|
|
49
|
-
if (!elapsedTimer) return;
|
|
50
|
-
clearInterval(elapsedTimer);
|
|
51
|
-
elapsedTimer = null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Ticks at most once a second and only touches the time cells, so expanding
|
|
55
|
-
// the list never re-renders (and never steals focus from) the rows.
|
|
56
|
-
function startElapsedTimer() {
|
|
57
|
-
stopElapsedTimer();
|
|
58
|
-
elapsedTimer = setInterval(updateElapsedCells, 1000);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function updateElapsedCells() {
|
|
62
|
-
var bar = document.getElementById('background-tasks-bar');
|
|
63
|
-
if (!bar || !expanded) {
|
|
64
|
-
stopElapsedTimer();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
var now = Date.now();
|
|
68
|
-
var cells = bar.querySelectorAll('.background-task-elapsed');
|
|
69
|
-
for (var i = 0; i < cells.length; i++) {
|
|
70
|
-
var startedAt = Number(cells[i].dataset.startedAt);
|
|
71
|
-
cells[i].textContent = formatElapsed(startedAt, now);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function activityDotsHtml() {
|
|
76
|
-
return '<span class="background-tasks-activity" aria-hidden="true"><i></i><i></i><i></i></span>';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
37
|
// Ambient tasks are the CLI's own housekeeping. The SDK asks hosts to keep
|
|
80
38
|
// them out of activity indicators; Clay still lists them so the user can see
|
|
81
39
|
// what runs on their behalf. Absent means false (Codex has no ambient concept,
|
|
@@ -96,83 +54,34 @@ export function splitTasks(tasks) {
|
|
|
96
54
|
return { user: user, ambient: ambient };
|
|
97
55
|
}
|
|
98
56
|
|
|
99
|
-
function taskRowHtml(task, now) {
|
|
100
|
-
var taskType = task.task_type || 'other';
|
|
101
|
-
var icon = TASK_TYPE_ICONS[taskType] || TASK_TYPE_ICONS.other;
|
|
102
|
-
var description = task.description || 'Background task';
|
|
103
|
-
var startedAt = typeof task.started_at === 'number' ? task.started_at : 0;
|
|
104
|
-
var elapsed = formatElapsed(startedAt, now);
|
|
105
|
-
return '<div class="background-task-row' + (isAmbientTask(task) ? ' ambient' : '') + '">' +
|
|
106
|
-
'<span class="background-task-icon" title="' + escapeHtml(taskType) + '">' + iconHtml(icon) + '</span>' +
|
|
107
|
-
'<span class="background-task-description" title="' + escapeHtml(description) + '">' +
|
|
108
|
-
escapeHtml(description) + '</span>' +
|
|
109
|
-
'<span class="background-task-meta">' +
|
|
110
|
-
'<span class="background-task-type">' + escapeHtml(taskType) + '</span>' +
|
|
111
|
-
'<span class="background-task-elapsed" data-started-at="' + startedAt + '">' + escapeHtml(elapsed) + '</span>' +
|
|
112
|
-
'</span>' +
|
|
113
|
-
'<button type="button" class="background-task-stop" data-task-id="' + escapeHtml(task.task_id || '') + '" ' +
|
|
114
|
-
'aria-label="Stop ' + escapeHtml(description) + '">Stop</button>' +
|
|
115
|
-
'</div>';
|
|
116
|
-
}
|
|
117
|
-
|
|
118
57
|
function renderBackgroundTasks() {
|
|
119
|
-
var
|
|
120
|
-
var split = splitTasks(
|
|
121
|
-
var
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
'<span class="background-tasks-chevron" aria-hidden="true">' + iconHtml('chevron-right') + '</span>' +
|
|
145
|
-
'</button>';
|
|
146
|
-
|
|
147
|
-
if (expanded) {
|
|
148
|
-
var now = Date.now();
|
|
149
|
-
html += '<div class="background-tasks-list" id="background-tasks-list">';
|
|
150
|
-
for (var i = 0; i < split.user.length; i++) html += taskRowHtml(split.user[i], now);
|
|
151
|
-
// Housekeeping is shown, not hidden: the user can see what Clay runs on
|
|
152
|
-
// its own and stop a runaway one. Kept visually subordinate to real work.
|
|
153
|
-
if (split.ambient.length > 0) {
|
|
154
|
-
html += '<div class="background-tasks-section-label">Housekeeping</div>';
|
|
155
|
-
for (var a = 0; a < split.ambient.length; a++) html += taskRowHtml(split.ambient[a], now);
|
|
58
|
+
var existing = document.getElementById('background-tasks-bar'); if (existing) existing.remove();
|
|
59
|
+
var split = splitTasks(store.get('backgroundTaskCatalog') || store.get('activeBackgroundTasks') || []);
|
|
60
|
+
var tasks = split.user.length ? split.user.concat(split.ambient) : [];
|
|
61
|
+
var root = document.getElementById('background-task-tabs');
|
|
62
|
+
if (!tasks.length) { if (root) root.remove(); return; }
|
|
63
|
+
var messages = document.getElementById('messages'); if (!messages) return;
|
|
64
|
+
if (!root) { root = document.createElement('div'); root.id = 'background-task-tabs'; root.className = 'split-view-workers background-task-tabs'; root.setAttribute('aria-label', 'Background tasks'); messages.parentNode.insertBefore(root, messages); }
|
|
65
|
+
var signature = JSON.stringify(tasks.map(function (task) { return [task.task_id, task.run_id, task.active, task.description]; }));
|
|
66
|
+
if (root.dataset.signature === signature) return;
|
|
67
|
+
root.dataset.signature = signature; root.textContent = '';
|
|
68
|
+
tasks.forEach(function (task) {
|
|
69
|
+
var pill = document.createElement('span'); pill.className = 'split-view-worker-pill' + (task.ambient ? ' background-task-ambient' : '');
|
|
70
|
+
var button = document.createElement('button'); button.type = 'button'; button.className = 'split-view-worker-chip'; button.dataset.backgroundTask = task.task_id;
|
|
71
|
+
bindAgentMessageBubble(button, backgroundPreviewKey(task));
|
|
72
|
+
if (task.preview) rememberAgentMessage(backgroundPreviewKey(task), task.preview, false);
|
|
73
|
+
button.title = (task.ambient ? 'Housekeeping · ' : '') + (task.description || 'Background task') + (task.started_at ? ' · ' + formatElapsed(task.started_at) : '') + (task.active === false ? ' · Finished' : ' · Running'); button.setAttribute('aria-haspopup', 'dialog');
|
|
74
|
+
var view = store.get('backgroundTaskViewer'); button.setAttribute('aria-expanded', String(!!view && view.taskId === task.task_id));
|
|
75
|
+
var dot = document.createElement('span'); dot.className = 'split-view-worker-dot'; dot.dataset.state = task.active === false ? 'ready' : 'working';
|
|
76
|
+
var label = document.createElement('span'); label.className = 'split-view-worker-name'; label.textContent = task.description || 'Background task';
|
|
77
|
+
dot.setAttribute('aria-label', task.active === false ? 'Finished' : 'Running');
|
|
78
|
+
button.append(dot, label); button.addEventListener('click', function () { openBackgroundTask(task); }); pill.appendChild(button);
|
|
79
|
+
if (task.active !== false) {
|
|
80
|
+
var stop = document.createElement('button'); stop.type = 'button'; stop.className = 'split-view-worker-remove'; stop.setAttribute('aria-label', 'Stop ' + (task.description || 'background task')); stop.title = 'Stop task'; stop.innerHTML = iconHtml('square');
|
|
81
|
+
stop.addEventListener('click', function () { var ws = getWs(); if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'background_task_stop', requestId: crypto.randomUUID(), taskId: task.task_id, runId: task.run_id || null, sessionOriginId: store.get('sessionOriginId') })); });
|
|
82
|
+
pill.appendChild(stop);
|
|
156
83
|
}
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
bar.innerHTML = html;
|
|
160
|
-
if (!existing) inputArea.parentNode.insertBefore(bar, inputArea);
|
|
161
|
-
refreshIcons();
|
|
162
|
-
|
|
163
|
-
bar.querySelector('.background-tasks-toggle').addEventListener('click', function () {
|
|
164
|
-
expanded = !expanded;
|
|
165
|
-
renderBackgroundTasks();
|
|
84
|
+
root.appendChild(pill);
|
|
166
85
|
});
|
|
167
|
-
|
|
168
|
-
var stopButtons = bar.querySelectorAll('.background-task-stop');
|
|
169
|
-
for (var j = 0; j < stopButtons.length; j++) {
|
|
170
|
-
stopButtons[j].addEventListener('click', function () {
|
|
171
|
-
var ws = getWs();
|
|
172
|
-
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'stop_task', taskId: this.dataset.taskId }));
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (expanded) startElapsedTimer();
|
|
177
|
-
else stopElapsedTimer();
|
|
86
|
+
refreshIcons();
|
|
178
87
|
}
|
|
@@ -39,7 +39,7 @@ function appendIdentity(row, options, avatarClass) {
|
|
|
39
39
|
|
|
40
40
|
export function createAssistantBubble(options) {
|
|
41
41
|
options = options || {};
|
|
42
|
-
var row = document.createElement("div");
|
|
42
|
+
var row = document.createElement(options.tagName || "div");
|
|
43
43
|
row.className = "msg-assistant";
|
|
44
44
|
if (options.turn !== undefined && options.turn !== null) row.dataset.turn = options.turn;
|
|
45
45
|
var contentWrap = appendIdentity(row, options, "dm-bubble-avatar-mate");
|
|
@@ -52,7 +52,7 @@ export function createAssistantBubble(options) {
|
|
|
52
52
|
|
|
53
53
|
export function createUserBubble(options) {
|
|
54
54
|
options = options || {};
|
|
55
|
-
var row = document.createElement("div");
|
|
55
|
+
var row = document.createElement(options.tagName || "div");
|
|
56
56
|
row.className = "msg-user";
|
|
57
57
|
if (options.turn !== undefined && options.turn !== null) row.dataset.turn = options.turn;
|
|
58
58
|
var contentWrap = appendIdentity(row, options, "dm-bubble-avatar-me");
|
|
@@ -70,11 +70,17 @@ function assistantContent(row) {
|
|
|
70
70
|
return row ? row.querySelector(".md-content") : null;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
export function renderAssistantBubbleText(row, text, highlight) {
|
|
73
|
+
export function renderAssistantBubbleText(row, text, highlight, options) {
|
|
74
74
|
var content = assistantContent(row);
|
|
75
75
|
if (!content) return null;
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
var html = renderMarkdown(text || "");
|
|
77
|
+
if (options && options.readOnly) html = DOMPurify.sanitize(html, {
|
|
78
|
+
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'],
|
|
79
|
+
ALLOWED_ATTR: ['href', 'title', 'target', 'rel', 'start', 'class'], ALLOW_DATA_ATTR: false
|
|
80
|
+
});
|
|
81
|
+
content.innerHTML = html;
|
|
82
|
+
if (options && options.readOnly) content.querySelectorAll('[class]').forEach(function (element) { if (element.tagName !== 'CODE' && element.tagName !== 'PRE') element.removeAttribute('class'); });
|
|
83
|
+
if (!options || !options.readOnly) enhanceClaySessionLinks(content);
|
|
78
84
|
if (highlight) highlightCodeBlocks(content);
|
|
79
85
|
return content;
|
|
80
86
|
}
|
|
@@ -63,10 +63,11 @@ export function renderLocalFileLink(href, title, text) {
|
|
|
63
63
|
var label = text || target.path;
|
|
64
64
|
var tooltip = target.path + (target.line ? ":" + target.line + (target.column ? ":" + target.column : "") : "");
|
|
65
65
|
var state = store.snap();
|
|
66
|
+
var fragment = target.href.indexOf('#') >= 0 && !target.line ? decode(target.href.slice(target.href.indexOf('#') + 1)) : '';
|
|
66
67
|
var projectAttr = state.currentSlug ? ' data-file-project-slug="' + escapeHtml(String(state.currentSlug)) + '"' : '';
|
|
67
68
|
var sessionAttr = state.activeSessionId != null ? ' data-file-session-id="' + escapeHtml(String(state.activeSessionId)) + '"' : '';
|
|
68
69
|
return '<button type="button" class="clay-file-link" data-file-path="' + escapeHtml(target.path) + '"' +
|
|
69
|
-
projectAttr + sessionAttr +
|
|
70
|
+
projectAttr + sessionAttr + (fragment ? ' data-file-heading="' + escapeHtml(fragment) + '"' : '') +
|
|
70
71
|
(target.line ? ' data-file-line="' + target.line + '"' : '') +
|
|
71
72
|
(target.column ? ' data-file-column="' + target.column + '"' : '') +
|
|
72
73
|
' title="' + escapeHtml(tooltip) + '" aria-label="Open file ' + escapeHtml(tooltip) + '">' +
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toolPaneScreen, toolPaneRendering } from './clay-tool-pane-screen.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
import { getWs } from './ws-ref.js';
|
|
3
4
|
import { livePaneBinding } from './pane-binding.js';
|
|
@@ -13,6 +14,8 @@ export function splitViewScreenSnapshot() {
|
|
|
13
14
|
viewId: view ? view.id : null, revision: view ? view.revision : 0, tree: view ? view.tree : null,
|
|
14
15
|
focusOrigin: state.projectSplitViewFocusedOrigin || null, singletonOrigin: state.projectSplitViewSingletonOrigin || null,
|
|
15
16
|
activeSessionId: state.activeSessionId || null,
|
|
17
|
+
tools: toolPaneScreen(view, hidden),
|
|
18
|
+
expandedPaneId: (toolPaneScreen(view, hidden).find(function (tool) { return tool.expanded; }) || {}).instanceId || null,
|
|
16
19
|
panes: Array.from(document.querySelectorAll('.split-pane-frame')).slice(0, 32).map(function (frame) {
|
|
17
20
|
var live = frame.clayPane && frame.clayPane.identity(), binding = live && live.binding;
|
|
18
21
|
var ready = !!livePaneBinding(frame), cached = !!frame.closest('.split-pane-cached');
|
|
@@ -60,10 +63,10 @@ export function applySplitViewNavigation(target) {
|
|
|
60
63
|
}
|
|
61
64
|
function renderedTarget(snapshot, target) {
|
|
62
65
|
var exact = snapshot && !snapshot.hidden && snapshot.viewId === target.view.id && snapshot.revision === target.view.revision &&
|
|
63
|
-
snapshot.focusOrigin === target.focusOrigin && JSON.stringify(snapshot.tree) === JSON.stringify(target.view.tree);
|
|
64
|
-
var expected = target.view.sessions.filter(function (session) { return !target.focusOrigin || session.sessionOriginId === target.focusOrigin; });
|
|
66
|
+
snapshot.focusOrigin === target.focusOrigin && (snapshot.expandedPaneId || null) === (target.view.workspace && target.view.workspace.presentation && target.view.workspace.presentation.expandedPaneId || null) && JSON.stringify(snapshot.tree) === JSON.stringify(target.view.tree);
|
|
67
|
+
var expected = target.view.sessions.filter(function (session) { return snapshot && !snapshot.expandedPaneId && (!target.focusOrigin || session.sessionOriginId === target.focusOrigin); });
|
|
65
68
|
var shown = snapshot ? snapshot.panes.filter(function (pane) { return pane.visible; }) : [];
|
|
66
|
-
return !!(exact && shown.length === expected.length && expected.every(function (session) { return shown.some(function (pane) { return pane.origin === session.sessionOriginId && pane.sessionId === session.id && pane.ready && !pane.cached; }); }));
|
|
69
|
+
return !!(exact && toolPaneRendering(snapshot, target.view) && shown.length === expected.length && expected.every(function (session) { return shown.some(function (pane) { return pane.origin === session.sessionOriginId && pane.sessionId === session.id && pane.ready && !pane.cached; }); }));
|
|
67
70
|
}
|
|
68
71
|
export function observeSplitViewRendering(msg, socket) {
|
|
69
72
|
var target = msg.target;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TOOL_PANES } from './tool-pane-tree.js';
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { visible } from './clay-navigation-render.js';
|
|
4
|
+
import { paneOwner } from './view-workspace-state.js';
|
|
5
|
+
import { filePaneState } from './file-pane-instances.js';
|
|
6
|
+
export function toolPaneScreen(view, hidden) {
|
|
7
|
+
if (!view || !view.workspace) return [];
|
|
8
|
+
var run = store.get('savedWorkspaceRuntime');
|
|
9
|
+
return view.workspace.panes.filter(function (pane) { return pane.kind !== 'session'; }).slice(0, 16).map(function (pane) {
|
|
10
|
+
var name = paneOwner(pane), panel = document.getElementById(pane.kind === 'file' ? name : TOOL_PANES[name] && TOOL_PANES[name].id);
|
|
11
|
+
var file = pane.kind === 'file' ? filePaneState(pane.instanceId) : null;
|
|
12
|
+
var unavailable = !!(run && run.unavailable[name] || file && file.error);
|
|
13
|
+
var exact = false;
|
|
14
|
+
if (pane.kind === 'workbench') exact = !!(panel && !panel.classList.contains('hidden') && (store.get('rightWorkbenchOwner') === name || (store.get('rightWorkbenchDockedOwners') || []).indexOf(name) >= 0));
|
|
15
|
+
else if (pane.kind === 'file') exact = !!(file && file.resource && file.resource.path === pane.resource.path && file.resource.mode === pane.resource.mode && file.resource.rendered === pane.resource.rendered && (file.content != null || file.binary));
|
|
16
|
+
else if (pane.kind === 'terminal') {
|
|
17
|
+
var terminal = store.get('savedViewTerminalResource');
|
|
18
|
+
exact = !!(terminal && terminal.id === pane.resource.id && terminal.identity === pane.resource.identity && terminal.runtimeId === pane.resource.runtimeId && (store.get('clayControlTerminalViews') || []).some(function (item) { return item.id === terminal.id && item.identity === terminal.identity && !item.exited; }));
|
|
19
|
+
} else {
|
|
20
|
+
var browser = store.get('sharedBrowser');
|
|
21
|
+
exact = !!(browser && browser.id === pane.resource.id && browser.phase !== 'ended' && store.get('controlRuntimeId') === pane.resource.runtimeId && store.get('sessionOriginId') === pane.resource.sessionOriginId);
|
|
22
|
+
}
|
|
23
|
+
var ready = !!(pane.open && panel && !unavailable && exact);
|
|
24
|
+
var shown = !!(!hidden && pane.open && panel && visible(panel));
|
|
25
|
+
var rect = shown ? panel.getBoundingClientRect() : null;
|
|
26
|
+
function pixel(value) { return Math.max(-100000, Math.min(100000, Math.round(value))); }
|
|
27
|
+
return { instanceId: pane.instanceId, kind: pane.kind, placement: panel && panel.dataset && ['docked', 'overlay'].indexOf(panel.dataset.workbenchPlacement) >= 0 ? panel.dataset.workbenchPlacement : null, expanded: !!(pane.open && panel && panel.classList && panel.classList.contains('panel-fullscreen')), visible: shown, ready: ready, unavailable: unavailable,
|
|
28
|
+
geometry: rect ? { x: pixel(rect.x), y: pixel(rect.y), width: Math.max(0, pixel(rect.width)), height: Math.max(0, pixel(rect.height)) } : null };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function toolPaneRendering(snapshot, view) {
|
|
32
|
+
var expected = view.workspace ? view.workspace.panes.filter(function (pane) { return pane.kind !== 'session' && pane.open && (!snapshot.focusOrigin || snapshot.expandedPaneId || pane.placement === 'overlay') && (!view.workspace.presentation || !view.workspace.presentation.expandedPaneId || view.workspace.presentation.expandedPaneId === pane.instanceId); }) : [];
|
|
33
|
+
var tools = snapshot.tools || [];
|
|
34
|
+
return expected.every(function (pane) { return tools.some(function (tool) { return tool.instanceId === pane.instanceId && tool.kind === pane.kind && tool.placement === pane.placement && !!tool.expanded === !!(view.workspace.presentation && view.workspace.presentation.expandedPaneId === pane.instanceId) && tool.visible && tool.ready && !tool.unavailable; }); }) && tools.filter(function (tool) { return tool.visible; }).length === expected.length;
|
|
35
|
+
}
|