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.
Files changed (188) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +255 -0
  3. package/lib/capsule-mate-turn.js +105 -0
  4. package/lib/capsule-pig-logic.js +300 -0
  5. package/lib/capsule-server-runtimes.js +39 -0
  6. package/lib/capsules/pig/display.js +198 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +84 -0
  9. package/lib/config.js +25 -1
  10. package/lib/daemon-projects.js +13 -8
  11. package/lib/daemon.js +127 -14
  12. package/lib/git-cli.js +95 -1
  13. package/lib/knowledge-import.js +436 -0
  14. package/lib/knowledge-record-store.js +188 -0
  15. package/lib/knowledge-search.js +240 -0
  16. package/lib/mate-knowledge-mcp-server.js +151 -0
  17. package/lib/mate-knowledge-migration.js +283 -0
  18. package/lib/mate-knowledge-service.js +488 -0
  19. package/lib/mate-knowledge-sync.js +482 -0
  20. package/lib/migrate-single-user.js +2 -0
  21. package/lib/notes-lifecycle.js +149 -0
  22. package/lib/notes.js +60 -3
  23. package/lib/os-users.js +3 -1
  24. package/lib/project-capsule-catalog.js +9 -1
  25. package/lib/project-capsule-turn.js +96 -0
  26. package/lib/project-connection.js +15 -1
  27. package/lib/project-file-history.js +223 -0
  28. package/lib/project-file-watch.js +77 -49
  29. package/lib/project-filesystem.js +31 -220
  30. package/lib/project-http.js +15 -1
  31. package/lib/project-knowledge.js +22 -0
  32. package/lib/project-log-context.js +172 -0
  33. package/lib/project-log-feedback-delivery.js +118 -0
  34. package/lib/project-logs-comments.js +137 -0
  35. package/lib/project-logs-context-state.js +42 -0
  36. package/lib/project-logs-mcp-server.js +328 -0
  37. package/lib/project-logs-query.js +185 -0
  38. package/lib/project-logs-root.js +75 -0
  39. package/lib/project-logs-schema.js +259 -0
  40. package/lib/project-logs-service.js +463 -0
  41. package/lib/project-logs-snapshot.js +205 -0
  42. package/lib/project-logs-store.js +495 -0
  43. package/lib/project-logs-versioning.js +132 -0
  44. package/lib/project-logs.js +400 -0
  45. package/lib/project-mate-interaction.js +14 -0
  46. package/lib/project-mate-knowledge.js +110 -0
  47. package/lib/project-memory.js +22 -0
  48. package/lib/project-message-delivery.js +70 -0
  49. package/lib/project-pair-lifecycle.js +460 -0
  50. package/lib/project-request-access.js +66 -0
  51. package/lib/project-session-handoff.js +1 -1
  52. package/lib/project-session-notes.js +64 -13
  53. package/lib/project-session-pair.js +159 -146
  54. package/lib/project-session-spawn.js +1 -1
  55. package/lib/project-sessions.js +71 -16
  56. package/lib/project-user-message.js +53 -4
  57. package/lib/project-worker-permission.js +430 -0
  58. package/lib/project-worker-proposal.js +208 -62
  59. package/lib/project.js +191 -6
  60. package/lib/public/app.js +64 -35
  61. package/lib/public/css/admin.css +2 -2
  62. package/lib/public/css/capsule-ui.css +38 -0
  63. package/lib/public/css/command-palette.css +45 -9
  64. package/lib/public/css/git-placard.css +154 -0
  65. package/lib/public/css/home-hub.css +4 -0
  66. package/lib/public/css/home-session-actions.css +66 -0
  67. package/lib/public/css/home-sidebar.css +66 -0
  68. package/lib/public/css/input.css +84 -3
  69. package/lib/public/css/messages.css +40 -1
  70. package/lib/public/css/mobile-nav.css +80 -0
  71. package/lib/public/css/notifications-center.css +67 -0
  72. package/lib/public/css/pane.css +97 -10
  73. package/lib/public/css/project-logs.css +198 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/session-actions.css +16 -101
  76. package/lib/public/css/sidebar.css +103 -0
  77. package/lib/public/css/sticky-notes.css +237 -229
  78. package/lib/public/css/worker-proposal.css +36 -3
  79. package/lib/public/index.html +43 -11
  80. package/lib/public/modules/admin.js +8 -8
  81. package/lib/public/modules/app-connection.js +124 -6
  82. package/lib/public/modules/app-home-hub.js +12 -8
  83. package/lib/public/modules/app-message-cards.js +2 -1
  84. package/lib/public/modules/app-message-router.js +1 -1
  85. package/lib/public/modules/app-messages.js +92 -11
  86. package/lib/public/modules/app-misc.js +2 -14
  87. package/lib/public/modules/app-notifications.js +11 -7
  88. package/lib/public/modules/app-panels.js +3 -2
  89. package/lib/public/modules/app-projects.js +56 -18
  90. package/lib/public/modules/app-rendering.js +1 -0
  91. package/lib/public/modules/capsule-preference.js +44 -0
  92. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  93. package/lib/public/modules/clay-log-links.js +70 -0
  94. package/lib/public/modules/command-palette.js +52 -15
  95. package/lib/public/modules/context-sources.js +0 -3
  96. package/lib/public/modules/git-agent-sessions.js +85 -0
  97. package/lib/public/modules/git-panel.js +111 -84
  98. package/lib/public/modules/git-placard.js +161 -0
  99. package/lib/public/modules/home-chat-empty-state.js +39 -0
  100. package/lib/public/modules/home-chat-identity.js +9 -0
  101. package/lib/public/modules/home-conversations-sheet.js +102 -48
  102. package/lib/public/modules/home-mate-chat.js +15 -35
  103. package/lib/public/modules/home-session-actions.js +6 -0
  104. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  105. package/lib/public/modules/home-sidebar.js +7 -0
  106. package/lib/public/modules/home-surface-boot.js +7 -3
  107. package/lib/public/modules/home-tool-frame.js +182 -0
  108. package/lib/public/modules/home-tools.js +138 -1
  109. package/lib/public/modules/input.js +38 -7
  110. package/lib/public/modules/markdown.js +2 -0
  111. package/lib/public/modules/mate-sidebar.js +0 -8
  112. package/lib/public/modules/message-delivery.js +97 -0
  113. package/lib/public/modules/paste-modal.js +84 -0
  114. package/lib/public/modules/project-activation.js +45 -0
  115. package/lib/public/modules/project-logs-render.js +405 -0
  116. package/lib/public/modules/project-logs.js +461 -0
  117. package/lib/public/modules/project-removal-target.js +26 -0
  118. package/lib/public/modules/scheduler.js +17 -3
  119. package/lib/public/modules/search-clay-chat.js +47 -6
  120. package/lib/public/modules/search-clay-controls.js +86 -0
  121. package/lib/public/modules/session-actions.js +29 -105
  122. package/lib/public/modules/session-hierarchy.js +54 -0
  123. package/lib/public/modules/sidebar-mobile.js +40 -15
  124. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  125. package/lib/public/modules/sidebar-sessions.js +77 -11
  126. package/lib/public/modules/sidebar.js +17 -5
  127. package/lib/public/modules/split-group-helpers.js +8 -0
  128. package/lib/public/modules/split-pair-ui.js +15 -4
  129. package/lib/public/modules/split-view.js +5 -2
  130. package/lib/public/modules/split-worker-runtime.js +24 -0
  131. package/lib/public/modules/sticky-note-markdown.js +5 -0
  132. package/lib/public/modules/sticky-notes-browser.js +326 -0
  133. package/lib/public/modules/sticky-notes-card.js +360 -0
  134. package/lib/public/modules/sticky-notes-editor.js +291 -0
  135. package/lib/public/modules/sticky-notes-shared.js +90 -0
  136. package/lib/public/modules/sticky-notes.js +170 -946
  137. package/lib/public/modules/thinking-lifecycle.js +175 -0
  138. package/lib/public/modules/thinking-summary.js +28 -0
  139. package/lib/public/modules/thinking-view.js +75 -0
  140. package/lib/public/modules/tool-palette-order.js +143 -0
  141. package/lib/public/modules/tool-palette-overlays.js +159 -0
  142. package/lib/public/modules/tool-palette.js +21 -188
  143. package/lib/public/modules/tools.js +31 -123
  144. package/lib/public/modules/update-snooze.js +169 -0
  145. package/lib/public/modules/user-settings.js +4 -0
  146. package/lib/public/modules/worker-pane-lock.js +176 -0
  147. package/lib/public/modules/worker-proposal-state.js +16 -0
  148. package/lib/public/modules/worker-proposal.js +49 -20
  149. package/lib/public/style.css +3 -1
  150. package/lib/sdk-bridge.js +136 -47
  151. package/lib/sdk-message-processor.js +36 -7
  152. package/lib/server-experimental-settings.js +49 -0
  153. package/lib/server-global-ws.js +11 -0
  154. package/lib/server-home-chat-events.js +34 -1
  155. package/lib/server-home-chat.js +62 -4
  156. package/lib/server-home-clay-entry.js +1 -1
  157. package/lib/server-home-clay-session-links.js +34 -5
  158. package/lib/server-settings.js +5 -0
  159. package/lib/server-tools.js +127 -9
  160. package/lib/server.js +137 -22
  161. package/lib/session-driver-eligibility.js +38 -0
  162. package/lib/session-driver-orchestration.js +31 -0
  163. package/lib/session-hygiene.js +3 -0
  164. package/lib/session-notes-mcp-server.js +25 -5
  165. package/lib/session-pair-factory.js +247 -0
  166. package/lib/session-pair-mcp-server.js +46 -8
  167. package/lib/session-pair-prompts.js +79 -0
  168. package/lib/session-pair-turn-control.js +153 -0
  169. package/lib/session-provenance.js +119 -0
  170. package/lib/session-spawn-mcp-server.js +1 -1
  171. package/lib/session-split-groups.js +4 -1
  172. package/lib/session-title-policy.js +60 -0
  173. package/lib/session-visibility.js +41 -0
  174. package/lib/sessions.js +57 -10
  175. package/lib/tools-registry.js +89 -10
  176. package/lib/update-snooze.js +437 -0
  177. package/lib/users-experimental-preferences.js +30 -0
  178. package/lib/users-permissions.js +6 -6
  179. package/lib/users.js +22 -16
  180. package/lib/workspace-query-access.js +102 -0
  181. package/lib/workspace-query-service.js +24 -17
  182. package/lib/worktree.js +25 -38
  183. package/lib/ws-schema.js +44 -7
  184. package/lib/yoke/adapters/claude-worker.js +16 -6
  185. package/lib/yoke/adapters/claude.js +12 -0
  186. package/lib/yoke/adapters/codex.js +24 -14
  187. package/lib/yoke/vendor-registry.js +1 -1
  188. 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
- canAccessProject: function (userId, status) {
310
- return users.canAccessProject(userId, {
311
- ownerId: status && status.projectOwnerId || null,
312
- visibility: status && status.visibility || "public",
313
- allowedUsers: status && status.allowedUsers || [],
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
- function setSecurityHeaders(res) {
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 home while keeping the last accessible project connected.
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 (preferredContextSlug && projects.has(preferredContextSlug)) {
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 && lastProject && projects.has(lastProject)) {
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.replace("<body>", '<body data-home-project-slug="' + targetSlug + '">');
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 (browse_dir, add_project, create_project,
944
- // clone_project, ping).
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
- var homePreferencesHandler = serverHomePreferences.attachHomePreferences({
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
+ };
@@ -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, and remove a note created by your session when it stops being useful instead of turning it into a completion log. Put a concise plain-text title on the first line, stay focused on one topic, and include only the context needed for future action.";
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 active notes before writing when you need to avoid duplicates or inspect full memory beyond the injected summary.",
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 + " Remove a note created by this same session when it is no longer true. Notes created by users or other sessions cannot be removed.",
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 remove." },
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
  },