clay-server 4.1.0-beta.5 → 4.1.0-beta.7
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/LICENSE +661 -21
- package/LICENSES/MIT-legacy.txt +21 -0
- package/LICENSES/README.md +7 -0
- package/README.md +2 -2
- package/bin/cli.js +0 -2
- package/lib/autonomous-run-record.js +32 -0
- package/lib/autonomous-run-scheduler.js +92 -0
- package/lib/daemon.js +0 -2
- package/lib/default-ai-runtime.js +117 -0
- package/lib/durable-scheduler-record.js +61 -0
- package/lib/durable-scheduler-store.js +185 -0
- package/lib/durable-scheduler.js +495 -0
- package/lib/git-cli.js +1 -0
- package/lib/issue-work-session.js +10 -0
- package/lib/issues-comments.js +40 -0
- package/lib/issues-commit-verification.js +30 -0
- package/lib/issues-mcp-server.js +27 -0
- package/lib/issues-schema.js +119 -0
- package/lib/issues-service.js +339 -0
- package/lib/issues-store.js +210 -0
- package/lib/loop-cron.js +47 -0
- package/lib/loop-durable-schedule.js +159 -0
- package/lib/loop-guidance.js +35 -0
- package/lib/loop-registry-occurrence.js +76 -0
- package/lib/model-entry.js +15 -0
- package/lib/project-autonomous-run.js +498 -0
- package/lib/project-capsule-turn.js +1 -0
- package/lib/project-connection.js +6 -1
- package/lib/project-delegated-session.js +1 -0
- package/lib/project-filesystem.js +10 -10
- package/lib/project-full-access.js +86 -0
- package/lib/project-http.js +3 -1
- package/lib/project-issue-launch.js +154 -0
- package/lib/project-issues.js +58 -0
- package/lib/project-logs-mcp-server.js +8 -12
- package/lib/project-loop-interview.js +163 -0
- package/lib/project-loop.js +150 -64
- package/lib/project-message-delivery.js +4 -2
- package/lib/project-models.js +71 -3
- package/lib/project-pair-autonomous-stop.js +16 -0
- package/lib/project-pair-owned-stop.js +18 -0
- package/lib/project-pair-task-control.js +14 -1
- package/lib/project-pending-message-queue.js +264 -0
- package/lib/project-scheduled-messages.js +316 -0
- package/lib/project-scheduled-task-execution.js +236 -0
- package/lib/project-scheduled-tasks.js +481 -0
- package/lib/project-session-pair.js +16 -19
- package/lib/project-sessions.js +114 -31
- package/lib/project-tool-llm-runtime.js +48 -0
- package/lib/project-user-message.js +202 -60
- package/lib/project-workspace-query.js +25 -0
- package/lib/project.js +361 -69
- package/lib/public/app.js +85 -22
- package/lib/public/css/default-ai.css +33 -0
- package/lib/public/css/home-hub.css +15 -2
- package/lib/public/css/home-sidebar.css +50 -2
- package/lib/public/css/input.css +160 -5
- package/lib/public/css/issues.css +101 -0
- package/lib/public/css/messages.css +19 -0
- package/lib/public/css/pane.css +58 -7
- package/lib/public/css/pending-message-queue.css +187 -0
- package/lib/public/css/rewind.css +11 -2
- package/lib/public/css/scheduled-tasks.css +122 -0
- package/lib/public/index.html +42 -150
- package/lib/public/modules/app-connection.js +3 -0
- package/lib/public/modules/app-cursors.js +29 -12
- package/lib/public/modules/app-loop-ui.js +4 -8
- package/lib/public/modules/app-message-router.js +8 -0
- package/lib/public/modules/app-messages.js +34 -9
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rate-limit.js +8 -5
- package/lib/public/modules/autonomous-run.js +285 -0
- package/lib/public/modules/clay-file-links.js +111 -0
- package/lib/public/modules/clay-issue-links.js +131 -0
- package/lib/public/modules/clay-log-links.js +2 -0
- package/lib/public/modules/cursor-sharing-preference.js +38 -0
- package/lib/public/modules/default-ai.js +377 -0
- package/lib/public/modules/filebrowser.js +43 -2
- package/lib/public/modules/home-sidebar.js +16 -8
- package/lib/public/modules/home-surface.js +5 -0
- package/lib/public/modules/input.js +11 -20
- package/lib/public/modules/issues-context.js +117 -0
- package/lib/public/modules/issues-render.js +29 -0
- package/lib/public/modules/issues.js +270 -0
- package/lib/public/modules/loop-interview.js +151 -0
- package/lib/public/modules/markdown.js +4 -0
- package/lib/public/modules/message-delivery.js +5 -3
- package/lib/public/modules/pending-message-image-editor.js +143 -0
- package/lib/public/modules/pending-message-queue-model.js +22 -0
- package/lib/public/modules/pending-message-queue.js +449 -0
- package/lib/public/modules/project-logs.js +2 -2
- package/lib/public/modules/scheduled-message-state.js +46 -0
- package/lib/public/modules/scheduled-task-edit-state.js +7 -0
- package/lib/public/modules/scheduled-task-engine.js +48 -0
- package/lib/public/modules/scheduled-task-form.js +122 -0
- package/lib/public/modules/scheduled-task-recurrence.js +46 -0
- package/lib/public/modules/scheduled-task-schedule-rule.js +115 -0
- package/lib/public/modules/scheduled-tasks.js +459 -0
- package/lib/public/modules/scheduler-config.js +6 -52
- package/lib/public/modules/scheduler.js +9 -21
- package/lib/public/modules/sidebar-mobile.js +3 -0
- package/lib/public/modules/sidebar.js +0 -11
- package/lib/public/modules/split-view.js +2 -0
- package/lib/public/modules/sticky-note-markdown.js +1 -0
- package/lib/public/modules/terminal.js +2 -0
- package/lib/public/modules/tool-palette-order.js +3 -3
- package/lib/public/modules/tools.js +5 -2
- package/lib/public/style.css +3 -0
- package/lib/record-revision.js +11 -0
- package/lib/schedule-rule.js +151 -0
- package/lib/schedule-validation.js +124 -0
- package/lib/scheduled-run-state.js +87 -0
- package/lib/scheduled-task-records.js +127 -0
- package/lib/scheduler.js +179 -196
- package/lib/sdk-bridge.js +157 -22
- package/lib/sdk-message-processor.js +3 -0
- package/lib/server-cursor-sharing.js +65 -0
- package/lib/server-default-ai.js +265 -0
- package/lib/server-durable-scheduler.js +26 -0
- package/lib/server-home-chat.js +29 -9
- package/lib/server-home-clay-entry.js +4 -1
- package/lib/server-home-debate-planning.js +32 -14
- package/lib/server-home-mate-creation.js +30 -11
- package/lib/server-home-models.js +14 -6
- package/lib/server-issue-links.js +18 -0
- package/lib/server.js +63 -0
- package/lib/session-notes-mcp-server.js +1 -1
- package/lib/session-pair-factory.js +42 -4
- package/lib/session-pair-query-start.js +22 -0
- package/lib/session-query-tool-bridge.js +52 -0
- package/lib/session-search.js +4 -1
- package/lib/session-split-groups.js +35 -1
- package/lib/sessions.js +51 -3
- package/lib/tool-llm.js +1 -0
- package/lib/users-default-ai-preferences.js +80 -0
- package/lib/users-home-surface-preferences.js +2 -1
- package/lib/users-preferences.js +35 -0
- package/lib/users-scheduled-task-preferences.js +44 -0
- package/lib/users.js +16 -0
- package/lib/workspace-query-projection.js +29 -0
- package/lib/workspace-query-service.js +19 -29
- package/lib/ws-schema.js +61 -1
- package/lib/yoke/adapters/antigravity.js +1 -0
- package/lib/yoke/adapters/claude-worker.js +11 -6
- package/lib/yoke/adapters/claude.js +35 -1
- package/lib/yoke/adapters/codex.js +3 -0
- package/lib/yoke/claude-user-input.js +2 -0
- package/lib/yoke/index.js +2 -0
- package/lib/yoke/mcp-bridge-server.js +7 -1
- package/lib/yoke/package.json +1 -1
- package/lib/yoke/session-tool-transport.js +53 -0
- package/lib/yoke/user-input.js +21 -7
- package/package.json +5 -6
- package/bin/claude-relay.js +0 -6
- package/lib/public/modules/app-loop-wizard.js +0 -439
package/lib/server.js
CHANGED
|
@@ -16,6 +16,8 @@ var serverMates = require("./server-mates");
|
|
|
16
16
|
var serverHomeChat = require("./server-home-chat");
|
|
17
17
|
var shouldSuppressHomeResponse = require("./home-response-notifications").shouldSuppressHomeResponse;
|
|
18
18
|
var serverHomePreferences = require("./server-home-preferences");
|
|
19
|
+
var serverDefaultAi = require("./server-default-ai");
|
|
20
|
+
var serverCursorSharing = require("./server-cursor-sharing");
|
|
19
21
|
var serverTools = require("./server-tools");
|
|
20
22
|
var serverAdmin = require("./server-admin");
|
|
21
23
|
var serverSettings = require("./server-settings");
|
|
@@ -28,6 +30,8 @@ var { attachMateKnowledgeService } = require("./mate-knowledge-service");
|
|
|
28
30
|
var serverEmail = require("./server-email");
|
|
29
31
|
var serverGlobalWs = require("./server-global-ws");
|
|
30
32
|
var { parseWsRequestUrl } = require("./ws-request");
|
|
33
|
+
var { attachDurableScheduler } = require("./durable-scheduler");
|
|
34
|
+
var { attachDurableSchedulerLifecycle } = require("./server-durable-scheduler");
|
|
31
35
|
|
|
32
36
|
var { CONFIG_DIR } = require("./config");
|
|
33
37
|
var { provisionLinuxUser } = require("./os-users");
|
|
@@ -185,6 +189,8 @@ function createServer(opts) {
|
|
|
185
189
|
var onUserProvisioned = opts.onUserProvisioned || null;
|
|
186
190
|
var onUserDeleted = opts.onUserDeleted || null;
|
|
187
191
|
var getRemovedProjects = opts.getRemovedProjects || function () { return []; };
|
|
192
|
+
var durableScheduler = null;
|
|
193
|
+
var durableSchedulerLifecycle = null;
|
|
188
194
|
|
|
189
195
|
// --- Auth module ---
|
|
190
196
|
var auth = serverAuth.attachAuth({
|
|
@@ -368,6 +374,26 @@ function createServer(opts) {
|
|
|
368
374
|
},
|
|
369
375
|
findUserById: function (userId) { return users.findUserById(userId); },
|
|
370
376
|
});
|
|
377
|
+
var issuesService = require("./issues-service").attachIssuesService({
|
|
378
|
+
getProjects: function () { return projects; },
|
|
379
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
380
|
+
canAccessProject: function (userId, status) {
|
|
381
|
+
if (!status || !status.slug || !onGetProjectAccess) return false;
|
|
382
|
+
var access = onGetProjectAccess(status.slug);
|
|
383
|
+
if (!access || access.error) return false;
|
|
384
|
+
return users.canAccessProject(userId, access) === true;
|
|
385
|
+
},
|
|
386
|
+
hasFullProjectAccess: function (userId, status) {
|
|
387
|
+
if (!status || !status.slug || !onGetProjectAccess) return false;
|
|
388
|
+
var access = onGetProjectAccess(status.slug);
|
|
389
|
+
if (!access || access.error) return false;
|
|
390
|
+
var governing = access.isWorktree && access.parentAccess ? access.parentAccess : access;
|
|
391
|
+
return users.canAccessProject(userId, governing) === true;
|
|
392
|
+
},
|
|
393
|
+
osUsersEnabled: function () { return osUsers; },
|
|
394
|
+
getOsUserInfoForActor: function (user) { return user && user.linuxUser ? require("./os-users").resolveOsUserInfo(user.linuxUser) : null; },
|
|
395
|
+
findUserById: function (userId) { return users.findUserById(userId); },
|
|
396
|
+
});
|
|
371
397
|
// Mate Knowledge reads the synchronized Knowledge records. The Mate list is
|
|
372
398
|
// derived from the owning user alone, so a binding can never span users.
|
|
373
399
|
var mateKnowledgeService = attachMateKnowledgeService({
|
|
@@ -1040,6 +1066,8 @@ function createServer(opts) {
|
|
|
1040
1066
|
// --- WebSocket ---
|
|
1041
1067
|
var wss = new WebSocketServer({ noServer: true });
|
|
1042
1068
|
var homePreferencesHandler = null;
|
|
1069
|
+
var defaultAiService = null;
|
|
1070
|
+
var cursorSharingService = null;
|
|
1043
1071
|
|
|
1044
1072
|
// Slug-less /ws handler: lets a user with no projects yet load the regular
|
|
1045
1073
|
// app shell and create/add/clone their first one. Only knows about the
|
|
@@ -1059,6 +1087,7 @@ function createServer(opts) {
|
|
|
1059
1087
|
},
|
|
1060
1088
|
onConnection: function (ws) {
|
|
1061
1089
|
sendProjectsUpdatedToClient(ws);
|
|
1090
|
+
if (cursorSharingService) cursorSharingService.sendStateTo(ws);
|
|
1062
1091
|
},
|
|
1063
1092
|
});
|
|
1064
1093
|
|
|
@@ -1302,7 +1331,9 @@ function createServer(opts) {
|
|
|
1302
1331
|
},
|
|
1303
1332
|
workspaceQueryService: workspaceQueryService,
|
|
1304
1333
|
projectLogsService: projectLogsService,
|
|
1334
|
+
issuesService: issuesService,
|
|
1305
1335
|
mateKnowledgeService: mateKnowledgeService,
|
|
1336
|
+
durableScheduler: durableScheduler,
|
|
1306
1337
|
canAccessProjectSlug: function (userId, targetSlug) {
|
|
1307
1338
|
var target = projects.get(targetSlug);
|
|
1308
1339
|
if (!target) return false;
|
|
@@ -1331,6 +1362,10 @@ function createServer(opts) {
|
|
|
1331
1362
|
port: portNum,
|
|
1332
1363
|
tls: !!tlsOptions,
|
|
1333
1364
|
authToken: pinHash || null,
|
|
1365
|
+
resolveDefaultAi: function (ws) {
|
|
1366
|
+
if (!defaultAiService) return Promise.resolve({ ready: false, error: "Default AI is not ready." });
|
|
1367
|
+
return defaultAiService.resolveForWs(ws);
|
|
1368
|
+
},
|
|
1334
1369
|
getProjectCount: function () { return projects.size; },
|
|
1335
1370
|
getProjectList: function (userId) {
|
|
1336
1371
|
return getVisibleProjects(userId);
|
|
@@ -1460,6 +1495,7 @@ function createServer(opts) {
|
|
|
1460
1495
|
onShutdown: onShutdown,
|
|
1461
1496
|
onRestart: onRestart,
|
|
1462
1497
|
onDmMessage: handleDmMessage,
|
|
1498
|
+
sendCursorSharingState: function (ws) { if (cursorSharingService) cursorSharingService.sendStateTo(ws); },
|
|
1463
1499
|
broadcastAll: broadcastAll,
|
|
1464
1500
|
notificationsModule: _globalNotifications,
|
|
1465
1501
|
refreshVendorAuthEverywhere: refreshVendorAuthEverywhere,
|
|
@@ -1503,6 +1539,11 @@ function createServer(opts) {
|
|
|
1503
1539
|
addProject: addProject,
|
|
1504
1540
|
workspaceQueryService: workspaceQueryService,
|
|
1505
1541
|
projectLogsService: projectLogsService,
|
|
1542
|
+
issuesService: issuesService,
|
|
1543
|
+
resolveDefaultAi: function (ws) {
|
|
1544
|
+
if (!defaultAiService) return Promise.resolve({ ready: false, error: "Default AI is not ready." });
|
|
1545
|
+
return defaultAiService.resolveForWs(ws);
|
|
1546
|
+
},
|
|
1506
1547
|
});
|
|
1507
1548
|
|
|
1508
1549
|
homePreferencesHandler = serverHomePreferences.attachHomePreferences({
|
|
@@ -1510,6 +1551,9 @@ function createServer(opts) {
|
|
|
1510
1551
|
projects: projects,
|
|
1511
1552
|
});
|
|
1512
1553
|
|
|
1554
|
+
defaultAiService = serverDefaultAi.attachDefaultAiService({ users: users, homeChatHandler: homeChatHandler, forEachAppClient: forEachAppClient });
|
|
1555
|
+
cursorSharingService = serverCursorSharing.attachCursorSharingService({ users: users, forEachAppClient: forEachAppClient });
|
|
1556
|
+
|
|
1513
1557
|
// --- User-installed home tools handler ---
|
|
1514
1558
|
var toolsHandler = serverTools.attachTools({
|
|
1515
1559
|
users: users,
|
|
@@ -1535,6 +1579,8 @@ function createServer(opts) {
|
|
|
1535
1579
|
}
|
|
1536
1580
|
|
|
1537
1581
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1582
|
+
if (cursorSharingService && cursorSharingService.handleMessage(ws, msg)) return true;
|
|
1583
|
+
if (defaultAiService && defaultAiService.handleMessage(ws, msg)) return true;
|
|
1538
1584
|
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1539
1585
|
if (dmHandler.handleMessage(ws, msg)) return true;
|
|
1540
1586
|
if (mateHandler && mateHandler.handleMessage(ws, msg)) return true;
|
|
@@ -1817,8 +1863,24 @@ function createServer(opts) {
|
|
|
1817
1863
|
});
|
|
1818
1864
|
}
|
|
1819
1865
|
|
|
1866
|
+
var schedulerNamespace = process.env.CLAY_DEV ? "dev" : "prod";
|
|
1867
|
+
durableScheduler = opts.durableScheduler || attachDurableScheduler({
|
|
1868
|
+
storageDir: path.join(CONFIG_DIR, "scheduler"),
|
|
1869
|
+
namespace: schedulerNamespace,
|
|
1870
|
+
globalConcurrency: opts.schedulerGlobalConcurrency || 4,
|
|
1871
|
+
perOwnerConcurrency: opts.schedulerPerOwnerConcurrency || 1,
|
|
1872
|
+
});
|
|
1873
|
+
durableSchedulerLifecycle = attachDurableSchedulerLifecycle({
|
|
1874
|
+
server: server,
|
|
1875
|
+
scheduler: durableScheduler,
|
|
1876
|
+
onError: function (err) {
|
|
1877
|
+
console.error("[server] Durable scheduler shutdown failed:", err && err.message ? err.message : err);
|
|
1878
|
+
},
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1820
1881
|
function destroyAll() {
|
|
1821
1882
|
var shutdowns = [];
|
|
1883
|
+
shutdowns.push(durableSchedulerLifecycle.shutdown());
|
|
1822
1884
|
projects.forEach(function (ctx, slug) {
|
|
1823
1885
|
console.log("[server] Destroying project:", slug);
|
|
1824
1886
|
var result = ctx.destroy();
|
|
@@ -1898,6 +1960,7 @@ function createServer(opts) {
|
|
|
1898
1960
|
forEachProject: forEachProject,
|
|
1899
1961
|
destroyProject: removeProject,
|
|
1900
1962
|
destroyAll: destroyAll,
|
|
1963
|
+
durableScheduler: durableScheduler,
|
|
1901
1964
|
setProjectLogContextState: setProjectLogContextState,
|
|
1902
1965
|
};
|
|
1903
1966
|
}
|
|
@@ -6,7 +6,7 @@ var MEMORY_CONTRACT =
|
|
|
6
6
|
"The sticky-note board persists across sessions and is a user-facing artifact shared with people and Clay agents, not private agent scratch space. " +
|
|
7
7
|
"Default to not writing. Create a note proactively only when the user explicitly asks to remember or track something, or when all of these are true: it will remain useful after the current task and session, it is not already adequately recorded in the repository or another note, and the user would likely be glad to find it on the board a week later. " +
|
|
8
8
|
"Good notes capture an unresolved commitment, durable product decision, user preference, constraint, or handoff that will materially change future work. " +
|
|
9
|
-
"Important exception for deferred defects: while doing code or technical work, actively create a sticky note when you discover a concrete defect, regression risk, security issue, or data-loss risk that is outside the current session goal and will remain unfixed when the turn ends. Do not wait for the user to ask.
|
|
9
|
+
"Important exception for deferred defects: while doing code or technical work, actively create a sticky note when you discover a concrete defect, regression risk, security issue, or data-loss risk that is outside the current session goal and will remain unfixed when the turn ends. Do not wait for the user to ask. For a Project Driver session with Issues authority, search, reuse, or create the Issue first and put the primary details there: observable evidence, affected component, impact, and next action. Then make the sticky note only a concise title, one-line actionable cue, and the actual opaque issue: reference; do not duplicate the full defect details in the note. If you lack Issues authority, route through an eligible Project Driver; never invent an issue reference or drop the alert. Check active notes first when a duplicate is plausible. Do not create defect notes for speculation, general cleanup ideas, or problems you fixed in the current session. " +
|
|
10
10
|
"Important exception for deferred proposals: when you propose work (a fix, follow-up, improvement, or next step) and the user defers it rather than declining it (\"let's do that later\", \"next time\", \"after this\"), actively create a sticky note before the topic moves on. Do not wait to be asked. Capture what was proposed, why it mattered, and the agreed timing if any. Deferred agreements scroll out of chat history quickly and are hard to track; the board is where they survive. If the user declines the idea outright, do not write a note. " +
|
|
11
11
|
"Never create a note merely because work is important, lengthy, spans agents or restarts, or might help another agent. Do not record completed work, implementation details, test results, investigation logs, transient blockers, conversation summaries, or announcements of your own activity. When uncertain, do not write. " +
|
|
12
12
|
"Updates are visible too: update only when durable state materially changes. " +
|
|
@@ -67,11 +67,11 @@ function attachPairFactory(ctx) {
|
|
|
67
67
|
throw new Error("model is not available for vendor \"" + vendor + "\": " + requested);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function validateEffort(vendor, effort) {
|
|
70
|
+
function validateEffort(vendor, effort, exact) {
|
|
71
71
|
var requested = typeof effort === "string" ? effort.trim() : "";
|
|
72
72
|
var fallback = (sm.currentEffortByVendor && sm.currentEffortByVendor[vendor]) || sm.currentEffort || "medium";
|
|
73
73
|
var clamped = yoke.clampEffort(vendor, requested || fallback) || null;
|
|
74
|
-
if (requested && !clamped) {
|
|
74
|
+
if (requested && (!clamped || (exact && clamped !== requested))) {
|
|
75
75
|
throw new Error("reasoning effort is not supported for vendor \"" + vendor + "\": " + requested);
|
|
76
76
|
}
|
|
77
77
|
return clamped;
|
|
@@ -79,13 +79,16 @@ function attachPairFactory(ctx) {
|
|
|
79
79
|
|
|
80
80
|
// Resolve and validate one session runtime request without creating
|
|
81
81
|
// anything. Throws on the first unusable field.
|
|
82
|
-
function preflightRuntime(spec, defaultVendor) {
|
|
82
|
+
function preflightRuntime(spec, defaultVendor, exact) {
|
|
83
83
|
var request = spec || {};
|
|
84
84
|
var vendor = validateVendor((typeof request.vendor === "string" && request.vendor.trim()) || defaultVendor);
|
|
85
|
+
var info = yoke.getVendorInfo(vendor) || {};
|
|
86
|
+
if (exact && !(typeof request.model === "string" && request.model.trim())) throw new Error("an explicit model is required for vendor \"" + vendor + "\"");
|
|
87
|
+
if (exact && Array.isArray(info.effortLevels) && info.effortLevels.length && !(typeof request.effort === "string" && request.effort.trim())) throw new Error("an explicit reasoning effort is required for vendor \"" + vendor + "\"");
|
|
85
88
|
return {
|
|
86
89
|
vendor: vendor,
|
|
87
90
|
model: validateModel(vendor, request.model),
|
|
88
|
-
effort: validateEffort(vendor, request.effort),
|
|
91
|
+
effort: validateEffort(vendor, request.effort, exact),
|
|
89
92
|
explicitEffort: !!(typeof request.effort === "string" && request.effort.trim()),
|
|
90
93
|
};
|
|
91
94
|
}
|
|
@@ -227,6 +230,39 @@ function attachPairFactory(ctx) {
|
|
|
227
230
|
return created;
|
|
228
231
|
}
|
|
229
232
|
|
|
233
|
+
function createOwnedPair(ownerId, args) {
|
|
234
|
+
if (ctx.isMate) throw new Error("Pair sessions are only available in projects");
|
|
235
|
+
var request = args || {};
|
|
236
|
+
var driverRuntime = preflightRuntime(request.driver || {}, "claude");
|
|
237
|
+
var workerRuntime = preflightRuntime(request.worker || {}, null);
|
|
238
|
+
var driver = sm.createSessionRaw({ ownerId: ownerId || null, vendor: driverRuntime.vendor, model: driverRuntime.model, effort: driverRuntime.effort });
|
|
239
|
+
var worker = null;
|
|
240
|
+
try {
|
|
241
|
+
driver.title = request.name ? request.name + " · Driver" : "Scheduled task · Driver";
|
|
242
|
+
if (driverRuntime.explicitEffort) driver.loopSettings = { effort: driverRuntime.effort };
|
|
243
|
+
worker = sm.createSessionRaw({ ownerId: ownerId || null, vendor: workerRuntime.vendor, model: workerRuntime.model, effort: workerRuntime.effort });
|
|
244
|
+
worker.title = request.name ? request.name + " · Split Worker" : "Scheduled task · Split Worker";
|
|
245
|
+
if (workerRuntime.explicitEffort) worker.loopSettings = { effort: workerRuntime.effort };
|
|
246
|
+
sessionProvenance.markWorker(driver, worker, sm.sessions);
|
|
247
|
+
if (typeof sm.saveSessionFile === "function") { sm.saveSessionFile(driver); sm.saveSessionFile(worker); }
|
|
248
|
+
var result = store.createOwned(ownerId || null, { members: [driver.localId, worker.localId], pair: { driverId: driver.localId, workerId: worker.localId }, name: request.name || "Scheduled task" });
|
|
249
|
+
if (!result.ok) throw new Error(result.error);
|
|
250
|
+
sm.broadcastSessionList();
|
|
251
|
+
return { driver: driver, worker: worker, group: result.group };
|
|
252
|
+
} catch (error) {
|
|
253
|
+
removeCreated(worker);
|
|
254
|
+
removeCreated(driver);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function removeOwnedPair(ownerId, created) {
|
|
260
|
+
if (!created || !created.group || !store.dissolveOwned(ownerId || null, created.group.id)) return false;
|
|
261
|
+
removeCreated(created.worker);
|
|
262
|
+
removeCreated(created.driver);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
230
266
|
function createPair(ws, msg) {
|
|
231
267
|
try {
|
|
232
268
|
var created = createPairRecord(ws, msg);
|
|
@@ -240,6 +276,8 @@ function attachPairFactory(ctx) {
|
|
|
240
276
|
createPair: createPair,
|
|
241
277
|
createPairRecord: createPairRecord,
|
|
242
278
|
createWorkerForDriver: createWorkerForDriver,
|
|
279
|
+
createOwnedPair: createOwnedPair,
|
|
280
|
+
removeOwnedPair: removeOwnedPair,
|
|
243
281
|
preflightRuntime: preflightRuntime,
|
|
244
282
|
preflightWorkerForDriver: preflightWorkerForDriver,
|
|
245
283
|
validateModel: validateModel,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function beginPairQuery(ctx, session, text) {
|
|
2
|
+
session._queryStartTs = Date.now();
|
|
3
|
+
var promise = ctx.sdk.startQuery(session, text, undefined, ctx.linuxUser);
|
|
4
|
+
var owner = {
|
|
5
|
+
session: session,
|
|
6
|
+
generation: Number(session._queryGeneration || 0),
|
|
7
|
+
};
|
|
8
|
+
session._pairResumeQuery = owner;
|
|
9
|
+
Promise.resolve(promise).catch(function (error) {
|
|
10
|
+
var current = ctx.sm.sessions.get(session.localId);
|
|
11
|
+
var ownsCurrent = current === session && session._pairResumeQuery === owner &&
|
|
12
|
+
Number(session._queryGeneration || 0) === owner.generation;
|
|
13
|
+
if (!ownsCurrent) return;
|
|
14
|
+
delete session._pairResumeQuery;
|
|
15
|
+
session.isProcessing = false;
|
|
16
|
+
ctx.sm.sendAndRecord(session, { type: "error", text: error.message || String(error) });
|
|
17
|
+
ctx.onProcessingChanged();
|
|
18
|
+
});
|
|
19
|
+
return owner;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { beginPairQuery: beginPairQuery };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Exact-query projection of SDK-owned tools into the project stdio MCP bridge.
|
|
2
|
+
|
|
3
|
+
var sessionToolTransport = require("./yoke/session-tool-transport");
|
|
4
|
+
|
|
5
|
+
function normalizeInputSchema(inputSchema) {
|
|
6
|
+
if (inputSchema && typeof inputSchema.type === "string") return inputSchema;
|
|
7
|
+
try {
|
|
8
|
+
var zod = require("zod");
|
|
9
|
+
var schema = inputSchema && inputSchema.safeParse ? inputSchema : zod.object(inputSchema || {});
|
|
10
|
+
return zod.toJSONSchema(schema);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return { type: "object", properties: {} };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createSessionQueryToolBridge(ctx) {
|
|
17
|
+
function defs(queryGeneration) {
|
|
18
|
+
if (!ctx.session || !ctx.sdk || !Number.isInteger(queryGeneration)) return [];
|
|
19
|
+
return ctx.sdk.getQueryToolDefs(ctx.session, queryGeneration) || [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function listTools(queryGeneration, normalizeToolSchema) {
|
|
23
|
+
var tools = [];
|
|
24
|
+
var queryTools = defs(queryGeneration);
|
|
25
|
+
var normalize = normalizeToolSchema || normalizeInputSchema;
|
|
26
|
+
for (var i = 0; i < queryTools.length; i++) {
|
|
27
|
+
var server = sessionToolTransport.bridgeServerForTool(queryTools[i].name);
|
|
28
|
+
if (!server) continue;
|
|
29
|
+
tools.push({
|
|
30
|
+
server: server,
|
|
31
|
+
name: queryTools[i].name,
|
|
32
|
+
description: queryTools[i].description || queryTools[i].name,
|
|
33
|
+
inputSchema: normalize(queryTools[i].inputSchema),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return tools;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function callTool(queryGeneration, serverName, toolName, args) {
|
|
40
|
+
var queryTools = defs(queryGeneration);
|
|
41
|
+
for (var i = 0; i < queryTools.length; i++) {
|
|
42
|
+
if (sessionToolTransport.bridgeServerForTool(queryTools[i].name) !== serverName || queryTools[i].name !== toolName) continue;
|
|
43
|
+
if (typeof queryTools[i].handler !== "function") return Promise.reject(new Error("Session tool handler unavailable: " + serverName + "/" + toolName));
|
|
44
|
+
return Promise.resolve(queryTools[i].handler(args || {}));
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { listTools: listTools, callTool: callTool };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { createSessionQueryToolBridge: createSessionQueryToolBridge, normalizeInputSchema: normalizeInputSchema };
|
package/lib/session-search.js
CHANGED
|
@@ -576,7 +576,10 @@ function searchPalette(projectSessions, query, opts) {
|
|
|
576
576
|
if (allDocs.length === 0) return [];
|
|
577
577
|
|
|
578
578
|
var index = buildIndex(allDocs);
|
|
579
|
-
|
|
579
|
+
// Rank every matching document before deduplication. A session with many
|
|
580
|
+
// matching turns must not consume the over-fetch window and hide other
|
|
581
|
+
// matching sessions from the palette.
|
|
582
|
+
var raw = searchIndex(index, query, allDocs.length);
|
|
580
583
|
|
|
581
584
|
// Deduplicate: keep best match per session
|
|
582
585
|
var bestPerSession = {};
|
|
@@ -179,6 +179,40 @@ function createSplitGroupStore(opts) {
|
|
|
179
179
|
return { ok: true, group: group };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
function createOwned(ownerId, msg) {
|
|
183
|
+
var members = msg && msg.members;
|
|
184
|
+
if (!Array.isArray(members) || members.length !== 2) return { ok: false, error: "A split group requires exactly two sessions" };
|
|
185
|
+
var left = sessions.get(members[0]);
|
|
186
|
+
var right = sessions.get(members[1]);
|
|
187
|
+
if (!left || !right || left === right) return { ok: false, error: "Session not found" };
|
|
188
|
+
if ((left.ownerId || null) !== (ownerId || null) || (right.ownerId || null) !== (ownerId || null)) return { ok: false, error: "Session owner does not match the scheduled task owner" };
|
|
189
|
+
if (groupForMember(left.localId) || groupForMember(right.localId)) return { ok: false, error: "A session can belong to only one split group" };
|
|
190
|
+
var pair = msg && msg.pair;
|
|
191
|
+
if (!pair || pair.driverId !== left.localId || pair.workerId !== right.localId) return { ok: false, error: "Pair roles must reference both split group members" };
|
|
192
|
+
var requestedName = typeof msg.name === "string" ? msg.name.trim().slice(0, 80) : "";
|
|
193
|
+
var group = {
|
|
194
|
+
id: "sg_" + Date.now().toString(36) + "_" + crypto.randomBytes(3).toString("hex"),
|
|
195
|
+
name: requestedName || autoGroupName(left.title, right.title), nameCustomized: !!requestedName,
|
|
196
|
+
ownerId: ownerId || null, members: members.slice(),
|
|
197
|
+
pair: { driverId: pair.driverId, workerId: pair.workerId }, createdAt: Date.now(),
|
|
198
|
+
};
|
|
199
|
+
groups.push(group);
|
|
200
|
+
try { save(); } catch (error) { groups.pop(); return { ok: false, error: error.message || String(error) }; }
|
|
201
|
+
broadcast();
|
|
202
|
+
onPairChanged(group);
|
|
203
|
+
return { ok: true, group: group };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function dissolveOwned(ownerId, id) {
|
|
207
|
+
var index = groups.findIndex(function (item) { return item.id === id && (item.ownerId || null) === (ownerId || null); });
|
|
208
|
+
if (index === -1) return false;
|
|
209
|
+
var removed = groups.splice(index, 1)[0];
|
|
210
|
+
try { save(); } catch (error) { groups.splice(index, 0, removed); return false; }
|
|
211
|
+
broadcast();
|
|
212
|
+
onPairChanged(removed);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
182
216
|
function rename(ws, msg) {
|
|
183
217
|
var group = groups.find(function (item) { return item.id === (msg && msg.id); });
|
|
184
218
|
if (!group) return { ok: false, error: "Split group not found" };
|
|
@@ -265,7 +299,7 @@ function createSplitGroupStore(opts) {
|
|
|
265
299
|
}
|
|
266
300
|
|
|
267
301
|
load();
|
|
268
|
-
return { create: create, rename: rename, setPair: setPair, dissolve: dissolve, dissolveBySession: dissolveBySession,
|
|
302
|
+
return { create: create, createOwned: createOwned, dissolveOwned: dissolveOwned, rename: rename, setPair: setPair, dissolve: dissolve, dissolveBySession: dissolveBySession,
|
|
269
303
|
refreshAutoName: refreshAutoName, refreshAnchors: refreshAnchors,
|
|
270
304
|
listFor: listFor, groupForMember: groupForMember, get groups() { return groups; } };
|
|
271
305
|
}
|
package/lib/sessions.js
CHANGED
|
@@ -8,6 +8,8 @@ var hygiene = require("./session-hygiene");
|
|
|
8
8
|
var backgroundTaskTiming = require("./background-task-timing");
|
|
9
9
|
var sessionProvenance = require("./session-provenance");
|
|
10
10
|
var sessionVisibility = require("./session-visibility");
|
|
11
|
+
var projectAutonomousRun = require("./project-autonomous-run").projectAutonomousRun;
|
|
12
|
+
var restoreStoredRun = require("./project-autonomous-run").restoreStoredRun;
|
|
11
13
|
|
|
12
14
|
function createSessionManager(opts) {
|
|
13
15
|
var cwd = opts.cwd;
|
|
@@ -17,6 +19,8 @@ function createSessionManager(opts) {
|
|
|
17
19
|
var sendAndRecord = null; // set after init via setSendAndRecord
|
|
18
20
|
var onSessionDone = opts.onSessionDone || function () {};
|
|
19
21
|
var onSessionDeleted = opts.onSessionDeleted || function () {};
|
|
22
|
+
var sessionDeletedListeners = [];
|
|
23
|
+
var sessionViewedListeners = [];
|
|
20
24
|
var onSessionRenamed = opts.onSessionRenamed || function () {};
|
|
21
25
|
var onSessionIdentityAssigned = opts.onSessionIdentityAssigned || function () {};
|
|
22
26
|
|
|
@@ -153,6 +157,7 @@ function createSessionManager(opts) {
|
|
|
153
157
|
};
|
|
154
158
|
if (session.ownerId) metaObj.ownerId = session.ownerId;
|
|
155
159
|
if (session.vendor) metaObj.vendor = session.vendor;
|
|
160
|
+
if (session.codexDynamicToolCatalogVersion) metaObj.codexDynamicToolCatalogVersion = session.codexDynamicToolCatalogVersion;
|
|
156
161
|
if (session.model) metaObj.model = session.model;
|
|
157
162
|
if (session.effort) metaObj.effort = session.effort;
|
|
158
163
|
// Persist the session's "born" mode so TUI sessions reappear after a
|
|
@@ -167,6 +172,11 @@ function createSessionManager(opts) {
|
|
|
167
172
|
if (session.dangerouslySkipPermissions) metaObj.dangerouslySkipPermissions = true;
|
|
168
173
|
if (session.permissionMode) metaObj.permissionMode = session.permissionMode;
|
|
169
174
|
if (session.permissionModeBeforeFullAccess) metaObj.permissionModeBeforeFullAccess = session.permissionModeBeforeFullAccess;
|
|
175
|
+
if (session.autonomousRun) metaObj.autonomousRun = session.autonomousRun;
|
|
176
|
+
if (session.loopInterviewBrief) metaObj.loopInterviewBrief = session.loopInterviewBrief;
|
|
177
|
+
if (session.loopInterviewHandoff) metaObj.loopInterviewHandoff = session.loopInterviewHandoff;
|
|
178
|
+
if (session.scheduledTaskDraft) metaObj.scheduledTaskDraft = session.scheduledTaskDraft;
|
|
179
|
+
if (session.scheduledTaskRun) metaObj.scheduledTaskRun = session.scheduledTaskRun;
|
|
170
180
|
metaObj.sessionVisibility = sessionVisibility.normalize(session.sessionVisibility);
|
|
171
181
|
if (session.sessionVisibilityExplicit) metaObj.sessionVisibilityExplicit = true;
|
|
172
182
|
if (session.bookmarked) metaObj.bookmarked = true;
|
|
@@ -293,6 +303,7 @@ function createSessionManager(opts) {
|
|
|
293
303
|
lastRewindUuid: m.lastRewindUuid || null,
|
|
294
304
|
};
|
|
295
305
|
if (m.vendor) session.vendor = m.vendor;
|
|
306
|
+
session.codexDynamicToolCatalogVersion = Number(m.codexDynamicToolCatalogVersion || 0);
|
|
296
307
|
session.model = m.model || deriveSessionModel(loaded[i].history);
|
|
297
308
|
session.effort = m.effort || null;
|
|
298
309
|
if (m.loop) session.loop = m.loop;
|
|
@@ -322,6 +333,11 @@ function createSessionManager(opts) {
|
|
|
322
333
|
session.dangerouslySkipPermissions = session.mode === "tui" && !!m.dangerouslySkipPermissions;
|
|
323
334
|
session.permissionMode = m.permissionMode || (session.mode === "gui" && m.dangerouslySkipPermissions ? "bypassPermissions" : null);
|
|
324
335
|
session.permissionModeBeforeFullAccess = m.permissionModeBeforeFullAccess || null;
|
|
336
|
+
session.autonomousRun = restoreStoredRun(m.autonomousRun);
|
|
337
|
+
session.loopInterviewBrief = m.loopInterviewBrief && typeof m.loopInterviewBrief === "object" ? m.loopInterviewBrief : null;
|
|
338
|
+
session.loopInterviewHandoff = m.loopInterviewHandoff && typeof m.loopInterviewHandoff === "object" ? m.loopInterviewHandoff : null;
|
|
339
|
+
session.scheduledTaskDraft = m.scheduledTaskDraft && typeof m.scheduledTaskDraft === "object" ? m.scheduledTaskDraft : null;
|
|
340
|
+
session.scheduledTaskRun = m.scheduledTaskRun && typeof m.scheduledTaskRun === "object" ? m.scheduledTaskRun : null;
|
|
325
341
|
session.terminalId = null;
|
|
326
342
|
session.runtimeMode = null;
|
|
327
343
|
session.runtimeTerminalId = null;
|
|
@@ -554,6 +570,13 @@ function createSessionManager(opts) {
|
|
|
554
570
|
runtimeTerminalId: typeof s.runtimeTerminalId === "number" ? s.runtimeTerminalId : null,
|
|
555
571
|
dangerouslySkipPermissions: !!s.dangerouslySkipPermissions,
|
|
556
572
|
permissionMode: s.permissionMode || null,
|
|
573
|
+
autonomousRun: s.autonomousRun ? {
|
|
574
|
+
id: s.autonomousRun.id,
|
|
575
|
+
state: s.autonomousRun.state,
|
|
576
|
+
continuationCount: s.autonomousRun.continuationCount || 0,
|
|
577
|
+
maxContinuations: s.autonomousRun.maxContinuations,
|
|
578
|
+
deadlineAt: s.autonomousRun.deadlineAt || null,
|
|
579
|
+
} : null,
|
|
557
580
|
};
|
|
558
581
|
}
|
|
559
582
|
|
|
@@ -636,6 +659,11 @@ function createSessionManager(opts) {
|
|
|
636
659
|
dangerouslySkipPermissions: !!(sessionOpts && sessionOpts.mode === "tui" && sessionOpts.dangerouslySkipPermissions),
|
|
637
660
|
permissionMode: (sessionOpts && sessionOpts.permissionMode) || null,
|
|
638
661
|
permissionModeBeforeFullAccess: null,
|
|
662
|
+
autonomousRun: null,
|
|
663
|
+
loopInterviewBrief: null,
|
|
664
|
+
loopInterviewHandoff: null,
|
|
665
|
+
scheduledTaskDraft: null,
|
|
666
|
+
scheduledTaskRun: null,
|
|
639
667
|
terminalId: null,
|
|
640
668
|
sessionOriginId: null,
|
|
641
669
|
};
|
|
@@ -678,6 +706,11 @@ function createSessionManager(opts) {
|
|
|
678
706
|
dangerouslySkipPermissions: !!(sessionOpts && sessionOpts.mode === "tui" && sessionOpts.dangerouslySkipPermissions),
|
|
679
707
|
permissionMode: (sessionOpts && sessionOpts.permissionMode) || null,
|
|
680
708
|
permissionModeBeforeFullAccess: null,
|
|
709
|
+
autonomousRun: null,
|
|
710
|
+
loopInterviewBrief: null,
|
|
711
|
+
loopInterviewHandoff: null,
|
|
712
|
+
scheduledTaskDraft: null,
|
|
713
|
+
scheduledTaskRun: null,
|
|
681
714
|
terminalId: null,
|
|
682
715
|
sessionOriginId: null,
|
|
683
716
|
};
|
|
@@ -738,6 +771,9 @@ function createSessionManager(opts) {
|
|
|
738
771
|
}
|
|
739
772
|
|
|
740
773
|
_send({ type: "history_done", lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens, contextUsage: session.lastContextUsage || null });
|
|
774
|
+
for (var vi = 0; vi < sessionViewedListeners.length; vi++) {
|
|
775
|
+
try { sessionViewedListeners[vi](session, targetWs, _send); } catch (e) {}
|
|
776
|
+
}
|
|
741
777
|
}
|
|
742
778
|
|
|
743
779
|
function switchSession(localId, targetWs, transform) {
|
|
@@ -768,7 +804,7 @@ function createSessionManager(opts) {
|
|
|
768
804
|
var _capsByVendor = capabilitiesByVendor || {};
|
|
769
805
|
var _sessionVendor = session.vendor || defaultVendor || "claude";
|
|
770
806
|
var _vendorCaps = _capsByVendor[_sessionVendor] || {};
|
|
771
|
-
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null, vendor: session.vendor || null, model: session.model || null, effort: session.effort || null, hasHistory: (session.history && session.history.length > 0), capabilities: _vendorCaps, isProcessing: !!session.isProcessing, mode: session.mode || "gui", terminalId: typeof session.terminalId === "number" ? session.terminalId : null, runtimeMode: session.runtimeMode || null, runtimeTerminalId: typeof session.runtimeTerminalId === "number" ? session.runtimeTerminalId : null, tuiSuspended: !!session.tuiSuspended, dangerouslySkipPermissions: !!session.dangerouslySkipPermissions, permissionMode: session.permissionMode || null });
|
|
807
|
+
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null, vendor: session.vendor || null, model: session.model || null, effort: session.effort || null, hasHistory: (session.history && session.history.length > 0), capabilities: _vendorCaps, isProcessing: !!session.isProcessing, mode: session.mode || "gui", terminalId: typeof session.terminalId === "number" ? session.terminalId : null, runtimeMode: session.runtimeMode || null, runtimeTerminalId: typeof session.runtimeTerminalId === "number" ? session.runtimeTerminalId : null, tuiSuspended: !!session.tuiSuspended, dangerouslySkipPermissions: !!session.dangerouslySkipPermissions, permissionMode: session.permissionMode || null, autonomousRun: projectAutonomousRun(session.autonomousRun), loopInterviewBrief: session.loopInterviewBrief || null, loopInterviewHandoff: session.loopInterviewHandoff || null, scheduledTaskDraft: session.scheduledTaskDraft || null });
|
|
772
808
|
// Send vendor-specific slash commands
|
|
773
809
|
var _vendorCmds = slashCommandsByVendor[_sessionVendor] || slashCommands || [];
|
|
774
810
|
_send({ type: "slash_commands", commands: _vendorCmds, vendor: _sessionVendor });
|
|
@@ -841,9 +877,14 @@ function createSessionManager(opts) {
|
|
|
841
877
|
}
|
|
842
878
|
|
|
843
879
|
sessions.delete(localId);
|
|
844
|
-
try { onSessionDeleted(localId); } catch (e) {
|
|
880
|
+
try { onSessionDeleted(localId, session); } catch (e) {
|
|
845
881
|
console.error("[session] Session deletion hook failed:", e.message || e);
|
|
846
882
|
}
|
|
883
|
+
for (var di = 0; di < sessionDeletedListeners.length; di++) {
|
|
884
|
+
try { sessionDeletedListeners[di](localId, session); } catch (e) {
|
|
885
|
+
console.error("[session] Session deletion listener failed:", e.message || e);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
847
888
|
|
|
848
889
|
if (activeSessionId === localId) {
|
|
849
890
|
var remaining = [...sessions.keys()];
|
|
@@ -882,9 +923,14 @@ function createSessionManager(opts) {
|
|
|
882
923
|
try { fs.unlinkSync(sessionFilePath(session.cliSessionId)); } catch(e) {}
|
|
883
924
|
}
|
|
884
925
|
sessions.delete(localId);
|
|
885
|
-
try { onSessionDeleted(localId); } catch (e) {
|
|
926
|
+
try { onSessionDeleted(localId, session); } catch (e) {
|
|
886
927
|
console.error("[session] Session deletion hook failed:", e.message || e);
|
|
887
928
|
}
|
|
929
|
+
for (var di = 0; di < sessionDeletedListeners.length; di++) {
|
|
930
|
+
try { sessionDeletedListeners[di](localId, session); } catch (e) {
|
|
931
|
+
console.error("[session] Session deletion listener failed:", e.message || e);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
888
934
|
}
|
|
889
935
|
|
|
890
936
|
function deleteSessionsBulk(localIds, targetWs) {
|
|
@@ -1250,6 +1296,8 @@ function createSessionManager(opts) {
|
|
|
1250
1296
|
sessions: sessions,
|
|
1251
1297
|
sessionsDir: sessionsDir,
|
|
1252
1298
|
setOnSessionDeleted: function (fn) { onSessionDeleted = fn || function () {}; },
|
|
1299
|
+
addOnSessionDeleted: function (fn) { if (typeof fn === "function") sessionDeletedListeners.push(fn); },
|
|
1300
|
+
addOnSessionViewed: function (fn) { if (typeof fn === "function") sessionViewedListeners.push(fn); },
|
|
1253
1301
|
setOnSessionRenamed: function (fn) { onSessionRenamed = fn || function () {}; },
|
|
1254
1302
|
setOnSessionIdentityAssigned: function (fn) { onSessionIdentityAssigned = fn || function () {}; },
|
|
1255
1303
|
notifySessionRenamed: function (localId) { onSessionRenamed(localId); },
|
package/lib/tool-llm.js
CHANGED
|
@@ -32,6 +32,7 @@ async function complete(opts) {
|
|
|
32
32
|
query: {
|
|
33
33
|
cwd: opts.cwd,
|
|
34
34
|
linuxUser: opts.linuxUser || undefined,
|
|
35
|
+
effort: selection.effort || undefined,
|
|
35
36
|
skipProjectInstructions: true,
|
|
36
37
|
skipSkills: true,
|
|
37
38
|
canUseTool: function () { return Promise.resolve({ behavior: "deny", message: "Capsule LLM calls cannot use tools." }); },
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Durable user-scoped Default AI preference storage.
|
|
2
|
+
|
|
3
|
+
var PREFERENCE_KEY = "defaultAiPreference";
|
|
4
|
+
var LEGACY_KEY = "scheduledTaskInterviewEngine";
|
|
5
|
+
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function validText(value, maxLength) {
|
|
11
|
+
return typeof value === "string" && value.length <= maxLength && !/[\u0000-\u001f\u007f]/.test(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function validatePreference(value) {
|
|
15
|
+
if (!isPlainObject(value)) return { ok: false, error: "Default AI preference must be an object." };
|
|
16
|
+
if (!validText(value.vendor, 40) || !/^[a-z0-9][a-z0-9_-]{0,39}$/.test(value.vendor)) return { ok: false, error: "Default AI vendor is invalid." };
|
|
17
|
+
if (value.model !== undefined && (!validText(value.model, 200) || (value.model !== "" && (value.model.trim() !== value.model || value.model.trim() === "")))) return { ok: false, error: "Default AI model is invalid." };
|
|
18
|
+
if (value.effort !== undefined && (!validText(value.effort, 40) || (value.effort !== "" && (value.effort.trim() !== value.effort || !/^[a-zA-Z0-9_-]{1,40}$/.test(value.effort))))) return { ok: false, error: "Default AI effort is invalid." };
|
|
19
|
+
return { ok: true, preference: { vendor: value.vendor, model: value.model === undefined ? "" : value.model, effort: value.effort === undefined ? "" : value.effort } };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function attachDefaultAiPreferences(deps) {
|
|
23
|
+
var loadUsers = deps.loadUsers;
|
|
24
|
+
var saveUsers = deps.saveUsers;
|
|
25
|
+
var isMultiUser = deps.isMultiUser || function () { return false; };
|
|
26
|
+
var configModule = deps.configModule || require("./config");
|
|
27
|
+
|
|
28
|
+
function userRecord(data, userId) {
|
|
29
|
+
var users = Array.isArray(data.users) ? data.users : [];
|
|
30
|
+
for (var i = 0; i < users.length; i++) if (users[i] && users[i].id === userId) return users[i];
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readValue(value, source) {
|
|
35
|
+
if (value === undefined) return { present: false, preference: null, source: null };
|
|
36
|
+
var checked = validatePreference(value);
|
|
37
|
+
if (!checked.ok) return { present: true, preference: null, source: source, error: checked.error };
|
|
38
|
+
return { present: true, preference: checked.preference, source: source };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getDefaultAiPreference(userId) {
|
|
42
|
+
var data;
|
|
43
|
+
try { data = loadUsers(); } catch (error) { return { present: false, preference: null, source: null, error: "Unable to read Default AI preference: " + (error.message || error) }; }
|
|
44
|
+
var record = userRecord(data, userId);
|
|
45
|
+
if (isMultiUser() && !record) return { present: false, preference: null, source: null, error: "User not found." };
|
|
46
|
+
if (record && Object.prototype.hasOwnProperty.call(record, PREFERENCE_KEY)) return readValue(record[PREFERENCE_KEY], "explicit");
|
|
47
|
+
if (record && Object.prototype.hasOwnProperty.call(record, LEGACY_KEY)) return readValue(record[LEGACY_KEY], "legacy");
|
|
48
|
+
if (userId === "default") {
|
|
49
|
+
var config;
|
|
50
|
+
try { config = configModule.loadConfig() || {}; } catch (configError) { return { present: false, preference: null, source: null, error: "Unable to read Default AI preference: " + (configError.message || configError) }; }
|
|
51
|
+
if (Object.prototype.hasOwnProperty.call(config, PREFERENCE_KEY)) return readValue(config[PREFERENCE_KEY], "explicit");
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(config, LEGACY_KEY)) return readValue(config[LEGACY_KEY], "legacy");
|
|
53
|
+
}
|
|
54
|
+
return { present: false, preference: null, source: null };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setDefaultAiPreference(userId, value) {
|
|
58
|
+
var checked = validatePreference(value);
|
|
59
|
+
if (!checked.ok) return { ok: false, error: checked.error };
|
|
60
|
+
var data;
|
|
61
|
+
try { data = loadUsers(); } catch (error3) { return { ok: false, error: "Unable to read Default AI preference: " + (error3.message || error3) }; }
|
|
62
|
+
var record = userRecord(data, userId);
|
|
63
|
+
if (isMultiUser() && !record) return { ok: false, error: "User not found." };
|
|
64
|
+
if (record) {
|
|
65
|
+
var nextData = Object.assign({}, data, { users: (data.users || []).map(function (item) { return item === record ? Object.assign({}, item, { defaultAiPreference: checked.preference }) : item; }) });
|
|
66
|
+
try { saveUsers(nextData); } catch (error) { return { ok: false, error: "Unable to save Default AI preference: " + (error.message || error) }; }
|
|
67
|
+
return { ok: true, preference: checked.preference };
|
|
68
|
+
}
|
|
69
|
+
if (userId !== "default") return { ok: false, error: "User not found." };
|
|
70
|
+
try {
|
|
71
|
+
var current = configModule.loadConfig() || {};
|
|
72
|
+
configModule.saveConfig(Object.assign({}, current, { defaultAiPreference: checked.preference }));
|
|
73
|
+
return { ok: true, preference: checked.preference };
|
|
74
|
+
} catch (error2) { return { ok: false, error: "Unable to save Default AI preference: " + (error2.message || error2) }; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { getDefaultAiPreference: getDefaultAiPreference, setDefaultAiPreference: setDefaultAiPreference, validatePreference: validatePreference };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { attachDefaultAiPreferences: attachDefaultAiPreferences, validatePreference: validatePreference, PREFERENCE_KEY: PREFERENCE_KEY };
|
|
@@ -5,7 +5,7 @@ function attachHomeSurfacePreferences(deps) {
|
|
|
5
5
|
var saveUsers = deps.saveUsers;
|
|
6
6
|
|
|
7
7
|
function defaults() {
|
|
8
|
-
return { surface: null, projectSlug: null, activeMateId: null, activeSessionByMate: {}, sidebarCollapsed: false, chatScope: "all", subSurface: "chat" };
|
|
8
|
+
return { surface: null, projectSlug: null, activeMateId: null, activeSessionByMate: {}, sidebarCollapsed: false, matesCollapsed: false, chatScope: "all", subSurface: "chat" };
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function validId(value) {
|
|
@@ -37,6 +37,7 @@ function attachHomeSurfacePreferences(deps) {
|
|
|
37
37
|
activeMateId: validId(source.activeMateId) ? source.activeMateId : null,
|
|
38
38
|
activeSessionByMate: normalizedSessions,
|
|
39
39
|
sidebarCollapsed: source.sidebarCollapsed === true,
|
|
40
|
+
matesCollapsed: source.matesCollapsed === true,
|
|
40
41
|
chatScope: source.chatScope === "current" ? "current" : "all",
|
|
41
42
|
subSurface: source.subSurface === "debates" ? "debates" : "chat",
|
|
42
43
|
};
|