clay-server 3.8.1 → 4.0.0-beta.10
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/ask-user-mcp-server.js +5 -119
- package/lib/background-task-timing.js +105 -0
- package/lib/builtin-mates.js +6 -6
- package/lib/claude-hook-installer.js +15 -0
- package/lib/clay-history-mcp-server.js +62 -297
- package/lib/daemon.js +23 -1
- package/lib/debate-model-selection.js +119 -0
- package/lib/home-debate-tool-policy.js +164 -0
- package/lib/home-response-notifications.js +26 -0
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +224 -0
- package/lib/mate-creation-mcp-server.js +55 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/mate-ready-creation.js +95 -0
- package/lib/mates.js +2 -0
- package/lib/notes.js +2 -1
- package/lib/project-ask-user.js +64 -0
- package/lib/project-capsule-catalog.js +101 -0
- package/lib/project-connection.js +17 -0
- package/lib/project-debate-proposal.js +133 -17
- package/lib/project-debate.js +214 -110
- package/lib/project-delegated-follow-up.js +84 -0
- package/lib/project-delegated-session.js +53 -0
- package/lib/project-filesystem.js +7 -9
- package/lib/project-knowledge.js +23 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-mcp-server.js +290 -0
- package/lib/project-logs-query.js +167 -0
- package/lib/project-logs-root.js +67 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +357 -0
- package/lib/project-logs-snapshot.js +202 -0
- package/lib/project-logs-store.js +498 -0
- package/lib/project-logs-versioning.js +128 -0
- package/lib/project-logs.js +347 -0
- package/lib/project-mate-creation-proposal.js +120 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +36 -7
- package/lib/project-models.js +157 -32
- package/lib/project-session-notes.js +2 -2
- package/lib/project-session-pair.js +8 -8
- package/lib/project-sessions.js +66 -51
- package/lib/project-vendor-login.js +414 -0
- package/lib/project-worker-proposal.js +25 -25
- package/lib/project-workspace-query.js +144 -0
- package/lib/project.js +433 -102
- package/lib/public/app.js +109 -48
- package/lib/public/css/avatar-imprints.css +0 -1
- package/lib/public/css/base.css +4 -0
- package/lib/public/css/capsule-ui.css +389 -0
- package/lib/public/css/command-palette.css +306 -6
- package/lib/public/css/home-capsule-library.css +243 -0
- package/lib/public/css/home-capsule-source.css +192 -0
- package/lib/public/css/home-debate-live.css +146 -0
- package/lib/public/css/home-debate-planning.css +125 -0
- package/lib/public/css/home-debates-archive.css +281 -0
- package/lib/public/css/home-hub.css +896 -405
- package/lib/public/css/home-mate-creation.css +34 -0
- package/lib/public/css/home-mate-model-picker.css +131 -0
- package/lib/public/css/home-mate-settings.css +344 -0
- package/lib/public/css/home-session-actions.css +265 -0
- package/lib/public/css/home-sidebar.css +667 -0
- package/lib/public/css/icon-strip.css +1 -2
- package/lib/public/css/input.css +79 -25
- package/lib/public/css/mates.css +7 -34
- package/lib/public/css/pane.css +1 -1
- package/lib/public/css/playbook.css +0 -80
- package/lib/public/css/project-logs.css +234 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/sticky-notes.css +53 -117
- package/lib/public/css/tui-attention.css +0 -44
- package/lib/public/css/workspace-assignment.css +45 -0
- package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
- package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
- package/lib/public/index.html +96 -74
- package/lib/public/modules/app-connection.js +100 -5
- package/lib/public/modules/app-dm.js +43 -160
- package/lib/public/modules/app-home-hub.js +328 -585
- package/lib/public/modules/app-message-cards.js +242 -0
- package/lib/public/modules/app-message-router.js +84 -0
- package/lib/public/modules/app-messages.js +124 -38
- package/lib/public/modules/app-notifications.js +8 -119
- package/lib/public/modules/app-panels.js +43 -2
- package/lib/public/modules/app-projects.js +9 -1
- package/lib/public/modules/app-rendering.js +21 -602
- package/lib/public/modules/avatar-imprint.js +11 -8
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/chat-bubble-renderer.js +152 -0
- package/lib/public/modules/chat-render-runtime.js +198 -0
- package/lib/public/modules/command-palette.js +225 -436
- package/lib/public/modules/dm-render.js +82 -0
- package/lib/public/modules/home-capsule-creation-intent.js +31 -0
- package/lib/public/modules/home-capsule-library.js +146 -0
- package/lib/public/modules/home-capsule-source.js +383 -0
- package/lib/public/modules/home-chat-identity.js +35 -0
- package/lib/public/modules/home-chat-scroll.js +77 -0
- package/lib/public/modules/home-chat-stream-state.js +34 -0
- package/lib/public/modules/home-composer-focus.js +25 -0
- package/lib/public/modules/home-conversations-sheet.js +179 -0
- package/lib/public/modules/home-debate-activity.js +48 -0
- package/lib/public/modules/home-debate-controls.js +295 -0
- package/lib/public/modules/home-debate-launch.js +41 -0
- package/lib/public/modules/home-debate-live.js +284 -0
- package/lib/public/modules/home-debate-models.js +73 -0
- package/lib/public/modules/home-debate-planning.js +335 -0
- package/lib/public/modules/home-debates-archive.js +279 -0
- package/lib/public/modules/home-dock-resize.js +74 -0
- package/lib/public/modules/home-dock.js +446 -0
- package/lib/public/modules/home-mate-chat.js +496 -0
- package/lib/public/modules/home-mate-creation.js +109 -0
- package/lib/public/modules/home-mate-model-picker.js +281 -0
- package/lib/public/modules/home-mate-selection.js +38 -0
- package/lib/public/modules/home-mate-settings-menu.js +131 -0
- package/lib/public/modules/home-mate-settings.js +383 -0
- package/lib/public/modules/home-session-actions.js +373 -0
- package/lib/public/modules/home-session-model-confirmation.js +13 -0
- package/lib/public/modules/home-shell.js +9 -0
- package/lib/public/modules/home-sidebar-chat-list.js +63 -0
- package/lib/public/modules/home-sidebar.js +165 -0
- package/lib/public/modules/home-sub-surface.js +19 -0
- package/lib/public/modules/home-surface-boot.js +68 -0
- package/lib/public/modules/home-surface.js +141 -0
- package/lib/public/modules/home-tools.js +271 -0
- package/lib/public/modules/markdown.js +50 -2
- package/lib/public/modules/mate-management.js +86 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/mate-wizard.js +0 -7
- package/lib/public/modules/mention.js +17 -19
- package/lib/public/modules/pane-bridge.js +9 -0
- package/lib/public/modules/profile.js +4 -5
- package/lib/public/modules/project-logs-ambient.js +238 -0
- package/lib/public/modules/project-logs-render.js +359 -0
- package/lib/public/modules/project-logs.js +328 -0
- package/lib/public/modules/project-settings.js +34 -28
- package/lib/public/modules/project-switcher.js +11 -5
- package/lib/public/modules/project-workspace-assignment.js +23 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/search-clay-chat.js +402 -0
- package/lib/public/modules/server-settings.js +14 -12
- package/lib/public/modules/session-actions.js +1 -1
- package/lib/public/modules/sidebar-mates.js +56 -552
- package/lib/public/modules/sidebar-mobile.js +12 -73
- package/lib/public/modules/sidebar-presence.js +37 -0
- package/lib/public/modules/sidebar-sessions.js +25 -1
- package/lib/public/modules/split-pair-ui.js +13 -13
- package/lib/public/modules/split-view.js +7 -0
- package/lib/public/modules/sticky-notes.js +106 -53
- package/lib/public/modules/tool-input-composition.js +48 -0
- package/lib/public/modules/tool-llm-status.js +167 -0
- package/lib/public/modules/tool-palette.js +76 -4
- package/lib/public/modules/tool-renderer-advanced.js +285 -0
- package/lib/public/modules/tool-renderer-chart.js +158 -0
- package/lib/public/modules/tool-renderer-semantics.js +35 -0
- package/lib/public/modules/tool-renderer.js +464 -0
- package/lib/public/modules/tool-runtime.js +309 -0
- package/lib/public/modules/tool-ui-evaluator.js +97 -0
- package/lib/public/modules/tool-ui-tree.js +21 -0
- package/lib/public/modules/tui-attention.js +10 -0
- package/lib/public/modules/user-settings.js +16 -23
- package/lib/public/modules/vendor-login.js +287 -0
- package/lib/public/modules/worker-proposal.js +2 -2
- package/lib/public/modules/workspace-assignment-card.js +149 -0
- package/lib/public/modules/workspace-assignment-routing.js +4 -0
- package/lib/public/style.css +14 -2
- package/lib/runtime-env.js +65 -0
- package/lib/sdk-bridge.js +120 -32
- package/lib/sdk-message-processor.js +34 -13
- package/lib/server-home-capsule-creation.js +36 -0
- package/lib/server-home-chat-events.js +287 -0
- package/lib/server-home-chat.js +518 -0
- package/lib/server-home-clay-entry.js +65 -0
- package/lib/server-home-clay-session-links.js +26 -0
- package/lib/server-home-debate-planning.js +214 -0
- package/lib/server-home-debates.js +137 -0
- package/lib/server-home-mate-creation.js +261 -0
- package/lib/server-home-models.js +241 -0
- package/lib/server-home-preferences.js +61 -0
- package/lib/server-mates.js +37 -0
- package/lib/server-tools.js +377 -0
- package/lib/server.js +166 -59
- package/lib/session-pair-mcp-server.js +6 -6
- package/lib/sessions.js +22 -4
- package/lib/tool-capsule-source.js +142 -0
- package/lib/tool-control-mcp-server.js +126 -0
- package/lib/tool-llm.js +48 -0
- package/lib/tool-storage.js +77 -0
- package/lib/tool-ui-spec-advanced.js +82 -0
- package/lib/tool-ui-spec.js +261 -0
- package/lib/tools-registry.js +286 -0
- package/lib/users-home-dock-preferences.js +77 -0
- package/lib/users-home-surface-preferences.js +99 -0
- package/lib/users-preferences.js +4 -7
- package/lib/users.js +12 -0
- package/lib/whats-new-content.js +3 -3
- package/lib/workspace-assignment-service.js +420 -0
- package/lib/workspace-query-mcp-server.js +154 -0
- package/lib/workspace-query-service.js +492 -0
- package/lib/ws-schema.js +107 -12
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/antigravity.js +2 -1
- package/lib/yoke/adapters/claude-worker.js +11 -4
- package/lib/yoke/adapters/claude.js +141 -46
- package/lib/yoke/adapters/codex.js +79 -11
- package/lib/yoke/adapters/kiro.js +2 -1
- package/lib/yoke/claude-user-input.js +38 -0
- package/lib/yoke/codex-app-server.js +20 -2
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/codex-user-input.js +64 -0
- package/lib/yoke/complete-once.js +164 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/interface.js +19 -0
- package/lib/yoke/mcp-bridge-server.js +2 -6
- package/lib/yoke/user-input.js +254 -0
- package/package.json +4 -3
- package/lib/public/css/home-chat.css +0 -380
- package/lib/public/modules/home-chat.js +0 -438
- package/lib/server-clay-home.js +0 -245
|
@@ -4,8 +4,15 @@ var PAPER = "#f1efe8";
|
|
|
4
4
|
var INK = "#1b1b1a";
|
|
5
5
|
var LIGHT_BASE = "#e7e5de";
|
|
6
6
|
var LIGHT_FIELD = "#333330";
|
|
7
|
-
var
|
|
8
|
-
var
|
|
7
|
+
var MUTED_INDIGO = "#72778f";
|
|
8
|
+
var MATE_MARK_PALETTE = [
|
|
9
|
+
{ background: "#6f7489", foreground: PAPER },
|
|
10
|
+
{ background: "#78816c", foreground: PAPER },
|
|
11
|
+
{ background: "#92756d", foreground: PAPER },
|
|
12
|
+
{ background: "#6f8382", foreground: PAPER },
|
|
13
|
+
{ background: "#887b91", foreground: PAPER },
|
|
14
|
+
{ background: "#8a806a", foreground: PAPER },
|
|
15
|
+
];
|
|
9
16
|
|
|
10
17
|
function hashString(value) {
|
|
11
18
|
var text = String(value || "anonymous");
|
|
@@ -71,7 +78,7 @@ export function imprintSvg(options) {
|
|
|
71
78
|
var darkBase = random() > 0.52;
|
|
72
79
|
var base = darkBase ? INK : LIGHT_BASE;
|
|
73
80
|
var field = darkBase ? PAPER : LIGHT_FIELD;
|
|
74
|
-
var accent =
|
|
81
|
+
var accent = MUTED_INDIGO;
|
|
75
82
|
var rotation = Math.floor(random() * 4) * 90;
|
|
76
83
|
var bandWidth = between(random, 37, 56);
|
|
77
84
|
var secondaryWidth = between(random, 14, 24);
|
|
@@ -114,11 +121,7 @@ export function mateMarkSvg(options) {
|
|
|
114
121
|
var size = Math.max(12, Math.min(256, Number(settings.size) || 64));
|
|
115
122
|
var identity = String(settings.seed || "M").trim();
|
|
116
123
|
var initial = Array.from(identity || "M")[0].toUpperCase();
|
|
117
|
-
var palette = [
|
|
118
|
-
{ background: REGISTRATION_INDIGO, foreground: PAPER },
|
|
119
|
-
{ background: BRAND_GREEN, foreground: INK },
|
|
120
|
-
{ background: PAPER, foreground: REGISTRATION_INDIGO },
|
|
121
|
-
][initial.codePointAt(0) % 3];
|
|
124
|
+
var palette = MATE_MARK_PALETTE[initial.codePointAt(0) % MATE_MARK_PALETTE.length];
|
|
122
125
|
var clipId = "mate-mark";
|
|
123
126
|
|
|
124
127
|
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
|
|
@@ -1,55 +1,178 @@
|
|
|
1
|
+
// background-tasks-ui.js - Background task status line above the composer.
|
|
2
|
+
//
|
|
3
|
+
// Collapsed it reads as a quiet status line, not a button demanding attention:
|
|
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.
|
|
10
|
+
|
|
1
11
|
import { store } from './store.js';
|
|
2
12
|
import { getWs } from './ws-ref.js';
|
|
3
13
|
import { escapeHtml } from './utils.js';
|
|
14
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
4
15
|
|
|
5
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" };
|
|
6
22
|
|
|
7
23
|
export function initBackgroundTasksUi() {
|
|
8
|
-
store.subscribe(function(state, prev) {
|
|
24
|
+
store.subscribe(function (state, prev) {
|
|
9
25
|
if (state.activeBackgroundTasks !== prev.activeBackgroundTasks) renderBackgroundTasks();
|
|
10
26
|
});
|
|
11
27
|
renderBackgroundTasks();
|
|
12
28
|
}
|
|
13
29
|
|
|
30
|
+
// "45s", "2m 14s", "1h 03m" - short enough to sit in a row without wrapping.
|
|
31
|
+
export function formatElapsed(startedAt, now) {
|
|
32
|
+
if (typeof startedAt !== "number" || !isFinite(startedAt) || startedAt <= 0) return "";
|
|
33
|
+
var current = typeof now === "number" ? now : Date.now();
|
|
34
|
+
var totalSeconds = Math.floor((current - startedAt) / 1000);
|
|
35
|
+
if (totalSeconds < 0) totalSeconds = 0;
|
|
36
|
+
if (totalSeconds < 60) return totalSeconds + "s";
|
|
37
|
+
var minutes = Math.floor(totalSeconds / 60);
|
|
38
|
+
var seconds = totalSeconds % 60;
|
|
39
|
+
if (minutes < 60) return minutes + "m " + pad(seconds) + "s";
|
|
40
|
+
var hours = Math.floor(minutes / 60);
|
|
41
|
+
return hours + "h " + pad(minutes % 60) + "m";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pad(value) {
|
|
45
|
+
return value < 10 ? "0" + value : String(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
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
|
+
// Ambient tasks are the CLI's own housekeeping. The SDK asks hosts to keep
|
|
80
|
+
// them out of activity indicators; Clay still lists them so the user can see
|
|
81
|
+
// what runs on their behalf. Absent means false (Codex has no ambient concept,
|
|
82
|
+
// and older CLI builds report only user work).
|
|
83
|
+
export function isAmbientTask(task) {
|
|
84
|
+
return !!(task && task.ambient === true);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function splitTasks(tasks) {
|
|
88
|
+
var user = [];
|
|
89
|
+
var ambient = [];
|
|
90
|
+
var list = Array.isArray(tasks) ? tasks : [];
|
|
91
|
+
for (var i = 0; i < list.length; i++) {
|
|
92
|
+
if (!list[i]) continue;
|
|
93
|
+
if (isAmbientTask(list[i])) ambient.push(list[i]);
|
|
94
|
+
else user.push(list[i]);
|
|
95
|
+
}
|
|
96
|
+
return { user: user, ambient: ambient };
|
|
97
|
+
}
|
|
98
|
+
|
|
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
|
+
|
|
14
118
|
function renderBackgroundTasks() {
|
|
15
119
|
var tasks = store.get('activeBackgroundTasks') || [];
|
|
120
|
+
var split = splitTasks(tasks);
|
|
16
121
|
var existing = document.getElementById('background-tasks-bar');
|
|
17
|
-
|
|
122
|
+
// Ambient-only means nothing the user asked for is running, so the composer
|
|
123
|
+
// stays quiet: no bar, no dots, no count.
|
|
124
|
+
if (split.user.length === 0) {
|
|
18
125
|
if (existing) existing.remove();
|
|
19
126
|
expanded = false;
|
|
127
|
+
stopElapsedTimer();
|
|
20
128
|
return;
|
|
21
129
|
}
|
|
22
130
|
var inputArea = document.getElementById('input-area');
|
|
23
131
|
if (!inputArea || !inputArea.parentNode) return;
|
|
132
|
+
|
|
24
133
|
var bar = existing || document.createElement('div');
|
|
25
134
|
bar.id = 'background-tasks-bar';
|
|
26
|
-
bar.className = 'background-tasks-bar';
|
|
27
|
-
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
135
|
+
bar.className = 'background-tasks-bar' + (expanded ? ' expanded' : '');
|
|
136
|
+
|
|
137
|
+
var taskLabel = split.user.length === 1 ? 'background task' : 'background tasks';
|
|
138
|
+
var summary = split.user.length + ' ' + taskLabel;
|
|
139
|
+
var html = '<button type="button" class="background-tasks-toggle" aria-expanded="' + expanded + '"' +
|
|
140
|
+
' aria-controls="background-tasks-list" aria-label="' + summary + ', ' +
|
|
141
|
+
(expanded ? 'collapse' : 'expand') + ' details">' +
|
|
142
|
+
activityDotsHtml() +
|
|
143
|
+
'<span class="background-tasks-summary">' + summary + '</span>' +
|
|
144
|
+
'<span class="background-tasks-chevron" aria-hidden="true">' + iconHtml('chevron-right') + '</span>' +
|
|
145
|
+
'</button>';
|
|
146
|
+
|
|
31
147
|
if (expanded) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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);
|
|
39
156
|
}
|
|
40
157
|
html += '</div>';
|
|
41
158
|
}
|
|
42
159
|
bar.innerHTML = html;
|
|
43
160
|
if (!existing) inputArea.parentNode.insertBefore(bar, inputArea);
|
|
44
|
-
|
|
161
|
+
refreshIcons();
|
|
162
|
+
|
|
163
|
+
bar.querySelector('.background-tasks-toggle').addEventListener('click', function () {
|
|
45
164
|
expanded = !expanded;
|
|
46
165
|
renderBackgroundTasks();
|
|
47
166
|
});
|
|
167
|
+
|
|
48
168
|
var stopButtons = bar.querySelectorAll('.background-task-stop');
|
|
49
169
|
for (var j = 0; j < stopButtons.length; j++) {
|
|
50
|
-
stopButtons[j].addEventListener('click', function() {
|
|
170
|
+
stopButtons[j].addEventListener('click', function () {
|
|
51
171
|
var ws = getWs();
|
|
52
172
|
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'stop_task', taskId: this.dataset.taskId }));
|
|
53
173
|
});
|
|
54
174
|
}
|
|
175
|
+
|
|
176
|
+
if (expanded) startElapsedTimer();
|
|
177
|
+
else stopElapsedTimer();
|
|
55
178
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// chat-bubble-renderer.js - Stateless project-style chat bubble rendering.
|
|
2
|
+
|
|
3
|
+
import { copyToClipboard } from './utils.js';
|
|
4
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, enhanceClaySessionLinks } from './markdown.js';
|
|
5
|
+
|
|
6
|
+
var copyCleanups = new WeakMap();
|
|
7
|
+
|
|
8
|
+
function appendIdentity(row, options, avatarClass) {
|
|
9
|
+
if (options.avatarUrl) {
|
|
10
|
+
var avatar = document.createElement("img");
|
|
11
|
+
avatar.className = "dm-bubble-avatar " + avatarClass;
|
|
12
|
+
avatar.src = options.avatarUrl;
|
|
13
|
+
avatar.alt = "";
|
|
14
|
+
row.appendChild(avatar);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var content = document.createElement("div");
|
|
18
|
+
content.className = "dm-bubble-content";
|
|
19
|
+
if (options.name || options.time) {
|
|
20
|
+
var header = document.createElement("div");
|
|
21
|
+
header.className = "dm-bubble-header";
|
|
22
|
+
if (options.name) {
|
|
23
|
+
var name = document.createElement("span");
|
|
24
|
+
name.className = "dm-bubble-name";
|
|
25
|
+
name.textContent = options.name;
|
|
26
|
+
header.appendChild(name);
|
|
27
|
+
}
|
|
28
|
+
if (options.time) {
|
|
29
|
+
var time = document.createElement("span");
|
|
30
|
+
time.className = "dm-bubble-time";
|
|
31
|
+
time.textContent = options.time;
|
|
32
|
+
header.appendChild(time);
|
|
33
|
+
}
|
|
34
|
+
content.appendChild(header);
|
|
35
|
+
}
|
|
36
|
+
row.appendChild(content);
|
|
37
|
+
return content;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createAssistantBubble(options) {
|
|
41
|
+
options = options || {};
|
|
42
|
+
var row = document.createElement("div");
|
|
43
|
+
row.className = "msg-assistant";
|
|
44
|
+
if (options.turn !== undefined && options.turn !== null) row.dataset.turn = options.turn;
|
|
45
|
+
var contentWrap = appendIdentity(row, options, "dm-bubble-avatar-mate");
|
|
46
|
+
var content = document.createElement("div");
|
|
47
|
+
content.className = "md-content";
|
|
48
|
+
content.dir = "auto";
|
|
49
|
+
contentWrap.appendChild(content);
|
|
50
|
+
return row;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createUserBubble(options) {
|
|
54
|
+
options = options || {};
|
|
55
|
+
var row = document.createElement("div");
|
|
56
|
+
row.className = "msg-user";
|
|
57
|
+
if (options.turn !== undefined && options.turn !== null) row.dataset.turn = options.turn;
|
|
58
|
+
var contentWrap = appendIdentity(row, options, "dm-bubble-avatar-me");
|
|
59
|
+
var bubble = document.createElement("div");
|
|
60
|
+
bubble.className = "bubble";
|
|
61
|
+
bubble.dir = "auto";
|
|
62
|
+
var text = document.createElement("span");
|
|
63
|
+
text.textContent = options.text || "";
|
|
64
|
+
bubble.appendChild(text);
|
|
65
|
+
contentWrap.appendChild(bubble);
|
|
66
|
+
return row;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function assistantContent(row) {
|
|
70
|
+
return row ? row.querySelector(".md-content") : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function renderAssistantBubbleText(row, text, highlight) {
|
|
74
|
+
var content = assistantContent(row);
|
|
75
|
+
if (!content) return null;
|
|
76
|
+
content.innerHTML = renderMarkdown(text || "");
|
|
77
|
+
enhanceClaySessionLinks(content);
|
|
78
|
+
if (highlight) highlightCodeBlocks(content);
|
|
79
|
+
return content;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function enhanceAssistantBubble(row, renderMermaid) {
|
|
83
|
+
var content = assistantContent(row);
|
|
84
|
+
if (!content) return;
|
|
85
|
+
enhanceClaySessionLinks(content);
|
|
86
|
+
highlightCodeBlocks(content);
|
|
87
|
+
if (renderMermaid) renderMermaidBlocks(content);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function addAssistantCopyHandler(row, rawText) {
|
|
91
|
+
if (!row || !rawText || row.dataset.copyBound === "true") return;
|
|
92
|
+
row.dataset.copyBound = "true";
|
|
93
|
+
var primed = false;
|
|
94
|
+
var resetTimer = null;
|
|
95
|
+
var isTouchDevice = "ontouchstart" in window;
|
|
96
|
+
var hint = document.createElement("div");
|
|
97
|
+
hint.className = "msg-copy-hint";
|
|
98
|
+
hint.textContent = (isTouchDevice ? "Tap" : "Click") + " to grab this";
|
|
99
|
+
row.appendChild(hint);
|
|
100
|
+
|
|
101
|
+
function reset() {
|
|
102
|
+
primed = false;
|
|
103
|
+
row.classList.remove("copy-primed", "copy-done");
|
|
104
|
+
hint.textContent = (isTouchDevice ? "Tap" : "Click") + " to grab this";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
row.addEventListener("click", function (event) {
|
|
108
|
+
if (event.target.closest("a, pre, code")) return;
|
|
109
|
+
var selection = window.getSelection();
|
|
110
|
+
if (selection && selection.toString().length > 0) return;
|
|
111
|
+
if (!primed) {
|
|
112
|
+
primed = true;
|
|
113
|
+
row.classList.add("copy-primed");
|
|
114
|
+
hint.textContent = isTouchDevice ? "Tap again to grab" : "Click again to grab";
|
|
115
|
+
clearTimeout(resetTimer);
|
|
116
|
+
resetTimer = setTimeout(reset, 3000);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
clearTimeout(resetTimer);
|
|
120
|
+
copyToClipboard(rawText).then(function () {
|
|
121
|
+
row.classList.remove("copy-primed");
|
|
122
|
+
row.classList.add("copy-done");
|
|
123
|
+
hint.textContent = "Grabbed!";
|
|
124
|
+
resetTimer = setTimeout(reset, 1500);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function handleOutsideClick(event) {
|
|
129
|
+
if (primed && !row.contains(event.target)) reset();
|
|
130
|
+
}
|
|
131
|
+
document.addEventListener("click", handleOutsideClick);
|
|
132
|
+
copyCleanups.set(row, function () {
|
|
133
|
+
clearTimeout(resetTimer);
|
|
134
|
+
document.removeEventListener("click", handleOutsideClick);
|
|
135
|
+
copyCleanups.delete(row);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function finalizeAssistantBubble(row, rawText, enableCopy) {
|
|
140
|
+
renderAssistantBubbleText(row, rawText, false);
|
|
141
|
+
enhanceAssistantBubble(row, true);
|
|
142
|
+
if (enableCopy) addAssistantCopyHandler(row, rawText);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function disposeChatBubbleTree(root) {
|
|
146
|
+
if (!root) return;
|
|
147
|
+
var rows = root.querySelectorAll('.msg-assistant[data-copy-bound="true"]');
|
|
148
|
+
for (var i = 0; i < rows.length; i++) {
|
|
149
|
+
var cleanup = copyCleanups.get(rows[i]);
|
|
150
|
+
if (cleanup) cleanup();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// chat-render-runtime.js - Shared project transcript state and scrolling.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { getMessagesEl } from './dom-refs.js';
|
|
5
|
+
|
|
6
|
+
export var VENDOR_AVATARS = {
|
|
7
|
+
claude: "/claude-code-avatar.png",
|
|
8
|
+
codex: "/codex-avatar.png",
|
|
9
|
+
antigravity: "/antigravity-avatar.png",
|
|
10
|
+
opencode: "/opencode-avatar.svg",
|
|
11
|
+
kimi: "/kimi-avatar.svg",
|
|
12
|
+
grok: "/grok-avatar.svg",
|
|
13
|
+
copilot: "/copilot-avatar.svg",
|
|
14
|
+
qwen: "/qwen-avatar.svg",
|
|
15
|
+
junie: "/junie-avatar.svg",
|
|
16
|
+
kiro: "/kiro-avatar.svg",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export var VENDOR_NAMES = {
|
|
20
|
+
claude: "Claude Code",
|
|
21
|
+
codex: "Codex",
|
|
22
|
+
antigravity: "Antigravity CLI",
|
|
23
|
+
opencode: "OpenCode",
|
|
24
|
+
kimi: "Kimi Code",
|
|
25
|
+
grok: "Grok Build",
|
|
26
|
+
copilot: "GitHub Copilot CLI",
|
|
27
|
+
qwen: "Qwen Code",
|
|
28
|
+
junie: "Junie CLI",
|
|
29
|
+
kiro: "Kiro CLI",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export var VENDOR_HOMEPAGES = {
|
|
33
|
+
claude: "https://claude.com/product/claude-code",
|
|
34
|
+
codex: "https://openai.com/codex/",
|
|
35
|
+
antigravity: "https://antigravity.google/product/antigravity-cli",
|
|
36
|
+
opencode: "https://opencode.ai/",
|
|
37
|
+
kimi: "https://www.kimi.com/code/",
|
|
38
|
+
grok: "https://docs.x.ai/build/overview",
|
|
39
|
+
copilot: "https://github.com/features/copilot/cli",
|
|
40
|
+
qwen: "https://qwenlm.github.io/qwen-code-docs/",
|
|
41
|
+
junie: "https://junie.jetbrains.com/",
|
|
42
|
+
kiro: "https://kiro.dev/",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
var NEW_MSG_BTN_DEFAULT = "\u2193 Latest";
|
|
46
|
+
var NEW_MSG_BTN_ACTIVITY = "\u2193 New activity";
|
|
47
|
+
var turnCounter = 0;
|
|
48
|
+
var prependAnchor = null;
|
|
49
|
+
var activityEl = null;
|
|
50
|
+
var isUserScrolledUp = false;
|
|
51
|
+
var stickyBottom = false;
|
|
52
|
+
var stickyBottomQuietTimer = null;
|
|
53
|
+
var stickyBottomCeilingTimer = null;
|
|
54
|
+
var stickyBottomQuietMs = 750;
|
|
55
|
+
var stickyBottomCeilingMs = 8000;
|
|
56
|
+
var stickyBottomResizeObs = null;
|
|
57
|
+
var stickyBottomInputBound = false;
|
|
58
|
+
|
|
59
|
+
export function getStickyBottom() { return stickyBottom; }
|
|
60
|
+
|
|
61
|
+
function pinToBottomNow() {
|
|
62
|
+
var messagesEl = getMessagesEl();
|
|
63
|
+
if (!messagesEl) return;
|
|
64
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ensureStickyInfrastructure() {
|
|
68
|
+
var messagesEl = getMessagesEl();
|
|
69
|
+
if (!messagesEl) return;
|
|
70
|
+
if (!stickyBottomResizeObs && typeof ResizeObserver !== "undefined") {
|
|
71
|
+
stickyBottomResizeObs = new ResizeObserver(function () {
|
|
72
|
+
if (!stickyBottom) return;
|
|
73
|
+
pinToBottomNow();
|
|
74
|
+
if (stickyBottomQuietTimer) clearTimeout(stickyBottomQuietTimer);
|
|
75
|
+
stickyBottomQuietTimer = setTimeout(disarmStickyBottom, stickyBottomQuietMs);
|
|
76
|
+
});
|
|
77
|
+
stickyBottomResizeObs.observe(messagesEl);
|
|
78
|
+
var kids = messagesEl.children;
|
|
79
|
+
for (var i = 0; i < kids.length; i++) stickyBottomResizeObs.observe(kids[i]);
|
|
80
|
+
}
|
|
81
|
+
if (!stickyBottomInputBound) {
|
|
82
|
+
stickyBottomInputBound = true;
|
|
83
|
+
var disarmOnUserScroll = function () { disarmStickyBottom(); };
|
|
84
|
+
messagesEl.addEventListener("wheel", disarmOnUserScroll, { passive: true });
|
|
85
|
+
messagesEl.addEventListener("touchmove", disarmOnUserScroll, { passive: true });
|
|
86
|
+
document.addEventListener("keydown", function (event) {
|
|
87
|
+
if (!stickyBottom) return;
|
|
88
|
+
if (event.key === "PageUp" || event.key === "Home" || event.key === "ArrowUp") disarmStickyBottom();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function armStickyBottom(durationMs) {
|
|
94
|
+
if (prependAnchor) return;
|
|
95
|
+
ensureStickyInfrastructure();
|
|
96
|
+
stickyBottom = true;
|
|
97
|
+
isUserScrolledUp = false;
|
|
98
|
+
var newMsgBtn = document.getElementById("new-msg-btn");
|
|
99
|
+
if (newMsgBtn) {
|
|
100
|
+
newMsgBtn.classList.add("hidden");
|
|
101
|
+
newMsgBtn.textContent = NEW_MSG_BTN_DEFAULT;
|
|
102
|
+
}
|
|
103
|
+
pinToBottomNow();
|
|
104
|
+
if (stickyBottomResizeObs) {
|
|
105
|
+
var messagesEl = getMessagesEl();
|
|
106
|
+
if (messagesEl) {
|
|
107
|
+
var kids = messagesEl.children;
|
|
108
|
+
for (var i = 0; i < kids.length; i++) {
|
|
109
|
+
try { stickyBottomResizeObs.observe(kids[i]); } catch (e) {}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
stickyBottomQuietMs = durationMs || 750;
|
|
114
|
+
if (stickyBottomQuietTimer) clearTimeout(stickyBottomQuietTimer);
|
|
115
|
+
stickyBottomQuietTimer = setTimeout(disarmStickyBottom, stickyBottomQuietMs);
|
|
116
|
+
if (stickyBottomCeilingTimer) clearTimeout(stickyBottomCeilingTimer);
|
|
117
|
+
stickyBottomCeilingTimer = setTimeout(disarmStickyBottom, stickyBottomCeilingMs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function disarmStickyBottom() {
|
|
121
|
+
stickyBottom = false;
|
|
122
|
+
if (stickyBottomQuietTimer) { clearTimeout(stickyBottomQuietTimer); stickyBottomQuietTimer = null; }
|
|
123
|
+
if (stickyBottomCeilingTimer) { clearTimeout(stickyBottomCeilingTimer); stickyBottomCeilingTimer = null; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function initRendering() {
|
|
127
|
+
store.subscribe(function (state, prev) {
|
|
128
|
+
if (state.currentVendor !== prev.currentVendor) {
|
|
129
|
+
var inputEl = document.getElementById("input");
|
|
130
|
+
if (inputEl) inputEl.placeholder = "Message " + (VENDOR_NAMES[state.currentVendor] || VENDOR_NAMES.claude) + "...";
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getTurnCounter() { return turnCounter; }
|
|
136
|
+
export function setTurnCounter(value) { turnCounter = value; }
|
|
137
|
+
export function getPrependAnchor() { return prependAnchor; }
|
|
138
|
+
export function setPrependAnchor(value) { prependAnchor = value; }
|
|
139
|
+
export function getActivityEl() { return activityEl; }
|
|
140
|
+
export function setActivityEl(value) { activityEl = value; }
|
|
141
|
+
export function getIsUserScrolledUp() { return isUserScrolledUp; }
|
|
142
|
+
export function setIsUserScrolledUp(value) { isUserScrolledUp = value; }
|
|
143
|
+
|
|
144
|
+
export function addToMessages(element) {
|
|
145
|
+
var messagesEl = getMessagesEl();
|
|
146
|
+
if (prependAnchor) messagesEl.insertBefore(element, prependAnchor);
|
|
147
|
+
else messagesEl.appendChild(element);
|
|
148
|
+
var scheduled = document.getElementById("scheduled-msg-bubble");
|
|
149
|
+
if (scheduled && element !== scheduled && scheduled.parentNode === messagesEl) messagesEl.appendChild(scheduled);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function scrollToBottom() {
|
|
153
|
+
if (prependAnchor) return;
|
|
154
|
+
var messagesEl = getMessagesEl();
|
|
155
|
+
if (!messagesEl) return;
|
|
156
|
+
var distFromBottom = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
|
|
157
|
+
if (distFromBottom > 150 || isUserScrolledUp) {
|
|
158
|
+
if (distFromBottom > 150) isUserScrolledUp = true;
|
|
159
|
+
var newMsgBtn = document.getElementById("new-msg-btn");
|
|
160
|
+
if (newMsgBtn) {
|
|
161
|
+
newMsgBtn.textContent = NEW_MSG_BTN_ACTIVITY;
|
|
162
|
+
newMsgBtn.classList.remove("hidden");
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
requestAnimationFrame(function () {
|
|
167
|
+
var dist = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
|
|
168
|
+
if (dist > 150) {
|
|
169
|
+
isUserScrolledUp = true;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function forceScrollToBottom() {
|
|
177
|
+
if (prependAnchor) return;
|
|
178
|
+
armStickyBottom(750);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function getMsgTime() {
|
|
182
|
+
var timestamp = store.get('currentMsgTs');
|
|
183
|
+
var date = timestamp ? new Date(timestamp) : new Date();
|
|
184
|
+
var time = String(date.getHours()).padStart(2, "0") + ":" + String(date.getMinutes()).padStart(2, "0");
|
|
185
|
+
var now = new Date();
|
|
186
|
+
if (date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()) return time;
|
|
187
|
+
return (date.getMonth() + 1) + "/" + date.getDate() + " " + time;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function shouldGroupMessage(senderClass) {
|
|
191
|
+
var state = store.snap();
|
|
192
|
+
if (state.replayingHistory && !state.currentMsgTs) return false;
|
|
193
|
+
var prev = getMessagesEl().lastElementChild;
|
|
194
|
+
if (!prev || !prev.classList.contains(senderClass)) return false;
|
|
195
|
+
var prevTime = prev.querySelector(".dm-bubble-time");
|
|
196
|
+
if (!prevTime) return false;
|
|
197
|
+
return prevTime.textContent === getMsgTime();
|
|
198
|
+
}
|