clay-server 4.0.0-beta.2 → 4.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/background-task-timing.js +105 -0
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +14 -1
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-vendor-login.js +20 -3
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +178 -6
- package/lib/public/app.js +58 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +163 -28
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/index.html +45 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +109 -4
- package/lib/public/modules/app-home-hub.js +19 -7
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +84 -7
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +46 -4
- package/lib/public/modules/app-projects.js +13 -8
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +21 -1
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +132 -46
- package/lib/sdk-message-processor.js +60 -14
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +124 -18
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +61 -11
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +2 -2
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +81 -4
- package/lib/yoke/adapters/codex.js +63 -15
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +3 -3
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");
|
|
@@ -306,18 +308,50 @@ function createServer(opts) {
|
|
|
306
308
|
var workspaceQueryService = attachWorkspaceQueryService({
|
|
307
309
|
getProjects: function () { return projects; },
|
|
308
310
|
isMultiUser: function () { return users.isMultiUser(); },
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
311
|
+
// Project access is resolved from the authoritative project record, not
|
|
312
|
+
// from getStatus(), which carries no visibility or allowedUsers fields and
|
|
313
|
+
// would otherwise default every project to public.
|
|
314
|
+
onGetProjectAccess: onGetProjectAccess,
|
|
315
|
+
canAccessProject: function (userId, access) {
|
|
316
|
+
return users.canAccessProject(userId, access) === true;
|
|
315
317
|
},
|
|
316
318
|
resolveMate: function (userId, mateId) {
|
|
317
319
|
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
318
320
|
},
|
|
319
321
|
assignmentService: assignmentService,
|
|
320
322
|
});
|
|
323
|
+
// Project access is resolved from the authoritative project record, not from
|
|
324
|
+
// getStatus(), which carries no visibility or allowedUsers fields.
|
|
325
|
+
var projectLogsService = attachProjectLogsService({
|
|
326
|
+
getProjects: function () { return projects; },
|
|
327
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
328
|
+
canAccessProject: function (userId, status) {
|
|
329
|
+
if (!status || !status.slug || !onGetProjectAccess) return false;
|
|
330
|
+
var access = onGetProjectAccess(status.slug);
|
|
331
|
+
if (!access || access.error) return false;
|
|
332
|
+
return users.canAccessProject(userId, access) === true;
|
|
333
|
+
},
|
|
334
|
+
resolveMate: function (userId, mateId) {
|
|
335
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
336
|
+
},
|
|
337
|
+
findUserById: function (userId) { return users.findUserById(userId); },
|
|
338
|
+
});
|
|
339
|
+
// Mate Knowledge reads the synchronized Knowledge records. The Mate list is
|
|
340
|
+
// derived from the owning user alone, so a binding can never span users.
|
|
341
|
+
var mateKnowledgeService = attachMateKnowledgeService({
|
|
342
|
+
getProjects: function () { return projects; },
|
|
343
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
344
|
+
resolveMate: function (userId, mateId) {
|
|
345
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
346
|
+
},
|
|
347
|
+
listMates: function (userId) {
|
|
348
|
+
var mateCtx = mates.buildMateCtx(users.isMultiUser() ? userId : null);
|
|
349
|
+
var all = mates.getAllMates(mateCtx) || [];
|
|
350
|
+
return all.map(function (mate) {
|
|
351
|
+
return { id: mate.id, name: mate.name || null, createdBy: mate.createdBy || null, dir: mates.getMateDir(mateCtx, mate.id) };
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
});
|
|
321
355
|
|
|
322
356
|
// --- Admin module ---
|
|
323
357
|
var admin = serverAdmin.attachAdmin({
|
|
@@ -351,6 +385,9 @@ function createServer(opts) {
|
|
|
351
385
|
projects: projects,
|
|
352
386
|
opts: opts,
|
|
353
387
|
CONFIG_DIR: CONFIG_DIR,
|
|
388
|
+
onCapsulesPreferenceChanged: function (userId) {
|
|
389
|
+
if (toolsHandler) toolsHandler.refreshMateCatalogs(userId);
|
|
390
|
+
},
|
|
354
391
|
});
|
|
355
392
|
|
|
356
393
|
var palette = serverPalette.attachPalette({
|
|
@@ -388,16 +425,38 @@ function createServer(opts) {
|
|
|
388
425
|
securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
|
|
389
426
|
}
|
|
390
427
|
|
|
391
|
-
|
|
428
|
+
// Capsule rich-Display frames are served by a second listener on the same
|
|
429
|
+
// hostname but a different port, so frame-src cannot stay at the default
|
|
430
|
+
// 'self'. The allowance is computed from the request's own hostname with
|
|
431
|
+
// any port, never a wildcard host, and only for the scheme the app itself
|
|
432
|
+
// speaks. If the host header is unusable the CSP stays at 'self' and the
|
|
433
|
+
// frame silently falls back to the floor Display.
|
|
434
|
+
function frameSrcFor(req) {
|
|
435
|
+
var host = req && req.headers && typeof req.headers.host === "string" ? req.headers.host : "";
|
|
436
|
+
if (!host) return "'self'";
|
|
437
|
+
var scheme = tlsOptions ? "https" : "http";
|
|
438
|
+
var hostname;
|
|
439
|
+
try {
|
|
440
|
+
hostname = new URL(scheme + "://" + host).hostname;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
return "'self'";
|
|
443
|
+
}
|
|
444
|
+
if (!hostname) return "'self'";
|
|
445
|
+
if (hostname.indexOf(":") !== -1) hostname = "[" + hostname + "]";
|
|
446
|
+
return "'self' " + scheme + "://" + hostname + ":*";
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function setSecurityHeaders(res, req) {
|
|
392
450
|
var keys = Object.keys(securityHeaders);
|
|
393
451
|
for (var i = 0; i < keys.length; i++) {
|
|
394
452
|
res.setHeader(keys[i], securityHeaders[keys[i]]);
|
|
395
453
|
}
|
|
454
|
+
res.setHeader("Content-Security-Policy", securityHeaders["Content-Security-Policy"] + " frame-src " + frameSrcFor(req) + ";");
|
|
396
455
|
}
|
|
397
456
|
|
|
398
457
|
// --- HTTP handler ---
|
|
399
458
|
var appHandler = function (req, res) {
|
|
400
|
-
setSecurityHeaders(res);
|
|
459
|
+
setSecurityHeaders(res, req);
|
|
401
460
|
var fullUrl = req.url.split("?")[0];
|
|
402
461
|
|
|
403
462
|
// --- Auth routes (delegated to server-auth) ---
|
|
@@ -937,17 +996,24 @@ function createServer(opts) {
|
|
|
937
996
|
|
|
938
997
|
// --- WebSocket ---
|
|
939
998
|
var wss = new WebSocketServer({ noServer: true });
|
|
999
|
+
var homePreferencesHandler = null;
|
|
940
1000
|
|
|
941
1001
|
// Slug-less /ws handler: lets a user with no projects yet load the regular
|
|
942
1002
|
// app shell and create/add/clone their first one. Only knows about the
|
|
943
|
-
// small set of bootstrap messages
|
|
944
|
-
//
|
|
1003
|
+
// small set of bootstrap messages, including the Home preferences required
|
|
1004
|
+
// to dismiss the workspace restoration screen.
|
|
945
1005
|
var globalWs = serverGlobalWs.attachGlobalWs({
|
|
946
1006
|
osUsers: osUsers,
|
|
947
1007
|
usersModule: users,
|
|
948
1008
|
onAddProject: onAddProject,
|
|
949
1009
|
onCreateProject: onCreateProject,
|
|
950
1010
|
onCloneProject: onCloneProject,
|
|
1011
|
+
onHomePreferenceMessage: function (ws, msg) {
|
|
1012
|
+
return homePreferencesHandler ? homePreferencesHandler.handleMessage(ws, msg) : false;
|
|
1013
|
+
},
|
|
1014
|
+
onAppMessage: function (ws, msg) {
|
|
1015
|
+
return handleDmMessage(ws, msg);
|
|
1016
|
+
},
|
|
951
1017
|
});
|
|
952
1018
|
|
|
953
1019
|
server.on("upgrade", function (req, socket, head) {
|
|
@@ -1155,6 +1221,24 @@ function createServer(opts) {
|
|
|
1155
1221
|
}
|
|
1156
1222
|
|
|
1157
1223
|
// --- Project management ---
|
|
1224
|
+
function refreshVendorAuthEverywhere(vendor, linuxUser) {
|
|
1225
|
+
var refreshes = [];
|
|
1226
|
+
projects.forEach(function (projectCtx) {
|
|
1227
|
+
if (!projectCtx || typeof projectCtx.refreshVendorAuth !== "function") return;
|
|
1228
|
+
try {
|
|
1229
|
+
refreshes.push(Promise.resolve(projectCtx.refreshVendorAuth(vendor, linuxUser)).catch(function (err) {
|
|
1230
|
+
console.error("[server] Cross-project " + vendor + " auth refresh failed:", err && err.message ? err.message : err);
|
|
1231
|
+
return false;
|
|
1232
|
+
}));
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
console.error("[server] Cross-project " + vendor + " auth refresh threw:", err && err.message ? err.message : err);
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
return Promise.all(refreshes).then(function (results) {
|
|
1238
|
+
return results.some(function (refreshed) { return !!refreshed; });
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1158
1242
|
function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
|
|
1159
1243
|
if (projects.has(slug)) return false;
|
|
1160
1244
|
var extra = extraOpts || {};
|
|
@@ -1164,6 +1248,7 @@ function createServer(opts) {
|
|
|
1164
1248
|
title: title || null,
|
|
1165
1249
|
icon: icon || null,
|
|
1166
1250
|
projectOwnerId: projectOwnerId || null,
|
|
1251
|
+
projectKnowledgeId: extra.projectKnowledgeId || (worktreeMeta && worktreeMeta.projectKnowledgeId) || null,
|
|
1167
1252
|
worktreeMeta: worktreeMeta || null,
|
|
1168
1253
|
isMate: extra.isMate || false,
|
|
1169
1254
|
mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
|
|
@@ -1174,6 +1259,8 @@ function createServer(opts) {
|
|
|
1174
1259
|
return mateHandler.createReadyMateFromInterview(ws, userId, definition);
|
|
1175
1260
|
},
|
|
1176
1261
|
workspaceQueryService: workspaceQueryService,
|
|
1262
|
+
projectLogsService: projectLogsService,
|
|
1263
|
+
mateKnowledgeService: mateKnowledgeService,
|
|
1177
1264
|
getProjectAccess: function () {
|
|
1178
1265
|
return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
|
|
1179
1266
|
},
|
|
@@ -1327,6 +1414,7 @@ function createServer(opts) {
|
|
|
1327
1414
|
onDmMessage: handleDmMessage,
|
|
1328
1415
|
broadcastAll: broadcastAll,
|
|
1329
1416
|
notificationsModule: _globalNotifications,
|
|
1417
|
+
refreshVendorAuthEverywhere: refreshVendorAuthEverywhere,
|
|
1330
1418
|
getProject: function (s) { return projects.get(s) || null; },
|
|
1331
1419
|
isUserOnline: isUserOnline,
|
|
1332
1420
|
});
|
|
@@ -1342,6 +1430,10 @@ function createServer(opts) {
|
|
|
1342
1430
|
return true;
|
|
1343
1431
|
}
|
|
1344
1432
|
|
|
1433
|
+
function setProjectLogContextState(slug, status) {
|
|
1434
|
+
return projectLogsService.setContextState(slug, status);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1345
1437
|
// --- DM message handler (delegated to server-dm + server-mates inline) ---
|
|
1346
1438
|
var dmHandler = serverDm.attachDm({
|
|
1347
1439
|
users: users,
|
|
@@ -1362,9 +1454,10 @@ function createServer(opts) {
|
|
|
1362
1454
|
projects: projects,
|
|
1363
1455
|
addProject: addProject,
|
|
1364
1456
|
workspaceQueryService: workspaceQueryService,
|
|
1457
|
+
projectLogsService: projectLogsService,
|
|
1365
1458
|
});
|
|
1366
1459
|
|
|
1367
|
-
|
|
1460
|
+
homePreferencesHandler = serverHomePreferences.attachHomePreferences({
|
|
1368
1461
|
users: users,
|
|
1369
1462
|
projects: projects,
|
|
1370
1463
|
});
|
|
@@ -1373,6 +1466,17 @@ function createServer(opts) {
|
|
|
1373
1466
|
var toolsHandler = serverTools.attachTools({
|
|
1374
1467
|
users: users,
|
|
1375
1468
|
projects: projects,
|
|
1469
|
+
// The rich-Display frame listener must speak the same scheme as the app,
|
|
1470
|
+
// or the sandboxed frame would be blocked as mixed content under TLS.
|
|
1471
|
+
tlsOptions: tlsOptions,
|
|
1472
|
+
// Lets a Capsule turn handed to the mate seat wake the opponent Mate's
|
|
1473
|
+
// project, registering it first if it has never been opened.
|
|
1474
|
+
findMateProject: homeChatHandler.findMateProject,
|
|
1475
|
+
isCapsulesEnabled: function (userId) {
|
|
1476
|
+
if (users.isMultiUser()) return users.getCapsulesEnabled(userId);
|
|
1477
|
+
var daemonConfig = onGetDaemonConfig ? onGetDaemonConfig() : {};
|
|
1478
|
+
return daemonConfig.capsulesEnabled === true;
|
|
1479
|
+
},
|
|
1376
1480
|
});
|
|
1377
1481
|
|
|
1378
1482
|
// --- Mate handler ---
|
|
@@ -1383,13 +1487,14 @@ function createServer(opts) {
|
|
|
1383
1487
|
}
|
|
1384
1488
|
|
|
1385
1489
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1386
|
-
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return;
|
|
1387
|
-
if (dmHandler.handleMessage(ws, msg)) return;
|
|
1388
|
-
if (mateHandler && mateHandler.handleMessage(ws, msg)) return;
|
|
1389
|
-
if (emailHandler.handleMessage(ws, msg)) return;
|
|
1390
|
-
if (homeChatHandler.handleMessage(ws, msg)) return;
|
|
1391
|
-
if (homePreferencesHandler.handleMessage(ws, msg)) return;
|
|
1392
|
-
if (toolsHandler.handleMessage(ws, msg)) return;
|
|
1490
|
+
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1491
|
+
if (dmHandler.handleMessage(ws, msg)) return true;
|
|
1492
|
+
if (mateHandler && mateHandler.handleMessage(ws, msg)) return true;
|
|
1493
|
+
if (emailHandler.handleMessage(ws, msg)) return true;
|
|
1494
|
+
if (homeChatHandler.handleMessage(ws, msg)) return true;
|
|
1495
|
+
if (homePreferencesHandler.handleMessage(ws, msg)) return true;
|
|
1496
|
+
if (toolsHandler.handleMessage(ws, msg)) return true;
|
|
1497
|
+
return false;
|
|
1393
1498
|
}
|
|
1394
1499
|
|
|
1395
1500
|
function removeProject(slug) {
|
|
@@ -1683,6 +1788,7 @@ function createServer(opts) {
|
|
|
1683
1788
|
forEachProject: forEachProject,
|
|
1684
1789
|
destroyProject: removeProject,
|
|
1685
1790
|
destroyAll: destroyAll,
|
|
1791
|
+
setProjectLogContextState: setProjectLogContextState,
|
|
1686
1792
|
};
|
|
1687
1793
|
}
|
|
1688
1794
|
|
|
@@ -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
|
}
|
|
@@ -9,14 +9,18 @@ var MEMORY_CONTRACT =
|
|
|
9
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. Include the observable evidence, affected component, likely impact, and a clear next action. 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
|
-
"Updates are visible too: update only when durable state materially changes
|
|
12
|
+
"Updates are visible too: update only when durable state materially changes. " +
|
|
13
|
+
"A note is an attention item, so finishing it means closing it, never erasing it: call close_note once the thing it is asking for is done, and the note leaves the active board while the record stays and can be reopened. Do not turn a note into a completion log instead of closing it. " +
|
|
14
|
+
"Put a concise plain-text title on the first line, stay focused on one topic, and include only the context needed for future action.";
|
|
13
15
|
|
|
14
16
|
function getToolDefs(handlers) {
|
|
15
17
|
return [
|
|
16
18
|
{
|
|
17
19
|
name: "list_notes",
|
|
18
|
-
description: MEMORY_CONTRACT + " List the
|
|
19
|
-
inputSchema: buildShape({
|
|
20
|
+
description: MEMORY_CONTRACT + " List the notes before writing when you need to avoid duplicates or inspect full memory beyond the injected summary. Lists open notes by default; pass state to see closed ones.",
|
|
21
|
+
inputSchema: buildShape({
|
|
22
|
+
state: { type: "string", enum: ["open", "closed", "all"], description: "Which notes to list. Defaults to open, the notes still asking for action." },
|
|
23
|
+
}),
|
|
20
24
|
handler: function (args) { return handlers.list(args || {}); },
|
|
21
25
|
},
|
|
22
26
|
{
|
|
@@ -29,11 +33,27 @@ function getToolDefs(handlers) {
|
|
|
29
33
|
}, ["text"]),
|
|
30
34
|
handler: function (args) { return handlers.write(args || {}); },
|
|
31
35
|
},
|
|
36
|
+
{
|
|
37
|
+
name: "close_note",
|
|
38
|
+
description: MEMORY_CONTRACT + " Close a note created by this same session once what it asked for is done. Closing is not deletion: the note leaves the active board, stays queryable under state closed, and can be reopened. Notes created by users or other sessions cannot be closed by this session.",
|
|
39
|
+
inputSchema: buildShape({
|
|
40
|
+
id: { type: "string", description: "Id of the note to close." },
|
|
41
|
+
}, ["id"]),
|
|
42
|
+
handler: function (args) { return handlers.close(args || {}); },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "reopen_note",
|
|
46
|
+
description: MEMORY_CONTRACT + " Reopen a closed note created by this same session when it turns out to still need action. The note returns to the active board unchanged.",
|
|
47
|
+
inputSchema: buildShape({
|
|
48
|
+
id: { type: "string", description: "Id of the note to reopen." },
|
|
49
|
+
}, ["id"]),
|
|
50
|
+
handler: function (args) { return handlers.reopen(args || {}); },
|
|
51
|
+
},
|
|
32
52
|
{
|
|
33
53
|
name: "remove_note",
|
|
34
|
-
description: MEMORY_CONTRACT + "
|
|
54
|
+
description: MEMORY_CONTRACT + " Deprecated: use close_note. This tool no longer deletes anything and now closes the note instead, so an older caller cannot destroy a record. Same ownership rule as close_note.",
|
|
35
55
|
inputSchema: buildShape({
|
|
36
|
-
id: { type: "string", description: "Id of the note to
|
|
56
|
+
id: { type: "string", description: "Id of the note to close. Nothing is deleted." },
|
|
37
57
|
}, ["id"]),
|
|
38
58
|
handler: function (args) { return handlers.remove(args || {}); },
|
|
39
59
|
},
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Split-group pair creation.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from project-session-pair.js, which operates a pair once it
|
|
4
|
+
// exists; this module is only about how one comes into being. Both the
|
|
5
|
+
// explicit "Add Split Worker", accepted proposal, and transactional replacement
|
|
6
|
+
// flows land here, so vendor validation, owner derivation and the group record
|
|
7
|
+
// are written in exactly one place.
|
|
8
|
+
//
|
|
9
|
+
// Ownership always comes from the connection or the Driver session, never from
|
|
10
|
+
// the incoming message.
|
|
11
|
+
//
|
|
12
|
+
// CONTEXT CONTRACT: this module reads `sm`, `splitStore`, `isMate`,
|
|
13
|
+
// `usersModule` and `sendTo` off its own argument, all at the top level.
|
|
14
|
+
// attachSessionPair's context already carries all five, so it is passed
|
|
15
|
+
// through unchanged. Re-wrapping it (it was once `{ sm, splitStore, ctx }`)
|
|
16
|
+
// leaves `isMate`, `usersModule` and `sendTo` undefined, which silently
|
|
17
|
+
// disables the Mate guard, breaks the pair_session_create reply, and throws
|
|
18
|
+
// "Cannot read properties of undefined (reading 'isMultiUser')" the moment a
|
|
19
|
+
// pair is created for an owned session — single-user installs escape it only
|
|
20
|
+
// because a Driver with no ownerId short-circuits that expression. Add any new
|
|
21
|
+
// dependency as a top-level field of that same context.
|
|
22
|
+
//
|
|
23
|
+
// Two ordering rules matter and are load-bearing:
|
|
24
|
+
//
|
|
25
|
+
// 1. Nothing is created until everything is validated. Driver capability and
|
|
26
|
+
// both runtime requests are settled first, so a rejected request leaves no
|
|
27
|
+
// orphan session behind. Any installed, available model may be the Driver;
|
|
28
|
+
// model choice is the user's rather than a server-side tier policy.
|
|
29
|
+
// 2. `preflightRuntime` is side-effect free, so a caller that is about to
|
|
30
|
+
// destroy something (replacement dissolving a live pair) can validate the
|
|
31
|
+
// replacement first and abort while the old pair is still intact.
|
|
32
|
+
|
|
33
|
+
var driverEligibility = require("./session-driver-eligibility");
|
|
34
|
+
var models = require("./project-models");
|
|
35
|
+
var yoke = require("./yoke");
|
|
36
|
+
var sessionProvenance = require("./session-provenance");
|
|
37
|
+
|
|
38
|
+
function attachPairFactory(ctx) {
|
|
39
|
+
var sm = ctx.sm;
|
|
40
|
+
var store = ctx.splitStore;
|
|
41
|
+
|
|
42
|
+
function validateVendor(vendor) {
|
|
43
|
+
var installed = sm.installedVendors || [];
|
|
44
|
+
if (!vendor || installed.indexOf(vendor) === -1) throw new Error("vendor is not installed: " + (vendor || "unknown"));
|
|
45
|
+
return vendor;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// An explicitly requested model must exist in the server's own catalog for
|
|
49
|
+
// that vendor, matched through the repo's canonical matcher so an alias, an
|
|
50
|
+
// id or a resolvedModel all count. Caller text is never taken on trust: if
|
|
51
|
+
// the catalog is not populated we cannot confirm the model, so an explicit
|
|
52
|
+
// request fails closed rather than being forwarded blindly.
|
|
53
|
+
function validateModel(vendor, model) {
|
|
54
|
+
var requested = typeof model === "string" ? model.trim() : "";
|
|
55
|
+
if (!requested) return null;
|
|
56
|
+
var catalog = (sm.modelsByVendor && sm.modelsByVendor[vendor]) || [];
|
|
57
|
+
if (catalog.length === 0) {
|
|
58
|
+
throw new Error("no models are available yet for vendor \"" + vendor +
|
|
59
|
+
"\", so model \"" + requested + "\" cannot be confirmed");
|
|
60
|
+
}
|
|
61
|
+
for (var i = 0; i < catalog.length; i++) {
|
|
62
|
+
if (models.modelEntryMatches(catalog[i], requested)) return requested;
|
|
63
|
+
}
|
|
64
|
+
throw new Error("model is not available for vendor \"" + vendor + "\": " + requested);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validateEffort(vendor, effort) {
|
|
68
|
+
var requested = typeof effort === "string" ? effort.trim() : "";
|
|
69
|
+
var fallback = (sm.currentEffortByVendor && sm.currentEffortByVendor[vendor]) || sm.currentEffort || "medium";
|
|
70
|
+
var clamped = yoke.clampEffort(vendor, requested || fallback) || null;
|
|
71
|
+
if (requested && !clamped) {
|
|
72
|
+
throw new Error("reasoning effort is not supported for vendor \"" + vendor + "\": " + requested);
|
|
73
|
+
}
|
|
74
|
+
return clamped;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Resolve and validate one session runtime request without creating
|
|
78
|
+
// anything. Throws on the first unusable field.
|
|
79
|
+
function preflightRuntime(spec, defaultVendor) {
|
|
80
|
+
var request = spec || {};
|
|
81
|
+
var vendor = validateVendor((typeof request.vendor === "string" && request.vendor.trim()) || defaultVendor);
|
|
82
|
+
return {
|
|
83
|
+
vendor: vendor,
|
|
84
|
+
model: validateModel(vendor, request.model),
|
|
85
|
+
effort: validateEffort(vendor, request.effort),
|
|
86
|
+
explicitEffort: !!(typeof request.effort === "string" && request.effort.trim()),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The default Worker vendor for an accepted proposal or replacement prefers
|
|
91
|
+
// a different engine from the Driver's own, so the pair does not double up.
|
|
92
|
+
function defaultWorkerVendorFor(driver) {
|
|
93
|
+
var installed = sm.installedVendors || [];
|
|
94
|
+
if (installed.length === 0) throw new Error("no coding agent is installed for a Split Worker session");
|
|
95
|
+
if (installed.indexOf("codex") !== -1 && driver.vendor !== "codex") return "codex";
|
|
96
|
+
for (var i = 0; i < installed.length; i++) {
|
|
97
|
+
if (installed[i] !== driver.vendor) return installed[i];
|
|
98
|
+
}
|
|
99
|
+
return installed[0];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Side-effect-free check that this exact Driver could create this exact
|
|
103
|
+
// Worker right now. Used by replacement before it touches anything.
|
|
104
|
+
function preflightWorkerForDriver(driver, args) {
|
|
105
|
+
if (ctx.isMate) throw new Error("Pair sessions are only available in projects");
|
|
106
|
+
if (!driver) throw new Error("driver session not found");
|
|
107
|
+
var verdict = driverEligibility.evaluateDriverSession(driver, sm);
|
|
108
|
+
if (!verdict.ok) throw new Error(verdict.error);
|
|
109
|
+
var options = args || {};
|
|
110
|
+
var vendor = typeof options.workerVendor === "string" ? options.workerVendor.trim() : "";
|
|
111
|
+
return preflightRuntime({
|
|
112
|
+
vendor: vendor || defaultWorkerVendorFor(driver),
|
|
113
|
+
model: options.workerModel,
|
|
114
|
+
effort: options.workerEffort,
|
|
115
|
+
}, null);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createPairRecord(ws, msg) {
|
|
119
|
+
if (ctx.isMate) throw new Error("Pair sessions are only available in projects");
|
|
120
|
+
var driverSpec = msg.driver || {};
|
|
121
|
+
var workerSpec = msg.worker || {};
|
|
122
|
+
var ownerId = ws._clayUser && ctx.usersModule.isMultiUser() ? ws._clayUser.id : null;
|
|
123
|
+
|
|
124
|
+
// --- Validate everything first; create nothing yet. ---
|
|
125
|
+
|
|
126
|
+
var workerRuntime = preflightRuntime(workerSpec, sm.lastVendor || "codex");
|
|
127
|
+
|
|
128
|
+
var existingDriver = null;
|
|
129
|
+
var driverRuntime = null;
|
|
130
|
+
var groupName;
|
|
131
|
+
if (Number.isInteger(driverSpec.sessionId)) {
|
|
132
|
+
// "Add Split Worker" on an existing session: the current session becomes
|
|
133
|
+
// the Driver with its full conversation context intact.
|
|
134
|
+
existingDriver = sm.sessions.get(driverSpec.sessionId);
|
|
135
|
+
if (!existingDriver) throw new Error("driver session not found");
|
|
136
|
+
if ((existingDriver.ownerId || null) !== ownerId) throw new Error("driver session access denied");
|
|
137
|
+
if (store.groupForMember(existingDriver.localId)) throw new Error("this session is already in a split group");
|
|
138
|
+
var existingVerdict = driverEligibility.evaluateDriverSession(existingDriver, sm);
|
|
139
|
+
if (!existingVerdict.ok) throw new Error(existingVerdict.error);
|
|
140
|
+
groupName = undefined; // auto name from member titles
|
|
141
|
+
} else {
|
|
142
|
+
driverRuntime = preflightRuntime(driverSpec, "claude");
|
|
143
|
+
groupName = "Agent pair";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- Everything is valid; now create. ---
|
|
147
|
+
|
|
148
|
+
var driver = existingDriver;
|
|
149
|
+
var createdDriver = null;
|
|
150
|
+
if (!driver) {
|
|
151
|
+
driver = sm.createSessionRaw({
|
|
152
|
+
ownerId: ownerId,
|
|
153
|
+
vendor: driverRuntime.vendor,
|
|
154
|
+
model: driverRuntime.model,
|
|
155
|
+
effort: driverRuntime.effort,
|
|
156
|
+
});
|
|
157
|
+
createdDriver = driver;
|
|
158
|
+
driver.title = "Driver · " + ((yoke.getVendorInfo(driverRuntime.vendor) || {}).displayName || driverRuntime.vendor);
|
|
159
|
+
if (driverRuntime.explicitEffort) driver.loopSettings = { effort: driverRuntime.effort };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
var worker = sm.createSessionRaw({
|
|
163
|
+
ownerId: ownerId,
|
|
164
|
+
vendor: workerRuntime.vendor,
|
|
165
|
+
model: workerRuntime.model,
|
|
166
|
+
effort: workerRuntime.effort,
|
|
167
|
+
});
|
|
168
|
+
worker.title = "Split Worker · " + ((yoke.getVendorInfo(workerRuntime.vendor) || {}).displayName || workerRuntime.vendor);
|
|
169
|
+
if (workerRuntime.explicitEffort) worker.loopSettings = { effort: workerRuntime.effort };
|
|
170
|
+
|
|
171
|
+
var result;
|
|
172
|
+
try {
|
|
173
|
+
// Creation provenance is durable and independent from the active split
|
|
174
|
+
// group. Persist both ends before the group's broadcast so the Worker
|
|
175
|
+
// never flashes as unrelated and survives a restart with its parentage.
|
|
176
|
+
sessionProvenance.markWorker(driver, worker, sm.sessions);
|
|
177
|
+
if (typeof sm.saveSessionFile === "function") {
|
|
178
|
+
sm.saveSessionFile(driver);
|
|
179
|
+
sm.saveSessionFile(worker);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
result = store.create(ws, {
|
|
183
|
+
members: [driver.localId, worker.localId],
|
|
184
|
+
pair: { driverId: driver.localId, workerId: worker.localId },
|
|
185
|
+
name: groupName,
|
|
186
|
+
});
|
|
187
|
+
if (!result.ok) throw new Error(result.error);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
// Any failure after session creation must remove exactly the sessions
|
|
190
|
+
// made by this call. An existing Driver is never removed.
|
|
191
|
+
removeCreated(worker);
|
|
192
|
+
removeCreated(createdDriver);
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
195
|
+
sm.broadcastSessionList();
|
|
196
|
+
return { driver: driver, worker: worker, group: result.group };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function removeCreated(session) {
|
|
200
|
+
if (!session) return;
|
|
201
|
+
if (typeof sm.deleteSessionQuiet === "function") {
|
|
202
|
+
try { sm.deleteSessionQuiet(session.localId); return; } catch (e) {}
|
|
203
|
+
}
|
|
204
|
+
try { sm.sessions.delete(session.localId); } catch (e) {}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function createWorkerForDriver(driver, args) {
|
|
208
|
+
// Validates the Driver's capability and the whole Worker runtime before
|
|
209
|
+
// any session is made, so a rejection creates nothing.
|
|
210
|
+
var runtime = preflightWorkerForDriver(driver, args);
|
|
211
|
+
var ws = {
|
|
212
|
+
_clayActiveSession: driver.localId,
|
|
213
|
+
_clayUser: driver.ownerId ? { id: driver.ownerId } : null,
|
|
214
|
+
};
|
|
215
|
+
var created = createPairRecord(ws, {
|
|
216
|
+
driver: { sessionId: driver.localId },
|
|
217
|
+
worker: {
|
|
218
|
+
vendor: runtime.vendor,
|
|
219
|
+
model: runtime.model || "",
|
|
220
|
+
effort: runtime.explicitEffort ? (args && args.workerEffort) || "" : "",
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
sm.sendToSession(driver, { type: "pair_session_created", ok: true, group: created.group });
|
|
224
|
+
return created;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function createPair(ws, msg) {
|
|
228
|
+
try {
|
|
229
|
+
var created = createPairRecord(ws, msg);
|
|
230
|
+
ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
231
|
+
} catch (e) {
|
|
232
|
+
ctx.sendTo(ws, { type: "pair_session_created", ok: false, error: e.message || String(e) });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
createPair: createPair,
|
|
238
|
+
createPairRecord: createPairRecord,
|
|
239
|
+
createWorkerForDriver: createWorkerForDriver,
|
|
240
|
+
preflightRuntime: preflightRuntime,
|
|
241
|
+
preflightWorkerForDriver: preflightWorkerForDriver,
|
|
242
|
+
validateModel: validateModel,
|
|
243
|
+
validateVendor: validateVendor,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = { attachPairFactory: attachPairFactory };
|