clay-server 4.0.0-beta.2 → 4.0.0-beta.21
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/background-task-timing.js +105 -0
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -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/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +14 -1
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-vendor-login.js +20 -3
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +178 -6
- package/lib/public/app.js +58 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +163 -28
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/index.html +45 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +109 -4
- package/lib/public/modules/app-home-hub.js +19 -7
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +84 -7
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +46 -4
- package/lib/public/modules/app-projects.js +13 -8
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +21 -1
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +132 -46
- package/lib/sdk-message-processor.js +60 -14
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +124 -18
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +61 -11
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +2 -2
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +81 -4
- package/lib/yoke/adapters/codex.js +63 -15
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +3 -3
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Extracted from app.js (PR-34)
|
|
3
3
|
|
|
4
4
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
5
|
-
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
6
5
|
import { getWs } from './ws-ref.js';
|
|
7
6
|
import { updateBrowserTabList } from './context-sources.js';
|
|
8
7
|
import { setExtensionConnected } from './mcp-ui.js';
|
|
8
|
+
import { initPasteModal } from './paste-modal.js';
|
|
9
9
|
|
|
10
10
|
// --- Module-owned state ---
|
|
11
11
|
var confirmCallback = null;
|
|
@@ -44,6 +44,7 @@ export function flushPendingExtMessages() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function initMisc() {
|
|
47
|
+
initPasteModal();
|
|
47
48
|
// --- Confirm modal listeners ---
|
|
48
49
|
var confirmModal = document.getElementById("confirm-modal");
|
|
49
50
|
var confirmOk = document.getElementById("confirm-ok");
|
|
@@ -258,19 +259,6 @@ export function closeImageModal() {
|
|
|
258
259
|
if (modal) modal.classList.add("hidden");
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
export function showPasteModal(text) {
|
|
262
|
-
var modal = document.getElementById("paste-modal");
|
|
263
|
-
var body = document.getElementById("paste-modal-body");
|
|
264
|
-
if (!modal || !body) return;
|
|
265
|
-
body.textContent = text;
|
|
266
|
-
modal.classList.remove("hidden");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export function closePasteModal() {
|
|
270
|
-
var modal = document.getElementById("paste-modal");
|
|
271
|
-
if (modal) modal.classList.add("hidden");
|
|
272
|
-
}
|
|
273
|
-
|
|
274
262
|
export function showConfirm(text, onConfirm, okLabel, destructive) {
|
|
275
263
|
var confirmText = document.getElementById("confirm-text");
|
|
276
264
|
var confirmOk = document.getElementById("confirm-ok");
|
|
@@ -14,6 +14,7 @@ import { openTuiModal } from './tui-attention.js';
|
|
|
14
14
|
import { requestVendorLogin } from './vendor-login.js';
|
|
15
15
|
import { startUrgentBlink, stopUrgentBlink } from './app-favicon.js';
|
|
16
16
|
import { playDoneSound, isNotifSoundEnabled } from './notifications.js';
|
|
17
|
+
import { attachSnoozeControl, setSnoozeBannerRemover } from './update-snooze.js';
|
|
17
18
|
var notifications = [];
|
|
18
19
|
var unreadCount = 0;
|
|
19
20
|
var bannerContainer = null;
|
|
@@ -56,6 +57,10 @@ export function initAppNotifications() {
|
|
|
56
57
|
bellBtn = document.getElementById("notif-center-btn");
|
|
57
58
|
badgeEl = document.getElementById("notif-center-badge");
|
|
58
59
|
|
|
60
|
+
// Banner teardown lives here (exit animation, clear-all sync), so the
|
|
61
|
+
// snooze module borrows it rather than importing this module back.
|
|
62
|
+
setSnoozeBannerRemover(removeBanner);
|
|
63
|
+
|
|
59
64
|
// Split panes are secondary clients inside the parent shell. They keep
|
|
60
65
|
// notification state in sync, but the parent owns the single visible
|
|
61
66
|
// banner surface for the whole split view.
|
|
@@ -708,13 +713,11 @@ export function showUpdateBanner(msg) {
|
|
|
708
713
|
banner.setAttribute("data-notif-id", "_update");
|
|
709
714
|
banner.setAttribute("data-update-version", msg.version);
|
|
710
715
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
'</div>';
|
|
717
|
-
}
|
|
716
|
+
// Headless installs update from the shell, so they get no install button —
|
|
717
|
+
// but they still get the action row, because Snooze applies either way.
|
|
718
|
+
var actionsHtml = '<div class="notif-banner-actions">' +
|
|
719
|
+
(isHeadless ? '' : '<button class="notif-banner-update-now">Update now</button>') +
|
|
720
|
+
'</div>';
|
|
718
721
|
|
|
719
722
|
banner.innerHTML =
|
|
720
723
|
'<div class="notif-banner-icon"><img src="/clay-studio-symbol.png" width="32" height="32" alt="Clay Studio"></div>' +
|
|
@@ -729,6 +732,7 @@ export function showUpdateBanner(msg) {
|
|
|
729
732
|
'<button class="notif-banner-close">' + iconHtml("x") + '</button>';
|
|
730
733
|
|
|
731
734
|
bannerContainer.appendChild(banner);
|
|
735
|
+
attachSnoozeControl(banner.querySelector(".notif-banner-actions"));
|
|
732
736
|
refreshIcons();
|
|
733
737
|
|
|
734
738
|
requestAnimationFrame(function () {
|
|
@@ -10,7 +10,7 @@ import { setupModelPicker, renderModelPicker, requestVendorModels, prepareModelP
|
|
|
10
10
|
|
|
11
11
|
// --- Module-owned state (not in store) ---
|
|
12
12
|
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
13
|
-
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
13
|
+
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
|
|
14
14
|
var ctxPopoverEl = null;
|
|
15
15
|
var ctxHoverTimer = null;
|
|
16
16
|
var statusRefreshTimer = null;
|
|
@@ -72,6 +72,10 @@ var contextCacheReadEl = null;
|
|
|
72
72
|
var contextCacheWriteEl = null;
|
|
73
73
|
var contextModelEl = null;
|
|
74
74
|
var contextCostEl = null;
|
|
75
|
+
var contextThinkingEl = null;
|
|
76
|
+
var contextThinkingRow = null;
|
|
77
|
+
var contextCostBasisEl = null;
|
|
78
|
+
var contextCostBasisRow = null;
|
|
75
79
|
var contextTurnsEl = null;
|
|
76
80
|
var contextMini = null;
|
|
77
81
|
var contextMiniFill = null;
|
|
@@ -87,7 +91,7 @@ var MODE_FULL_AUTO = { value: "bypassPermissions", label: "Full auto" };
|
|
|
87
91
|
var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
88
92
|
var EFFORT_LEVELS_BY_VENDOR = {
|
|
89
93
|
claude: ["low", "medium", "high", "xhigh", "max"],
|
|
90
|
-
codex: ["
|
|
94
|
+
codex: ["low", "medium", "high", "xhigh", "max"],
|
|
91
95
|
antigravity: [],
|
|
92
96
|
opencode: [],
|
|
93
97
|
kiro: ["low", "medium", "high", "xhigh", "max"],
|
|
@@ -111,7 +115,8 @@ var CODEX_WEBSEARCH_OPTIONS = [
|
|
|
111
115
|
var KNOWN_CONTEXT_WINDOWS = {
|
|
112
116
|
"opus-4-6": 1000000,
|
|
113
117
|
"claude-sonnet-4": 1000000,
|
|
114
|
-
"gpt-
|
|
118
|
+
"gpt-6-astra": 1050000,
|
|
119
|
+
"gpt-5.6": 1050000,
|
|
115
120
|
"gpt-5.5": 1048576,
|
|
116
121
|
"gpt-5.4": 1048576,
|
|
117
122
|
"gpt-5.3": 1048576,
|
|
@@ -449,6 +454,10 @@ export function initPanels() {
|
|
|
449
454
|
contextCacheWriteEl = $("context-cache-write");
|
|
450
455
|
contextModelEl = $("context-model");
|
|
451
456
|
contextCostEl = $("context-cost");
|
|
457
|
+
contextThinkingEl = $("context-thinking");
|
|
458
|
+
contextThinkingRow = $("context-thinking-row");
|
|
459
|
+
contextCostBasisEl = $("context-cost-basis");
|
|
460
|
+
contextCostBasisRow = $("context-cost-basis-row");
|
|
452
461
|
contextTurnsEl = $("context-turns");
|
|
453
462
|
contextMini = $("context-mini");
|
|
454
463
|
contextMiniFill = $("context-mini-fill");
|
|
@@ -776,6 +785,16 @@ export function updateContextPanel() {
|
|
|
776
785
|
contextCacheWriteEl.textContent = formatTokens(contextData.cacheWrite);
|
|
777
786
|
contextModelEl.textContent = contextData.model;
|
|
778
787
|
contextCostEl.textContent = "$" + contextData.cost.toFixed(4);
|
|
788
|
+
// Optional SDK fields: shown only once the CLI has actually reported them.
|
|
789
|
+
if (contextThinkingRow && contextThinkingEl) {
|
|
790
|
+
var hasThinking = typeof contextData.thinking === "number";
|
|
791
|
+
contextThinkingRow.classList.toggle("hidden", !hasThinking);
|
|
792
|
+
if (hasThinking) contextThinkingEl.textContent = formatTokens(contextData.thinking);
|
|
793
|
+
}
|
|
794
|
+
if (contextCostBasisRow && contextCostBasisEl) {
|
|
795
|
+
contextCostBasisRow.classList.toggle("hidden", !contextData.costBasis);
|
|
796
|
+
if (contextData.costBasis) contextCostBasisEl.textContent = contextData.costBasis;
|
|
797
|
+
}
|
|
779
798
|
contextTurnsEl.textContent = String(contextData.turns);
|
|
780
799
|
}
|
|
781
800
|
|
|
@@ -811,6 +830,29 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
811
830
|
contextData.model = displayModel;
|
|
812
831
|
contextData.contextWindow = resolveContextWindow(displayModel, mu.contextWindow);
|
|
813
832
|
if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
|
|
833
|
+
|
|
834
|
+
// thinkingTokens and costBasis are per-model and optional: absent on
|
|
835
|
+
// older CLI builds and, for a resumed session, on the turns that ran
|
|
836
|
+
// before the field existed. Leave them null in that case so the rows
|
|
837
|
+
// stay hidden rather than claiming a confident zero.
|
|
838
|
+
var thinkingTotal = null;
|
|
839
|
+
var basis = null;
|
|
840
|
+
var basisMixed = false;
|
|
841
|
+
for (var mi = 0; mi < models.length; mi++) {
|
|
842
|
+
var entry = modelUsage[models[mi]];
|
|
843
|
+
if (!entry) continue;
|
|
844
|
+
if (typeof entry.thinkingTokens === "number") {
|
|
845
|
+
thinkingTotal = (thinkingTotal || 0) + entry.thinkingTokens;
|
|
846
|
+
}
|
|
847
|
+
if (entry.costBasis) {
|
|
848
|
+
if (basis === null) basis = entry.costBasis;
|
|
849
|
+
else if (basis !== entry.costBasis) basisMixed = true;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
contextData.thinking = thinkingTotal;
|
|
853
|
+
// Several models in one query can be priced differently; say so rather
|
|
854
|
+
// than reporting whichever happened to come first.
|
|
855
|
+
contextData.costBasis = basisMixed ? "mixed" : basis;
|
|
814
856
|
}
|
|
815
857
|
}
|
|
816
858
|
if (!store.get('replayingHistory')) updateContextPanel();
|
|
@@ -832,7 +874,7 @@ export function applyContextView(view) {
|
|
|
832
874
|
}
|
|
833
875
|
|
|
834
876
|
export function resetContextData() {
|
|
835
|
-
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
877
|
+
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
|
|
836
878
|
store.set({ richContextUsage: null });
|
|
837
879
|
// hideCtxPopover + updateContextPanel handled by store subscriber
|
|
838
880
|
}
|
|
@@ -19,7 +19,7 @@ import { exitDmMode } from './app-dm.js';
|
|
|
19
19
|
import { isHomeHubVisible, hideHomeHub, showHomeHub } from './app-home-hub.js';
|
|
20
20
|
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
21
21
|
import { resetFileBrowser } from './filebrowser.js';
|
|
22
|
-
import {
|
|
22
|
+
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
23
23
|
import { hideMemory } from './mate-memory.js';
|
|
24
24
|
import { isSchedulerOpen, closeScheduler, resetScheduler } from './scheduler.js';
|
|
25
25
|
import { connect, cancelReconnect, setStatus } from './app-connection.js';
|
|
@@ -34,6 +34,8 @@ import { resetRateLimitState } from './app-rate-limit.js';
|
|
|
34
34
|
import { closeSessionInfoPopover } from './app-header.js';
|
|
35
35
|
import { resetDebateState } from './debate.js';
|
|
36
36
|
import { removeDebateBottomBar } from './app-debate-ui.js';
|
|
37
|
+
import { closeProjectSettings } from './project-settings.js';
|
|
38
|
+
import { chooseProjectAfterRemoval } from './project-removal-target.js';
|
|
37
39
|
|
|
38
40
|
// --- Module-owned state ---
|
|
39
41
|
var cachedProjects = [];
|
|
@@ -415,13 +417,15 @@ export function switchProject(slug) {
|
|
|
415
417
|
if (!slug) return;
|
|
416
418
|
document.cookie = "clay_last_project=" + encodeURIComponent(slug) + "; Path=/; SameSite=Strict; Max-Age=31536000";
|
|
417
419
|
var st = store.snap();
|
|
420
|
+
var targetWsPath = "/p/" + slug + "/ws";
|
|
421
|
+
var alreadyInProject = slug === st.currentSlug && st.wsPath === targetWsPath;
|
|
418
422
|
var wasDm = st.dmMode;
|
|
419
423
|
var wasMate = st.dmMode && st.dmTargetUser && st.dmTargetUser.isMate;
|
|
420
424
|
if (st.dmMode) exitDmMode(wasMate);
|
|
421
425
|
closeWhatsNewArticle();
|
|
422
426
|
if (isHomeHubVisible()) {
|
|
423
427
|
hideHomeHub();
|
|
424
|
-
if (
|
|
428
|
+
if (alreadyInProject) {
|
|
425
429
|
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
426
430
|
history.replaceState(null, "", "/p/" + slug + "/");
|
|
427
431
|
} else {
|
|
@@ -430,7 +434,7 @@ export function switchProject(slug) {
|
|
|
430
434
|
return;
|
|
431
435
|
}
|
|
432
436
|
}
|
|
433
|
-
if (
|
|
437
|
+
if (alreadyInProject) {
|
|
434
438
|
var ws = getWs();
|
|
435
439
|
if (wasDm && ws && ws.readyState === 1) {
|
|
436
440
|
ws.send(JSON.stringify({ type: "switch_session", id: store.get('activeSessionId') }));
|
|
@@ -438,13 +442,13 @@ export function switchProject(slug) {
|
|
|
438
442
|
return;
|
|
439
443
|
}
|
|
440
444
|
resetFileBrowser();
|
|
441
|
-
|
|
445
|
+
closeNotesBrowser();
|
|
442
446
|
hideMemory();
|
|
443
447
|
if (isSchedulerOpen()) closeScheduler();
|
|
444
448
|
resetScheduler(slug);
|
|
445
449
|
store.set({ currentSlug: slug });
|
|
446
450
|
store.set({ basePath: "/p/" + slug + "/" });
|
|
447
|
-
store.set({ wsPath:
|
|
451
|
+
store.set({ wsPath: targetWsPath });
|
|
448
452
|
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
449
453
|
history.replaceState(null, "", "/p/" + slug + "/");
|
|
450
454
|
} else {
|
|
@@ -572,9 +576,10 @@ export function handleRemoveProjectResult(msg) {
|
|
|
572
576
|
var currentSlug = store.get('currentSlug');
|
|
573
577
|
if (msg.slug === currentSlug) {
|
|
574
578
|
var isWorktree = msg.slug.indexOf("--") !== -1;
|
|
575
|
-
var
|
|
579
|
+
var targetSlug = chooseProjectAfterRemoval(cachedProjects, msg.slug);
|
|
576
580
|
|
|
577
581
|
showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
|
|
582
|
+
closeProjectSettings();
|
|
578
583
|
|
|
579
584
|
// Suppress disconnect overlay and reconnect by detaching the WS
|
|
580
585
|
var ws = getWs();
|
|
@@ -602,8 +607,8 @@ export function handleRemoveProjectResult(msg) {
|
|
|
602
607
|
renderProjectList();
|
|
603
608
|
resetClientState();
|
|
604
609
|
|
|
605
|
-
if (
|
|
606
|
-
switchProject(
|
|
610
|
+
if (targetSlug) {
|
|
611
|
+
switchProject(targetSlug);
|
|
607
612
|
} else {
|
|
608
613
|
showHomeHub();
|
|
609
614
|
}
|
|
@@ -111,6 +111,7 @@ export function finalizeAssistantBlock() {
|
|
|
111
111
|
|
|
112
112
|
export function showClaudePreThinking() {
|
|
113
113
|
if (getChatLayout() !== "channel") return;
|
|
114
|
+
removeMatePreThinking();
|
|
114
115
|
var vendor = store.get('currentVendor') || "claude";
|
|
115
116
|
var vendorAvatar = VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude;
|
|
116
117
|
var vendorName = VENDOR_NAMES[vendor] || VENDOR_NAMES.claude;
|
|
@@ -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,44 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { requestTools } from './home-tools.js';
|
|
3
|
+
import { closeHomeDock } from './home-dock.js';
|
|
4
|
+
import { showToast } from './utils.js';
|
|
5
|
+
|
|
6
|
+
function applyCapsulesEnabled(enabled, refreshTools) {
|
|
7
|
+
var want = enabled === true;
|
|
8
|
+
store.set({ capsulesEnabled: want, capsulesPreferenceLoaded: true });
|
|
9
|
+
if (document.body) document.body.classList.toggle('capsules-disabled', !want);
|
|
10
|
+
if (!want && store.get('dockOpen') === true) closeHomeDock();
|
|
11
|
+
if (refreshTools !== false) requestTools();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function initCapsulePreference() {
|
|
15
|
+
var toggle = document.getElementById('us-capsules-enabled');
|
|
16
|
+
if (!toggle) return;
|
|
17
|
+
toggle.addEventListener('change', function () {
|
|
18
|
+
var previous = store.get('capsulesEnabled') === true;
|
|
19
|
+
var want = this.checked === true;
|
|
20
|
+
applyCapsulesEnabled(want, false);
|
|
21
|
+
fetch('/api/user/capsules-enabled', {
|
|
22
|
+
method: 'PUT',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ enabled: want }),
|
|
25
|
+
}).then(function (response) {
|
|
26
|
+
if (!response.ok) throw new Error('Could not save the Capsules preference.');
|
|
27
|
+
return response.json();
|
|
28
|
+
}).then(function (data) {
|
|
29
|
+
applyCapsulesEnabled(data.capsulesEnabled === true, true);
|
|
30
|
+
showToast(data.capsulesEnabled ? 'Experimental Capsules enabled' : 'Experimental Capsules disabled');
|
|
31
|
+
}).catch(function () {
|
|
32
|
+
toggle.checked = previous;
|
|
33
|
+
applyCapsulesEnabled(previous, true);
|
|
34
|
+
showToast('Could not save the Capsules preference.', 'error');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function syncCapsulePreference(enabled) {
|
|
40
|
+
var want = enabled === true;
|
|
41
|
+
var toggle = document.getElementById('us-capsules-enabled');
|
|
42
|
+
if (toggle) toggle.checked = want;
|
|
43
|
+
applyCapsulesEnabled(want, true);
|
|
44
|
+
}
|
|
@@ -105,7 +105,7 @@ export function addAssistantCopyHandler(row, rawText) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
row.addEventListener("click", function (event) {
|
|
108
|
-
if (event.target.closest("a, pre, code")) return;
|
|
108
|
+
if (event.target.closest("a, button, pre, code")) return;
|
|
109
109
|
var selection = window.getSelection();
|
|
110
110
|
if (selection && selection.toString().length > 0) return;
|
|
111
111
|
if (!primed) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function parseClayLogReferences(text) {
|
|
2
|
+
var pattern = /\[clayos\/(log:[A-Za-z0-9_-]{24})(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]|(^|[^A-Za-z0-9_/:])(log:[A-Za-z0-9_-]{24})(?![A-Za-z0-9_-])/g;
|
|
3
|
+
var results = [];
|
|
4
|
+
var match;
|
|
5
|
+
while ((match = pattern.exec(text || ""))) {
|
|
6
|
+
results.push({
|
|
7
|
+
start: match.index,
|
|
8
|
+
end: pattern.lastIndex,
|
|
9
|
+
prefix: match[3] || "",
|
|
10
|
+
ref: match[1] || match[4],
|
|
11
|
+
label: match[2] ? match[2].trim() : "",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isExactClayLogReference(text) {
|
|
18
|
+
return /^log:[A-Za-z0-9_-]{24}$/.test((text || "").trim());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createLogLink(ref, labelText) {
|
|
22
|
+
var button = document.createElement("button");
|
|
23
|
+
button.type = "button";
|
|
24
|
+
button.className = "clayos-log-link";
|
|
25
|
+
button.contentEditable = "false";
|
|
26
|
+
button.dataset.logRef = ref;
|
|
27
|
+
button.setAttribute("aria-label", "Open Project Log" + (labelText ? " " + labelText : ""));
|
|
28
|
+
var label = document.createElement("span");
|
|
29
|
+
label.textContent = "Log";
|
|
30
|
+
button.appendChild(label);
|
|
31
|
+
if (labelText) {
|
|
32
|
+
var meta = document.createElement("small");
|
|
33
|
+
meta.textContent = labelText;
|
|
34
|
+
button.appendChild(meta);
|
|
35
|
+
}
|
|
36
|
+
return button;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function enhanceClayLogLinks(root) {
|
|
40
|
+
var codeNodes = root.querySelectorAll("code");
|
|
41
|
+
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
42
|
+
var code = codeNodes[codeIndex];
|
|
43
|
+
var codeText = (code.textContent || "").trim();
|
|
44
|
+
if (code.closest("pre") || !isExactClayLogReference(codeText)) continue;
|
|
45
|
+
code.parentNode.replaceChild(createLogLink(codeText, ""), code);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
49
|
+
var nodes = [];
|
|
50
|
+
var node;
|
|
51
|
+
while ((node = walker.nextNode())) {
|
|
52
|
+
if (!node.parentElement || node.parentElement.closest("code, pre, a, button")) continue;
|
|
53
|
+
if (parseClayLogReferences(node.nodeValue).length) nodes.push(node);
|
|
54
|
+
}
|
|
55
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
56
|
+
var text = nodes[i].nodeValue || "";
|
|
57
|
+
var matches = parseClayLogReferences(text);
|
|
58
|
+
var fragment = document.createDocumentFragment();
|
|
59
|
+
var offset = 0;
|
|
60
|
+
for (var j = 0; j < matches.length; j++) {
|
|
61
|
+
var match = matches[j];
|
|
62
|
+
fragment.appendChild(document.createTextNode(text.slice(offset, match.start)));
|
|
63
|
+
if (match.prefix) fragment.appendChild(document.createTextNode(match.prefix));
|
|
64
|
+
fragment.appendChild(createLogLink(match.ref, match.label));
|
|
65
|
+
offset = match.end;
|
|
66
|
+
}
|
|
67
|
+
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
68
|
+
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
69
|
+
}
|
|
70
|
+
}
|