clay-server 3.8.1 → 4.0.0-beta.10

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 (222) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/background-task-timing.js +105 -0
  3. package/lib/builtin-mates.js +6 -6
  4. package/lib/claude-hook-installer.js +15 -0
  5. package/lib/clay-history-mcp-server.js +62 -297
  6. package/lib/daemon.js +23 -1
  7. package/lib/debate-model-selection.js +119 -0
  8. package/lib/home-debate-tool-policy.js +164 -0
  9. package/lib/home-response-notifications.js +26 -0
  10. package/lib/knowledge-import.js +436 -0
  11. package/lib/knowledge-record-store.js +188 -0
  12. package/lib/knowledge-search.js +224 -0
  13. package/lib/mate-creation-mcp-server.js +55 -0
  14. package/lib/mate-knowledge-mcp-server.js +151 -0
  15. package/lib/mate-knowledge-migration.js +283 -0
  16. package/lib/mate-knowledge-service.js +488 -0
  17. package/lib/mate-knowledge-sync.js +482 -0
  18. package/lib/mate-ready-creation.js +95 -0
  19. package/lib/mates.js +2 -0
  20. package/lib/notes.js +2 -1
  21. package/lib/project-ask-user.js +64 -0
  22. package/lib/project-capsule-catalog.js +101 -0
  23. package/lib/project-connection.js +17 -0
  24. package/lib/project-debate-proposal.js +133 -17
  25. package/lib/project-debate.js +214 -110
  26. package/lib/project-delegated-follow-up.js +84 -0
  27. package/lib/project-delegated-session.js +53 -0
  28. package/lib/project-filesystem.js +7 -9
  29. package/lib/project-knowledge.js +23 -0
  30. package/lib/project-logs-comments.js +137 -0
  31. package/lib/project-logs-mcp-server.js +290 -0
  32. package/lib/project-logs-query.js +167 -0
  33. package/lib/project-logs-root.js +67 -0
  34. package/lib/project-logs-schema.js +259 -0
  35. package/lib/project-logs-service.js +357 -0
  36. package/lib/project-logs-snapshot.js +202 -0
  37. package/lib/project-logs-store.js +498 -0
  38. package/lib/project-logs-versioning.js +128 -0
  39. package/lib/project-logs.js +347 -0
  40. package/lib/project-mate-creation-proposal.js +120 -0
  41. package/lib/project-mate-interaction.js +14 -0
  42. package/lib/project-mate-knowledge.js +110 -0
  43. package/lib/project-memory.js +36 -7
  44. package/lib/project-models.js +157 -32
  45. package/lib/project-session-notes.js +2 -2
  46. package/lib/project-session-pair.js +8 -8
  47. package/lib/project-sessions.js +66 -51
  48. package/lib/project-vendor-login.js +414 -0
  49. package/lib/project-worker-proposal.js +25 -25
  50. package/lib/project-workspace-query.js +144 -0
  51. package/lib/project.js +433 -102
  52. package/lib/public/app.js +109 -48
  53. package/lib/public/css/avatar-imprints.css +0 -1
  54. package/lib/public/css/base.css +4 -0
  55. package/lib/public/css/capsule-ui.css +389 -0
  56. package/lib/public/css/command-palette.css +306 -6
  57. package/lib/public/css/home-capsule-library.css +243 -0
  58. package/lib/public/css/home-capsule-source.css +192 -0
  59. package/lib/public/css/home-debate-live.css +146 -0
  60. package/lib/public/css/home-debate-planning.css +125 -0
  61. package/lib/public/css/home-debates-archive.css +281 -0
  62. package/lib/public/css/home-hub.css +896 -405
  63. package/lib/public/css/home-mate-creation.css +34 -0
  64. package/lib/public/css/home-mate-model-picker.css +131 -0
  65. package/lib/public/css/home-mate-settings.css +344 -0
  66. package/lib/public/css/home-session-actions.css +265 -0
  67. package/lib/public/css/home-sidebar.css +667 -0
  68. package/lib/public/css/icon-strip.css +1 -2
  69. package/lib/public/css/input.css +79 -25
  70. package/lib/public/css/mates.css +7 -34
  71. package/lib/public/css/pane.css +1 -1
  72. package/lib/public/css/playbook.css +0 -80
  73. package/lib/public/css/project-logs.css +234 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/sticky-notes.css +53 -117
  76. package/lib/public/css/tui-attention.css +0 -44
  77. package/lib/public/css/workspace-assignment.css +45 -0
  78. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  79. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  80. package/lib/public/index.html +96 -74
  81. package/lib/public/modules/app-connection.js +100 -5
  82. package/lib/public/modules/app-dm.js +43 -160
  83. package/lib/public/modules/app-home-hub.js +328 -585
  84. package/lib/public/modules/app-message-cards.js +242 -0
  85. package/lib/public/modules/app-message-router.js +84 -0
  86. package/lib/public/modules/app-messages.js +124 -38
  87. package/lib/public/modules/app-notifications.js +8 -119
  88. package/lib/public/modules/app-panels.js +43 -2
  89. package/lib/public/modules/app-projects.js +9 -1
  90. package/lib/public/modules/app-rendering.js +21 -602
  91. package/lib/public/modules/avatar-imprint.js +11 -8
  92. package/lib/public/modules/background-tasks-ui.js +139 -16
  93. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  94. package/lib/public/modules/chat-render-runtime.js +198 -0
  95. package/lib/public/modules/command-palette.js +225 -436
  96. package/lib/public/modules/dm-render.js +82 -0
  97. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  98. package/lib/public/modules/home-capsule-library.js +146 -0
  99. package/lib/public/modules/home-capsule-source.js +383 -0
  100. package/lib/public/modules/home-chat-identity.js +35 -0
  101. package/lib/public/modules/home-chat-scroll.js +77 -0
  102. package/lib/public/modules/home-chat-stream-state.js +34 -0
  103. package/lib/public/modules/home-composer-focus.js +25 -0
  104. package/lib/public/modules/home-conversations-sheet.js +179 -0
  105. package/lib/public/modules/home-debate-activity.js +48 -0
  106. package/lib/public/modules/home-debate-controls.js +295 -0
  107. package/lib/public/modules/home-debate-launch.js +41 -0
  108. package/lib/public/modules/home-debate-live.js +284 -0
  109. package/lib/public/modules/home-debate-models.js +73 -0
  110. package/lib/public/modules/home-debate-planning.js +335 -0
  111. package/lib/public/modules/home-debates-archive.js +279 -0
  112. package/lib/public/modules/home-dock-resize.js +74 -0
  113. package/lib/public/modules/home-dock.js +446 -0
  114. package/lib/public/modules/home-mate-chat.js +496 -0
  115. package/lib/public/modules/home-mate-creation.js +109 -0
  116. package/lib/public/modules/home-mate-model-picker.js +281 -0
  117. package/lib/public/modules/home-mate-selection.js +38 -0
  118. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  119. package/lib/public/modules/home-mate-settings.js +383 -0
  120. package/lib/public/modules/home-session-actions.js +373 -0
  121. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  122. package/lib/public/modules/home-shell.js +9 -0
  123. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  124. package/lib/public/modules/home-sidebar.js +165 -0
  125. package/lib/public/modules/home-sub-surface.js +19 -0
  126. package/lib/public/modules/home-surface-boot.js +68 -0
  127. package/lib/public/modules/home-surface.js +141 -0
  128. package/lib/public/modules/home-tools.js +271 -0
  129. package/lib/public/modules/markdown.js +50 -2
  130. package/lib/public/modules/mate-management.js +86 -0
  131. package/lib/public/modules/mate-sidebar.js +0 -8
  132. package/lib/public/modules/mate-wizard.js +0 -7
  133. package/lib/public/modules/mention.js +17 -19
  134. package/lib/public/modules/pane-bridge.js +9 -0
  135. package/lib/public/modules/profile.js +4 -5
  136. package/lib/public/modules/project-logs-ambient.js +238 -0
  137. package/lib/public/modules/project-logs-render.js +359 -0
  138. package/lib/public/modules/project-logs.js +328 -0
  139. package/lib/public/modules/project-settings.js +34 -28
  140. package/lib/public/modules/project-switcher.js +11 -5
  141. package/lib/public/modules/project-workspace-assignment.js +23 -0
  142. package/lib/public/modules/scheduler.js +17 -3
  143. package/lib/public/modules/search-clay-chat.js +402 -0
  144. package/lib/public/modules/server-settings.js +14 -12
  145. package/lib/public/modules/session-actions.js +1 -1
  146. package/lib/public/modules/sidebar-mates.js +56 -552
  147. package/lib/public/modules/sidebar-mobile.js +12 -73
  148. package/lib/public/modules/sidebar-presence.js +37 -0
  149. package/lib/public/modules/sidebar-sessions.js +25 -1
  150. package/lib/public/modules/split-pair-ui.js +13 -13
  151. package/lib/public/modules/split-view.js +7 -0
  152. package/lib/public/modules/sticky-notes.js +106 -53
  153. package/lib/public/modules/tool-input-composition.js +48 -0
  154. package/lib/public/modules/tool-llm-status.js +167 -0
  155. package/lib/public/modules/tool-palette.js +76 -4
  156. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  157. package/lib/public/modules/tool-renderer-chart.js +158 -0
  158. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  159. package/lib/public/modules/tool-renderer.js +464 -0
  160. package/lib/public/modules/tool-runtime.js +309 -0
  161. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  162. package/lib/public/modules/tool-ui-tree.js +21 -0
  163. package/lib/public/modules/tui-attention.js +10 -0
  164. package/lib/public/modules/user-settings.js +16 -23
  165. package/lib/public/modules/vendor-login.js +287 -0
  166. package/lib/public/modules/worker-proposal.js +2 -2
  167. package/lib/public/modules/workspace-assignment-card.js +149 -0
  168. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  169. package/lib/public/style.css +14 -2
  170. package/lib/runtime-env.js +65 -0
  171. package/lib/sdk-bridge.js +120 -32
  172. package/lib/sdk-message-processor.js +34 -13
  173. package/lib/server-home-capsule-creation.js +36 -0
  174. package/lib/server-home-chat-events.js +287 -0
  175. package/lib/server-home-chat.js +518 -0
  176. package/lib/server-home-clay-entry.js +65 -0
  177. package/lib/server-home-clay-session-links.js +26 -0
  178. package/lib/server-home-debate-planning.js +214 -0
  179. package/lib/server-home-debates.js +137 -0
  180. package/lib/server-home-mate-creation.js +261 -0
  181. package/lib/server-home-models.js +241 -0
  182. package/lib/server-home-preferences.js +61 -0
  183. package/lib/server-mates.js +37 -0
  184. package/lib/server-tools.js +377 -0
  185. package/lib/server.js +166 -59
  186. package/lib/session-pair-mcp-server.js +6 -6
  187. package/lib/sessions.js +22 -4
  188. package/lib/tool-capsule-source.js +142 -0
  189. package/lib/tool-control-mcp-server.js +126 -0
  190. package/lib/tool-llm.js +48 -0
  191. package/lib/tool-storage.js +77 -0
  192. package/lib/tool-ui-spec-advanced.js +82 -0
  193. package/lib/tool-ui-spec.js +261 -0
  194. package/lib/tools-registry.js +286 -0
  195. package/lib/users-home-dock-preferences.js +77 -0
  196. package/lib/users-home-surface-preferences.js +99 -0
  197. package/lib/users-preferences.js +4 -7
  198. package/lib/users.js +12 -0
  199. package/lib/whats-new-content.js +3 -3
  200. package/lib/workspace-assignment-service.js +420 -0
  201. package/lib/workspace-query-mcp-server.js +154 -0
  202. package/lib/workspace-query-service.js +492 -0
  203. package/lib/ws-schema.js +107 -12
  204. package/lib/yoke/adapters/acp.js +2 -1
  205. package/lib/yoke/adapters/antigravity.js +2 -1
  206. package/lib/yoke/adapters/claude-worker.js +11 -4
  207. package/lib/yoke/adapters/claude.js +141 -46
  208. package/lib/yoke/adapters/codex.js +79 -11
  209. package/lib/yoke/adapters/kiro.js +2 -1
  210. package/lib/yoke/claude-user-input.js +38 -0
  211. package/lib/yoke/codex-app-server.js +20 -2
  212. package/lib/yoke/codex-background-tasks.js +8 -2
  213. package/lib/yoke/codex-user-input.js +64 -0
  214. package/lib/yoke/complete-once.js +164 -0
  215. package/lib/yoke/index.js +5 -1
  216. package/lib/yoke/interface.js +19 -0
  217. package/lib/yoke/mcp-bridge-server.js +2 -6
  218. package/lib/yoke/user-input.js +254 -0
  219. package/package.json +4 -3
  220. package/lib/public/css/home-chat.css +0 -380
  221. package/lib/public/modules/home-chat.js +0 -438
  222. package/lib/server-clay-home.js +0 -245
package/lib/server.js CHANGED
@@ -13,10 +13,18 @@ var serverSkills = require("./server-skills");
13
13
  var claudeHookInstaller = require("./claude-hook-installer");
14
14
  var serverDm = require("./server-dm");
15
15
  var serverMates = require("./server-mates");
16
- var serverClayHome = require("./server-clay-home");
16
+ var serverHomeChat = require("./server-home-chat");
17
+ var shouldSuppressHomeResponse = require("./home-response-notifications").shouldSuppressHomeResponse;
18
+ var serverHomePreferences = require("./server-home-preferences");
19
+ var serverTools = require("./server-tools");
17
20
  var serverAdmin = require("./server-admin");
18
21
  var serverSettings = require("./server-settings");
19
22
  var serverPalette = require("./server-palette");
23
+ var workspaceQueryModule = require("./workspace-query-service");
24
+ var { attachWorkspaceQueryService } = workspaceQueryModule;
25
+ var { attachWorkspaceAssignmentService } = require("./workspace-assignment-service");
26
+ var { attachProjectLogsService } = require("./project-logs-service");
27
+ var { attachMateKnowledgeService } = require("./mate-knowledge-service");
20
28
  var serverEmail = require("./server-email");
21
29
  var serverGlobalWs = require("./server-global-ws");
22
30
  var { parseWsRequestUrl } = require("./ws-request");
@@ -291,6 +299,59 @@ function createServer(opts) {
291
299
 
292
300
  // --- Project registry ---
293
301
  var projects = new Map(); // slug → projectContext
302
+ var assignmentService = attachWorkspaceAssignmentService({
303
+ storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
304
+ getProjects: function () { return projects; },
305
+ isMultiUser: function () { return users.isMultiUser(); },
306
+ sessionRef: workspaceQueryModule.sessionRef,
307
+ });
308
+ var workspaceQueryService = attachWorkspaceQueryService({
309
+ getProjects: function () { return projects; },
310
+ isMultiUser: function () { return users.isMultiUser(); },
311
+ canAccessProject: function (userId, status) {
312
+ return users.canAccessProject(userId, {
313
+ ownerId: status && status.projectOwnerId || null,
314
+ visibility: status && status.visibility || "public",
315
+ allowedUsers: status && status.allowedUsers || [],
316
+ });
317
+ },
318
+ resolveMate: function (userId, mateId) {
319
+ return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
320
+ },
321
+ assignmentService: assignmentService,
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
+ });
294
355
 
295
356
  // --- Admin module ---
296
357
  var admin = serverAdmin.attachAdmin({
@@ -355,7 +416,7 @@ function createServer(opts) {
355
416
  // origin. External sites still cannot frame Clay; frame-ancestors below
356
417
  // is the modern equivalent for browsers that honor CSP.
357
418
  "X-Frame-Options": "SAMEORIGIN",
358
- "Content-Security-Policy": "default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src * data: blob:; connect-src 'self' ws: wss: https://cdn.jsdelivr.net https://esm.sh https://api.open-meteo.com https://ipapi.co; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;",
419
+ "Content-Security-Policy": "default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src * data: blob:; connect-src 'self' ws: wss: https://cdn.jsdelivr.net https://esm.sh https://api.open-meteo.com https://ipapi.co; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;",
359
420
  };
360
421
  if (tlsOptions) {
361
422
  securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
@@ -655,7 +716,7 @@ function createServer(opts) {
655
716
  // --- Skills routes (delegated to server-skills) ---
656
717
  if (skills.handleRequest(req, res, fullUrl)) return;
657
718
 
658
- // Root path — redirect to first accessible project
719
+ // Root path — render home while keeping the last accessible project connected.
659
720
  if (fullUrl === "/" && req.method === "GET") {
660
721
  if (!isRequestAuthed(req)) {
661
722
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -665,9 +726,23 @@ function createServer(opts) {
665
726
  if (projects.size > 0) {
666
727
  var targetSlug = null;
667
728
  var reqUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
729
+ var homePreference = typeof users.getHomeSurfacePreference === "function"
730
+ ? users.getHomeSurfacePreference(reqUser ? reqUser.id : "default")
731
+ : null;
732
+ var preferredContextSlug = homePreference && homePreference.projectSlug;
733
+ if (preferredContextSlug && projects.has(preferredContextSlug)) {
734
+ if (reqUser && onGetProjectAccess) {
735
+ var preferredAccess = onGetProjectAccess(preferredContextSlug);
736
+ if (preferredAccess && !preferredAccess.error && users.canAccessProject(reqUser.id, preferredAccess)) {
737
+ targetSlug = preferredContextSlug;
738
+ }
739
+ } else {
740
+ targetSlug = preferredContextSlug;
741
+ }
742
+ }
668
743
  // Check for last-visited project cookie
669
744
  var lastProject = parseCookies(req)["clay_last_project"];
670
- if (lastProject && projects.has(lastProject)) {
745
+ if (!targetSlug && lastProject && projects.has(lastProject)) {
671
746
  if (reqUser && onGetProjectAccess) {
672
747
  var lpAccess = onGetProjectAccess(lastProject);
673
748
  if (lpAccess && !lpAccess.error && users.canAccessProject(reqUser.id, lpAccess)) {
@@ -692,8 +767,15 @@ function createServer(opts) {
692
767
  });
693
768
  }
694
769
  if (targetSlug) {
695
- res.writeHead(302, { "Location": "/p/" + targetSlug + "/" });
696
- res.end();
770
+ try {
771
+ var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
772
+ homeHtml = homeHtml.replace("<body>", '<body data-home-project-slug="' + targetSlug + '">');
773
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
774
+ res.end(homeHtml);
775
+ } catch (e) {
776
+ res.writeHead(500, { "Content-Type": "text/plain" });
777
+ res.end("App shell missing.");
778
+ }
697
779
  return;
698
780
  }
699
781
  }
@@ -1028,10 +1110,9 @@ function createServer(opts) {
1028
1110
  unreadMap[wsSlug] = 0;
1029
1111
  }
1030
1112
  ctx.handleConnection(ws, wsUser);
1031
- // Tear down the home-chat subscription (if any) on socket close so
1032
- // we don't leak callbacks against Clay's session manager.
1113
+ // Tear down the embedded mate chat subscription on socket close.
1033
1114
  ws.on("close", function () {
1034
- try { clayHomeHandler.handleDisconnection(ws); } catch (e) {}
1115
+ try { homeChatHandler.handleDisconnection(ws); } catch (e) {}
1035
1116
  });
1036
1117
  });
1037
1118
  });
@@ -1108,6 +1189,24 @@ function createServer(opts) {
1108
1189
  }
1109
1190
 
1110
1191
  // --- Project management ---
1192
+ function refreshVendorAuthEverywhere(vendor, linuxUser) {
1193
+ var refreshes = [];
1194
+ projects.forEach(function (projectCtx) {
1195
+ if (!projectCtx || typeof projectCtx.refreshVendorAuth !== "function") return;
1196
+ try {
1197
+ refreshes.push(Promise.resolve(projectCtx.refreshVendorAuth(vendor, linuxUser)).catch(function (err) {
1198
+ console.error("[server] Cross-project " + vendor + " auth refresh failed:", err && err.message ? err.message : err);
1199
+ return false;
1200
+ }));
1201
+ } catch (err) {
1202
+ console.error("[server] Cross-project " + vendor + " auth refresh threw:", err && err.message ? err.message : err);
1203
+ }
1204
+ });
1205
+ return Promise.all(refreshes).then(function (results) {
1206
+ return results.some(function (refreshed) { return !!refreshed; });
1207
+ });
1208
+ }
1209
+
1111
1210
  function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
1112
1211
  if (projects.has(slug)) return false;
1113
1212
  var extra = extraOpts || {};
@@ -1119,8 +1218,22 @@ function createServer(opts) {
1119
1218
  projectOwnerId: projectOwnerId || null,
1120
1219
  worktreeMeta: worktreeMeta || null,
1121
1220
  isMate: extra.isMate || false,
1221
+ mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
1122
1222
  mateDisplayName: extra.mateDisplayName || "",
1123
1223
  isHostAgent: !!extra.isHostAgent,
1224
+ createReadyMate: function (ws, userId, definition) {
1225
+ if (!mateHandler || typeof mateHandler.createReadyMateFromInterview !== "function") throw new Error("Mate creation is unavailable.");
1226
+ return mateHandler.createReadyMateFromInterview(ws, userId, definition);
1227
+ },
1228
+ workspaceQueryService: workspaceQueryService,
1229
+ projectLogsService: projectLogsService,
1230
+ mateKnowledgeService: mateKnowledgeService,
1231
+ getProjectAccess: function () {
1232
+ return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
1233
+ },
1234
+ shouldSuppressResponseNotification: function (session) {
1235
+ return shouldSuppressHomeResponse(projects, slug, session);
1236
+ },
1124
1237
  pushModule: pushModule,
1125
1238
  debug: debug,
1126
1239
  dangerouslySkipPermissions: dangerouslySkipPermissions,
@@ -1143,52 +1256,6 @@ function createServer(opts) {
1143
1256
  });
1144
1257
  return list;
1145
1258
  },
1146
- getAllProjectSessions: function () {
1147
- var allSessions = [];
1148
- projects.forEach(function (pCtx, pSlug) {
1149
- if (pSlug === slug) return; // skip self
1150
- var status = pCtx.getStatus();
1151
- if (status.isWorktree) return;
1152
- var pSm = pCtx.getSessionManager();
1153
- if (!pSm) return;
1154
- var projectTitle = status.title || status.project || pSlug;
1155
- pSm.sessions.forEach(function (s) {
1156
- if (!s.hidden && s.history && s.history.length > 0) {
1157
- s._projectTitle = projectTitle;
1158
- allSessions.push(s);
1159
- }
1160
- });
1161
- });
1162
- return allSessions;
1163
- },
1164
- // Like getAllProjectSessions but returns the per-project grouped
1165
- // shape that session-search.searchPalette expects. Includes self
1166
- // (so the host agent can search its own past Clay conversations).
1167
- // Used by clay-history-mcp-server.
1168
- getAllProjectsWithSessions: function () {
1169
- var out = [];
1170
- projects.forEach(function (pCtx, pSlug) {
1171
- var status = pCtx.getStatus();
1172
- if (status.isWorktree) return;
1173
- var pSm = pCtx.getSessionManager();
1174
- if (!pSm) return;
1175
- var sessions = [];
1176
- pSm.sessions.forEach(function (s) {
1177
- if (s.hidden) return;
1178
- sessions.push(s);
1179
- });
1180
- if (sessions.length === 0) return;
1181
- out.push({
1182
- projectSlug: pSlug,
1183
- projectTitle: status.title || status.project || pSlug,
1184
- projectIcon: status.icon || null,
1185
- isMate: !!status.isMate,
1186
- mateId: status.mateId || null,
1187
- sessions: sessions,
1188
- });
1189
- });
1190
- return out;
1191
- },
1192
1259
  getHubSchedules: function () {
1193
1260
  var allSchedules = [];
1194
1261
  projects.forEach(function (ctx, s) {
@@ -1277,6 +1344,29 @@ function createServer(opts) {
1277
1344
  onSetProjectDefaultMode: onSetProjectDefaultMode,
1278
1345
  onGetProjectLastVendor: onGetProjectLastVendor,
1279
1346
  onSetProjectLastVendor: onSetProjectLastVendor,
1347
+ listControllableTools: function (userId) {
1348
+ return toolsHandler ? toolsHandler.installedManifests(userId) : [];
1349
+ },
1350
+ controlToolForMate: function (userId, mateId, toolId, kind, payload) {
1351
+ if (!toolsHandler) return Promise.reject(new Error("Tool control is unavailable."));
1352
+ return toolsHandler.controlForMate(userId, mateId, toolId, kind, payload);
1353
+ },
1354
+ installToolForMate: function (userId, input) {
1355
+ if (!toolsHandler) return Promise.reject(new Error("Tool installation is unavailable."));
1356
+ return toolsHandler.installForMate(userId, input);
1357
+ },
1358
+ sourceToolForMate: function (userId, toolId) {
1359
+ if (!toolsHandler) return Promise.reject(new Error("Capsule source access is unavailable."));
1360
+ return toolsHandler.sourceForMate(userId, toolId);
1361
+ },
1362
+ updateToolForMate: function (userId, toolId, input) {
1363
+ if (!toolsHandler) return Promise.reject(new Error("Capsule source updates are unavailable."));
1364
+ return toolsHandler.updateForMate(userId, toolId, input);
1365
+ },
1366
+ removeToolForMate: function (userId, toolId) {
1367
+ if (!toolsHandler) return Promise.reject(new Error("Tool removal is unavailable."));
1368
+ return toolsHandler.removeForMate(userId, toolId);
1369
+ },
1280
1370
  onGetProjectMcpServers: onGetProjectMcpServers,
1281
1371
  onSetProjectMcpServers: onSetProjectMcpServers,
1282
1372
  onGetDaemonConfig: onGetDaemonConfig,
@@ -1291,10 +1381,12 @@ function createServer(opts) {
1291
1381
  onDmMessage: handleDmMessage,
1292
1382
  broadcastAll: broadcastAll,
1293
1383
  notificationsModule: _globalNotifications,
1384
+ refreshVendorAuthEverywhere: refreshVendorAuthEverywhere,
1294
1385
  getProject: function (s) { return projects.get(s) || null; },
1295
1386
  isUserOnline: isUserOnline,
1296
1387
  });
1297
1388
  projects.set(slug, ctx);
1389
+ assignmentService.reconcileProject(slug);
1298
1390
  // ctx.warmup() is now deferred to the first websocket connection into
1299
1391
  // this project (see project-connection.js handleConnection). Warming
1300
1392
  // every project at startup spawned a CodexAppServer and an mcp-bridge
@@ -1318,12 +1410,24 @@ function createServer(opts) {
1318
1410
  // --- Email account handler (per-user email account management) ---
1319
1411
  var emailHandler = serverEmail.attachEmail({ users: users });
1320
1412
 
1321
- // --- Clay home chat handler (host agent chat embedded in home hub) ---
1322
- var clayHomeHandler = serverClayHome.attachClayHome({
1413
+ // --- Embedded mate chat handler ---
1414
+ var homeChatHandler = serverHomeChat.attachHomeChat({
1323
1415
  users: users,
1324
1416
  mates: mates,
1325
1417
  projects: projects,
1326
1418
  addProject: addProject,
1419
+ workspaceQueryService: workspaceQueryService,
1420
+ });
1421
+
1422
+ var homePreferencesHandler = serverHomePreferences.attachHomePreferences({
1423
+ users: users,
1424
+ projects: projects,
1425
+ });
1426
+
1427
+ // --- User-installed home tools handler ---
1428
+ var toolsHandler = serverTools.attachTools({
1429
+ users: users,
1430
+ projects: projects,
1327
1431
  });
1328
1432
 
1329
1433
  // --- Mate handler ---
@@ -1333,11 +1437,14 @@ function createServer(opts) {
1333
1437
  if (mateHandler) mateHandler.scheduleRegistryRefresh();
1334
1438
  }
1335
1439
 
1336
- function handleDmMessage(ws, msg) {
1440
+ function handleDmMessage(ws, msg, routedProjectSlug) {
1441
+ if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return;
1337
1442
  if (dmHandler.handleMessage(ws, msg)) return;
1338
1443
  if (mateHandler && mateHandler.handleMessage(ws, msg)) return;
1339
1444
  if (emailHandler.handleMessage(ws, msg)) return;
1340
- if (clayHomeHandler.handleMessage(ws, msg)) return;
1445
+ if (homeChatHandler.handleMessage(ws, msg)) return;
1446
+ if (homePreferencesHandler.handleMessage(ws, msg)) return;
1447
+ if (toolsHandler.handleMessage(ws, msg)) return;
1341
1448
  }
1342
1449
 
1343
1450
  function removeProject(slug) {
@@ -6,14 +6,14 @@ function getToolDefs(handlers) {
6
6
  return [
7
7
  {
8
8
  name: "send_to_partner",
9
- description: "Delegate one concrete task to the Worker. If this session has no pair yet, Clay creates a real Worker session and opens it visibly in the right pane before starting the task. A completed turn does not end the Worker session: reuse the same Worker for follow-up implementation and corrections instead of taking over its work. Detached completions are pushed back automatically. A delegated turn cannot delegate back, so keep orchestration one hop deep.",
9
+ description: "Delegate one concrete task to the visible Split Worker. If this session has no pair yet, Clay creates a real Split Worker session and opens it visibly in the right pane before starting the task. A completed turn does not end the Split Worker session: reuse the same Split Worker for follow-up implementation and corrections instead of taking over its work. Detached completions are pushed back automatically. A delegated turn cannot delegate back, so keep orchestration one hop deep.",
10
10
  inputSchema: buildShape({
11
11
  message: { type: "string", description: "The complete task or question for the partner." },
12
12
  wait: { type: "boolean", description: "Wait for the partner's turn to finish. Defaults to true." },
13
13
  timeoutSeconds: { type: "number", description: "Maximum wait in seconds, from 1 to 900. Defaults to 300." },
14
- workerVendor: { type: "string", description: "Optional Worker vendor to use only when creating a new pair. Defaults to a suitable installed vendor." },
15
- workerModel: { type: "string", description: "Optional Worker model to use only when creating a new pair." },
16
- workerEffort: { type: "string", description: "Optional Worker reasoning effort to use only when creating a new pair." },
14
+ workerVendor: { type: "string", description: "Optional Split Worker vendor to use only when creating a new pair. Defaults to a suitable installed vendor." },
15
+ workerModel: { type: "string", description: "Optional Split Worker model to use only when creating a new pair." },
16
+ workerEffort: { type: "string", description: "Optional Split Worker reasoning effort to use only when creating a new pair." },
17
17
  }, ["message"]),
18
18
  handler: function (args) { return handlers.send(args || {}); },
19
19
  },
@@ -27,13 +27,13 @@ function getToolDefs(handlers) {
27
27
  },
28
28
  {
29
29
  name: "interrupt_partner",
30
- description: "Interrupt the Worker's current task. Use this when the task is going in the wrong direction, needs to be reprioritized, or must stop before the next instruction. The Worker returns any partial result to you for review.",
30
+ description: "Interrupt the Split Worker's current task. Use this when the task is going in the wrong direction, needs to be reprioritized, or must stop before the next instruction. The Split Worker returns any partial result to you for review.",
31
31
  inputSchema: buildShape({}),
32
32
  handler: function (args) { return handlers.interrupt(args || {}); },
33
33
  },
34
34
  {
35
35
  name: "close_partner",
36
- description: "Close the visible Worker pane and dissolve the Driver/Worker pair while preserving both session histories. If the Worker is still running, Clay interrupts it before closing the pair.",
36
+ description: "Close the visible Split Worker pane and dissolve the Driver/Split Worker pair while preserving both session histories. If the Split Worker is still running, Clay interrupts it before closing the pair.",
37
37
  inputSchema: buildShape({}),
38
38
  handler: function (args) { return handlers.close(args || {}); },
39
39
  },
package/lib/sessions.js CHANGED
@@ -5,6 +5,7 @@ var utils = require("./utils");
5
5
  var users = require("./users");
6
6
  var { CODEX_DEFAULTS } = require("./codex-defaults");
7
7
  var hygiene = require("./session-hygiene");
8
+ var backgroundTaskTiming = require("./background-task-timing");
8
9
 
9
10
  function createSessionManager(opts) {
10
11
  var cwd = opts.cwd;
@@ -170,9 +171,15 @@ function createSessionManager(opts) {
170
171
  if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
171
172
  if (session.loop) metaObj.loop = session.loop;
172
173
  if (session.spawn) metaObj.spawn = session.spawn;
174
+ if (session.assignment) metaObj.assignment = session.assignment;
173
175
  if (session.handoff) metaObj.handoff = session.handoff;
174
176
  if (session.debateState) metaObj.debateState = session.debateState;
175
177
  if (session.debateSetupMode) metaObj.debateSetupMode = true;
178
+ if (session.homeDebatePlanning) metaObj.homeDebatePlanning = true;
179
+ if (session.homeDebatePhase) metaObj.homeDebatePhase = session.homeDebatePhase;
180
+ if (session.homeDebateInitialTopic) metaObj.homeDebateInitialTopic = session.homeDebateInitialTopic;
181
+ if (session.homeClayEntryRequestId) metaObj.homeClayEntryRequestId = session.homeClayEntryRequestId;
182
+ if (session.homeClayEntryMode) metaObj.homeClayEntryMode = session.homeClayEntryMode;
176
183
  var meta = JSON.stringify(metaObj);
177
184
  var lines = [meta];
178
185
  for (var i = 0; i < session.history.length; i++) {
@@ -283,9 +290,15 @@ function createSessionManager(opts) {
283
290
  session.effort = m.effort || null;
284
291
  if (m.loop) session.loop = m.loop;
285
292
  if (m.spawn) session.spawn = m.spawn;
293
+ if (m.assignment) session.assignment = m.assignment;
286
294
  if (m.handoff) session.handoff = m.handoff;
287
295
  if (m.debateState) session.debateState = m.debateState;
288
296
  if (m.debateSetupMode) session.debateSetupMode = true;
297
+ if (m.homeDebatePlanning) session.homeDebatePlanning = true;
298
+ if (m.homeDebatePhase) session.homeDebatePhase = m.homeDebatePhase;
299
+ if (m.homeDebateInitialTopic) session.homeDebateInitialTopic = m.homeDebateInitialTopic;
300
+ if (m.homeClayEntryRequestId) session.homeClayEntryRequestId = m.homeClayEntryRequestId;
301
+ if (m.homeClayEntryMode) session.homeClayEntryMode = m.homeClayEntryMode;
289
302
  if (m.ownerId) session.ownerId = m.ownerId;
290
303
  // Born-TUI session: PTY is gone after restart, but the cliSessionId
291
304
  // is still resumable via `claude --resume <id>`. We mark the mode
@@ -496,7 +509,9 @@ function createSessionManager(opts) {
496
509
  title: s.title || "New Session",
497
510
  active: isActive,
498
511
  isProcessing: s.isProcessing,
499
- backgroundTaskCount: (s.activeBackgroundTasks || []).length,
512
+ // Sidebar badge: user work only. Ambient housekeeping is surfaced in the
513
+ // composer's expanded list, never as a count demanding attention.
514
+ backgroundTaskCount: backgroundTaskTiming.countUserTasks(s.activeBackgroundTasks),
500
515
  lastActivity: s.lastActivity || s.createdAt || 0,
501
516
  loop: loop,
502
517
  spawn: s.spawn || null,
@@ -865,6 +880,7 @@ function createSessionManager(opts) {
865
880
  if (sendEach) {
866
881
  var data = JSON.stringify(obj);
867
882
  sendEach(function (ws) {
883
+ if (session.homeDebatePlanning === true) return;
868
884
  if (ws._clayActiveSession === session.localId && ws.readyState === 1) {
869
885
  ws.send(data);
870
886
  }
@@ -881,8 +897,8 @@ function createSessionManager(opts) {
881
897
  if (!outgoingObj._ts) outgoingObj._ts = obj._ts;
882
898
  session.history.push(obj);
883
899
  appendToSessionFile(session, obj);
884
- // Per-session out-of-band subscribers (used by home-chat to mirror
885
- // Clay session events into a parallel UI without joining the project's
900
+ // Per-session out-of-band subscribers mirror mate session events into
901
+ // a parallel UI without joining the project's
886
902
  // ws clients set). Subscribers receive the same obj that goes to ws
887
903
  // clients; they are responsible for any transform + dispatch.
888
904
  if (session._subscribers && session._subscribers.size > 0) {
@@ -895,6 +911,7 @@ function createSessionManager(opts) {
895
911
  var data = JSON.stringify(outgoingObj);
896
912
  var ioData = null;
897
913
  sendEach(function (ws) {
914
+ if (session.homeDebatePlanning === true) return;
898
915
  if (ws._clayActiveSession === session.localId) {
899
916
  if (ws.readyState === 1) ws.send(data);
900
917
  } else if (session.isProcessing && !session._ioThrottle) {
@@ -993,11 +1010,12 @@ function createSessionManager(opts) {
993
1010
  activeSessionId = mostRecent.localId;
994
1011
  }
995
1012
 
996
- function searchSessions(query) {
1013
+ function searchSessions(query, canInclude) {
997
1014
  if (!query) return [];
998
1015
  var q = query.toLowerCase();
999
1016
  var results = [];
1000
1017
  sessions.forEach(function (session) {
1018
+ if (typeof canInclude === "function" && !canInclude(session)) return;
1001
1019
  var titleMatch = (session.title || "New Session").toLowerCase().indexOf(q) !== -1;
1002
1020
  var contentMatch = false;
1003
1021
  for (var i = 0; i < session.history.length; i++) {
@@ -0,0 +1,142 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+ var crypto = require("crypto");
4
+
5
+ var METADATA_FILE = ".capsule-metadata.json";
6
+
7
+ function metadataPath(root) {
8
+ return path.join(root, METADATA_FILE);
9
+ }
10
+
11
+ function readMetadata(root) {
12
+ try {
13
+ var parsed = JSON.parse(fs.readFileSync(metadataPath(root), "utf8"));
14
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
15
+ } catch (error) {
16
+ return {};
17
+ }
18
+ }
19
+
20
+ function writeFileAtomic(file, value) {
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ var temporary = file + ".tmp-" + process.pid + "-" + crypto.randomBytes(6).toString("hex");
23
+ try {
24
+ fs.writeFileSync(temporary, value, { encoding: "utf8", mode: 0o600 });
25
+ fs.renameSync(temporary, file);
26
+ } catch (error) {
27
+ try { fs.rmSync(temporary, { force: true }); } catch (cleanupError) {}
28
+ throw error;
29
+ }
30
+ }
31
+
32
+ function sanitizedMetadata(root, toolId) {
33
+ var record = readMetadata(root)[toolId];
34
+ return { mateEditingAllowed: !!(record && record.mateEditingAllowed === true) };
35
+ }
36
+
37
+ function setMateEditingAllowed(root, toolId, allowed) {
38
+ var all = readMetadata(root);
39
+ all[toolId] = { mateEditingAllowed: allowed === true };
40
+ writeFileAtomic(metadataPath(root), JSON.stringify(all, null, 2) + "\n");
41
+ return sanitizedMetadata(root, toolId);
42
+ }
43
+
44
+ function removeMetadata(root, toolId) {
45
+ var all = readMetadata(root);
46
+ if (!Object.prototype.hasOwnProperty.call(all, toolId)) return;
47
+ delete all[toolId];
48
+ writeFileAtomic(metadataPath(root), JSON.stringify(all, null, 2) + "\n");
49
+ }
50
+
51
+ function revisionForParts(manifestSource, uiSource, logicSource) {
52
+ var hash = crypto.createHash("sha256");
53
+ var parts = [manifestSource, uiSource, logicSource === null ? "" : logicSource];
54
+ for (var i = 0; i < parts.length; i++) {
55
+ var bytes = Buffer.from(parts[i], "utf8");
56
+ hash.update(String(bytes.length));
57
+ hash.update(":");
58
+ hash.update(bytes);
59
+ }
60
+ return hash.digest("hex");
61
+ }
62
+
63
+ function readSource(directory) {
64
+ var manifestSource = fs.readFileSync(path.join(directory, "manifest.json"), "utf8");
65
+ var uiSource = fs.readFileSync(path.join(directory, "ui.json"), "utf8");
66
+ var manifest = JSON.parse(manifestSource);
67
+ var uiTree = JSON.parse(uiSource);
68
+ var runtime = manifest.runtime || "worker";
69
+ var logicSource = runtime === "worker" ? fs.readFileSync(path.join(directory, "logic.js"), "utf8") : null;
70
+ return {
71
+ manifest: manifest,
72
+ uiTree: uiTree,
73
+ logicSource: logicSource,
74
+ logicAvailable: runtime === "worker",
75
+ revision: revisionForParts(manifestSource, uiSource, logicSource),
76
+ };
77
+ }
78
+
79
+ function replaceSourceFiles(directory, sources) {
80
+ var names = ["manifest.json", "ui.json", "logic.js"];
81
+ var transaction = crypto.randomBytes(6).toString("hex");
82
+ var staged = [];
83
+ var backups = [];
84
+ try {
85
+ for (var i = 0; i < names.length; i++) {
86
+ var target = path.join(directory, names[i]);
87
+ var stage = target + ".next-" + transaction;
88
+ staged.push({ target: target, stage: stage });
89
+ fs.writeFileSync(stage, sources[names[i]], "utf8");
90
+ }
91
+ for (var j = 0; j < staged.length; j++) {
92
+ var backup = staged[j].target + ".previous-" + transaction;
93
+ fs.renameSync(staged[j].target, backup);
94
+ backups.push({ target: staged[j].target, backup: backup });
95
+ fs.renameSync(staged[j].stage, staged[j].target);
96
+ }
97
+ } catch (error) {
98
+ for (var ri = backups.length - 1; ri >= 0; ri--) {
99
+ try {
100
+ fs.rmSync(backups[ri].target, { force: true });
101
+ fs.renameSync(backups[ri].backup, backups[ri].target);
102
+ } catch (rollbackError) {}
103
+ }
104
+ for (var si = 0; si < staged.length; si++) {
105
+ try { fs.rmSync(staged[si].stage, { force: true }); } catch (cleanupError) {}
106
+ }
107
+ throw error;
108
+ }
109
+ for (var k = 0; k < backups.length; k++) {
110
+ try { fs.rmSync(backups[k].backup, { force: true }); } catch (cleanupError) {}
111
+ }
112
+ }
113
+
114
+ function updateSource(directory, input, validators) {
115
+ var current = readSource(directory);
116
+ if (!current.logicAvailable) throw new Error("Server-managed Capsules cannot be edited.");
117
+ if (!input || typeof input !== "object") throw new Error("Complete Capsule source is required.");
118
+ if (typeof input.baseRevision !== "string" || input.baseRevision !== current.revision) {
119
+ throw new Error("Capsule source changed. Read the latest source and retry your update.");
120
+ }
121
+ validators.validateManifest(input.manifest, { allowServerRuntime: false });
122
+ validators.validateUiNode(input.uiTree);
123
+ if (input.manifest.id !== current.manifest.id) throw new Error("Capsule updates cannot change the Capsule ID.");
124
+ if ((input.manifest.runtime || "worker") !== "worker") throw new Error("Capsule updates cannot change the runtime.");
125
+ if (typeof input.logicSource !== "string" || !input.logicSource.trim()) throw new Error("Tool logic source is required.");
126
+ var manifest = Object.assign({}, input.manifest, { name: input.manifest.name.trim(), runtime: "worker" });
127
+ replaceSourceFiles(directory, {
128
+ "manifest.json": JSON.stringify(manifest, null, 2) + "\n",
129
+ "ui.json": JSON.stringify(input.uiTree, null, 2) + "\n",
130
+ "logic.js": input.logicSource,
131
+ });
132
+ return readSource(directory);
133
+ }
134
+
135
+ module.exports = {
136
+ METADATA_FILE: METADATA_FILE,
137
+ sanitizedMetadata: sanitizedMetadata,
138
+ setMateEditingAllowed: setMateEditingAllowed,
139
+ removeMetadata: removeMetadata,
140
+ readSource: readSource,
141
+ updateSource: updateSource,
142
+ };