clay-server 4.0.0-beta.9 → 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 -7
- 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
package/lib/server-tools.js
CHANGED
|
@@ -2,14 +2,29 @@ var toolsRegistry = require("./tools-registry");
|
|
|
2
2
|
var toolStorage = require("./tool-storage");
|
|
3
3
|
var crypto = require("crypto");
|
|
4
4
|
var toolLlm = require("./tool-llm");
|
|
5
|
+
var capsuleFloor = require("./capsule-display-floor");
|
|
6
|
+
var capsuleServerRuntimes = require("./capsule-server-runtimes");
|
|
7
|
+
var capsuleFrameServer = require("./capsule-frame-server");
|
|
8
|
+
var capsuleMateTurn = require("./capsule-mate-turn");
|
|
5
9
|
|
|
6
10
|
function attachTools(deps) {
|
|
7
11
|
var users = deps.users;
|
|
8
12
|
var projects = deps.projects;
|
|
9
|
-
var
|
|
13
|
+
var serverRuntimes = deps.serverRuntimes || capsuleServerRuntimes;
|
|
14
|
+
var frameServer = deps.frameServer || capsuleFrameServer.createCapsuleFrameServer({ tlsOptions: deps.tlsOptions || null });
|
|
15
|
+
var mateTurn = deps.mateTurn || capsuleMateTurn.attachCapsuleMateTurn({
|
|
16
|
+
users: users,
|
|
17
|
+
findMateProject: deps.findMateProject || null,
|
|
18
|
+
broadcastToUser: function (targetUserId, payload) { broadcastToUser(targetUserId, payload); },
|
|
19
|
+
});
|
|
10
20
|
var controlTimeoutMs = deps.controlTimeoutMs || 15000;
|
|
11
21
|
var llmControlTimeoutMs = deps.llmControlTimeoutMs || 90000;
|
|
12
22
|
var pendingControl = Object.create(null);
|
|
23
|
+
var isCapsulesEnabled = deps.isCapsulesEnabled || function () { return true; };
|
|
24
|
+
|
|
25
|
+
function requireCapsulesEnabled(userId) {
|
|
26
|
+
if (!isCapsulesEnabled(userId)) throw new Error("Capsules are disabled in User Settings.");
|
|
27
|
+
}
|
|
13
28
|
|
|
14
29
|
function contextFor(userId) {
|
|
15
30
|
var multiUser = users.isMultiUser();
|
|
@@ -68,6 +83,7 @@ function attachTools(deps) {
|
|
|
68
83
|
}
|
|
69
84
|
|
|
70
85
|
function installedManifests(userId) {
|
|
86
|
+
if (!isCapsulesEnabled(userId)) return [];
|
|
71
87
|
var ctx = contextFor(userId);
|
|
72
88
|
return toolsRegistry.listTools(ctx).filter(function (manifest) { return !manifest.error; });
|
|
73
89
|
}
|
|
@@ -137,20 +153,52 @@ function attachTools(deps) {
|
|
|
137
153
|
});
|
|
138
154
|
}
|
|
139
155
|
|
|
156
|
+
// One act pipeline for server-runtime Capsules. The human's button click and
|
|
157
|
+
// the Mate's clay_tool_act call both arrive here, so the same Logic enforces
|
|
158
|
+
// the same rules for both and neither gets a private path. The seat is set
|
|
159
|
+
// from the server-resolved actor, never from caller-supplied text.
|
|
160
|
+
//
|
|
161
|
+
// The caller gets state and nothing else: a Mate never sees Display. Every
|
|
162
|
+
// successful act additionally pushes its causal {seq, actor, action,
|
|
163
|
+
// previous, next} event to the user's own connected clients, so a watching
|
|
164
|
+
// Display re-renders on any Logic change no matter who caused it.
|
|
165
|
+
async function controlServerCapsule(userId, installed, actor, callerId, kind, payload) {
|
|
166
|
+
var runtime = serverRuntimes.createRuntime(installed.manifest.id, contextFor(userId));
|
|
167
|
+
if (!runtime) throw new Error("No trusted server runtime is registered for this capsule.");
|
|
168
|
+
var context = { userId: userId, actor: actor, callerId: callerId };
|
|
169
|
+
if (kind === "snapshot") return runtime.snapshot(context);
|
|
170
|
+
if (kind === "act") {
|
|
171
|
+
var result = await runtime.act(context, payload.actionId, payload.args || {});
|
|
172
|
+
if (result && result.event) {
|
|
173
|
+
broadcastToUser(userId, { type: "tool_server_event", toolId: installed.manifest.id, event: result.event });
|
|
174
|
+
// The game round-trips: a Mate act names the opponent seat, and a
|
|
175
|
+
// human act that hands the turn over wakes that Mate to play it.
|
|
176
|
+
if (actor === "mate" && callerId) mateTurn.rememberOpponent(userId, installed.manifest.id, callerId);
|
|
177
|
+
mateTurn.maybeNudge(userId, installed.manifest, actor, result.event);
|
|
178
|
+
return result.state;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
if (kind === "set" && runtime.set) return runtime.set(context, payload.controlId, payload.value);
|
|
183
|
+
throw new Error("The server-runtime capsule does not support '" + kind + "'.");
|
|
184
|
+
}
|
|
185
|
+
|
|
140
186
|
function controlForMate(userId, mateId, toolId, kind, payload) {
|
|
187
|
+
requireCapsulesEnabled(userId);
|
|
141
188
|
payload = payload || {};
|
|
142
189
|
var ctx = contextFor(userId);
|
|
143
190
|
toolsRegistry.listTools(ctx);
|
|
144
191
|
var installed = toolsRegistry.getTool(ctx, toolId);
|
|
145
192
|
if (!installed) return Promise.reject(new Error("Tool is not installed."));
|
|
193
|
+
// Skills are gated by Display: the Mate loses a Capsule at the same moment
|
|
194
|
+
// the human loses the declarative floor it is operated through.
|
|
195
|
+
if (!capsuleFloor.hasUsableFloor(installed.manifest, installed.uiTree)) {
|
|
196
|
+
return Promise.reject(new Error("This Capsule has no usable human Display, so it is unavailable to Mates."));
|
|
197
|
+
}
|
|
146
198
|
if (installed.manifest.runtime === "server") {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (kind === "snapshot") return Promise.resolve(adapter.snapshot(context));
|
|
151
|
-
if (kind === "act") return Promise.resolve(adapter.act(context, payload.actionId, payload.args || {}));
|
|
152
|
-
if (kind === "set" && adapter.set) return Promise.resolve(adapter.set(context, payload.controlId, payload.value));
|
|
153
|
-
return Promise.reject(new Error("The server-runtime capsule does not support '" + kind + "'."));
|
|
199
|
+
return Promise.resolve().then(function () {
|
|
200
|
+
return controlServerCapsule(userId, installed, "mate", mateId, kind, payload);
|
|
201
|
+
});
|
|
154
202
|
}
|
|
155
203
|
var permissions = installed.manifest.permissions || [];
|
|
156
204
|
var timeoutMs = kind === "act" && permissions.indexOf("llm") !== -1 ? llmControlTimeoutMs : controlTimeoutMs;
|
|
@@ -211,6 +259,7 @@ function attachTools(deps) {
|
|
|
211
259
|
}
|
|
212
260
|
|
|
213
261
|
function installForMate(userId, input) {
|
|
262
|
+
requireCapsulesEnabled(userId);
|
|
214
263
|
var installed = toolsRegistry.installTool(contextFor(userId), input);
|
|
215
264
|
refreshMateCatalogs(userId);
|
|
216
265
|
broadcastToUser(userId, { type: "tool_installed", tool: installed });
|
|
@@ -218,6 +267,7 @@ function attachTools(deps) {
|
|
|
218
267
|
}
|
|
219
268
|
|
|
220
269
|
function sourceForMate(userId, toolId) {
|
|
270
|
+
requireCapsulesEnabled(userId);
|
|
221
271
|
var ctx = contextFor(userId);
|
|
222
272
|
var installed = toolsRegistry.getTool(ctx, toolId);
|
|
223
273
|
if (!installed) throw new Error("Tool not found.");
|
|
@@ -229,6 +279,7 @@ function attachTools(deps) {
|
|
|
229
279
|
}
|
|
230
280
|
|
|
231
281
|
function updateForMate(userId, toolId, input) {
|
|
282
|
+
requireCapsulesEnabled(userId);
|
|
232
283
|
var ctx = contextFor(userId);
|
|
233
284
|
var installed = toolsRegistry.getTool(ctx, toolId);
|
|
234
285
|
if (!installed) throw new Error("Tool not found.");
|
|
@@ -243,6 +294,7 @@ function attachTools(deps) {
|
|
|
243
294
|
}
|
|
244
295
|
|
|
245
296
|
function setMateAccess(userId, toolId, allowed, requestId) {
|
|
297
|
+
requireCapsulesEnabled(userId);
|
|
246
298
|
var ctx = contextFor(userId);
|
|
247
299
|
var tool = toolsRegistry.getTool(ctx, toolId);
|
|
248
300
|
if (!tool) throw new Error("Tool not found.");
|
|
@@ -255,6 +307,7 @@ function attachTools(deps) {
|
|
|
255
307
|
}
|
|
256
308
|
|
|
257
309
|
function removeForMate(userId, toolId) {
|
|
310
|
+
requireCapsulesEnabled(userId);
|
|
258
311
|
var removed = toolsRegistry.removeTool(contextFor(userId), toolId);
|
|
259
312
|
if (!removed) throw new Error("Tool not found.");
|
|
260
313
|
refreshMateCatalogs(userId);
|
|
@@ -262,8 +315,19 @@ function attachTools(deps) {
|
|
|
262
315
|
return { removed: true, toolId: toolId };
|
|
263
316
|
}
|
|
264
317
|
|
|
318
|
+
// The human half of the shared server-Capsule pipeline. Same Logic, same
|
|
319
|
+
// rules, no AI in the path.
|
|
320
|
+
function controlForUser(userId, toolId, kind, payload) {
|
|
321
|
+
requireCapsulesEnabled(userId);
|
|
322
|
+
var ctx = contextFor(userId);
|
|
323
|
+
var installed = toolsRegistry.getTool(ctx, toolId);
|
|
324
|
+
if (!installed) throw new Error("Tool is not installed.");
|
|
325
|
+
if (installed.manifest.runtime !== "server") throw new Error("This Capsule does not run on the server.");
|
|
326
|
+
return controlServerCapsule(userId, installed, "human", "user", kind, payload || {});
|
|
327
|
+
}
|
|
328
|
+
|
|
265
329
|
function handleMessage(ws, msg) {
|
|
266
|
-
var messageTypes = ["tools_list", "tool_get", "tool_install", "tool_remove", "tool_storage_op", "tool_llm_op", "tool_llm_config_get", "tool_control_response", "tool_source_get", "tool_mate_access_set"];
|
|
330
|
+
var messageTypes = ["tools_list", "tool_get", "tool_install", "tool_remove", "tool_storage_op", "tool_llm_op", "tool_llm_config_get", "tool_control_response", "tool_source_get", "tool_mate_access_set", "tool_server_control", "tool_frame_url"];
|
|
267
331
|
if (!msg || messageTypes.indexOf(msg.type) === -1) return false;
|
|
268
332
|
var userId;
|
|
269
333
|
if (users.isMultiUser()) {
|
|
@@ -279,6 +343,12 @@ function attachTools(deps) {
|
|
|
279
343
|
return true;
|
|
280
344
|
}
|
|
281
345
|
|
|
346
|
+
if (!isCapsulesEnabled(userId)) {
|
|
347
|
+
if (msg.type === "tools_list") send(ws, { type: "tools_state", tools: [] });
|
|
348
|
+
else sendError(ws, msg, new Error("Capsules are disabled in User Settings."));
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
282
352
|
if (msg.type === "tools_list") {
|
|
283
353
|
run(ws, msg, function () {
|
|
284
354
|
send(ws, { type: "tools_state", tools: fullToolList(ctx) });
|
|
@@ -312,6 +382,52 @@ function attachTools(deps) {
|
|
|
312
382
|
});
|
|
313
383
|
return true;
|
|
314
384
|
}
|
|
385
|
+
if (msg.type === "tool_frame_url") {
|
|
386
|
+
// The rich element renders on a separate anonymous origin, so the frame
|
|
387
|
+
// URL is minted here, on the authenticated socket, as a short-lived
|
|
388
|
+
// one-time token bound to this user's tools root. The rich element is
|
|
389
|
+
// additive only: it exists solely when the floor already validated at
|
|
390
|
+
// registration, and losing it never costs the human the floor.
|
|
391
|
+
Promise.resolve().then(function () {
|
|
392
|
+
var ctx = contextFor(userId);
|
|
393
|
+
var installed = toolsRegistry.getTool(ctx, msg.toolId);
|
|
394
|
+
if (!installed) throw new Error("Tool is not installed.");
|
|
395
|
+
if (!installed.hasRichDisplay) throw new Error("This Capsule ships no rich Display element.");
|
|
396
|
+
return frameServer.issueFrameUrl(ctx, msg.toolId);
|
|
397
|
+
}).then(function (frame) {
|
|
398
|
+
send(ws, { type: "tool_frame_url_state", toolId: msg.toolId, requestId: msg.requestId || null, ok: true, frame: frame });
|
|
399
|
+
}).catch(function (error) {
|
|
400
|
+
send(ws, {
|
|
401
|
+
type: "tool_frame_url_state",
|
|
402
|
+
toolId: msg.toolId,
|
|
403
|
+
requestId: msg.requestId || null,
|
|
404
|
+
ok: false,
|
|
405
|
+
error: error && error.message ? error.message : "The rich Display is unavailable.",
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
if (msg.type === "tool_server_control") {
|
|
411
|
+
Promise.resolve().then(function () {
|
|
412
|
+
return controlForUser(userId, msg.toolId, msg.kind, {
|
|
413
|
+
actionId: msg.actionId,
|
|
414
|
+
args: msg.args || {},
|
|
415
|
+
controlId: msg.controlId,
|
|
416
|
+
value: msg.value,
|
|
417
|
+
});
|
|
418
|
+
}).then(function (state) {
|
|
419
|
+
send(ws, { type: "tool_server_state", toolId: msg.toolId, requestId: msg.requestId || null, ok: true, state: state });
|
|
420
|
+
}).catch(function (error) {
|
|
421
|
+
send(ws, {
|
|
422
|
+
type: "tool_server_state",
|
|
423
|
+
toolId: msg.toolId,
|
|
424
|
+
requestId: msg.requestId || null,
|
|
425
|
+
ok: false,
|
|
426
|
+
error: error && error.message ? error.message : "The Capsule action failed.",
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
315
431
|
if (msg.type === "tool_install") {
|
|
316
432
|
run(ws, msg, function () {
|
|
317
433
|
installForMate(userId, {
|
|
@@ -366,11 +482,13 @@ function attachTools(deps) {
|
|
|
366
482
|
handleMessage: handleMessage,
|
|
367
483
|
installedManifests: installedManifests,
|
|
368
484
|
controlForMate: controlForMate,
|
|
485
|
+
controlForUser: controlForUser,
|
|
369
486
|
installForMate: installForMate,
|
|
370
487
|
sourceForMate: sourceForMate,
|
|
371
488
|
updateForMate: updateForMate,
|
|
372
489
|
setMateAccess: setMateAccess,
|
|
373
490
|
removeForMate: removeForMate,
|
|
491
|
+
refreshMateCatalogs: refreshMateCatalogs,
|
|
374
492
|
};
|
|
375
493
|
}
|
|
376
494
|
|
package/lib/server.js
CHANGED
|
@@ -23,6 +23,8 @@ var serverPalette = require("./server-palette");
|
|
|
23
23
|
var workspaceQueryModule = require("./workspace-query-service");
|
|
24
24
|
var { attachWorkspaceQueryService } = workspaceQueryModule;
|
|
25
25
|
var { attachWorkspaceAssignmentService } = require("./workspace-assignment-service");
|
|
26
|
+
var { attachProjectLogsService } = require("./project-logs-service");
|
|
27
|
+
var { attachMateKnowledgeService } = require("./mate-knowledge-service");
|
|
26
28
|
var serverEmail = require("./server-email");
|
|
27
29
|
var serverGlobalWs = require("./server-global-ws");
|
|
28
30
|
var { parseWsRequestUrl } = require("./ws-request");
|
|
@@ -122,6 +124,14 @@ function stripPrefix(urlPath, slug) {
|
|
|
122
124
|
return rest || "/";
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
function injectRootProjectSlug(html, slug) {
|
|
128
|
+
var bodyPattern = /<body\b([^>]*)>/i;
|
|
129
|
+
if (!bodyPattern.test(html)) throw new Error("App shell body is missing");
|
|
130
|
+
return html.replace(bodyPattern, function (_match, attributes) {
|
|
131
|
+
return '<body' + attributes + ' data-home-project-slug="' + slug + '">';
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
/**
|
|
126
136
|
* Create a multi-project server.
|
|
127
137
|
* opts: { tlsOptions, caPath, pinHash, port, debug, dangerouslySkipPermissions }
|
|
@@ -306,18 +316,50 @@ function createServer(opts) {
|
|
|
306
316
|
var workspaceQueryService = attachWorkspaceQueryService({
|
|
307
317
|
getProjects: function () { return projects; },
|
|
308
318
|
isMultiUser: function () { return users.isMultiUser(); },
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
// Project access is resolved from the authoritative project record, not
|
|
320
|
+
// from getStatus(), which carries no visibility or allowedUsers fields and
|
|
321
|
+
// would otherwise default every project to public.
|
|
322
|
+
onGetProjectAccess: onGetProjectAccess,
|
|
323
|
+
canAccessProject: function (userId, access) {
|
|
324
|
+
return users.canAccessProject(userId, access) === true;
|
|
315
325
|
},
|
|
316
326
|
resolveMate: function (userId, mateId) {
|
|
317
327
|
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
318
328
|
},
|
|
319
329
|
assignmentService: assignmentService,
|
|
320
330
|
});
|
|
331
|
+
// Project access is resolved from the authoritative project record, not from
|
|
332
|
+
// getStatus(), which carries no visibility or allowedUsers fields.
|
|
333
|
+
var projectLogsService = attachProjectLogsService({
|
|
334
|
+
getProjects: function () { return projects; },
|
|
335
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
336
|
+
canAccessProject: function (userId, status) {
|
|
337
|
+
if (!status || !status.slug || !onGetProjectAccess) return false;
|
|
338
|
+
var access = onGetProjectAccess(status.slug);
|
|
339
|
+
if (!access || access.error) return false;
|
|
340
|
+
return users.canAccessProject(userId, access) === true;
|
|
341
|
+
},
|
|
342
|
+
resolveMate: function (userId, mateId) {
|
|
343
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
344
|
+
},
|
|
345
|
+
findUserById: function (userId) { return users.findUserById(userId); },
|
|
346
|
+
});
|
|
347
|
+
// Mate Knowledge reads the synchronized Knowledge records. The Mate list is
|
|
348
|
+
// derived from the owning user alone, so a binding can never span users.
|
|
349
|
+
var mateKnowledgeService = attachMateKnowledgeService({
|
|
350
|
+
getProjects: function () { return projects; },
|
|
351
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
352
|
+
resolveMate: function (userId, mateId) {
|
|
353
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
354
|
+
},
|
|
355
|
+
listMates: function (userId) {
|
|
356
|
+
var mateCtx = mates.buildMateCtx(users.isMultiUser() ? userId : null);
|
|
357
|
+
var all = mates.getAllMates(mateCtx) || [];
|
|
358
|
+
return all.map(function (mate) {
|
|
359
|
+
return { id: mate.id, name: mate.name || null, createdBy: mate.createdBy || null, dir: mates.getMateDir(mateCtx, mate.id) };
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
});
|
|
321
363
|
|
|
322
364
|
// --- Admin module ---
|
|
323
365
|
var admin = serverAdmin.attachAdmin({
|
|
@@ -351,6 +393,9 @@ function createServer(opts) {
|
|
|
351
393
|
projects: projects,
|
|
352
394
|
opts: opts,
|
|
353
395
|
CONFIG_DIR: CONFIG_DIR,
|
|
396
|
+
onCapsulesPreferenceChanged: function (userId) {
|
|
397
|
+
if (toolsHandler) toolsHandler.refreshMateCatalogs(userId);
|
|
398
|
+
},
|
|
354
399
|
});
|
|
355
400
|
|
|
356
401
|
var palette = serverPalette.attachPalette({
|
|
@@ -388,16 +433,38 @@ function createServer(opts) {
|
|
|
388
433
|
securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
|
|
389
434
|
}
|
|
390
435
|
|
|
391
|
-
|
|
436
|
+
// Capsule rich-Display frames are served by a second listener on the same
|
|
437
|
+
// hostname but a different port, so frame-src cannot stay at the default
|
|
438
|
+
// 'self'. The allowance is computed from the request's own hostname with
|
|
439
|
+
// any port, never a wildcard host, and only for the scheme the app itself
|
|
440
|
+
// speaks. If the host header is unusable the CSP stays at 'self' and the
|
|
441
|
+
// frame silently falls back to the floor Display.
|
|
442
|
+
function frameSrcFor(req) {
|
|
443
|
+
var host = req && req.headers && typeof req.headers.host === "string" ? req.headers.host : "";
|
|
444
|
+
if (!host) return "'self'";
|
|
445
|
+
var scheme = tlsOptions ? "https" : "http";
|
|
446
|
+
var hostname;
|
|
447
|
+
try {
|
|
448
|
+
hostname = new URL(scheme + "://" + host).hostname;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return "'self'";
|
|
451
|
+
}
|
|
452
|
+
if (!hostname) return "'self'";
|
|
453
|
+
if (hostname.indexOf(":") !== -1) hostname = "[" + hostname + "]";
|
|
454
|
+
return "'self' " + scheme + "://" + hostname + ":*";
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function setSecurityHeaders(res, req) {
|
|
392
458
|
var keys = Object.keys(securityHeaders);
|
|
393
459
|
for (var i = 0; i < keys.length; i++) {
|
|
394
460
|
res.setHeader(keys[i], securityHeaders[keys[i]]);
|
|
395
461
|
}
|
|
462
|
+
res.setHeader("Content-Security-Policy", securityHeaders["Content-Security-Policy"] + " frame-src " + frameSrcFor(req) + ";");
|
|
396
463
|
}
|
|
397
464
|
|
|
398
465
|
// --- HTTP handler ---
|
|
399
466
|
var appHandler = function (req, res) {
|
|
400
|
-
setSecurityHeaders(res);
|
|
467
|
+
setSecurityHeaders(res, req);
|
|
401
468
|
var fullUrl = req.url.split("?")[0];
|
|
402
469
|
|
|
403
470
|
// --- Auth routes (delegated to server-auth) ---
|
|
@@ -682,7 +749,8 @@ function createServer(opts) {
|
|
|
682
749
|
// --- Skills routes (delegated to server-skills) ---
|
|
683
750
|
if (skills.handleRequest(req, res, fullUrl)) return;
|
|
684
751
|
|
|
685
|
-
// Root path — render
|
|
752
|
+
// Root path — render the default project workspace while keeping Home
|
|
753
|
+
// available as an explicit client-side surface.
|
|
686
754
|
if (fullUrl === "/" && req.method === "GET") {
|
|
687
755
|
if (!isRequestAuthed(req)) {
|
|
688
756
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -692,11 +760,16 @@ function createServer(opts) {
|
|
|
692
760
|
if (projects.size > 0) {
|
|
693
761
|
var targetSlug = null;
|
|
694
762
|
var reqUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
|
|
763
|
+
function isOrdinaryProject(slug) {
|
|
764
|
+
var candidate = slug ? projects.get(slug) : null;
|
|
765
|
+
var status = candidate && candidate.getStatus ? candidate.getStatus() : null;
|
|
766
|
+
return !!candidate && (!status || status.isMate !== true);
|
|
767
|
+
}
|
|
695
768
|
var homePreference = typeof users.getHomeSurfacePreference === "function"
|
|
696
769
|
? users.getHomeSurfacePreference(reqUser ? reqUser.id : "default")
|
|
697
770
|
: null;
|
|
698
771
|
var preferredContextSlug = homePreference && homePreference.projectSlug;
|
|
699
|
-
if (
|
|
772
|
+
if (isOrdinaryProject(preferredContextSlug)) {
|
|
700
773
|
if (reqUser && onGetProjectAccess) {
|
|
701
774
|
var preferredAccess = onGetProjectAccess(preferredContextSlug);
|
|
702
775
|
if (preferredAccess && !preferredAccess.error && users.canAccessProject(reqUser.id, preferredAccess)) {
|
|
@@ -708,7 +781,7 @@ function createServer(opts) {
|
|
|
708
781
|
}
|
|
709
782
|
// Check for last-visited project cookie
|
|
710
783
|
var lastProject = parseCookies(req)["clay_last_project"];
|
|
711
|
-
if (!targetSlug &&
|
|
784
|
+
if (!targetSlug && isOrdinaryProject(lastProject)) {
|
|
712
785
|
if (reqUser && onGetProjectAccess) {
|
|
713
786
|
var lpAccess = onGetProjectAccess(lastProject);
|
|
714
787
|
if (lpAccess && !lpAccess.error && users.canAccessProject(reqUser.id, lpAccess)) {
|
|
@@ -722,6 +795,7 @@ function createServer(opts) {
|
|
|
722
795
|
if (!targetSlug) {
|
|
723
796
|
projects.forEach(function (ctx, s) {
|
|
724
797
|
if (targetSlug) return;
|
|
798
|
+
if (!isOrdinaryProject(s)) return;
|
|
725
799
|
if (reqUser && onGetProjectAccess) {
|
|
726
800
|
var access = onGetProjectAccess(s);
|
|
727
801
|
if (access && !access.error && users.canAccessProject(reqUser.id, access)) {
|
|
@@ -735,7 +809,7 @@ function createServer(opts) {
|
|
|
735
809
|
if (targetSlug) {
|
|
736
810
|
try {
|
|
737
811
|
var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
|
|
738
|
-
homeHtml = homeHtml
|
|
812
|
+
homeHtml = injectRootProjectSlug(homeHtml, targetSlug);
|
|
739
813
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
740
814
|
res.end(homeHtml);
|
|
741
815
|
} catch (e) {
|
|
@@ -937,17 +1011,24 @@ function createServer(opts) {
|
|
|
937
1011
|
|
|
938
1012
|
// --- WebSocket ---
|
|
939
1013
|
var wss = new WebSocketServer({ noServer: true });
|
|
1014
|
+
var homePreferencesHandler = null;
|
|
940
1015
|
|
|
941
1016
|
// Slug-less /ws handler: lets a user with no projects yet load the regular
|
|
942
1017
|
// app shell and create/add/clone their first one. Only knows about the
|
|
943
|
-
// small set of bootstrap messages
|
|
944
|
-
//
|
|
1018
|
+
// small set of bootstrap messages, including the Home preferences required
|
|
1019
|
+
// to dismiss the workspace restoration screen.
|
|
945
1020
|
var globalWs = serverGlobalWs.attachGlobalWs({
|
|
946
1021
|
osUsers: osUsers,
|
|
947
1022
|
usersModule: users,
|
|
948
1023
|
onAddProject: onAddProject,
|
|
949
1024
|
onCreateProject: onCreateProject,
|
|
950
1025
|
onCloneProject: onCloneProject,
|
|
1026
|
+
onHomePreferenceMessage: function (ws, msg) {
|
|
1027
|
+
return homePreferencesHandler ? homePreferencesHandler.handleMessage(ws, msg) : false;
|
|
1028
|
+
},
|
|
1029
|
+
onAppMessage: function (ws, msg) {
|
|
1030
|
+
return handleDmMessage(ws, msg);
|
|
1031
|
+
},
|
|
951
1032
|
});
|
|
952
1033
|
|
|
953
1034
|
server.on("upgrade", function (req, socket, head) {
|
|
@@ -1182,6 +1263,7 @@ function createServer(opts) {
|
|
|
1182
1263
|
title: title || null,
|
|
1183
1264
|
icon: icon || null,
|
|
1184
1265
|
projectOwnerId: projectOwnerId || null,
|
|
1266
|
+
projectKnowledgeId: extra.projectKnowledgeId || (worktreeMeta && worktreeMeta.projectKnowledgeId) || null,
|
|
1185
1267
|
worktreeMeta: worktreeMeta || null,
|
|
1186
1268
|
isMate: extra.isMate || false,
|
|
1187
1269
|
mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
|
|
@@ -1192,6 +1274,21 @@ function createServer(opts) {
|
|
|
1192
1274
|
return mateHandler.createReadyMateFromInterview(ws, userId, definition);
|
|
1193
1275
|
},
|
|
1194
1276
|
workspaceQueryService: workspaceQueryService,
|
|
1277
|
+
projectLogsService: projectLogsService,
|
|
1278
|
+
mateKnowledgeService: mateKnowledgeService,
|
|
1279
|
+
canAccessProjectSlug: function (userId, targetSlug) {
|
|
1280
|
+
var target = projects.get(targetSlug);
|
|
1281
|
+
if (!target) return false;
|
|
1282
|
+
var status = target.getStatus();
|
|
1283
|
+
var access;
|
|
1284
|
+
if (status.isMate) {
|
|
1285
|
+
access = { visibility: "private", ownerId: status.projectOwnerId, allowedUsers: [] };
|
|
1286
|
+
} else {
|
|
1287
|
+
if (!onGetProjectAccess) return false;
|
|
1288
|
+
access = onGetProjectAccess(status.parentSlug || targetSlug);
|
|
1289
|
+
}
|
|
1290
|
+
return !!(access && !access.error && users.canAccessProject(userId, access));
|
|
1291
|
+
},
|
|
1195
1292
|
getProjectAccess: function () {
|
|
1196
1293
|
return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
|
|
1197
1294
|
},
|
|
@@ -1361,6 +1458,10 @@ function createServer(opts) {
|
|
|
1361
1458
|
return true;
|
|
1362
1459
|
}
|
|
1363
1460
|
|
|
1461
|
+
function setProjectLogContextState(slug, status) {
|
|
1462
|
+
return projectLogsService.setContextState(slug, status);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1364
1465
|
// --- DM message handler (delegated to server-dm + server-mates inline) ---
|
|
1365
1466
|
var dmHandler = serverDm.attachDm({
|
|
1366
1467
|
users: users,
|
|
@@ -1381,9 +1482,10 @@ function createServer(opts) {
|
|
|
1381
1482
|
projects: projects,
|
|
1382
1483
|
addProject: addProject,
|
|
1383
1484
|
workspaceQueryService: workspaceQueryService,
|
|
1485
|
+
projectLogsService: projectLogsService,
|
|
1384
1486
|
});
|
|
1385
1487
|
|
|
1386
|
-
|
|
1488
|
+
homePreferencesHandler = serverHomePreferences.attachHomePreferences({
|
|
1387
1489
|
users: users,
|
|
1388
1490
|
projects: projects,
|
|
1389
1491
|
});
|
|
@@ -1392,6 +1494,17 @@ function createServer(opts) {
|
|
|
1392
1494
|
var toolsHandler = serverTools.attachTools({
|
|
1393
1495
|
users: users,
|
|
1394
1496
|
projects: projects,
|
|
1497
|
+
// The rich-Display frame listener must speak the same scheme as the app,
|
|
1498
|
+
// or the sandboxed frame would be blocked as mixed content under TLS.
|
|
1499
|
+
tlsOptions: tlsOptions,
|
|
1500
|
+
// Lets a Capsule turn handed to the mate seat wake the opponent Mate's
|
|
1501
|
+
// project, registering it first if it has never been opened.
|
|
1502
|
+
findMateProject: homeChatHandler.findMateProject,
|
|
1503
|
+
isCapsulesEnabled: function (userId) {
|
|
1504
|
+
if (users.isMultiUser()) return users.getCapsulesEnabled(userId);
|
|
1505
|
+
var daemonConfig = onGetDaemonConfig ? onGetDaemonConfig() : {};
|
|
1506
|
+
return daemonConfig.capsulesEnabled === true;
|
|
1507
|
+
},
|
|
1395
1508
|
});
|
|
1396
1509
|
|
|
1397
1510
|
// --- Mate handler ---
|
|
@@ -1402,13 +1515,14 @@ function createServer(opts) {
|
|
|
1402
1515
|
}
|
|
1403
1516
|
|
|
1404
1517
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1405
|
-
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return;
|
|
1406
|
-
if (dmHandler.handleMessage(ws, msg)) return;
|
|
1407
|
-
if (mateHandler && mateHandler.handleMessage(ws, msg)) return;
|
|
1408
|
-
if (emailHandler.handleMessage(ws, msg)) return;
|
|
1409
|
-
if (homeChatHandler.handleMessage(ws, msg)) return;
|
|
1410
|
-
if (homePreferencesHandler.handleMessage(ws, msg)) return;
|
|
1411
|
-
if (toolsHandler.handleMessage(ws, msg)) return;
|
|
1518
|
+
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1519
|
+
if (dmHandler.handleMessage(ws, msg)) return true;
|
|
1520
|
+
if (mateHandler && mateHandler.handleMessage(ws, msg)) return true;
|
|
1521
|
+
if (emailHandler.handleMessage(ws, msg)) return true;
|
|
1522
|
+
if (homeChatHandler.handleMessage(ws, msg)) return true;
|
|
1523
|
+
if (homePreferencesHandler.handleMessage(ws, msg)) return true;
|
|
1524
|
+
if (toolsHandler.handleMessage(ws, msg)) return true;
|
|
1525
|
+
return false;
|
|
1412
1526
|
}
|
|
1413
1527
|
|
|
1414
1528
|
function removeProject(slug) {
|
|
@@ -1702,6 +1816,7 @@ function createServer(opts) {
|
|
|
1702
1816
|
forEachProject: forEachProject,
|
|
1703
1817
|
destroyProject: removeProject,
|
|
1704
1818
|
destroyAll: destroyAll,
|
|
1819
|
+
setProjectLogContextState: setProjectLogContextState,
|
|
1705
1820
|
};
|
|
1706
1821
|
}
|
|
1707
1822
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Driver capability for a visible Driver/Split Worker pair.
|
|
2
|
+
//
|
|
3
|
+
// Model and vendor choice belongs to the user. A session does not need to
|
|
4
|
+
// clear a model-tier threshold to coordinate a Split Worker; runtime catalog
|
|
5
|
+
// validation belongs to session-pair-factory.js and applies equally to every
|
|
6
|
+
// requested role. The only session-level exclusion here is structural: an
|
|
7
|
+
// embedded terminal has no project chat surface on which to host the Driver
|
|
8
|
+
// controls or the paired pane.
|
|
9
|
+
|
|
10
|
+
var sessionProvenance = require("./session-provenance");
|
|
11
|
+
|
|
12
|
+
function evaluateDriverSession(session) {
|
|
13
|
+
if (!session) {
|
|
14
|
+
return { ok: false, error: "No session was bound to this request." };
|
|
15
|
+
}
|
|
16
|
+
if (session.mode === "tui") {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
error: "An embedded terminal session cannot take the Driver role.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (sessionProvenance.isWorker(session)) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
error: "A Split Worker session cannot take the Driver role.",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, error: null };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isEligibleDriverSession(session) {
|
|
32
|
+
return evaluateDriverSession(session).ok;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
evaluateDriverSession: evaluateDriverSession,
|
|
37
|
+
isEligibleDriverSession: isEligibleDriverSession,
|
|
38
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Model-tier policy for proactive Split Worker orchestration.
|
|
2
|
+
//
|
|
3
|
+
// Structural Driver eligibility remains model-agnostic. This policy only
|
|
4
|
+
// decides whether a model should be prompted to judge when delegation helps.
|
|
5
|
+
|
|
6
|
+
var HIGH_TIER_PATTERNS = {
|
|
7
|
+
claude: [/(?:^|-)fable(?:-|$)/i, /(?:^|-)opus(?:-|$)/i],
|
|
8
|
+
codex: [/^gpt-6(?:-|$)/i, /^gpt-[0-9]+(?:\.[0-9]+)?-sol(?:-|$)/i],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function resolvedModel(session, sm) {
|
|
12
|
+
if (!session) return "";
|
|
13
|
+
var vendor = session.vendor || "claude";
|
|
14
|
+
var defaults = sm && sm.defaultModelByVendor || {};
|
|
15
|
+
return session.model || defaults[vendor] || "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isHighTierDriverSession(session, sm) {
|
|
19
|
+
if (!session) return false;
|
|
20
|
+
var patterns = HIGH_TIER_PATTERNS[session.vendor || "claude"] || [];
|
|
21
|
+
var model = resolvedModel(session, sm);
|
|
22
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
23
|
+
if (patterns[i].test(model)) return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
isHighTierDriverSession: isHighTierDriverSession,
|
|
30
|
+
resolvedModel: resolvedModel,
|
|
31
|
+
};
|
package/lib/session-hygiene.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// "New session" request and which abandoned blanks are safe to sweep.
|
|
3
3
|
// Pure decision logic only -- deletion/switching stays in sessions.js.
|
|
4
4
|
|
|
5
|
+
var sessionProvenance = require("./session-provenance");
|
|
6
|
+
|
|
5
7
|
var BLANK_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
6
8
|
var CLAUDE_INTERRUPTED_TEXT = "[Request interrupted by user]";
|
|
7
9
|
|
|
@@ -19,6 +21,7 @@ function isBlankSession(s) {
|
|
|
19
21
|
&& !s.bookmarked
|
|
20
22
|
&& !s.spawn
|
|
21
23
|
&& !s.loop
|
|
24
|
+
&& !sessionProvenance.isWorker(s)
|
|
22
25
|
&& (s.mode || "gui") !== "tui"
|
|
23
26
|
&& s.terminalId == null;
|
|
24
27
|
}
|