clay-server 4.0.0-beta.9 → 4.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-request-access.js +66 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +71 -16
- package/lib/project-user-message.js +53 -4
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +191 -6
- package/lib/public/app.js +64 -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 +45 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +92 -11
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-surface-boot.js +7 -3
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-activation.js +45 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/search-clay-chat.js +47 -6
- package/lib/public/modules/search-clay-controls.js +86 -0
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +31 -123
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +136 -47
- package/lib/sdk-message-processor.js +36 -7
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +34 -1
- package/lib/server-home-chat.js +62 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +137 -22
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +41 -0
- package/lib/sessions.js +57 -10
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +6 -6
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +44 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
package/lib/server.js
CHANGED
|
@@ -23,6 +23,8 @@ var serverPalette = require("./server-palette");
|
|
|
23
23
|
var workspaceQueryModule = require("./workspace-query-service");
|
|
24
24
|
var { attachWorkspaceQueryService } = workspaceQueryModule;
|
|
25
25
|
var { attachWorkspaceAssignmentService } = require("./workspace-assignment-service");
|
|
26
|
+
var { attachProjectLogsService } = require("./project-logs-service");
|
|
27
|
+
var { attachMateKnowledgeService } = require("./mate-knowledge-service");
|
|
26
28
|
var serverEmail = require("./server-email");
|
|
27
29
|
var serverGlobalWs = require("./server-global-ws");
|
|
28
30
|
var { parseWsRequestUrl } = require("./ws-request");
|
|
@@ -122,6 +124,14 @@ function stripPrefix(urlPath, slug) {
|
|
|
122
124
|
return rest || "/";
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
function injectRootProjectSlug(html, slug) {
|
|
128
|
+
var bodyPattern = /<body\b([^>]*)>/i;
|
|
129
|
+
if (!bodyPattern.test(html)) throw new Error("App shell body is missing");
|
|
130
|
+
return html.replace(bodyPattern, function (_match, attributes) {
|
|
131
|
+
return '<body' + attributes + ' data-home-project-slug="' + slug + '">';
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
/**
|
|
126
136
|
* Create a multi-project server.
|
|
127
137
|
* opts: { tlsOptions, caPath, pinHash, port, debug, dangerouslySkipPermissions }
|
|
@@ -306,18 +316,50 @@ function createServer(opts) {
|
|
|
306
316
|
var workspaceQueryService = attachWorkspaceQueryService({
|
|
307
317
|
getProjects: function () { return projects; },
|
|
308
318
|
isMultiUser: function () { return users.isMultiUser(); },
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
// Project access is resolved from the authoritative project record, not
|
|
320
|
+
// from getStatus(), which carries no visibility or allowedUsers fields and
|
|
321
|
+
// would otherwise default every project to public.
|
|
322
|
+
onGetProjectAccess: onGetProjectAccess,
|
|
323
|
+
canAccessProject: function (userId, access) {
|
|
324
|
+
return users.canAccessProject(userId, access) === true;
|
|
315
325
|
},
|
|
316
326
|
resolveMate: function (userId, mateId) {
|
|
317
327
|
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
318
328
|
},
|
|
319
329
|
assignmentService: assignmentService,
|
|
320
330
|
});
|
|
331
|
+
// Project access is resolved from the authoritative project record, not from
|
|
332
|
+
// getStatus(), which carries no visibility or allowedUsers fields.
|
|
333
|
+
var projectLogsService = attachProjectLogsService({
|
|
334
|
+
getProjects: function () { return projects; },
|
|
335
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
336
|
+
canAccessProject: function (userId, status) {
|
|
337
|
+
if (!status || !status.slug || !onGetProjectAccess) return false;
|
|
338
|
+
var access = onGetProjectAccess(status.slug);
|
|
339
|
+
if (!access || access.error) return false;
|
|
340
|
+
return users.canAccessProject(userId, access) === true;
|
|
341
|
+
},
|
|
342
|
+
resolveMate: function (userId, mateId) {
|
|
343
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
344
|
+
},
|
|
345
|
+
findUserById: function (userId) { return users.findUserById(userId); },
|
|
346
|
+
});
|
|
347
|
+
// Mate Knowledge reads the synchronized Knowledge records. The Mate list is
|
|
348
|
+
// derived from the owning user alone, so a binding can never span users.
|
|
349
|
+
var mateKnowledgeService = attachMateKnowledgeService({
|
|
350
|
+
getProjects: function () { return projects; },
|
|
351
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
352
|
+
resolveMate: function (userId, mateId) {
|
|
353
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
354
|
+
},
|
|
355
|
+
listMates: function (userId) {
|
|
356
|
+
var mateCtx = mates.buildMateCtx(users.isMultiUser() ? userId : null);
|
|
357
|
+
var all = mates.getAllMates(mateCtx) || [];
|
|
358
|
+
return all.map(function (mate) {
|
|
359
|
+
return { id: mate.id, name: mate.name || null, createdBy: mate.createdBy || null, dir: mates.getMateDir(mateCtx, mate.id) };
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
});
|
|
321
363
|
|
|
322
364
|
// --- Admin module ---
|
|
323
365
|
var admin = serverAdmin.attachAdmin({
|
|
@@ -351,6 +393,9 @@ function createServer(opts) {
|
|
|
351
393
|
projects: projects,
|
|
352
394
|
opts: opts,
|
|
353
395
|
CONFIG_DIR: CONFIG_DIR,
|
|
396
|
+
onCapsulesPreferenceChanged: function (userId) {
|
|
397
|
+
if (toolsHandler) toolsHandler.refreshMateCatalogs(userId);
|
|
398
|
+
},
|
|
354
399
|
});
|
|
355
400
|
|
|
356
401
|
var palette = serverPalette.attachPalette({
|
|
@@ -388,16 +433,38 @@ function createServer(opts) {
|
|
|
388
433
|
securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
|
|
389
434
|
}
|
|
390
435
|
|
|
391
|
-
|
|
436
|
+
// Capsule rich-Display frames are served by a second listener on the same
|
|
437
|
+
// hostname but a different port, so frame-src cannot stay at the default
|
|
438
|
+
// 'self'. The allowance is computed from the request's own hostname with
|
|
439
|
+
// any port, never a wildcard host, and only for the scheme the app itself
|
|
440
|
+
// speaks. If the host header is unusable the CSP stays at 'self' and the
|
|
441
|
+
// frame silently falls back to the floor Display.
|
|
442
|
+
function frameSrcFor(req) {
|
|
443
|
+
var host = req && req.headers && typeof req.headers.host === "string" ? req.headers.host : "";
|
|
444
|
+
if (!host) return "'self'";
|
|
445
|
+
var scheme = tlsOptions ? "https" : "http";
|
|
446
|
+
var hostname;
|
|
447
|
+
try {
|
|
448
|
+
hostname = new URL(scheme + "://" + host).hostname;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return "'self'";
|
|
451
|
+
}
|
|
452
|
+
if (!hostname) return "'self'";
|
|
453
|
+
if (hostname.indexOf(":") !== -1) hostname = "[" + hostname + "]";
|
|
454
|
+
return "'self' " + scheme + "://" + hostname + ":*";
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function setSecurityHeaders(res, req) {
|
|
392
458
|
var keys = Object.keys(securityHeaders);
|
|
393
459
|
for (var i = 0; i < keys.length; i++) {
|
|
394
460
|
res.setHeader(keys[i], securityHeaders[keys[i]]);
|
|
395
461
|
}
|
|
462
|
+
res.setHeader("Content-Security-Policy", securityHeaders["Content-Security-Policy"] + " frame-src " + frameSrcFor(req) + ";");
|
|
396
463
|
}
|
|
397
464
|
|
|
398
465
|
// --- HTTP handler ---
|
|
399
466
|
var appHandler = function (req, res) {
|
|
400
|
-
setSecurityHeaders(res);
|
|
467
|
+
setSecurityHeaders(res, req);
|
|
401
468
|
var fullUrl = req.url.split("?")[0];
|
|
402
469
|
|
|
403
470
|
// --- Auth routes (delegated to server-auth) ---
|
|
@@ -682,7 +749,8 @@ function createServer(opts) {
|
|
|
682
749
|
// --- Skills routes (delegated to server-skills) ---
|
|
683
750
|
if (skills.handleRequest(req, res, fullUrl)) return;
|
|
684
751
|
|
|
685
|
-
// Root path — render
|
|
752
|
+
// Root path — render the default project workspace while keeping Home
|
|
753
|
+
// available as an explicit client-side surface.
|
|
686
754
|
if (fullUrl === "/" && req.method === "GET") {
|
|
687
755
|
if (!isRequestAuthed(req)) {
|
|
688
756
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -692,11 +760,16 @@ function createServer(opts) {
|
|
|
692
760
|
if (projects.size > 0) {
|
|
693
761
|
var targetSlug = null;
|
|
694
762
|
var reqUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
|
|
763
|
+
function isOrdinaryProject(slug) {
|
|
764
|
+
var candidate = slug ? projects.get(slug) : null;
|
|
765
|
+
var status = candidate && candidate.getStatus ? candidate.getStatus() : null;
|
|
766
|
+
return !!candidate && (!status || status.isMate !== true);
|
|
767
|
+
}
|
|
695
768
|
var homePreference = typeof users.getHomeSurfacePreference === "function"
|
|
696
769
|
? users.getHomeSurfacePreference(reqUser ? reqUser.id : "default")
|
|
697
770
|
: null;
|
|
698
771
|
var preferredContextSlug = homePreference && homePreference.projectSlug;
|
|
699
|
-
if (
|
|
772
|
+
if (isOrdinaryProject(preferredContextSlug)) {
|
|
700
773
|
if (reqUser && onGetProjectAccess) {
|
|
701
774
|
var preferredAccess = onGetProjectAccess(preferredContextSlug);
|
|
702
775
|
if (preferredAccess && !preferredAccess.error && users.canAccessProject(reqUser.id, preferredAccess)) {
|
|
@@ -708,7 +781,7 @@ function createServer(opts) {
|
|
|
708
781
|
}
|
|
709
782
|
// Check for last-visited project cookie
|
|
710
783
|
var lastProject = parseCookies(req)["clay_last_project"];
|
|
711
|
-
if (!targetSlug &&
|
|
784
|
+
if (!targetSlug && isOrdinaryProject(lastProject)) {
|
|
712
785
|
if (reqUser && onGetProjectAccess) {
|
|
713
786
|
var lpAccess = onGetProjectAccess(lastProject);
|
|
714
787
|
if (lpAccess && !lpAccess.error && users.canAccessProject(reqUser.id, lpAccess)) {
|
|
@@ -722,6 +795,7 @@ function createServer(opts) {
|
|
|
722
795
|
if (!targetSlug) {
|
|
723
796
|
projects.forEach(function (ctx, s) {
|
|
724
797
|
if (targetSlug) return;
|
|
798
|
+
if (!isOrdinaryProject(s)) return;
|
|
725
799
|
if (reqUser && onGetProjectAccess) {
|
|
726
800
|
var access = onGetProjectAccess(s);
|
|
727
801
|
if (access && !access.error && users.canAccessProject(reqUser.id, access)) {
|
|
@@ -735,7 +809,7 @@ function createServer(opts) {
|
|
|
735
809
|
if (targetSlug) {
|
|
736
810
|
try {
|
|
737
811
|
var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
|
|
738
|
-
homeHtml = homeHtml
|
|
812
|
+
homeHtml = injectRootProjectSlug(homeHtml, targetSlug);
|
|
739
813
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
740
814
|
res.end(homeHtml);
|
|
741
815
|
} catch (e) {
|
|
@@ -937,17 +1011,24 @@ function createServer(opts) {
|
|
|
937
1011
|
|
|
938
1012
|
// --- WebSocket ---
|
|
939
1013
|
var wss = new WebSocketServer({ noServer: true });
|
|
1014
|
+
var homePreferencesHandler = null;
|
|
940
1015
|
|
|
941
1016
|
// Slug-less /ws handler: lets a user with no projects yet load the regular
|
|
942
1017
|
// app shell and create/add/clone their first one. Only knows about the
|
|
943
|
-
// small set of bootstrap messages
|
|
944
|
-
//
|
|
1018
|
+
// small set of bootstrap messages, including the Home preferences required
|
|
1019
|
+
// to dismiss the workspace restoration screen.
|
|
945
1020
|
var globalWs = serverGlobalWs.attachGlobalWs({
|
|
946
1021
|
osUsers: osUsers,
|
|
947
1022
|
usersModule: users,
|
|
948
1023
|
onAddProject: onAddProject,
|
|
949
1024
|
onCreateProject: onCreateProject,
|
|
950
1025
|
onCloneProject: onCloneProject,
|
|
1026
|
+
onHomePreferenceMessage: function (ws, msg) {
|
|
1027
|
+
return homePreferencesHandler ? homePreferencesHandler.handleMessage(ws, msg) : false;
|
|
1028
|
+
},
|
|
1029
|
+
onAppMessage: function (ws, msg) {
|
|
1030
|
+
return handleDmMessage(ws, msg);
|
|
1031
|
+
},
|
|
951
1032
|
});
|
|
952
1033
|
|
|
953
1034
|
server.on("upgrade", function (req, socket, head) {
|
|
@@ -1182,6 +1263,7 @@ function createServer(opts) {
|
|
|
1182
1263
|
title: title || null,
|
|
1183
1264
|
icon: icon || null,
|
|
1184
1265
|
projectOwnerId: projectOwnerId || null,
|
|
1266
|
+
projectKnowledgeId: extra.projectKnowledgeId || (worktreeMeta && worktreeMeta.projectKnowledgeId) || null,
|
|
1185
1267
|
worktreeMeta: worktreeMeta || null,
|
|
1186
1268
|
isMate: extra.isMate || false,
|
|
1187
1269
|
mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
|
|
@@ -1192,6 +1274,21 @@ function createServer(opts) {
|
|
|
1192
1274
|
return mateHandler.createReadyMateFromInterview(ws, userId, definition);
|
|
1193
1275
|
},
|
|
1194
1276
|
workspaceQueryService: workspaceQueryService,
|
|
1277
|
+
projectLogsService: projectLogsService,
|
|
1278
|
+
mateKnowledgeService: mateKnowledgeService,
|
|
1279
|
+
canAccessProjectSlug: function (userId, targetSlug) {
|
|
1280
|
+
var target = projects.get(targetSlug);
|
|
1281
|
+
if (!target) return false;
|
|
1282
|
+
var status = target.getStatus();
|
|
1283
|
+
var access;
|
|
1284
|
+
if (status.isMate) {
|
|
1285
|
+
access = { visibility: "private", ownerId: status.projectOwnerId, allowedUsers: [] };
|
|
1286
|
+
} else {
|
|
1287
|
+
if (!onGetProjectAccess) return false;
|
|
1288
|
+
access = onGetProjectAccess(status.parentSlug || targetSlug);
|
|
1289
|
+
}
|
|
1290
|
+
return !!(access && !access.error && users.canAccessProject(userId, access));
|
|
1291
|
+
},
|
|
1195
1292
|
getProjectAccess: function () {
|
|
1196
1293
|
return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
|
|
1197
1294
|
},
|
|
@@ -1361,6 +1458,10 @@ function createServer(opts) {
|
|
|
1361
1458
|
return true;
|
|
1362
1459
|
}
|
|
1363
1460
|
|
|
1461
|
+
function setProjectLogContextState(slug, status) {
|
|
1462
|
+
return projectLogsService.setContextState(slug, status);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1364
1465
|
// --- DM message handler (delegated to server-dm + server-mates inline) ---
|
|
1365
1466
|
var dmHandler = serverDm.attachDm({
|
|
1366
1467
|
users: users,
|
|
@@ -1381,9 +1482,10 @@ function createServer(opts) {
|
|
|
1381
1482
|
projects: projects,
|
|
1382
1483
|
addProject: addProject,
|
|
1383
1484
|
workspaceQueryService: workspaceQueryService,
|
|
1485
|
+
projectLogsService: projectLogsService,
|
|
1384
1486
|
});
|
|
1385
1487
|
|
|
1386
|
-
|
|
1488
|
+
homePreferencesHandler = serverHomePreferences.attachHomePreferences({
|
|
1387
1489
|
users: users,
|
|
1388
1490
|
projects: projects,
|
|
1389
1491
|
});
|
|
@@ -1392,6 +1494,17 @@ function createServer(opts) {
|
|
|
1392
1494
|
var toolsHandler = serverTools.attachTools({
|
|
1393
1495
|
users: users,
|
|
1394
1496
|
projects: projects,
|
|
1497
|
+
// The rich-Display frame listener must speak the same scheme as the app,
|
|
1498
|
+
// or the sandboxed frame would be blocked as mixed content under TLS.
|
|
1499
|
+
tlsOptions: tlsOptions,
|
|
1500
|
+
// Lets a Capsule turn handed to the mate seat wake the opponent Mate's
|
|
1501
|
+
// project, registering it first if it has never been opened.
|
|
1502
|
+
findMateProject: homeChatHandler.findMateProject,
|
|
1503
|
+
isCapsulesEnabled: function (userId) {
|
|
1504
|
+
if (users.isMultiUser()) return users.getCapsulesEnabled(userId);
|
|
1505
|
+
var daemonConfig = onGetDaemonConfig ? onGetDaemonConfig() : {};
|
|
1506
|
+
return daemonConfig.capsulesEnabled === true;
|
|
1507
|
+
},
|
|
1395
1508
|
});
|
|
1396
1509
|
|
|
1397
1510
|
// --- Mate handler ---
|
|
@@ -1402,13 +1515,14 @@ function createServer(opts) {
|
|
|
1402
1515
|
}
|
|
1403
1516
|
|
|
1404
1517
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1405
|
-
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return;
|
|
1406
|
-
if (dmHandler.handleMessage(ws, msg)) return;
|
|
1407
|
-
if (mateHandler && mateHandler.handleMessage(ws, msg)) return;
|
|
1408
|
-
if (emailHandler.handleMessage(ws, msg)) return;
|
|
1409
|
-
if (homeChatHandler.handleMessage(ws, msg)) return;
|
|
1410
|
-
if (homePreferencesHandler.handleMessage(ws, msg)) return;
|
|
1411
|
-
if (toolsHandler.handleMessage(ws, msg)) return;
|
|
1518
|
+
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1519
|
+
if (dmHandler.handleMessage(ws, msg)) return true;
|
|
1520
|
+
if (mateHandler && mateHandler.handleMessage(ws, msg)) return true;
|
|
1521
|
+
if (emailHandler.handleMessage(ws, msg)) return true;
|
|
1522
|
+
if (homeChatHandler.handleMessage(ws, msg)) return true;
|
|
1523
|
+
if (homePreferencesHandler.handleMessage(ws, msg)) return true;
|
|
1524
|
+
if (toolsHandler.handleMessage(ws, msg)) return true;
|
|
1525
|
+
return false;
|
|
1412
1526
|
}
|
|
1413
1527
|
|
|
1414
1528
|
function removeProject(slug) {
|
|
@@ -1702,6 +1816,7 @@ function createServer(opts) {
|
|
|
1702
1816
|
forEachProject: forEachProject,
|
|
1703
1817
|
destroyProject: removeProject,
|
|
1704
1818
|
destroyAll: destroyAll,
|
|
1819
|
+
setProjectLogContextState: setProjectLogContextState,
|
|
1705
1820
|
};
|
|
1706
1821
|
}
|
|
1707
1822
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Driver capability for a visible Driver/Split Worker pair.
|
|
2
|
+
//
|
|
3
|
+
// Model and vendor choice belongs to the user. A session does not need to
|
|
4
|
+
// clear a model-tier threshold to coordinate a Split Worker; runtime catalog
|
|
5
|
+
// validation belongs to session-pair-factory.js and applies equally to every
|
|
6
|
+
// requested role. The only session-level exclusion here is structural: an
|
|
7
|
+
// embedded terminal has no project chat surface on which to host the Driver
|
|
8
|
+
// controls or the paired pane.
|
|
9
|
+
|
|
10
|
+
var sessionProvenance = require("./session-provenance");
|
|
11
|
+
|
|
12
|
+
function evaluateDriverSession(session) {
|
|
13
|
+
if (!session) {
|
|
14
|
+
return { ok: false, error: "No session was bound to this request." };
|
|
15
|
+
}
|
|
16
|
+
if (session.mode === "tui") {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
error: "An embedded terminal session cannot take the Driver role.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (sessionProvenance.isWorker(session)) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
error: "A Split Worker session cannot take the Driver role.",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, error: null };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isEligibleDriverSession(session) {
|
|
32
|
+
return evaluateDriverSession(session).ok;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
evaluateDriverSession: evaluateDriverSession,
|
|
37
|
+
isEligibleDriverSession: isEligibleDriverSession,
|
|
38
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Model-tier policy for proactive Split Worker orchestration.
|
|
2
|
+
//
|
|
3
|
+
// Structural Driver eligibility remains model-agnostic. This policy only
|
|
4
|
+
// decides whether a model should be prompted to judge when delegation helps.
|
|
5
|
+
|
|
6
|
+
var HIGH_TIER_PATTERNS = {
|
|
7
|
+
claude: [/(?:^|-)fable(?:-|$)/i, /(?:^|-)opus(?:-|$)/i],
|
|
8
|
+
codex: [/^gpt-6(?:-|$)/i, /^gpt-[0-9]+(?:\.[0-9]+)?-sol(?:-|$)/i],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function resolvedModel(session, sm) {
|
|
12
|
+
if (!session) return "";
|
|
13
|
+
var vendor = session.vendor || "claude";
|
|
14
|
+
var defaults = sm && sm.defaultModelByVendor || {};
|
|
15
|
+
return session.model || defaults[vendor] || "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isHighTierDriverSession(session, sm) {
|
|
19
|
+
if (!session) return false;
|
|
20
|
+
var patterns = HIGH_TIER_PATTERNS[session.vendor || "claude"] || [];
|
|
21
|
+
var model = resolvedModel(session, sm);
|
|
22
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
23
|
+
if (patterns[i].test(model)) return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
isHighTierDriverSession: isHighTierDriverSession,
|
|
30
|
+
resolvedModel: resolvedModel,
|
|
31
|
+
};
|
package/lib/session-hygiene.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// "New session" request and which abandoned blanks are safe to sweep.
|
|
3
3
|
// Pure decision logic only -- deletion/switching stays in sessions.js.
|
|
4
4
|
|
|
5
|
+
var sessionProvenance = require("./session-provenance");
|
|
6
|
+
|
|
5
7
|
var BLANK_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
6
8
|
var CLAUDE_INTERRUPTED_TEXT = "[Request interrupted by user]";
|
|
7
9
|
|
|
@@ -19,6 +21,7 @@ function isBlankSession(s) {
|
|
|
19
21
|
&& !s.bookmarked
|
|
20
22
|
&& !s.spawn
|
|
21
23
|
&& !s.loop
|
|
24
|
+
&& !sessionProvenance.isWorker(s)
|
|
22
25
|
&& (s.mode || "gui") !== "tui"
|
|
23
26
|
&& s.terminalId == null;
|
|
24
27
|
}
|
|
@@ -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
|
},
|