clay-server 4.1.0-beta.9 → 4.2.0-beta.1
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/context-usage.js +47 -0
- package/lib/daemon-project-access.js +14 -0
- package/lib/daemon.js +7 -0
- package/lib/issues-mcp-server.js +1 -1
- package/lib/issues-service.js +3 -1
- package/lib/loop-registry-occurrence.js +1 -0
- package/lib/notes.js +20 -3
- package/lib/os-users.js +6 -0
- package/lib/project-connection.js +14 -3
- package/lib/project-file-path.js +13 -0
- package/lib/project-file-watch.js +19 -8
- package/lib/project-filesystem.js +10 -16
- package/lib/project-http.js +53 -48
- package/lib/project-issue-launch.js +3 -1
- package/lib/project-logs-mcp-server.js +8 -8
- package/lib/project-logs-schema.js +1 -1
- package/lib/project-logs-service.js +46 -13
- package/lib/project-logs-store.js +17 -15
- package/lib/project-logs.js +70 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-notifications.js +1 -1
- package/lib/project-scheduled-result-store.js +131 -0
- package/lib/project-scheduled-task-execution.js +35 -2
- package/lib/project-session-notes.js +11 -4
- package/lib/project-sessions.js +39 -6
- package/lib/project-user-message.js +47 -0
- package/lib/project.js +57 -3
- package/lib/public/app.js +27 -9
- package/lib/public/css/avatar-imprints.css +1 -1
- package/lib/public/css/command-palette.css +57 -19
- package/lib/public/css/git-panel.css +13 -12
- package/lib/public/css/git-placard.css +4 -4
- package/lib/public/css/issues.css +79 -32
- package/lib/public/css/mobile-nav.css +7 -0
- package/lib/public/css/notifications-center.css +3 -3
- package/lib/public/css/project-logs.css +15 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +29 -0
- package/lib/public/index.html +10 -0
- package/lib/public/modules/app-connection.js +9 -1
- package/lib/public/modules/app-favicon.js +10 -0
- package/lib/public/modules/app-messages.js +50 -12
- package/lib/public/modules/app-notifications.js +2 -0
- package/lib/public/modules/app-panels.js +77 -23
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rate-limit.js +148 -61
- package/lib/public/modules/avatar-imprint.js +25 -70
- package/lib/public/modules/avatar.js +12 -4
- package/lib/public/modules/chat-render-runtime.js +20 -0
- package/lib/public/modules/clay-issue-links.js +24 -2
- package/lib/public/modules/clay-log-links.js +12 -1
- package/lib/public/modules/context-view-preference.js +143 -0
- package/lib/public/modules/default-vendor.js +116 -0
- package/lib/public/modules/filebrowser-tabs.js +1 -0
- package/lib/public/modules/filebrowser.js +45 -18
- package/lib/public/modules/issues-render.js +36 -10
- package/lib/public/modules/issues.js +95 -13
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/pane-bridge.js +14 -8
- package/lib/public/modules/pane-issue-bridge.js +27 -0
- package/lib/public/modules/permission-hints.js +13 -0
- package/lib/public/modules/profile.js +9 -9
- package/lib/public/modules/project-activation.js +49 -0
- package/lib/public/modules/project-logs-render.js +13 -3
- package/lib/public/modules/project-logs.js +45 -15
- package/lib/public/modules/project-session-visibility-settings.js +138 -0
- package/lib/public/modules/project-settings.js +5 -0
- package/lib/public/modules/right-workbench.js +44 -0
- package/lib/public/modules/scheduled-result-client.js +33 -0
- package/lib/public/modules/scheduled-result-display.js +50 -0
- package/lib/public/modules/scheduled-result-notification.js +31 -0
- package/lib/public/modules/scheduled-task-presentation.js +15 -0
- package/lib/public/modules/scheduled-tasks.js +9 -21
- package/lib/public/modules/sidebar-mobile.js +51 -3
- package/lib/public/modules/sidebar-projects.js +14 -3
- package/lib/public/modules/sidebar-sessions.js +65 -5
- package/lib/public/modules/split-session-boundary.js +28 -0
- package/lib/public/modules/split-view.js +42 -7
- package/lib/public/modules/sticky-notes-browser.js +130 -18
- package/lib/public/modules/sticky-notes.js +2 -3
- package/lib/public/modules/terminal.js +4 -4
- package/lib/public/modules/thinking-lifecycle.js +4 -1
- package/lib/public/modules/tools.js +5 -1
- package/lib/scheduled-run-state.js +10 -2
- package/lib/scheduler.js +1 -0
- package/lib/sdk-bridge.js +97 -61
- package/lib/sdk-message-processor.js +44 -13
- package/lib/sdk-skill-discovery.js +6 -28
- package/lib/sdk-turn-queue.js +14 -0
- package/lib/server-context-view.js +53 -0
- package/lib/server-default-vendor.js +60 -0
- package/lib/server.js +31 -7
- package/lib/session-notes-mcp-server.js +3 -5
- package/lib/session-pair-factory.js +2 -2
- package/lib/session-title-generator.js +89 -0
- package/lib/session-title-policy.js +8 -2
- package/lib/session-visibility.js +62 -1
- package/lib/sessions.js +28 -1
- package/lib/user-presence.js +6 -0
- package/lib/users-context-view-preferences.js +65 -0
- package/lib/users-default-vendor-preferences.js +80 -0
- package/lib/users.js +12 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/acp-agent-profiles.js +15 -2
- package/lib/yoke/adapters/acp.js +3 -2
- package/lib/yoke/adapters/antigravity.js +2 -2
- package/lib/yoke/adapters/claude-worker.js +11 -12
- package/lib/yoke/adapters/claude.js +75 -22
- package/lib/yoke/adapters/codex.js +216 -29
- package/lib/yoke/adapters/kiro.js +6 -5
- package/lib/yoke/claude-context-usage.js +57 -0
- package/lib/yoke/claude-message-correlation.js +31 -0
- package/lib/yoke/claude-model-switch.js +15 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/mcp-bridge-server.js +18 -5
- package/lib/yoke/session-skill-catalog.js +45 -0
- package/lib/yoke/skill-discovery.js +60 -20
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +5 -7
|
@@ -15,6 +15,7 @@ import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
|
15
15
|
import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperimentalVendor } from './app-rendering.js';
|
|
16
16
|
import { openGroup, separateGroup } from './split-view.js';
|
|
17
17
|
import { groupedSessionIds } from './split-group-helpers.js';
|
|
18
|
+
import { defaultVendorUnavailable, resolvePreferredVendor, saveDefaultVendor } from './default-vendor.js';
|
|
18
19
|
import { openPairDialog } from './split-pair-ui.js';
|
|
19
20
|
import {
|
|
20
21
|
hierarchyItemMatches,
|
|
@@ -41,6 +42,7 @@ var countdownContainer = null;
|
|
|
41
42
|
|
|
42
43
|
// --- Session context menu ---
|
|
43
44
|
var sessionCtxMenu = null;
|
|
45
|
+
var sessionCtxMenuAnchor = null;
|
|
44
46
|
var sessionCtxSessionId = null;
|
|
45
47
|
var draggedSessionId = null;
|
|
46
48
|
var draggedSessionBookmarked = false;
|
|
@@ -48,6 +50,18 @@ var headerSearchOpen = false;
|
|
|
48
50
|
var armedDeleteSessionId = null;
|
|
49
51
|
var armedDeleteTimer = null;
|
|
50
52
|
|
|
53
|
+
store.subscribe(function (state, previous) {
|
|
54
|
+
if (state.defaultVendorState !== previous.defaultVendorState || state.installedVendors !== previous.installedVendors) {
|
|
55
|
+
var reopenNewMenu = !!(sessionCtxMenu && sessionCtxMenuAnchor);
|
|
56
|
+
if (reopenNewMenu) closeSessionCtxMenu();
|
|
57
|
+
if (getSessionListEl()) renderSessionList();
|
|
58
|
+
if (reopenNewMenu) {
|
|
59
|
+
var freshAnchor = getSessionListEl().querySelector(".session-top-action.split-chevron");
|
|
60
|
+
if (freshAnchor) showNewSessionMenu(freshAnchor, resolveDefaultVendor());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
51
65
|
function sendSessionBookmark(sessionId, bookmarked) {
|
|
52
66
|
if (getWs() && store.get('connected')) {
|
|
53
67
|
getWs().send(JSON.stringify({ type: "set_session_bookmark", sessionId: sessionId, bookmarked: !!bookmarked }));
|
|
@@ -317,10 +331,7 @@ function appendSessionCloseButton(el, session) {
|
|
|
317
331
|
// installed-vendor priority (Claude, then Codex, then the remaining vendors).
|
|
318
332
|
export function resolveDefaultVendor() {
|
|
319
333
|
var installed = store.get('installedVendors') || [];
|
|
320
|
-
|
|
321
|
-
if (installed.indexOf(VENDOR_ORDER[i]) !== -1) return VENDOR_ORDER[i];
|
|
322
|
-
}
|
|
323
|
-
return "claude";
|
|
334
|
+
return resolvePreferredVendor(installed) || "claude";
|
|
324
335
|
}
|
|
325
336
|
|
|
326
337
|
// Single entry point for session creation from the sidebar. Optimistically
|
|
@@ -361,6 +372,7 @@ function renderSessionTopActions() {
|
|
|
361
372
|
mainBtn.className = "session-top-action split-main";
|
|
362
373
|
mainBtn.type = "button";
|
|
363
374
|
mainBtn.title = "New " + (VENDOR_NAMES[defaultVendor] || defaultVendor) + " session";
|
|
375
|
+
if (defaultVendorUnavailable(store.get('installedVendors') || [])) mainBtn.title += " (saved default is unavailable; using installed priority)";
|
|
364
376
|
mainBtn.innerHTML = '<img src="' + (VENDOR_AVATARS[defaultVendor] || VENDOR_AVATARS.claude) +
|
|
365
377
|
'" class="session-top-action-icon" alt=""><span>New session</span>';
|
|
366
378
|
mainBtn.addEventListener("click", function () {
|
|
@@ -394,6 +406,15 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
|
|
|
394
406
|
var menu = document.createElement("div");
|
|
395
407
|
menu.className = "session-ctx-menu session-new-menu";
|
|
396
408
|
var installed = store.get('installedVendors') || [];
|
|
409
|
+
var defaultVendorState = store.get('defaultVendorState') || {};
|
|
410
|
+
sessionCtxMenuAnchor = anchorBtn;
|
|
411
|
+
|
|
412
|
+
if (defaultVendorUnavailable(installed) && defaultVendorState.preference) {
|
|
413
|
+
var fallbackNote = document.createElement("div");
|
|
414
|
+
fallbackNote.className = "session-new-preference-status session-new-fallback-note";
|
|
415
|
+
fallbackNote.textContent = "Saved default " + (VENDOR_NAMES[defaultVendorState.preference] || defaultVendorState.preference) + " is unavailable. Using " + (VENDOR_NAMES[defaultVendor] || defaultVendor) + ".";
|
|
416
|
+
menu.appendChild(fallbackNote);
|
|
417
|
+
}
|
|
397
418
|
|
|
398
419
|
// Every known vendor is listed. Uninstalled ones stay visible but disabled,
|
|
399
420
|
// and clicking one opens the vendor's homepage so the user can install it.
|
|
@@ -401,6 +422,8 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
|
|
|
401
422
|
(function (vendor) {
|
|
402
423
|
var isInstalled = installed.indexOf(vendor) !== -1;
|
|
403
424
|
var name = VENDOR_NAMES[vendor] || vendor;
|
|
425
|
+
var row = document.createElement("div");
|
|
426
|
+
row.className = "session-new-vendor-row";
|
|
404
427
|
var item = document.createElement("button");
|
|
405
428
|
item.className = "session-ctx-item session-new-vendor";
|
|
406
429
|
if (!isInstalled) item.classList.add("disabled");
|
|
@@ -423,10 +446,46 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
|
|
|
423
446
|
}
|
|
424
447
|
startNewSession(vendor);
|
|
425
448
|
});
|
|
426
|
-
|
|
449
|
+
row.appendChild(item);
|
|
450
|
+
if (isInstalled) {
|
|
451
|
+
var defaultBtn = document.createElement("button");
|
|
452
|
+
defaultBtn.type = "button";
|
|
453
|
+
defaultBtn.className = "session-new-set-default";
|
|
454
|
+
defaultBtn.textContent = defaultVendorState.saving ? "Saving…" : (vendor === defaultVendor && !defaultVendorUnavailable(installed) ? "Default" : "Set default");
|
|
455
|
+
defaultBtn.disabled = defaultVendorState.saving === true;
|
|
456
|
+
defaultBtn.setAttribute("aria-label", "Set " + name + " as default vendor");
|
|
457
|
+
defaultBtn.title = "Use " + name + " for New session by default";
|
|
458
|
+
defaultBtn.addEventListener("click", function (e) {
|
|
459
|
+
e.preventDefault();
|
|
460
|
+
e.stopPropagation();
|
|
461
|
+
saveDefaultVendor(vendor);
|
|
462
|
+
});
|
|
463
|
+
row.appendChild(defaultBtn);
|
|
464
|
+
}
|
|
465
|
+
menu.appendChild(row);
|
|
427
466
|
})(VENDOR_ORDER[i]);
|
|
428
467
|
}
|
|
429
468
|
|
|
469
|
+
var automatic = document.createElement("button");
|
|
470
|
+
automatic.className = "session-ctx-item session-new-automatic";
|
|
471
|
+
automatic.type = "button";
|
|
472
|
+
automatic.textContent = "Use automatic default";
|
|
473
|
+
automatic.disabled = defaultVendorState.saving === true;
|
|
474
|
+
automatic.title = "Use Clay's installed-vendor priority";
|
|
475
|
+
automatic.addEventListener("click", function (e) {
|
|
476
|
+
e.stopPropagation();
|
|
477
|
+
closeSessionCtxMenu();
|
|
478
|
+
saveDefaultVendor("");
|
|
479
|
+
});
|
|
480
|
+
menu.appendChild(automatic);
|
|
481
|
+
if (defaultVendorState.error) {
|
|
482
|
+
var status = document.createElement("div");
|
|
483
|
+
status.className = "session-new-preference-status";
|
|
484
|
+
status.setAttribute("role", "alert");
|
|
485
|
+
status.textContent = defaultVendorState.error;
|
|
486
|
+
menu.appendChild(status);
|
|
487
|
+
}
|
|
488
|
+
|
|
430
489
|
if (installed.length > 0) {
|
|
431
490
|
var pairSep = document.createElement("div");
|
|
432
491
|
pairSep.className = "session-ctx-sep";
|
|
@@ -660,6 +719,7 @@ export function getExpandedLoopRuns() {
|
|
|
660
719
|
// --- Context menu ---
|
|
661
720
|
|
|
662
721
|
function closeSessionCtxMenu() {
|
|
722
|
+
sessionCtxMenuAnchor = null;
|
|
663
723
|
if (sessionCtxMenu) {
|
|
664
724
|
sessionCtxMenu.remove();
|
|
665
725
|
sessionCtxMenu = null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { isProjectSessionReady } from './project-activation.js';
|
|
3
|
+
|
|
4
|
+
export function isCurrentProjectSessionReady(state, socket) {
|
|
5
|
+
return isProjectSessionReady(state, socket);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function clearProjectSplitState() {
|
|
9
|
+
store.set({
|
|
10
|
+
splitPanes: null,
|
|
11
|
+
splitGroups: [],
|
|
12
|
+
splitDelegations: {},
|
|
13
|
+
splitGroupsProjectSlug: null,
|
|
14
|
+
sessionListProjectSlug: null,
|
|
15
|
+
activeSessionId: null,
|
|
16
|
+
sessionActivatedProjectSlug: null,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function markProjectSessionListHydrated() {
|
|
21
|
+
var slug = store.get('activeProjectSlug');
|
|
22
|
+
if (slug) store.set({ sessionListProjectSlug: slug });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function applyProjectSplitGroups(groups) {
|
|
26
|
+
var slug = store.get('activeProjectSlug');
|
|
27
|
+
store.set({ splitGroups: groups || [], splitGroupsProjectSlug: slug || null });
|
|
28
|
+
}
|
|
@@ -12,6 +12,9 @@ import { showConfirm } from './app-misc.js';
|
|
|
12
12
|
import { syncPairChrome } from './split-pair-ui.js';
|
|
13
13
|
import { presentMarkdownEdit } from './filebrowser.js';
|
|
14
14
|
import { autoStartLoginIfNeeded } from './vendor-login.js';
|
|
15
|
+
import { isCurrentProjectSessionReady } from './split-session-boundary.js';
|
|
16
|
+
import { splitPanesMatchProject, hasCurrentSplitGroup, reconcileRestoredSplit } from './project-activation.js';
|
|
17
|
+
import { handlePaneIssueMessage } from './pane-issue-bridge.js';
|
|
15
18
|
|
|
16
19
|
var host = null;
|
|
17
20
|
var nativeApp = null;
|
|
@@ -85,10 +88,11 @@ function showDropOverlay() {
|
|
|
85
88
|
dropOverlay.classList.add("visible");
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
function switchNativeSession(sessionId) {
|
|
91
|
+
function switchNativeSession(sessionId, expectedSlug) {
|
|
89
92
|
store.set({ splitPanes: null });
|
|
90
93
|
var ws = getWs();
|
|
91
|
-
|
|
94
|
+
var state = store.snap();
|
|
95
|
+
if ((!expectedSlug || expectedSlug === state.currentSlug) && isCurrentProjectSessionReady(state, ws)) {
|
|
92
96
|
ws.send(JSON.stringify({ type: "switch_session", id: sessionId }));
|
|
93
97
|
}
|
|
94
98
|
}
|
|
@@ -99,7 +103,7 @@ function closePane(index) {
|
|
|
99
103
|
if (split.groupId && getWs() && getWs().readyState === 1) {
|
|
100
104
|
getWs().send(JSON.stringify({ type: "split_group_dissolve", id: split.groupId }));
|
|
101
105
|
}
|
|
102
|
-
switchNativeSession(split.panes[index === 0 ? 1 : 0].sessionId);
|
|
106
|
+
switchNativeSession(split.panes[index === 0 ? 1 : 0].sessionId, split.panes[index === 0 ? 1 : 0].slug);
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
function startPaneRename(header, titleEl, pane) {
|
|
@@ -288,6 +292,7 @@ function handlePaneMessage(event) {
|
|
|
288
292
|
if (msg.message) autoStartLoginIfNeeded(msg.message);
|
|
289
293
|
return;
|
|
290
294
|
}
|
|
295
|
+
if (handlePaneIssueMessage(event, host)) return;
|
|
291
296
|
if (msg.type !== "clay-pane-context") return;
|
|
292
297
|
var frames = host.querySelectorAll(".split-pane-frame");
|
|
293
298
|
for (var i = 0; i < frames.length; i++) {
|
|
@@ -372,7 +377,7 @@ export function openGroup(group) {
|
|
|
372
377
|
var activeId = store.get('activeSessionId');
|
|
373
378
|
if (activeId !== group.members[0] && activeId !== group.members[1]) {
|
|
374
379
|
var ws = getWs();
|
|
375
|
-
if (
|
|
380
|
+
if (isCurrentProjectSessionReady(store.snap(), ws)) {
|
|
376
381
|
ws.send(JSON.stringify({ type: "switch_session", id: group.members[0] }));
|
|
377
382
|
}
|
|
378
383
|
}
|
|
@@ -384,7 +389,33 @@ export function openGroup(group) {
|
|
|
384
389
|
// group member, reopen that group. Safe to call often -- no-ops unless a
|
|
385
390
|
// group member is active natively with no split open.
|
|
386
391
|
export function maybeRestoreSplitGroup() {
|
|
387
|
-
|
|
392
|
+
var state = store.snap();
|
|
393
|
+
if (!isCurrentProjectSessionReady(state, getWs())) return;
|
|
394
|
+
var split = state.splitPanes;
|
|
395
|
+
if (split && !splitPanesMatchProject(split, state.currentSlug)) {
|
|
396
|
+
store.set({ splitPanes: null });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (state.paneMode) return;
|
|
400
|
+
if (split) {
|
|
401
|
+
var reconciliation = reconcileRestoredSplit(split, state.splitGroups);
|
|
402
|
+
if (reconciliation.action === "clear") {
|
|
403
|
+
store.set({ splitPanes: null });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (reconciliation.action === "rebuild") {
|
|
407
|
+
var rebuilt = reconciliation.group;
|
|
408
|
+
if (!sessionById(rebuilt.members[0]) || !sessionById(rebuilt.members[1])) {
|
|
409
|
+
store.set({ splitPanes: null });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
store.set({ splitPanes: {
|
|
413
|
+
groupId: rebuilt.id,
|
|
414
|
+
panes: [paneForSession(rebuilt.members[0]), paneForSession(rebuilt.members[1])],
|
|
415
|
+
} });
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
388
419
|
var activeId = store.get('activeSessionId');
|
|
389
420
|
if (!activeId) return;
|
|
390
421
|
var groups = store.get('splitGroups') || [];
|
|
@@ -505,12 +536,16 @@ export function initSplitView() {
|
|
|
505
536
|
}
|
|
506
537
|
}
|
|
507
538
|
if (state.splitGroups !== prev.splitGroups) {
|
|
539
|
+
if (!isCurrentProjectSessionReady(state, getWs())) return;
|
|
508
540
|
var split = state.splitPanes;
|
|
509
541
|
if (!split || !split.panes) return;
|
|
542
|
+
if (!splitPanesMatchProject(split, state.currentSlug)) {
|
|
543
|
+
store.set({ splitPanes: null });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
510
546
|
syncPairChrome(host, split);
|
|
511
547
|
if (split.groupId) {
|
|
512
|
-
|
|
513
|
-
if (!stillExists) switchNativeSession(split.panes[0].sessionId);
|
|
548
|
+
if (!hasCurrentSplitGroup(split, state.splitGroups)) switchNativeSession(split.panes[0].sessionId, state.currentSlug);
|
|
514
549
|
return;
|
|
515
550
|
}
|
|
516
551
|
var ids = [split.panes[0].sessionId, split.panes[1].sessionId];
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// the canvas over the conversation; this pane lists them, splits them into Open
|
|
5
5
|
// and Closed, and is where a note is completed or brought back.
|
|
6
6
|
//
|
|
7
|
-
// Completion here is Close
|
|
8
|
-
//
|
|
9
|
-
// There is deliberately no destructive control on this surface at all.
|
|
7
|
+
// Completion here is Close. Permanent deletion is available only for closed
|
|
8
|
+
// notes and always requires an explicit, accessible confirmation.
|
|
10
9
|
|
|
11
10
|
import { store } from './store.js';
|
|
12
11
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
@@ -14,25 +13,18 @@ import { renderMiniMarkdown, getTitle } from './sticky-note-markdown.js';
|
|
|
14
13
|
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
15
14
|
import { send } from './sticky-notes-shared.js';
|
|
16
15
|
import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
|
|
16
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
17
17
|
|
|
18
18
|
var panel = null;
|
|
19
19
|
var gridEl = null;
|
|
20
20
|
var tabsEl = null;
|
|
21
|
+
var deleteDialog = null;
|
|
22
|
+
var deleteTrigger = null;
|
|
23
|
+
var deleteRequestCounter = 0;
|
|
24
|
+
var deleteRequestTimer = null;
|
|
21
25
|
|
|
22
26
|
// Registered by app.js so this module never imports the other right-pane tools
|
|
23
27
|
// and cannot form an import cycle with them.
|
|
24
|
-
var exclusiveClosers = [];
|
|
25
|
-
|
|
26
|
-
export function registerExclusiveClosers(fns) {
|
|
27
|
-
exclusiveClosers = (fns || []).filter(function (fn) { return typeof fn === "function"; });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function closeOtherRightTools() {
|
|
31
|
-
for (var i = 0; i < exclusiveClosers.length; i++) {
|
|
32
|
-
try { exclusiveClosers[i](); } catch (e) { /* a missing tool must not block opening */ }
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
28
|
function isClosed(data) {
|
|
37
29
|
if (!data) return false;
|
|
38
30
|
if (data.state === "closed") return true;
|
|
@@ -74,11 +66,15 @@ function ensurePanel() {
|
|
|
74
66
|
'</div>' +
|
|
75
67
|
'</header>' +
|
|
76
68
|
'<div id="notes-browser-tabs" class="notes-browser-tabs" role="tablist" aria-label="Sticky Notes state"></div>' +
|
|
77
|
-
'<div id="notes-browser-grid" class="notes-browser-grid" role="list"></div>'
|
|
69
|
+
'<div id="notes-browser-grid" class="notes-browser-grid" role="list"></div>' +
|
|
70
|
+
'<div id="notes-delete-dialog" class="notes-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="notes-delete-heading">' +
|
|
71
|
+
'<div class="notes-delete-dialog-card"><h2 id="notes-delete-heading">Delete this note permanently?</h2><p>This permanently removes the note. It cannot be restored.</p><p class="notes-delete-dialog-title"></p><p class="notes-delete-dialog-status" role="status"></p><div class="notes-delete-dialog-actions"><button type="button" data-action="cancel">Cancel</button><button type="button" class="danger" data-action="delete">Delete permanently</button></div></div>' +
|
|
72
|
+
'</div>';
|
|
78
73
|
panels.appendChild(panel);
|
|
79
74
|
|
|
80
75
|
gridEl = panel.querySelector("#notes-browser-grid");
|
|
81
76
|
tabsEl = panel.querySelector("#notes-browser-tabs");
|
|
77
|
+
deleteDialog = panel.querySelector("#notes-delete-dialog");
|
|
82
78
|
|
|
83
79
|
panel.querySelector("#notes-browser-close").addEventListener("click", closeNotesBrowser);
|
|
84
80
|
panel.querySelector("#notes-browser-wide").addEventListener("click", function () {
|
|
@@ -87,6 +83,29 @@ function ensurePanel() {
|
|
|
87
83
|
panel.querySelector("#notes-browser-fullscreen").addEventListener("click", function () {
|
|
88
84
|
applyWindowState(store.get('notesBrowserWide'), !store.get('notesBrowserFullscreen'));
|
|
89
85
|
});
|
|
86
|
+
deleteDialog.querySelector('[data-action="cancel"]').addEventListener("click", closeDeleteDialog);
|
|
87
|
+
deleteDialog.querySelector('[data-action="delete"]').addEventListener("click", confirmDelete);
|
|
88
|
+
deleteDialog.addEventListener("click", function (event) { if (event.target === deleteDialog) closeDeleteDialog(); });
|
|
89
|
+
deleteDialog.addEventListener("keydown", function (event) {
|
|
90
|
+
if (event.key === "Escape") {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
closeDeleteDialog(true);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (event.key !== "Tab") return;
|
|
97
|
+
var focusable = deleteDialog.querySelectorAll("button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])");
|
|
98
|
+
if (!focusable.length) return;
|
|
99
|
+
var first = focusable[0];
|
|
100
|
+
var last = focusable[focusable.length - 1];
|
|
101
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
last.focus();
|
|
104
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
first.focus();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
90
109
|
refreshIcons();
|
|
91
110
|
}
|
|
92
111
|
|
|
@@ -177,7 +196,8 @@ function buildCard(data) {
|
|
|
177
196
|
title.textContent = getTitle(data.text) || "Untitled";
|
|
178
197
|
header.appendChild(title);
|
|
179
198
|
|
|
180
|
-
//
|
|
199
|
+
// Closing and reopening are lifecycle actions; permanent deletion is offered
|
|
200
|
+
// separately below only because this card is known to be closed.
|
|
181
201
|
var action = document.createElement("button");
|
|
182
202
|
action.type = "button";
|
|
183
203
|
action.className = "notes-browser-card-action";
|
|
@@ -211,6 +231,14 @@ function buildCard(data) {
|
|
|
211
231
|
card.appendChild(body);
|
|
212
232
|
|
|
213
233
|
if (closed) {
|
|
234
|
+
var deleteAction = document.createElement("button");
|
|
235
|
+
deleteAction.type = "button";
|
|
236
|
+
deleteAction.className = "notes-browser-card-action notes-browser-card-delete";
|
|
237
|
+
deleteAction.innerHTML = iconHtml("trash-2") + "<span>Delete permanently</span>";
|
|
238
|
+
deleteAction.title = "Permanently delete this closed note";
|
|
239
|
+
deleteAction.setAttribute("aria-label", "Delete permanently: " + title.textContent);
|
|
240
|
+
deleteAction.addEventListener("click", function (e) { e.stopPropagation(); openDeleteDialog(data, deleteAction); });
|
|
241
|
+
header.appendChild(deleteAction);
|
|
214
242
|
var meta = document.createElement("p");
|
|
215
243
|
meta.className = "notes-browser-card-meta";
|
|
216
244
|
meta.textContent = closedMeta(data);
|
|
@@ -235,6 +263,77 @@ function buildCard(data) {
|
|
|
235
263
|
return card;
|
|
236
264
|
}
|
|
237
265
|
|
|
266
|
+
function nextDeleteRequestId() {
|
|
267
|
+
deleteRequestCounter++;
|
|
268
|
+
return "delete-note-" + Date.now() + "-" + deleteRequestCounter;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function accountKey() {
|
|
272
|
+
return store.get('myUserId') ? String(store.get('myUserId')) : "single";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function openDeleteDialog(data, trigger) {
|
|
276
|
+
if (!deleteDialog || !data || !isClosed(data)) return;
|
|
277
|
+
deleteTrigger = trigger || null;
|
|
278
|
+
store.set({ notesDeletePending: { noteId: data.id, requestId: null, projectSlug: store.get('currentSlug') || null, accountKey: accountKey() } });
|
|
279
|
+
deleteDialog.querySelector(".notes-delete-dialog-title").textContent = getTitle(data.text) || "Untitled";
|
|
280
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "";
|
|
281
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
282
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
283
|
+
deleteDialog.classList.remove("hidden");
|
|
284
|
+
deleteDialog.querySelector('[data-action="cancel"]').focus();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function closeDeleteDialog(force) {
|
|
288
|
+
var pending = store.get('notesDeletePending');
|
|
289
|
+
if (!force && pending && pending.requestId) return;
|
|
290
|
+
if (deleteRequestTimer) { clearTimeout(deleteRequestTimer); deleteRequestTimer = null; }
|
|
291
|
+
if (deleteDialog) deleteDialog.classList.add("hidden");
|
|
292
|
+
store.set({ notesDeletePending: null });
|
|
293
|
+
if (deleteTrigger && deleteTrigger.isConnected) deleteTrigger.focus();
|
|
294
|
+
deleteTrigger = null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function confirmDelete() {
|
|
298
|
+
var pending = store.get('notesDeletePending');
|
|
299
|
+
if (!pending || !deleteDialog) return;
|
|
300
|
+
if (!store.get('connected')) {
|
|
301
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "Sticky Notes are unavailable while disconnected.";
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
var requestId = nextDeleteRequestId();
|
|
305
|
+
store.set({ notesDeletePending: Object.assign({}, pending, { requestId: requestId }) });
|
|
306
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = true;
|
|
307
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = true;
|
|
308
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "Deleting permanently...";
|
|
309
|
+
send({ type: "note_delete_permanently", id: pending.noteId, requestId: requestId });
|
|
310
|
+
deleteRequestTimer = setTimeout(function () {
|
|
311
|
+
var active = store.get('notesDeletePending');
|
|
312
|
+
if (!deleteDialog || !active || active.requestId !== requestId) return;
|
|
313
|
+
store.set({ notesDeletePending: Object.assign({}, active, { requestId: null }) });
|
|
314
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
315
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
316
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "The delete request timed out. Try again.";
|
|
317
|
+
deleteRequestTimer = null;
|
|
318
|
+
}, 10000);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function handleNoteDeleteResult(msg) {
|
|
322
|
+
var pending = store.get('notesDeletePending');
|
|
323
|
+
if (!deleteDialog || !msg || msg.projectSlug !== store.get('currentSlug') ||
|
|
324
|
+
msg.requestId !== (pending && pending.requestId) || !pending || msg.noteId !== pending.noteId ||
|
|
325
|
+
msg.projectSlug !== pending.projectSlug || pending.accountKey !== accountKey()) return;
|
|
326
|
+
if (deleteRequestTimer) { clearTimeout(deleteRequestTimer); deleteRequestTimer = null; }
|
|
327
|
+
if (msg.ok) {
|
|
328
|
+
closeDeleteDialog(true);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
store.set({ notesDeletePending: Object.assign({}, pending, { requestId: null }) });
|
|
332
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
333
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
334
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = msg.error || "The note could not be deleted.";
|
|
335
|
+
}
|
|
336
|
+
|
|
238
337
|
function emptyState(tab) {
|
|
239
338
|
var empty = document.createElement("div");
|
|
240
339
|
empty.className = "notes-browser-empty";
|
|
@@ -253,6 +352,11 @@ function emptyState(tab) {
|
|
|
253
352
|
export function renderNotesBrowser() {
|
|
254
353
|
if (!gridEl) return;
|
|
255
354
|
var all = listNoteData();
|
|
355
|
+
var pending = store.get('notesDeletePending');
|
|
356
|
+
if (pending) {
|
|
357
|
+
var pendingCurrent = all.filter(function (item) { return item && item.id === pending.noteId; })[0];
|
|
358
|
+
if (!pendingCurrent || !isClosed(pendingCurrent)) closeDeleteDialog(true);
|
|
359
|
+
}
|
|
256
360
|
var open = openNotes(all);
|
|
257
361
|
var closed = closedNotes(all);
|
|
258
362
|
renderTabs(open.length, closed.length);
|
|
@@ -283,7 +387,7 @@ export function renderNotesBrowser() {
|
|
|
283
387
|
export function openNotesBrowser() {
|
|
284
388
|
ensurePanel();
|
|
285
389
|
if (!panel) return;
|
|
286
|
-
|
|
390
|
+
claimRightWorkbench("notes-browser");
|
|
287
391
|
// The floating canvas would otherwise sit on top of the pane.
|
|
288
392
|
hideNotes();
|
|
289
393
|
panel.classList.remove("hidden");
|
|
@@ -295,6 +399,10 @@ export function openNotesBrowser() {
|
|
|
295
399
|
}
|
|
296
400
|
|
|
297
401
|
export function closeNotesBrowser() {
|
|
402
|
+
// Reset destructive UI state even when the panel was already closed. Right
|
|
403
|
+
// workbench/project transitions can race with panel visibility updates.
|
|
404
|
+
closeDeleteDialog(true);
|
|
405
|
+
releaseRightWorkbench("notes-browser");
|
|
298
406
|
if (!store.get('notesBrowserOpen')) return;
|
|
299
407
|
if (panel) panel.classList.add("hidden");
|
|
300
408
|
// Fullscreen is always dropped on close so the next open is a bounded pane.
|
|
@@ -311,12 +419,16 @@ export function isNotesBrowserOpen() {
|
|
|
311
419
|
}
|
|
312
420
|
|
|
313
421
|
export function initNotesBrowser() {
|
|
422
|
+
registerRightWorkbench("notes-browser", closeNotesBrowser);
|
|
314
423
|
document.addEventListener("keydown", function (e) {
|
|
315
424
|
if (e.key !== "Escape") return;
|
|
425
|
+
if (deleteDialog && !deleteDialog.classList.contains("hidden")) return;
|
|
316
426
|
if (!store.get('notesBrowserOpen')) return;
|
|
317
427
|
closeNotesBrowser();
|
|
318
428
|
});
|
|
319
429
|
store.subscribe(function (state, previous) {
|
|
430
|
+
if (state.connected !== previous.connected || state.myUserId !== previous.myUserId ||
|
|
431
|
+
state.isMultiUserMode !== previous.isMultiUserMode) closeDeleteDialog(true);
|
|
320
432
|
if (state.currentSlug === previous.currentSlug) return;
|
|
321
433
|
// A different project starts from a closed, bounded, Open-tab pane.
|
|
322
434
|
closeNotesBrowser();
|
|
@@ -424,13 +424,12 @@ export function handleNoteWritten(msg) {
|
|
|
424
424
|
// an older daemon on the other end of the socket still must not corrupt state.
|
|
425
425
|
export function handleNoteDeleted(msg) {
|
|
426
426
|
var entry = notes.get(msg.id);
|
|
427
|
-
if (
|
|
428
|
-
|
|
427
|
+
if (entry) entry.el.remove();
|
|
428
|
+
clearNoteTimers(msg.id);
|
|
429
429
|
notes.delete(msg.id);
|
|
430
430
|
updateBadge();
|
|
431
431
|
updateSidebarBadge();
|
|
432
432
|
|
|
433
|
-
clearNoteTimers(msg.id);
|
|
434
433
|
refreshBrowser();
|
|
435
434
|
}
|
|
436
435
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { closeSidebar } from './sidebar.js';
|
|
3
|
-
import {
|
|
3
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
4
4
|
import { copyToClipboard } from './utils.js';
|
|
5
5
|
import { getTerminalTheme } from './theme.js';
|
|
6
6
|
import { getTerminalFontFamily, getTerminalFontSize, onTerminalFontChange } from './terminal-prefs.js';
|
|
7
7
|
import { createKeyToolbar } from './terminal-toolbar.js';
|
|
8
|
-
import { closeIssues } from './issues.js';
|
|
9
8
|
|
|
10
9
|
var ctx;
|
|
11
10
|
var tabs = new Map(); // termId -> { id, title, exited, xterm, fitAddon, bodyEl }
|
|
@@ -136,6 +135,7 @@ function createMultiLineLinkProvider(xterm) {
|
|
|
136
135
|
// --- Init ---
|
|
137
136
|
export function initTerminal(_ctx) {
|
|
138
137
|
ctx = _ctx;
|
|
138
|
+
registerRightWorkbench("terminal", closeTerminal);
|
|
139
139
|
|
|
140
140
|
// Close panel button
|
|
141
141
|
document.getElementById("terminal-close").addEventListener("click", function () {
|
|
@@ -193,8 +193,7 @@ export function openTerminal() {
|
|
|
193
193
|
var container = ctx.terminalContainerEl;
|
|
194
194
|
|
|
195
195
|
// Hide file viewer if open (also unwatches)
|
|
196
|
-
|
|
197
|
-
closeIssues();
|
|
196
|
+
claimRightWorkbench("terminal");
|
|
198
197
|
|
|
199
198
|
container.classList.remove("hidden");
|
|
200
199
|
isOpen = true;
|
|
@@ -224,6 +223,7 @@ export function openTerminal() {
|
|
|
224
223
|
|
|
225
224
|
// --- Close terminal panel (hide, detach, but keep PTYs alive) ---
|
|
226
225
|
export function closeTerminal() {
|
|
226
|
+
releaseRightWorkbench("terminal");
|
|
227
227
|
var container = ctx.terminalContainerEl;
|
|
228
228
|
container.classList.remove("panel-fullscreen");
|
|
229
229
|
container.classList.add("hidden");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { store } from './store.js';
|
|
4
|
-
import { addToMessages, scrollToBottom } from './chat-render-runtime.js';
|
|
4
|
+
import { addToMessages, keepActiveThinkingAtTail, scrollToBottom } from './chat-render-runtime.js';
|
|
5
5
|
import { prepareThinkingView, bindThinkingView, updateThinkingView, beginThinkingView, setThinkingViewLive, finishThinkingView } from './thinking-view.js';
|
|
6
6
|
|
|
7
7
|
function getThinkingState() {
|
|
@@ -92,6 +92,7 @@ export function startThinkingSegment() {
|
|
|
92
92
|
segmentText: "",
|
|
93
93
|
startTime: Date.now(),
|
|
94
94
|
});
|
|
95
|
+
if (!store.get('replayingHistory')) keepActiveThinkingAtTail();
|
|
95
96
|
beginThinkingView(thinkingState.el, !store.get('replayingHistory') && !isMateThinking());
|
|
96
97
|
showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
|
|
97
98
|
refreshIcons();
|
|
@@ -109,6 +110,7 @@ export function appendThinkingText(text) {
|
|
|
109
110
|
detailsText: thinkingState.detailsText + separator + text,
|
|
110
111
|
latestText: segmentText.trim() ? segmentText : thinkingState.latestText,
|
|
111
112
|
});
|
|
113
|
+
if (!store.get('replayingHistory')) keepActiveThinkingAtTail();
|
|
112
114
|
updateThinkingView(thinkingState.el, thinkingState.segmentText || thinkingState.latestText, thinkingState.detailsText);
|
|
113
115
|
maybeScrollToBottom();
|
|
114
116
|
}
|
|
@@ -156,6 +158,7 @@ export function restoreThinkingState(saved) {
|
|
|
156
158
|
export function resumeThinkingAfterReplay(processing) {
|
|
157
159
|
var thinkingState = getThinkingState();
|
|
158
160
|
if (!thinkingState || !thinkingState.active || !processing) return;
|
|
161
|
+
keepActiveThinkingAtTail();
|
|
159
162
|
setThinkingViewLive(thinkingState.el, !isMateThinking());
|
|
160
163
|
showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
|
|
161
164
|
}
|
|
@@ -6,6 +6,7 @@ import { openFile } from './filebrowser.js';
|
|
|
6
6
|
import { mateAvatarUrl } from './avatar.js';
|
|
7
7
|
import { getChatLayout } from './theme.js';
|
|
8
8
|
import { store } from './store.js';
|
|
9
|
+
import { applyPermissionHints } from './permission-hints.js';
|
|
9
10
|
import { VENDOR_NAMES } from './app-rendering.js';
|
|
10
11
|
import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
|
|
11
12
|
import { findAskUserCard, getAskUserRequestKey, hasAskUserRequest, rememberAskUserRequest, markAskUserRequestAnswered, saveAskUserState, restoreAskUserState, resetAskUserState } from './ask-user-state.js';
|
|
@@ -542,7 +543,7 @@ function permissionInputSummary(toolName, input) {
|
|
|
542
543
|
}
|
|
543
544
|
}
|
|
544
545
|
|
|
545
|
-
export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason, mateId, vendor) {
|
|
546
|
+
export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason, mateId, vendor, hints) {
|
|
546
547
|
if (pendingPermissions[requestId]) return;
|
|
547
548
|
ctx.finalizeAssistantBlock();
|
|
548
549
|
stopThinking();
|
|
@@ -551,17 +552,20 @@ export function renderPermissionRequest(requestId, toolName, toolInput, decision
|
|
|
551
552
|
// ExitPlanMode: render as plan confirmation instead of generic permission
|
|
552
553
|
if (toolName === "ExitPlanMode") {
|
|
553
554
|
renderPlanPermission(requestId);
|
|
555
|
+
applyPermissionHints(pendingPermissions[requestId], { defaultToNo: hints && hints.defaultToNo });
|
|
554
556
|
return;
|
|
555
557
|
}
|
|
556
558
|
|
|
557
559
|
// Channel layout or Mate DM: conversational "Can I ...?" style
|
|
558
560
|
if ((ctx.isMateDm && ctx.isMateDm()) || getChatLayout() === "channel") {
|
|
559
561
|
renderConversationalPermission(requestId, toolName, toolInput, mateId, vendor);
|
|
562
|
+
applyPermissionHints(pendingPermissions[requestId], hints);
|
|
560
563
|
return;
|
|
561
564
|
}
|
|
562
565
|
|
|
563
566
|
// Bubble layout: formal "Permission Required" dialog
|
|
564
567
|
renderFormalPermission(requestId, toolName, toolInput, decisionReason);
|
|
568
|
+
applyPermissionHints(pendingPermissions[requestId], hints);
|
|
565
569
|
}
|
|
566
570
|
|
|
567
571
|
function renderFormalPermission(requestId, toolName, toolInput, decisionReason) {
|
|
@@ -42,9 +42,11 @@ function createScheduledRunState(ctx) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function complete(id, runId, result) {
|
|
45
|
-
|
|
45
|
+
var completed = null;
|
|
46
|
+
var saved = mutate(id, function (rec) {
|
|
46
47
|
if (!rec.activeRun || rec.activeRun.runId !== runId) return false;
|
|
47
48
|
var run = Object.assign({}, rec.activeRun, result || {}, { finishedAt: Date.now() });
|
|
49
|
+
completed = run;
|
|
48
50
|
rec.lastRunAt = run.startedAt || Date.now();
|
|
49
51
|
rec.lastRunResult = run.outcome || "failed";
|
|
50
52
|
rec.runs = Array.isArray(rec.runs) ? rec.runs : [];
|
|
@@ -54,6 +56,8 @@ function createScheduledRunState(ctx) {
|
|
|
54
56
|
rec.activeRun = null;
|
|
55
57
|
return true;
|
|
56
58
|
});
|
|
59
|
+
if (saved && completed && ctx.onTerminal) ctx.onTerminal(ctx.getRecord(id), completed);
|
|
60
|
+
return saved;
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
function recover(rec) {
|
|
@@ -81,7 +85,11 @@ function createScheduledRunState(ctx) {
|
|
|
81
85
|
return true;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
function notifyTerminal(rec, run) {
|
|
89
|
+
if (ctx.onTerminal && rec && run) ctx.onTerminal(rec, run);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { begin: begin, complete: complete, notifyTerminal: notifyTerminal, prepareRecord: prepareRecord, recordLegacy: recordLegacy, recover: recover, update: update };
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
module.exports = { createScheduledRunState: createScheduledRunState };
|