clay-server 5.3.0-beta.6 → 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/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 +426 -414
- 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,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
|
}
|
|
@@ -8,8 +8,12 @@ function state() { return store.get('contextualClayGeometry') || {}; }
|
|
|
8
8
|
function surface() { return document.getElementById('contextual-clay'); }
|
|
9
9
|
function narrow() { return window.matchMedia('(max-width: 768px)').matches; }
|
|
10
10
|
function rectValue(rect) { return { left: rect.left, top: rect.top, width: rect.width, height: rect.height }; }
|
|
11
|
+
function maximumWidth() {
|
|
12
|
+
var style = getComputedStyle(surface());
|
|
13
|
+
return (parseFloat(style.getPropertyValue('--mate-content-width')) || 860) + (parseFloat(style.getPropertyValue('--contextual-chat-gutter')) || 80);
|
|
14
|
+
}
|
|
11
15
|
function bounded(value) {
|
|
12
|
-
return { left: Math.round(Math.max(0, Math.min(16384, value.left))), top: Math.round(Math.max(0, Math.min(16384, value.top))), width: Math.round(Math.max(340, Math.min(
|
|
16
|
+
return { left: Math.round(Math.max(0, Math.min(16384, value.left))), top: Math.round(Math.max(0, Math.min(16384, value.top))), width: Math.round(Math.max(340, Math.min(maximumWidth(), value.width))), height: Math.round(Math.max(360, Math.min(2160, value.height))) };
|
|
13
17
|
}
|
|
14
18
|
function setState(patch) { store.set({ contextualClayGeometry: Object.assign({}, state(), patch) }); }
|
|
15
19
|
function defaultGeometry() {
|
|
@@ -22,7 +26,7 @@ export function positionContextualClay() {
|
|
|
22
26
|
var hint = renderContextualMateHint();
|
|
23
27
|
if (!element || narrow() || (store.get('contextualClay') || {}).mode === 'opening') return;
|
|
24
28
|
var desired = state().preview || state().desired || defaultGeometry();
|
|
25
|
-
var width = Math.min(desired.width, window.innerWidth - 16);
|
|
29
|
+
var width = Math.min(desired.width, maximumWidth(), window.innerWidth - 16);
|
|
26
30
|
var height = Math.min(desired.height, window.innerHeight - 16);
|
|
27
31
|
var left = Math.max(8, Math.min(desired.left, window.innerWidth - width - 8));
|
|
28
32
|
var top = Math.max(8, Math.min(desired.top, window.innerHeight - height - 8));
|
|
@@ -78,7 +82,7 @@ function move(event) {
|
|
|
78
82
|
if (!state().preview && Math.hypot(dx, dy) < 3) return;
|
|
79
83
|
var value = Object.assign({}, drag.start);
|
|
80
84
|
if (drag.edge) {
|
|
81
|
-
if (drag.edge.indexOf('e') >= 0) value.width = Math.max(340, Math.min(
|
|
85
|
+
if (drag.edge.indexOf('e') >= 0) value.width = Math.max(340, Math.min(maximumWidth(), window.innerWidth - value.left - 8, value.width + dx));
|
|
82
86
|
if (drag.edge.indexOf('s') >= 0) value.height = Math.max(360, Math.min(2160, window.innerHeight - value.top - 8, value.height + dy));
|
|
83
87
|
} else {
|
|
84
88
|
value.left = Math.max(8, Math.min(window.innerWidth - value.width - 8, value.left + dx));
|
|
@@ -174,7 +174,7 @@ export function paneOutgoing(data) {
|
|
|
174
174
|
if (message.paneBinding && !samePaneBinding(binding, message.paneBinding)) return null;
|
|
175
175
|
var gesture = (store.get('paneActionEvents') || []).find(function (entry) { return entry.event.isTrusted && entry.event.eventPhase && samePaneBinding(entry.binding, binding); });
|
|
176
176
|
var action = store.get('paneCallbackBinding') || gesture && gesture.binding;
|
|
177
|
-
var read = /^(get_|list_)|(_get|_list|_options)$/.test(message.type) || message.type === 'load_more_history';
|
|
177
|
+
var read = /^(get_|list_)|(_get|_list|_options)$/.test(message.type) || message.type === 'load_more_history' || message.type === 'background_task_read';
|
|
178
178
|
if (!message.paneBinding && !read && !samePaneBinding(action, binding)) return null;
|
|
179
179
|
return JSON.stringify(Object.assign({}, message, { paneBinding: message.paneBinding || action || binding }));
|
|
180
180
|
}
|
|
@@ -221,7 +221,7 @@ export function initPaneActivation() {
|
|
|
221
221
|
event.preventDefault(); event.stopImmediatePropagation();
|
|
222
222
|
}
|
|
223
223
|
}, true);
|
|
224
|
-
['click', 'input', 'change', 'keydown', 'keyup', 'submit', 'blur'].forEach(function (name) {
|
|
224
|
+
['click', 'input', 'change', 'keydown', 'keyup', 'submit', 'blur', 'pointerup'].forEach(function (name) {
|
|
225
225
|
document.addEventListener(name, function (event) {
|
|
226
226
|
var keys = store.get('paneHeldKeys') || {};
|
|
227
227
|
var binding = store.get('paneCommittedBinding');
|
|
@@ -229,7 +229,9 @@ export function initPaneActivation() {
|
|
|
229
229
|
var staleKey = (name === 'keyup' || name === 'keydown' && event.repeat) && previousKey && !samePaneBinding(previousKey, binding);
|
|
230
230
|
if (name === 'keydown' && !event.repeat) keys[event.code || event.key] = binding;
|
|
231
231
|
if (name === 'keyup') delete keys[event.code || event.key];
|
|
232
|
-
|
|
232
|
+
// A release from an earlier conversation must not authorize a drag mutation.
|
|
233
|
+
var stalePointer = name === 'pointerup' && !samePaneBinding(store.get('panePointerBinding'), binding);
|
|
234
|
+
if (staleKey || stalePointer || store.get('panePendingBinding') || store.get('paneSuspended') || !store.get('paneReady')) {
|
|
233
235
|
event.preventDefault(); event.stopImmediatePropagation(); return;
|
|
234
236
|
}
|
|
235
237
|
var events = (store.get('paneActionEvents') || []).filter(function (entry) { return entry.event.eventPhase; });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Pane chrome uses the existing JS tooltip service, including lazy workbenches.
|
|
2
|
+
import { registerTooltip, pruneTooltip } from './tooltip.js';
|
|
3
|
+
|
|
4
|
+
var HEADERS = '.split-pane-header, .tool-pane-header, .file-viewer-tabbar, .terminal-header, .shared-browser-header, .split-view-workers, #top-bar .top-bar-actions';
|
|
5
|
+
function sync(header) {
|
|
6
|
+
header.querySelectorAll('[title], button, [role="button"], a[aria-label]').forEach(function (element) {
|
|
7
|
+
var label = element.getAttribute('title') || element.getAttribute('aria-label') || element.getAttribute('data-tip');
|
|
8
|
+
if (!label || !label.trim()) return;
|
|
9
|
+
if (element.matches('button, [role="button"], a') && (!element.hasAttribute('aria-label') || element.dataset.tooltipGeneratedLabel === 'true' && element.hasAttribute('title'))) {
|
|
10
|
+
element.dataset.tooltipGeneratedLabel = 'true';
|
|
11
|
+
if (element.getAttribute('aria-label') !== label) element.setAttribute('aria-label', label);
|
|
12
|
+
}
|
|
13
|
+
if (element.getAttribute('data-tip') !== label || element.hasAttribute('title')) registerTooltip(element, label);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function initPaneHeaderTooltips() {
|
|
17
|
+
document.querySelectorAll(HEADERS).forEach(sync);
|
|
18
|
+
var observer = new MutationObserver(function (records) {
|
|
19
|
+
pruneTooltip();
|
|
20
|
+
var headers = new Set();
|
|
21
|
+
records.forEach(function (record) {
|
|
22
|
+
var parent = record.target.nodeType === 1 && record.target.closest(HEADERS); if (parent) headers.add(parent);
|
|
23
|
+
(record.addedNodes || []).forEach(function (node) {
|
|
24
|
+
if (node.nodeType !== 1) return;
|
|
25
|
+
if (node.matches(HEADERS)) headers.add(node);
|
|
26
|
+
node.querySelectorAll(HEADERS).forEach(function (header) { headers.add(header); });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
headers.forEach(function (header) { if (header.isConnected) sync(header); });
|
|
30
|
+
});
|
|
31
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title', 'aria-label'] });
|
|
32
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { queueContext, sameQueueContext } from './pending-message-queue-recovery.js';
|
|
3
|
+
|
|
4
|
+
function validDrag(drag) {
|
|
5
|
+
var queue = store.get('pendingMessageQueue') || {};
|
|
6
|
+
return drag && queueContext(store.snap()).enabled && sameQueueContext(drag.context, queueContext(store.snap())) && queue.revision === drag.revision;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function paintPendingQueueDrag(element) {
|
|
10
|
+
var drag = store.get('pendingMessageQueueDrag');
|
|
11
|
+
element.classList.toggle('is-reordering', !!drag && drag.moved);
|
|
12
|
+
element.querySelectorAll('[data-pending-id]').forEach(function (row) {
|
|
13
|
+
row.classList.toggle('is-dragging', !!drag && drag.moved && row.dataset.pendingId === drag.id);
|
|
14
|
+
row.classList.toggle('is-drop-target', !!drag && drag.moved && row.dataset.pendingId === drag.targetId);
|
|
15
|
+
row.classList.toggle('is-drop-after', !!drag && drag.after && row.dataset.pendingId === drag.targetId);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function bindPendingQueueDrag(element, reorder) {
|
|
20
|
+
function cleanupDrag(event) {
|
|
21
|
+
var drag = store.get('pendingMessageQueueDrag');
|
|
22
|
+
if (!drag || (event && drag.pointerId !== event.pointerId)) return null;
|
|
23
|
+
store.set({ pendingMessageQueueDrag: null });
|
|
24
|
+
if (element.hasPointerCapture(drag.pointerId)) element.releasePointerCapture(drag.pointerId);
|
|
25
|
+
paintPendingQueueDrag(element);
|
|
26
|
+
return drag;
|
|
27
|
+
}
|
|
28
|
+
element.addEventListener('pointerdown', function (event) {
|
|
29
|
+
var row = event.target.closest('[data-pending-id]');
|
|
30
|
+
var handle = row && row.querySelector('[data-action="drag"]');
|
|
31
|
+
if (event.target.closest('textarea, input, a, [contenteditable="true"]') || (event.target.closest('button') && !event.target.closest('[data-action="drag"]'))) return;
|
|
32
|
+
if (!handle || handle.disabled || !event.isPrimary || event.button !== 0) return;
|
|
33
|
+
if (store.get('pendingMessageQueueEdit') && store.get('pendingMessageQueueEdit').id === row.dataset.pendingId) return;
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
handle.focus({ preventScroll: true });
|
|
36
|
+
// Rows can be replaced by a live queue refresh during this gesture.
|
|
37
|
+
element.setPointerCapture(event.pointerId);
|
|
38
|
+
store.set({ pendingMessageQueueDrag: { id: row.dataset.pendingId, pointerId: event.pointerId, targetId: null, context: queueContext(store.snap()), revision: (store.get('pendingMessageQueue') || {}).revision, startX: event.clientX, startY: event.clientY, moved: false } });
|
|
39
|
+
});
|
|
40
|
+
element.addEventListener('pointermove', function (event) {
|
|
41
|
+
var drag = store.get('pendingMessageQueueDrag');
|
|
42
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
43
|
+
if (!validDrag(drag)) { cleanupDrag(event); return; }
|
|
44
|
+
var moved = drag.moved || Math.abs(event.clientX - drag.startX) + Math.abs(event.clientY - drag.startY) > 4;
|
|
45
|
+
var hit = document.elementFromPoint(event.clientX, event.clientY);
|
|
46
|
+
var row = hit && hit.closest('[data-pending-id]');
|
|
47
|
+
var targetId = row && element.contains(row) && row.querySelector('button[data-action="drag"]:not(:disabled)') && row.dataset.pendingId !== drag.id ? row.dataset.pendingId : null;
|
|
48
|
+
var rows = Array.from(element.querySelectorAll('[data-pending-id]'));
|
|
49
|
+
var source = rows.findIndex(function (candidate) { return candidate.dataset.pendingId === drag.id; });
|
|
50
|
+
store.set({ pendingMessageQueueDrag: Object.assign({}, drag, { moved: moved, targetId: targetId, after: targetId && rows.indexOf(row) > source }) });
|
|
51
|
+
paintPendingQueueDrag(element);
|
|
52
|
+
});
|
|
53
|
+
element.addEventListener('pointerup', function (event) {
|
|
54
|
+
var drag = cleanupDrag(event);
|
|
55
|
+
if (validDrag(drag) && drag.moved && drag.targetId) reorder(drag.id, 0, drag.targetId);
|
|
56
|
+
});
|
|
57
|
+
element.addEventListener('pointercancel', cleanupDrag);
|
|
58
|
+
element.addEventListener('lostpointercapture', cleanupDrag);
|
|
59
|
+
element.addEventListener('keydown', function (event) { if (event.key === 'Escape') cleanupDrag(); });
|
|
60
|
+
window.addEventListener('blur', function () { cleanupDrag(); });
|
|
61
|
+
store.subscribe(function (state, previous) {
|
|
62
|
+
var drag = state.pendingMessageQueueDrag;
|
|
63
|
+
if (drag && !validDrag(drag)) cleanupDrag();
|
|
64
|
+
else if (!drag && previous.pendingMessageQueueDrag) {
|
|
65
|
+
if (element.hasPointerCapture(previous.pendingMessageQueueDrag.pointerId)) element.releasePointerCapture(previous.pendingMessageQueueDrag.pointerId);
|
|
66
|
+
paintPendingQueueDrag(element);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -5,6 +5,7 @@ import { refreshIcons } from './icons.js';
|
|
|
5
5
|
import { activeQueueItems, hasFutureScheduledPending, moveQueueId, matchesQueueContext, canApplyQueueRevision } from './pending-message-queue-model.js';
|
|
6
6
|
import { emptyQueue, queueContext, sameQueueContext, queueScopeChanged, queueReconnected, queueCanRender, createQueueHydrationRecovery, canonicalQueue } from './pending-message-queue-recovery.js';
|
|
7
7
|
import { imageSrc, imageDraft, openImagePreview, chooseImages } from './pending-message-image-editor.js';
|
|
8
|
+
import { bindPendingQueueDrag, paintPendingQueueDrag } from './pending-message-queue-drag.js';
|
|
8
9
|
import { setScheduleBtnDisabled } from './scheduled-message-state.js';
|
|
9
10
|
|
|
10
11
|
export { hasFutureScheduledPending };
|
|
@@ -391,6 +392,7 @@ function render() {
|
|
|
391
392
|
html += '</div>';
|
|
392
393
|
queueEl.innerHTML = html;
|
|
393
394
|
refreshIcons();
|
|
395
|
+
paintPendingQueueDrag(queueEl);
|
|
394
396
|
restoreFocus(focus);
|
|
395
397
|
}
|
|
396
398
|
|
|
@@ -426,39 +428,7 @@ function bindEvents() {
|
|
|
426
428
|
var row = handle.closest("[data-pending-id]");
|
|
427
429
|
reorder(row.dataset.pendingId, event.key === "ArrowUp" ? -1 : 1, null);
|
|
428
430
|
});
|
|
429
|
-
queueEl
|
|
430
|
-
var handle = event.target.closest('[data-action="drag"]');
|
|
431
|
-
if (!handle) return;
|
|
432
|
-
var row = handle.closest("[data-pending-id]");
|
|
433
|
-
handle.setPointerCapture(event.pointerId);
|
|
434
|
-
store.set({ pendingMessageQueueDrag: { id: row.dataset.pendingId, pointerId: event.pointerId, targetId: null } });
|
|
435
|
-
row.classList.add("is-dragging");
|
|
436
|
-
});
|
|
437
|
-
queueEl.addEventListener("pointermove", function (event) {
|
|
438
|
-
var drag = store.get('pendingMessageQueueDrag');
|
|
439
|
-
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
440
|
-
var target = document.elementFromPoint(event.clientX, event.clientY);
|
|
441
|
-
var row = target && target.closest("[data-pending-id]");
|
|
442
|
-
var targetId = row && row.dataset.pendingId !== drag.id ? row.dataset.pendingId : null;
|
|
443
|
-
queueEl.querySelectorAll(".is-drop-target").forEach(function (node) { node.classList.remove("is-drop-target"); });
|
|
444
|
-
if (targetId && ownedPendingIds().indexOf(targetId) >= 0) row.classList.add("is-drop-target");
|
|
445
|
-
store.set({ pendingMessageQueueDrag: Object.assign({}, drag, { targetId: targetId }) });
|
|
446
|
-
});
|
|
447
|
-
function cleanupDrag(event) {
|
|
448
|
-
var drag = store.get('pendingMessageQueueDrag');
|
|
449
|
-
if (!drag || drag.pointerId !== event.pointerId) return null;
|
|
450
|
-
store.set({ pendingMessageQueueDrag: null });
|
|
451
|
-
queueEl.querySelectorAll(".is-dragging, .is-drop-target").forEach(function (node) { node.classList.remove("is-dragging", "is-drop-target"); });
|
|
452
|
-
return drag;
|
|
453
|
-
}
|
|
454
|
-
function finishDrag(event) {
|
|
455
|
-
var drag = cleanupDrag(event);
|
|
456
|
-
if (!drag) return;
|
|
457
|
-
if (drag.targetId && ownedPendingIds().indexOf(drag.targetId) >= 0) reorder(drag.id, 0, drag.targetId);
|
|
458
|
-
}
|
|
459
|
-
queueEl.addEventListener("pointerup", finishDrag);
|
|
460
|
-
queueEl.addEventListener("pointercancel", cleanupDrag);
|
|
461
|
-
queueEl.addEventListener("lostpointercapture", cleanupDrag);
|
|
431
|
+
bindPendingQueueDrag(queueEl, reorder);
|
|
462
432
|
}
|
|
463
433
|
|
|
464
434
|
export function initPendingMessageQueue() {
|
|
@@ -46,9 +46,9 @@ export function createPermissionControl(className, ariaLabel) {
|
|
|
46
46
|
control.setAttribute("role", "group");
|
|
47
47
|
control.setAttribute("aria-label", ariaLabel || "Session permission mode");
|
|
48
48
|
control.innerHTML = '<div class="session-permission-segmented">' +
|
|
49
|
-
'<button type="button" data-permission-mode="default" aria-label="Ask permissions"
|
|
50
|
-
'<button type="button" data-permission-mode="auto" aria-label="Auto permissions"
|
|
51
|
-
'<button type="button" data-permission-mode="bypassPermissions" aria-label="YOLO permissions"
|
|
49
|
+
'<button type="button" data-permission-mode="default" aria-label="Ask permissions" data-tip="Ask before tool actions that need permission. Previously allowed actions can run without asking again."><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
|
|
50
|
+
'<button type="button" data-permission-mode="auto" aria-label="Auto permissions" data-tip="Let the runtime assess tool actions and approve them automatically when allowed. Some actions may still need your permission."><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
|
|
51
|
+
'<button type="button" data-permission-mode="bypassPermissions" aria-label="YOLO permissions" data-tip="Run tools, file changes and commands without asking for permission. Clay access restrictions still apply."><span class="permission-label">YOLO</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
|
|
52
52
|
'</div><span class="session-permission-status" aria-live="polite"></span>';
|
|
53
53
|
control.querySelector(".session-permission-status").setAttribute("aria-atomic", "true");
|
|
54
54
|
return control;
|
|
@@ -102,7 +102,7 @@ export function renderPermissionControl(control, state) {
|
|
|
102
102
|
control.classList.toggle("permission-globally-forced", globallyForced);
|
|
103
103
|
control.classList.toggle("permission-runtime-fallback", state.permissionMode === "auto" && !!state.effectivePermissionMode && state.effectivePermissionMode !== "auto");
|
|
104
104
|
control.setAttribute("aria-busy", pending ? "true" : "false");
|
|
105
|
-
var
|
|
105
|
+
var buttonDescriptions = {"default": "Ask before tool actions that need permission. Previously allowed actions can run without asking again.", "auto": "Let the runtime assess tool actions and approve them automatically when allowed. Some actions may still need your permission.", "bypassPermissions": "Run tools, file changes and commands without asking for permission. Clay access restrictions still apply."};
|
|
106
106
|
var buttons = control.querySelectorAll("[data-permission-mode]");
|
|
107
107
|
for (var i = 0; i < buttons.length; i++) {
|
|
108
108
|
var mode = buttons[i].dataset.permissionMode;
|
|
@@ -115,7 +115,8 @@ export function renderPermissionControl(control, state) {
|
|
|
115
115
|
buttons[i].classList.toggle("pending", requested);
|
|
116
116
|
buttons[i].setAttribute("aria-pressed", selected ? "true" : "false");
|
|
117
117
|
buttons[i].setAttribute("aria-busy", requested ? "true" : "false");
|
|
118
|
-
buttons[i].
|
|
118
|
+
buttons[i].setAttribute("data-tip", (unsupported ? "Auto is unavailable for this session. " : "") + buttonDescriptions[mode]);
|
|
119
|
+
buttons[i].removeAttribute("title");
|
|
119
120
|
}
|
|
120
121
|
var status = control.querySelector(".session-permission-status");
|
|
121
122
|
if (status) {
|