clay-server 4.0.0-beta.8 → 4.0.0
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/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 +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- 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-request-access.js +66 -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-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -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 +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- 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 +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- 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 +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- 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-surface-boot.js +7 -3
- 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-activation.js +45 -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-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- 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/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- 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 +31 -123
- 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 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -19
- 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 +137 -22
- 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 +41 -0
- package/lib/sessions.js +57 -10
- 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 +6 -6
- 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 +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Driver-operated Split Worker surface.
|
|
2
|
+
//
|
|
3
|
+
// A configured Split Worker executes work its Driver delegated, so its pane is
|
|
4
|
+
// an execution surface rather than a chat the person types into. This module
|
|
5
|
+
// takes the composer's footprint away and replaces it with one compact,
|
|
6
|
+
// non-interactive status line.
|
|
7
|
+
//
|
|
8
|
+
// What stays, deliberately: the transcript, streaming status, tool and
|
|
9
|
+
// permission progress, scrolling, and the stop control — the human keeps their
|
|
10
|
+
// emergency brake. What goes: the text input, attachments, send, the composer
|
|
11
|
+
// context row, and any Worker-local permission-mode affordance.
|
|
12
|
+
//
|
|
13
|
+
// The role is read from pair state only (`splitGroups` + the displayed
|
|
14
|
+
// session), which is server-broadcast and therefore correct across reconnect,
|
|
15
|
+
// project switch, session switch, dissolve, replacement and role change. There
|
|
16
|
+
// is no second copy of the role and nothing in localStorage. A plain ad-hoc
|
|
17
|
+
// side-by-side split has no pair roles, so it is never locked and keeps its
|
|
18
|
+
// composer.
|
|
19
|
+
//
|
|
20
|
+
// Accessibility: locked controls are removed from the tab order with `disabled`
|
|
21
|
+
// and `tabindex="-1"`, and the composer region is `aria-hidden` while the
|
|
22
|
+
// status line carries the explanation, so a screen reader is told the pane is
|
|
23
|
+
// Driver-controlled instead of finding focusable inputs that do nothing.
|
|
24
|
+
|
|
25
|
+
import { store } from './store.js';
|
|
26
|
+
|
|
27
|
+
var STATUS_ID = "worker-pane-status";
|
|
28
|
+
var LOCK_CLASS = "worker-pane-locked";
|
|
29
|
+
|
|
30
|
+
// Every interactive composer affordance. Queried lazily: a pane iframe and the
|
|
31
|
+
// main window share this markup, and some ids are mobile-only.
|
|
32
|
+
var LOCKED_CONTROL_IDS = [
|
|
33
|
+
"input",
|
|
34
|
+
"send-btn",
|
|
35
|
+
"attach-file-btn",
|
|
36
|
+
"attach-image-btn",
|
|
37
|
+
"input-more-btn",
|
|
38
|
+
"shell-command-btn",
|
|
39
|
+
"stt-btn",
|
|
40
|
+
"schedule-btn",
|
|
41
|
+
"ask-mate-btn",
|
|
42
|
+
"context-sources-add",
|
|
43
|
+
"composer-add-worker-btn",
|
|
44
|
+
"composer-handoff-btn",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function lockedControls() {
|
|
48
|
+
var found = [];
|
|
49
|
+
for (var i = 0; i < LOCKED_CONTROL_IDS.length; i++) {
|
|
50
|
+
var el = document.getElementById(LOCKED_CONTROL_IDS[i]);
|
|
51
|
+
if (el) found.push(el);
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The configured Worker of a live pair, for the session actually on screen.
|
|
57
|
+
// Returns false for an ad-hoc split, for the Driver, and for any ordinary
|
|
58
|
+
// session, so the composer is only ever taken away in the one real case.
|
|
59
|
+
export function isDriverOperatedView(state) {
|
|
60
|
+
var snapshot = state || store.snap();
|
|
61
|
+
var sessionId = snapshot.paneMode && snapshot.paneSessionId
|
|
62
|
+
? snapshot.paneSessionId
|
|
63
|
+
: snapshot.activeSessionId;
|
|
64
|
+
if (!sessionId) return false;
|
|
65
|
+
var groups = snapshot.splitGroups || [];
|
|
66
|
+
for (var i = 0; i < groups.length; i++) {
|
|
67
|
+
var pair = groups[i] && groups[i].pair;
|
|
68
|
+
if (!pair) continue;
|
|
69
|
+
if (pair.workerId === sessionId) return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ensureStatusLine(inputArea) {
|
|
75
|
+
var existing = document.getElementById(STATUS_ID);
|
|
76
|
+
if (existing) return existing;
|
|
77
|
+
var line = document.createElement("div");
|
|
78
|
+
line.id = STATUS_ID;
|
|
79
|
+
line.className = "worker-pane-status";
|
|
80
|
+
// A status region, not a live region: it states a standing condition rather
|
|
81
|
+
// than announcing a change, so it should not interrupt.
|
|
82
|
+
line.setAttribute("role", "status");
|
|
83
|
+
var mark = document.createElement("span");
|
|
84
|
+
mark.className = "worker-pane-status-mark";
|
|
85
|
+
mark.setAttribute("aria-hidden", "true");
|
|
86
|
+
var label = document.createElement("span");
|
|
87
|
+
label.className = "worker-pane-status-label";
|
|
88
|
+
label.textContent = "Controlled by Driver";
|
|
89
|
+
line.appendChild(mark);
|
|
90
|
+
line.appendChild(label);
|
|
91
|
+
inputArea.appendChild(line);
|
|
92
|
+
return line;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function applyLock(locked) {
|
|
96
|
+
var inputArea = document.getElementById("input-area");
|
|
97
|
+
var inputWrapper = document.getElementById("input-wrapper");
|
|
98
|
+
if (!inputArea) return;
|
|
99
|
+
|
|
100
|
+
document.body.classList.toggle(LOCK_CLASS, locked);
|
|
101
|
+
|
|
102
|
+
if (inputWrapper) {
|
|
103
|
+
// Hidden from assistive tech as well as sight; the status line speaks for
|
|
104
|
+
// the region instead.
|
|
105
|
+
if (locked) inputWrapper.setAttribute("aria-hidden", "true");
|
|
106
|
+
else inputWrapper.removeAttribute("aria-hidden");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
var controls = lockedControls();
|
|
110
|
+
for (var i = 0; i < controls.length; i++) {
|
|
111
|
+
var el = controls[i];
|
|
112
|
+
if (locked) {
|
|
113
|
+
if (el.dataset.workerLockPrev === undefined) {
|
|
114
|
+
el.dataset.workerLockPrev = el.disabled ? "1" : "0";
|
|
115
|
+
}
|
|
116
|
+
el.disabled = true;
|
|
117
|
+
el.setAttribute("tabindex", "-1");
|
|
118
|
+
} else if (el.dataset.workerLockPrev !== undefined) {
|
|
119
|
+
// Restore exactly what was there before the lock, so an independently
|
|
120
|
+
// disabled send button stays disabled when the pair dissolves.
|
|
121
|
+
el.disabled = el.dataset.workerLockPrev === "1";
|
|
122
|
+
el.removeAttribute("tabindex");
|
|
123
|
+
delete el.dataset.workerLockPrev;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var status = document.getElementById(STATUS_ID);
|
|
128
|
+
if (locked) {
|
|
129
|
+
status = ensureStatusLine(inputArea);
|
|
130
|
+
status.classList.add("visible");
|
|
131
|
+
} else if (status) {
|
|
132
|
+
status.classList.remove("visible");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Skip permissions and any other Worker-local permission-mode affordance are
|
|
137
|
+
// the Driver's to set, so they are removed from this pane only. The Driver pane
|
|
138
|
+
// and ordinary sessions are untouched.
|
|
139
|
+
function applyPermissionChrome(locked) {
|
|
140
|
+
var ids = ["skip-perms-pill", "config-chip-wrap"];
|
|
141
|
+
for (var i = 0; i < ids.length; i++) {
|
|
142
|
+
var el = document.getElementById(ids[i]);
|
|
143
|
+
if (!el) continue;
|
|
144
|
+
if (locked) {
|
|
145
|
+
if (el.dataset.workerLockHidden === undefined) {
|
|
146
|
+
el.dataset.workerLockHidden = el.classList.contains("hidden") ? "1" : "0";
|
|
147
|
+
}
|
|
148
|
+
el.classList.add("hidden");
|
|
149
|
+
el.setAttribute("aria-hidden", "true");
|
|
150
|
+
} else if (el.dataset.workerLockHidden !== undefined) {
|
|
151
|
+
if (el.dataset.workerLockHidden === "0") el.classList.remove("hidden");
|
|
152
|
+
el.removeAttribute("aria-hidden");
|
|
153
|
+
delete el.dataset.workerLockHidden;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function syncWorkerPaneLock(state) {
|
|
159
|
+
var locked = isDriverOperatedView(state);
|
|
160
|
+
applyLock(locked);
|
|
161
|
+
applyPermissionChrome(locked);
|
|
162
|
+
return locked;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function initWorkerPaneLock() {
|
|
166
|
+
syncWorkerPaneLock();
|
|
167
|
+
// Pair state is the source of truth, so every input that could change the
|
|
168
|
+
// role re-runs the same resolution: a dissolve, a replacement, a role swap,
|
|
169
|
+
// switching to another session, or a reconnect that reloads the groups.
|
|
170
|
+
store.subscribe(function (state, prev) {
|
|
171
|
+
if (state.splitGroups === prev.splitGroups
|
|
172
|
+
&& state.activeSessionId === prev.activeSessionId
|
|
173
|
+
&& state.paneSessionId === prev.paneSessionId) return;
|
|
174
|
+
syncWorkerPaneLock(state);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function workerProposalSelection(msg) {
|
|
2
|
+
var hasSelection = typeof msg.selectedVendor === "string" ||
|
|
3
|
+
typeof msg.selectedModel === "string" || typeof msg.selectedEffort === "string";
|
|
4
|
+
return {
|
|
5
|
+
selected: hasSelection,
|
|
6
|
+
vendor: hasSelection ? (msg.selectedVendor || "") : (msg.recommendedVendor || ""),
|
|
7
|
+
model: hasSelection ? (msg.selectedModel || "") : (msg.recommendedModel || ""),
|
|
8
|
+
effort: hasSelection ? (msg.selectedEffort || "") : (msg.recommendedEffort || "medium"),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function syncWorkerProposalSelection(msg, sync) {
|
|
13
|
+
var selection = workerProposalSelection(msg);
|
|
14
|
+
if (selection.selected && typeof sync === "function") sync(selection);
|
|
15
|
+
return selection;
|
|
16
|
+
}
|
|
@@ -2,6 +2,7 @@ import { getWs } from './ws-ref.js';
|
|
|
2
2
|
import { addToMessages, scrollToBottom, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
3
3
|
import { effortLevelsFor, effortDisplayName } from './app-panels.js';
|
|
4
4
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { workerProposalSelection, syncWorkerProposalSelection } from './worker-proposal-state.js';
|
|
5
6
|
|
|
6
7
|
function optionValue(entry) {
|
|
7
8
|
if (typeof entry === "string") return entry;
|
|
@@ -27,7 +28,7 @@ function planSteps(plan) {
|
|
|
27
28
|
}).filter(Boolean).slice(0, 8);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function buildVendorSelect(msg) {
|
|
31
|
+
function buildVendorSelect(msg, preferred) {
|
|
31
32
|
var select = document.createElement("select");
|
|
32
33
|
select.className = "worker-proposal-select worker-proposal-vendor";
|
|
33
34
|
var installed = (msg.options && msg.options.installedVendors) || [];
|
|
@@ -37,7 +38,7 @@ function buildVendorSelect(msg) {
|
|
|
37
38
|
option.textContent = VENDOR_NAMES[installed[i]] || installed[i];
|
|
38
39
|
select.appendChild(option);
|
|
39
40
|
}
|
|
40
|
-
if (installed.indexOf(
|
|
41
|
+
if (installed.indexOf(preferred) !== -1) select.value = preferred;
|
|
41
42
|
return select;
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -98,29 +99,38 @@ function statusLabel(status) {
|
|
|
98
99
|
if (status === "interrupted") return "Interrupted";
|
|
99
100
|
if (status === "declined") return "Continuing here";
|
|
100
101
|
if (status === "error") return "Needs attention";
|
|
101
|
-
return "
|
|
102
|
+
return "Awaiting your choice";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function decisionLabel(status, autoAccepted) {
|
|
106
|
+
if (autoAccepted) return "Driver recommendation auto-accepted under Full auto";
|
|
107
|
+
if (status === "pending") return "Runtime choice remains with you";
|
|
108
|
+
if (status === "declined") return "Runtime proposal declined by user";
|
|
109
|
+
return "Runtime configuration accepted by user";
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
function applyState(card, msg) {
|
|
105
113
|
var status = msg.status || "pending";
|
|
106
114
|
card.dataset.status = status;
|
|
107
|
-
if (msg.autoApproved) card.dataset.
|
|
108
|
-
|
|
115
|
+
if (msg.autoAccepted || msg.autoApproved) card.dataset.autoAccepted = "true";
|
|
116
|
+
if (msg.autoAccepted === false || msg.autoApproved === false) card.dataset.autoAccepted = "false";
|
|
117
|
+
var autoAccepted = card.dataset.autoAccepted === "true";
|
|
118
|
+
syncWorkerProposalSelection(msg, card._syncWorkerProposalSelection);
|
|
109
119
|
var badge = card.querySelector(".worker-proposal-status");
|
|
110
|
-
if (badge) badge.textContent = statusLabel(status) + (
|
|
120
|
+
if (badge) badge.textContent = statusLabel(status) + (autoAccepted ? " · auto-accepted" : "");
|
|
121
|
+
var decision = card.querySelector(".worker-proposal-decision");
|
|
122
|
+
if (decision) {
|
|
123
|
+
decision.textContent = decisionLabel(status, autoAccepted);
|
|
124
|
+
decision.classList.toggle("automatic", autoAccepted);
|
|
125
|
+
}
|
|
111
126
|
var error = card.querySelector(".worker-proposal-error");
|
|
112
127
|
if (error) {
|
|
113
128
|
error.textContent = msg.error || "";
|
|
114
129
|
error.classList.toggle("hidden", !msg.error);
|
|
115
130
|
}
|
|
116
|
-
var preview = card.querySelector(".worker-proposal-result");
|
|
117
|
-
if (preview && msg.resultPreview) {
|
|
118
|
-
preview.textContent = msg.resultPreview;
|
|
119
|
-
preview.classList.remove("hidden");
|
|
120
|
-
}
|
|
121
131
|
setControlsDisabled(card, status !== "pending");
|
|
122
132
|
var actions = card.querySelector(".worker-proposal-actions");
|
|
123
|
-
if (actions) actions.classList.toggle("hidden",
|
|
133
|
+
if (actions) actions.classList.toggle("hidden", autoAccepted);
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
function sendDecision(card, accepted) {
|
|
@@ -150,10 +160,12 @@ export function renderWorkerProposal(msg) {
|
|
|
150
160
|
card.className = "worker-proposal-card";
|
|
151
161
|
card.dataset.proposalId = msg.proposalId;
|
|
152
162
|
card.dataset.status = msg.status || "pending";
|
|
163
|
+
var runtimeSelection = workerProposalSelection(msg);
|
|
153
164
|
|
|
154
165
|
var header = document.createElement("div");
|
|
155
166
|
header.className = "worker-proposal-header";
|
|
156
|
-
|
|
167
|
+
var replacing = msg.action === "replace";
|
|
168
|
+
header.innerHTML = '<div class="worker-proposal-mark">' + iconHtml("git-branch") + '</div><div class="worker-proposal-heading"><span class="worker-proposal-kicker">DRIVER / SPLIT WORKER</span><strong>' + (replacing ? "Configure a replacement Split Worker" : "Configure a Split Worker") + '</strong></div><span class="worker-proposal-status"></span>';
|
|
157
169
|
card.appendChild(header);
|
|
158
170
|
|
|
159
171
|
var summary = document.createElement("p");
|
|
@@ -173,12 +185,24 @@ export function renderWorkerProposal(msg) {
|
|
|
173
185
|
card.appendChild(plan);
|
|
174
186
|
}
|
|
175
187
|
|
|
188
|
+
if (msg.recommendationRationale) {
|
|
189
|
+
var rationale = document.createElement("div");
|
|
190
|
+
rationale.className = "worker-proposal-rationale";
|
|
191
|
+
var rationaleLabel = document.createElement("span");
|
|
192
|
+
rationaleLabel.textContent = "Driver's recommendation rationale";
|
|
193
|
+
var rationaleText = document.createElement("p");
|
|
194
|
+
rationaleText.textContent = msg.recommendationRationale;
|
|
195
|
+
rationale.appendChild(rationaleLabel);
|
|
196
|
+
rationale.appendChild(rationaleText);
|
|
197
|
+
card.appendChild(rationale);
|
|
198
|
+
}
|
|
199
|
+
|
|
176
200
|
var config = document.createElement("div");
|
|
177
201
|
config.className = "worker-proposal-config";
|
|
178
202
|
var vendorField = document.createElement("label");
|
|
179
203
|
vendorField.className = "worker-proposal-field worker-proposal-vendor-field";
|
|
180
204
|
vendorField.innerHTML = '<span>Worker</span><span class="worker-proposal-vendor-control"><img alt=""></span>';
|
|
181
|
-
var vendorSelect = buildVendorSelect(msg);
|
|
205
|
+
var vendorSelect = buildVendorSelect(msg, runtimeSelection.vendor);
|
|
182
206
|
vendorField.querySelector(".worker-proposal-vendor-control").appendChild(vendorSelect);
|
|
183
207
|
config.appendChild(vendorField);
|
|
184
208
|
var modelField = document.createElement("label");
|
|
@@ -194,6 +218,10 @@ export function renderWorkerProposal(msg) {
|
|
|
194
218
|
config.appendChild(effortField);
|
|
195
219
|
card.appendChild(config);
|
|
196
220
|
|
|
221
|
+
var decision = document.createElement("div");
|
|
222
|
+
decision.className = "worker-proposal-decision";
|
|
223
|
+
card.appendChild(decision);
|
|
224
|
+
|
|
197
225
|
function syncVendor(preferredModel, preferredEffort) {
|
|
198
226
|
var vendor = vendorSelect.value;
|
|
199
227
|
var avatar = vendorField.querySelector("img");
|
|
@@ -201,31 +229,32 @@ export function renderWorkerProposal(msg) {
|
|
|
201
229
|
fillModels(modelSelect, vendor, msg, preferredModel);
|
|
202
230
|
fillEffort(card, vendor, modelSelect.value, msg, preferredEffort);
|
|
203
231
|
}
|
|
232
|
+
card._syncWorkerProposalSelection = function (selection) {
|
|
233
|
+
if (selection.vendor && vendorSelect.value !== selection.vendor) vendorSelect.value = selection.vendor;
|
|
234
|
+
syncVendor(selection.model, selection.effort);
|
|
235
|
+
};
|
|
204
236
|
vendorSelect.addEventListener("change", function () { syncVendor("", "medium"); });
|
|
205
237
|
modelSelect.addEventListener("change", function () {
|
|
206
238
|
var active = card.querySelector(".worker-proposal-effort-btn.active");
|
|
207
239
|
fillEffort(card, vendorSelect.value, modelSelect.value, msg, active ? active.dataset.effort : "medium");
|
|
208
240
|
});
|
|
209
|
-
syncVendor(
|
|
241
|
+
syncVendor(runtimeSelection.model, runtimeSelection.effort);
|
|
210
242
|
|
|
211
243
|
var error = document.createElement("div");
|
|
212
244
|
error.className = "worker-proposal-error hidden";
|
|
213
245
|
card.appendChild(error);
|
|
214
|
-
var result = document.createElement("div");
|
|
215
|
-
result.className = "worker-proposal-result hidden";
|
|
216
|
-
card.appendChild(result);
|
|
217
246
|
|
|
218
247
|
var actions = document.createElement("div");
|
|
219
248
|
actions.className = "worker-proposal-actions";
|
|
220
249
|
var decline = document.createElement("button");
|
|
221
250
|
decline.type = "button";
|
|
222
251
|
decline.className = "worker-proposal-action secondary";
|
|
223
|
-
decline.textContent = "Continue here";
|
|
252
|
+
decline.textContent = replacing ? "Keep current Worker" : "Continue here";
|
|
224
253
|
decline.addEventListener("click", function () { sendDecision(card, false); });
|
|
225
254
|
var accept = document.createElement("button");
|
|
226
255
|
accept.type = "button";
|
|
227
256
|
accept.className = "worker-proposal-action primary";
|
|
228
|
-
accept.innerHTML = iconHtml("panel-right-open") + "<span>Run with Split Worker</span>";
|
|
257
|
+
accept.innerHTML = iconHtml("panel-right-open") + "<span>" + (replacing ? "Replace Split Worker" : "Run with Split Worker") + "</span>";
|
|
229
258
|
accept.addEventListener("click", function () { sendDecision(card, true); });
|
|
230
259
|
actions.appendChild(decline);
|
|
231
260
|
actions.appendChild(accept);
|
package/lib/public/style.css
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
@import url("css/input.css?v=20260828-composer-depth1");
|
|
15
15
|
@import url("css/filebrowser.css?v=20260829-terminal-canvas1");
|
|
16
16
|
@import url("css/git-panel.css");
|
|
17
|
+
@import url("css/git-placard.css");
|
|
17
18
|
@import url("css/diff.css");
|
|
18
19
|
@import url("css/highlight.css");
|
|
19
20
|
@import url("css/server-settings.css");
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
@import url("css/mobile-nav.css?v=20260407");
|
|
23
24
|
@import url("css/loop.css");
|
|
24
25
|
@import url("css/scheduler.css");
|
|
26
|
+
@import url("css/project-logs.css");
|
|
25
27
|
@import url("css/scheduler-modal.css");
|
|
26
28
|
@import url("css/home-hub.css");
|
|
27
29
|
@import url("css/home-sidebar.css");
|
|
@@ -39,7 +41,7 @@
|
|
|
39
41
|
@import url("css/session-search.css");
|
|
40
42
|
@import url("css/tooltip.css");
|
|
41
43
|
@import url("css/mates.css?v=20260407");
|
|
42
|
-
@import url("css/command-palette.css?v=
|
|
44
|
+
@import url("css/command-palette.css?v=20260908-log-links2");
|
|
43
45
|
@import url("css/mention.css");
|
|
44
46
|
@import url("css/debate.css");
|
|
45
47
|
@import url("css/home-debate-planning.css");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -12,6 +12,7 @@ var { createMessageQueue } = require("./sdk-message-queue");
|
|
|
12
12
|
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
13
13
|
var homeDebateToolPolicy = require("./home-debate-tool-policy");
|
|
14
14
|
var yoke = require("./yoke");
|
|
15
|
+
var sessionTitlePolicy = require("./session-title-policy");
|
|
15
16
|
|
|
16
17
|
// Extract serializable tool descriptors from MCP server instances.
|
|
17
18
|
// Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
|
|
@@ -128,6 +129,9 @@ function createSDKBridge(opts) {
|
|
|
128
129
|
var clayTls = opts.clayTls || false;
|
|
129
130
|
var clayAuthToken = opts.clayAuthToken || null;
|
|
130
131
|
var getSessionToolDefs = opts.getSessionToolDefs || function () { return []; };
|
|
132
|
+
// Split Worker permission routing. A getter because the pair module is
|
|
133
|
+
// constructed before this bridge exists.
|
|
134
|
+
var getWorkerPermissionRouter = opts.getWorkerPermissionRouter || function () { return null; };
|
|
131
135
|
var onProcessingChanged = opts.onProcessingChanged || function () {};
|
|
132
136
|
var _cachedFreshAuthState = null;
|
|
133
137
|
var _cachedFreshAuthAt = 0;
|
|
@@ -601,27 +605,77 @@ function createSDKBridge(opts) {
|
|
|
601
605
|
return { behavior: "allow", updatedInput: input };
|
|
602
606
|
}
|
|
603
607
|
|
|
604
|
-
// Auto-approve
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
608
|
+
// Auto-approve the non-mutating configuration proposal and the visible
|
|
609
|
+
// Driver/Split Worker pair tools, including the Driver's answer to a Worker
|
|
610
|
+
// permission request. Creation and replacement still require a separate
|
|
611
|
+
// human decision in the proposal card; skip-permissions is never treated as
|
|
612
|
+
// that product choice. Every handler is confined to the caller's exact
|
|
613
|
+
// session/pair and re-checks eligibility, ownership and live identity.
|
|
614
|
+
//
|
|
615
|
+
// spawn_sessions is deliberately absent: it creates arbitrary sessions.
|
|
616
|
+
//
|
|
617
|
+
// Only the two name forms the bridge can genuinely emit are accepted:
|
|
618
|
+
//
|
|
619
|
+
// mcp__clay-sessions__<tool> every MCP-mounted vendor. The server
|
|
620
|
+
// segment is matched exactly, so a tool of
|
|
621
|
+
// the same name on another MCP server is
|
|
622
|
+
// never auto-approved.
|
|
623
|
+
// <tool> a Codex dynamic tool. Codex canonicalizes
|
|
624
|
+
// send_to_partner and read_partner to the MCP
|
|
625
|
+
// form itself, so only the remaining names
|
|
626
|
+
// can legitimately arrive bare.
|
|
627
|
+
var PAIR_MCP_PREFIX = "mcp__clay-sessions__";
|
|
628
|
+
var pairMcpTools = {
|
|
629
|
+
propose_worker: true,
|
|
630
|
+
send_to_partner: true,
|
|
631
|
+
read_partner: true,
|
|
632
|
+
partner_status: true,
|
|
633
|
+
replace_partner: true,
|
|
634
|
+
interrupt_partner: true,
|
|
635
|
+
close_partner: true,
|
|
636
|
+
record_partner_evaluation: true,
|
|
637
|
+
respond_to_worker_permission: true,
|
|
638
|
+
};
|
|
639
|
+
var pairDynamicTools = {
|
|
640
|
+
propose_worker: true,
|
|
641
|
+
partner_status: true,
|
|
642
|
+
replace_partner: true,
|
|
643
|
+
interrupt_partner: true,
|
|
644
|
+
close_partner: true,
|
|
645
|
+
record_partner_evaluation: true,
|
|
646
|
+
respond_to_worker_permission: true,
|
|
647
|
+
};
|
|
648
|
+
if (toolName.indexOf("mcp__") === 0) {
|
|
649
|
+
if (toolName.indexOf(PAIR_MCP_PREFIX) === 0
|
|
650
|
+
&& pairMcpTools[toolName.slice(PAIR_MCP_PREFIX.length)]) {
|
|
614
651
|
return { behavior: "allow", updatedInput: input };
|
|
615
652
|
}
|
|
653
|
+
} else if (pairDynamicTools[toolName]) {
|
|
654
|
+
return { behavior: "allow", updatedInput: input };
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Sticky-note operations are immediately visible and reversible on the
|
|
658
|
+
// shared board. The exact session-bound handlers still enforce ownership,
|
|
659
|
+
// lifecycle rules, and input validation, so a provider prompt adds no
|
|
660
|
+
// authority. Keep this confined to the exact Clay Notes namespace.
|
|
661
|
+
var NOTES_MCP_PREFIX = "mcp__clay-notes__";
|
|
662
|
+
var notesTools = {
|
|
663
|
+
list_notes: true,
|
|
664
|
+
write_note: true,
|
|
665
|
+
close_note: true,
|
|
666
|
+
reopen_note: true,
|
|
667
|
+
remove_note: true,
|
|
668
|
+
};
|
|
669
|
+
if (toolName.indexOf(NOTES_MCP_PREFIX) === 0
|
|
670
|
+
&& notesTools[toolName.slice(NOTES_MCP_PREFIX.length)]) {
|
|
671
|
+
return { behavior: "allow", updatedInput: input };
|
|
616
672
|
}
|
|
617
673
|
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|| toolName.indexOf("clay-notes__") !== -1;
|
|
624
|
-
if (isClayNotesTool && (notesToolName === "list_notes" || notesToolName === "write_note")) {
|
|
674
|
+
// Project Logs are exact-session, project-bound tools whose authority is
|
|
675
|
+
// enforced again by the server binding. Canonical changes are append-only
|
|
676
|
+
// revisions, and the surface exposes no destructive delete operation, so
|
|
677
|
+
// a second provider permission prompt adds no additional safety.
|
|
678
|
+
if (toolName.indexOf("mcp__clay-logs__") === 0) {
|
|
625
679
|
return { behavior: "allow", updatedInput: input };
|
|
626
680
|
}
|
|
627
681
|
|
|
@@ -720,6 +774,19 @@ function createSDKBridge(opts) {
|
|
|
720
774
|
// Full-auto mode: auto-approve everything except AskUserQuestion
|
|
721
775
|
// (which still needs to go through the user interaction flow).
|
|
722
776
|
var clayPermissionMode = session.permissionMode || sm.currentPermissionMode;
|
|
777
|
+
// A configured Split Worker is Driver-operated, so it runs under its
|
|
778
|
+
// Driver's permission mode, re-resolved from the live pair on every single
|
|
779
|
+
// decision. Nothing is copied at creation and the Worker's own persisted
|
|
780
|
+
// setting is never written, so toggling skip permissions on the Driver
|
|
781
|
+
// applies to the Worker's next call in either direction, mid-turn included.
|
|
782
|
+
var _wpMode = getWorkerPermissionRouter();
|
|
783
|
+
if (_wpMode && typeof _wpMode.inheritedPermissionMode === "function") {
|
|
784
|
+
var inherited = _wpMode.inheritedPermissionMode(session);
|
|
785
|
+
if (inherited) clayPermissionMode = inherited;
|
|
786
|
+
}
|
|
787
|
+
// AskUserQuestion is genuine user input, not an authorization, so it is
|
|
788
|
+
// excluded here exactly as it always was: a Driver in skip mode still
|
|
789
|
+
// cannot silently answer a question addressed to the person.
|
|
723
790
|
if (clayPermissionMode === "bypassPermissions" && toolName !== "AskUserQuestion") {
|
|
724
791
|
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
725
792
|
}
|
|
@@ -747,6 +814,22 @@ function createSDKBridge(opts) {
|
|
|
747
814
|
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
748
815
|
}
|
|
749
816
|
|
|
817
|
+
// A visible Split Worker's remaining prompts belong to its paired Driver,
|
|
818
|
+
// not to the Worker pane. This sits below every auto-decision above, so
|
|
819
|
+
// skip permissions, the whitelist, allowedTools and the loop/debate
|
|
820
|
+
// policies all still resolve first and never wait on a Driver.
|
|
821
|
+
var _wp = getWorkerPermissionRouter();
|
|
822
|
+
if (_wp) {
|
|
823
|
+
var routed = _wp.routeIfWorker(session, {
|
|
824
|
+
toolName: toolName,
|
|
825
|
+
input: input,
|
|
826
|
+
toolUseId: opts.toolUseID,
|
|
827
|
+
decisionReason: opts.decisionReason || "",
|
|
828
|
+
signal: opts.signal,
|
|
829
|
+
});
|
|
830
|
+
if (routed) return routed;
|
|
831
|
+
}
|
|
832
|
+
|
|
750
833
|
// Regular tool permission request: send to client and wait
|
|
751
834
|
return new Promise(function(resolve) {
|
|
752
835
|
var requestId = crypto.randomUUID();
|
|
@@ -1540,25 +1623,33 @@ function createSDKBridge(opts) {
|
|
|
1540
1623
|
if (selectedUserInputMode === "fallback") {
|
|
1541
1624
|
sessionToolDefs = sessionToolDefs.concat(yoke.userInput.fallbackToolDefs(sessionUserInputHandler));
|
|
1542
1625
|
}
|
|
1626
|
+
function findSessionToolDef(toolName) {
|
|
1627
|
+
// Codex restores a thread's original dynamic-tool catalog on resume, but
|
|
1628
|
+
// pair state can change after this query starts. Resolve live session
|
|
1629
|
+
// handlers first so the dispatcher cannot lag behind that catalog. Keep
|
|
1630
|
+
// the query snapshot as a fallback for query-bound tools such as the
|
|
1631
|
+
// structured-input fallback, whose handler exists only for this turn.
|
|
1632
|
+
var liveToolDefs = getSessionToolDefs(session) || [];
|
|
1633
|
+
for (var liveIndex = 0; liveIndex < liveToolDefs.length; liveIndex++) {
|
|
1634
|
+
if (liveToolDefs[liveIndex].name === toolName) return liveToolDefs[liveIndex];
|
|
1635
|
+
}
|
|
1636
|
+
for (var snapshotIndex = 0; snapshotIndex < sessionToolDefs.length; snapshotIndex++) {
|
|
1637
|
+
if (sessionToolDefs[snapshotIndex].name === toolName) return sessionToolDefs[snapshotIndex];
|
|
1638
|
+
}
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1543
1641
|
|
|
1544
|
-
//
|
|
1545
|
-
//
|
|
1546
|
-
//
|
|
1642
|
+
// Give fresh queries an immediate provisional label. The visible user
|
|
1643
|
+
// message in history wins over `text`, which may contain an internal
|
|
1644
|
+
// provider wrapper rather than what the person typed.
|
|
1547
1645
|
// Only applied to NEW sessions (no cliSessionId yet) — when resuming,
|
|
1548
1646
|
// the SDK ignores Options.title in favor of the persisted title.
|
|
1549
1647
|
var initialTitle = null;
|
|
1648
|
+
var initialTitleProvisional = false;
|
|
1550
1649
|
if (!session.cliSessionId && !session.titleManuallySet && !session.titleAutoGenerated) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
} else if (typeof text === "string") {
|
|
1555
|
-
// Derive a quick first-line snippet from the user's first message.
|
|
1556
|
-
// Skip if too short to be meaningful — fall back to autoGenerateTitle.
|
|
1557
|
-
var firstLine = text.replace(/\s+/g, " ").trim();
|
|
1558
|
-
if (firstLine.length >= 10) {
|
|
1559
|
-
initialTitle = firstLine.length > 60 ? firstLine.substring(0, 60) : firstLine;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1650
|
+
var initialTitleChoice = sessionTitlePolicy.selectInitialTitle(session, text);
|
|
1651
|
+
initialTitle = initialTitleChoice.title;
|
|
1652
|
+
initialTitleProvisional = initialTitleChoice.provisional;
|
|
1562
1653
|
}
|
|
1563
1654
|
|
|
1564
1655
|
var queryOpts = {
|
|
@@ -1587,10 +1678,9 @@ function createSDKBridge(opts) {
|
|
|
1587
1678
|
toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
|
|
1588
1679
|
dynamicTools: sessionToolDefs,
|
|
1589
1680
|
callDynamicTool: function (toolName, args) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1681
|
+
var toolDef = findSessionToolDef(toolName);
|
|
1682
|
+
if (toolDef && typeof toolDef.handler === "function") {
|
|
1683
|
+
return toolDef.handler(args || {});
|
|
1594
1684
|
}
|
|
1595
1685
|
return Promise.reject(new Error("Session tool not found: " + toolName));
|
|
1596
1686
|
},
|
|
@@ -1598,12 +1688,8 @@ function createSDKBridge(opts) {
|
|
|
1598
1688
|
abortController: linuxUser ? undefined : session.abortController,
|
|
1599
1689
|
canUseTool: function(toolName, input, toolOpts) {
|
|
1600
1690
|
var permissionName = toolName;
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
permissionName = sessionToolDefs[pti].permissionName;
|
|
1604
|
-
break;
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1691
|
+
var permissionToolDef = findSessionToolDef(toolName);
|
|
1692
|
+
if (permissionToolDef && permissionToolDef.permissionName) permissionName = permissionToolDef.permissionName;
|
|
1607
1693
|
return handleCanUseTool(session, permissionName, input, toolOpts);
|
|
1608
1694
|
},
|
|
1609
1695
|
onUserInputRequest: function(request, respond) {
|
|
@@ -1653,16 +1739,16 @@ function createSDKBridge(opts) {
|
|
|
1653
1739
|
try {
|
|
1654
1740
|
handle = await sessionAdapter.createQuery(queryOpts);
|
|
1655
1741
|
console.log("[sdk-bridge] createQuery returned handle, vendor=" + sessionAdapter.vendor);
|
|
1656
|
-
// SDK accepted the explicit title
|
|
1657
|
-
//
|
|
1658
|
-
//
|
|
1742
|
+
// SDK accepted the explicit title. Provisional labels remain eligible
|
|
1743
|
+
// for semantic generation after the first Home exchange (or the normal
|
|
1744
|
+
// project threshold); seeded titles remain fixed.
|
|
1659
1745
|
if (initialTitle && !session.title) {
|
|
1660
1746
|
session.title = initialTitle;
|
|
1661
|
-
session.titleAutoGenerated =
|
|
1747
|
+
session.titleAutoGenerated = !initialTitleProvisional;
|
|
1662
1748
|
sm.saveSessionFile(session);
|
|
1663
1749
|
sm.broadcastSessionList();
|
|
1664
1750
|
} else if (initialTitle && session.title === initialTitle) {
|
|
1665
|
-
session.titleAutoGenerated =
|
|
1751
|
+
session.titleAutoGenerated = !initialTitleProvisional;
|
|
1666
1752
|
sm.saveSessionFile(session);
|
|
1667
1753
|
}
|
|
1668
1754
|
} catch (e) {
|
|
@@ -2035,6 +2121,7 @@ function createSDKBridge(opts) {
|
|
|
2035
2121
|
// No active query — just store the model for next startQuery
|
|
2036
2122
|
session.model = model;
|
|
2037
2123
|
sm.saveSessionFile(session);
|
|
2124
|
+
sm.broadcastSessionList();
|
|
2038
2125
|
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
2039
2126
|
// Confirm against the session vendor so a non-default vendor does not
|
|
2040
2127
|
// receive model metadata labeled as the project's default adapter.
|
|
@@ -2048,6 +2135,7 @@ function createSDKBridge(opts) {
|
|
|
2048
2135
|
await session.queryInstance.setModel(model);
|
|
2049
2136
|
session.model = model;
|
|
2050
2137
|
sm.saveSessionFile(session);
|
|
2138
|
+
sm.broadcastSessionList();
|
|
2051
2139
|
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
2052
2140
|
var sessionVendor = session.vendor || (adapter && adapter.vendor) || "claude";
|
|
2053
2141
|
sm.defaultModelByVendor[sessionVendor] = model;
|
|
@@ -2070,6 +2158,7 @@ function createSDKBridge(opts) {
|
|
|
2070
2158
|
sm.currentEffortByVendor[effortVendor] = clamped;
|
|
2071
2159
|
session.effort = clamped;
|
|
2072
2160
|
sm.saveSessionFile(session);
|
|
2161
|
+
sm.broadcastSessionList();
|
|
2073
2162
|
sm.currentEffort = clamped;
|
|
2074
2163
|
if (!session.queryInstance) {
|
|
2075
2164
|
return clamped;
|