clay-server 3.8.1 → 4.0.0-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 (182) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/builtin-mates.js +6 -6
  3. package/lib/claude-hook-installer.js +15 -0
  4. package/lib/clay-history-mcp-server.js +62 -297
  5. package/lib/daemon.js +1 -1
  6. package/lib/debate-model-selection.js +119 -0
  7. package/lib/home-debate-tool-policy.js +164 -0
  8. package/lib/home-response-notifications.js +26 -0
  9. package/lib/mate-creation-mcp-server.js +55 -0
  10. package/lib/mate-ready-creation.js +95 -0
  11. package/lib/mates.js +2 -0
  12. package/lib/notes.js +2 -1
  13. package/lib/project-ask-user.js +64 -0
  14. package/lib/project-capsule-catalog.js +101 -0
  15. package/lib/project-debate-proposal.js +133 -17
  16. package/lib/project-debate.js +214 -110
  17. package/lib/project-delegated-follow-up.js +84 -0
  18. package/lib/project-delegated-session.js +53 -0
  19. package/lib/project-filesystem.js +7 -9
  20. package/lib/project-knowledge.js +1 -0
  21. package/lib/project-mate-creation-proposal.js +120 -0
  22. package/lib/project-memory.js +14 -7
  23. package/lib/project-models.js +157 -32
  24. package/lib/project-session-notes.js +2 -2
  25. package/lib/project-session-pair.js +8 -8
  26. package/lib/project-sessions.js +66 -51
  27. package/lib/project-worker-proposal.js +25 -25
  28. package/lib/project-workspace-query.js +144 -0
  29. package/lib/project.js +329 -102
  30. package/lib/public/app.js +85 -42
  31. package/lib/public/css/avatar-imprints.css +0 -1
  32. package/lib/public/css/base.css +4 -0
  33. package/lib/public/css/capsule-ui.css +389 -0
  34. package/lib/public/css/command-palette.css +306 -6
  35. package/lib/public/css/home-capsule-library.css +243 -0
  36. package/lib/public/css/home-capsule-source.css +192 -0
  37. package/lib/public/css/home-debate-live.css +146 -0
  38. package/lib/public/css/home-debate-planning.css +125 -0
  39. package/lib/public/css/home-debates-archive.css +281 -0
  40. package/lib/public/css/home-hub.css +896 -405
  41. package/lib/public/css/home-mate-creation.css +34 -0
  42. package/lib/public/css/home-mate-model-picker.css +131 -0
  43. package/lib/public/css/home-mate-settings.css +344 -0
  44. package/lib/public/css/home-session-actions.css +265 -0
  45. package/lib/public/css/home-sidebar.css +667 -0
  46. package/lib/public/css/icon-strip.css +1 -2
  47. package/lib/public/css/mates.css +7 -34
  48. package/lib/public/css/pane.css +1 -1
  49. package/lib/public/css/playbook.css +0 -80
  50. package/lib/public/css/sticky-notes.css +53 -117
  51. package/lib/public/css/tui-attention.css +0 -44
  52. package/lib/public/css/workspace-assignment.css +45 -0
  53. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  54. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  55. package/lib/public/index.html +93 -74
  56. package/lib/public/modules/app-connection.js +10 -1
  57. package/lib/public/modules/app-dm.js +43 -160
  58. package/lib/public/modules/app-home-hub.js +328 -585
  59. package/lib/public/modules/app-message-cards.js +242 -0
  60. package/lib/public/modules/app-message-router.js +84 -0
  61. package/lib/public/modules/app-messages.js +78 -20
  62. package/lib/public/modules/app-notifications.js +4 -2
  63. package/lib/public/modules/app-projects.js +9 -1
  64. package/lib/public/modules/app-rendering.js +21 -602
  65. package/lib/public/modules/avatar-imprint.js +11 -8
  66. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  67. package/lib/public/modules/chat-render-runtime.js +198 -0
  68. package/lib/public/modules/command-palette.js +225 -436
  69. package/lib/public/modules/dm-render.js +82 -0
  70. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  71. package/lib/public/modules/home-capsule-library.js +146 -0
  72. package/lib/public/modules/home-capsule-source.js +383 -0
  73. package/lib/public/modules/home-chat-identity.js +35 -0
  74. package/lib/public/modules/home-chat-scroll.js +77 -0
  75. package/lib/public/modules/home-chat-stream-state.js +34 -0
  76. package/lib/public/modules/home-composer-focus.js +25 -0
  77. package/lib/public/modules/home-conversations-sheet.js +179 -0
  78. package/lib/public/modules/home-debate-activity.js +48 -0
  79. package/lib/public/modules/home-debate-controls.js +295 -0
  80. package/lib/public/modules/home-debate-launch.js +41 -0
  81. package/lib/public/modules/home-debate-live.js +284 -0
  82. package/lib/public/modules/home-debate-models.js +73 -0
  83. package/lib/public/modules/home-debate-planning.js +335 -0
  84. package/lib/public/modules/home-debates-archive.js +279 -0
  85. package/lib/public/modules/home-dock-resize.js +74 -0
  86. package/lib/public/modules/home-dock.js +446 -0
  87. package/lib/public/modules/home-mate-chat.js +496 -0
  88. package/lib/public/modules/home-mate-creation.js +109 -0
  89. package/lib/public/modules/home-mate-model-picker.js +281 -0
  90. package/lib/public/modules/home-mate-selection.js +38 -0
  91. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  92. package/lib/public/modules/home-mate-settings.js +383 -0
  93. package/lib/public/modules/home-session-actions.js +373 -0
  94. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  95. package/lib/public/modules/home-shell.js +9 -0
  96. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  97. package/lib/public/modules/home-sidebar.js +158 -0
  98. package/lib/public/modules/home-sub-surface.js +19 -0
  99. package/lib/public/modules/home-surface-boot.js +68 -0
  100. package/lib/public/modules/home-surface.js +141 -0
  101. package/lib/public/modules/home-tools.js +271 -0
  102. package/lib/public/modules/markdown.js +50 -2
  103. package/lib/public/modules/mate-management.js +86 -0
  104. package/lib/public/modules/mate-wizard.js +0 -7
  105. package/lib/public/modules/mention.js +17 -19
  106. package/lib/public/modules/profile.js +4 -5
  107. package/lib/public/modules/project-settings.js +34 -28
  108. package/lib/public/modules/project-switcher.js +11 -5
  109. package/lib/public/modules/project-workspace-assignment.js +23 -0
  110. package/lib/public/modules/search-clay-chat.js +402 -0
  111. package/lib/public/modules/server-settings.js +14 -12
  112. package/lib/public/modules/session-actions.js +1 -1
  113. package/lib/public/modules/sidebar-mates.js +56 -552
  114. package/lib/public/modules/sidebar-mobile.js +7 -64
  115. package/lib/public/modules/sidebar-presence.js +37 -0
  116. package/lib/public/modules/split-pair-ui.js +13 -13
  117. package/lib/public/modules/sticky-notes.js +7 -6
  118. package/lib/public/modules/tool-input-composition.js +48 -0
  119. package/lib/public/modules/tool-llm-status.js +167 -0
  120. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  121. package/lib/public/modules/tool-renderer-chart.js +158 -0
  122. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  123. package/lib/public/modules/tool-renderer.js +464 -0
  124. package/lib/public/modules/tool-runtime.js +309 -0
  125. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  126. package/lib/public/modules/tool-ui-tree.js +21 -0
  127. package/lib/public/modules/user-settings.js +16 -23
  128. package/lib/public/modules/worker-proposal.js +2 -2
  129. package/lib/public/modules/workspace-assignment-card.js +149 -0
  130. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  131. package/lib/public/style.css +13 -2
  132. package/lib/runtime-env.js +65 -0
  133. package/lib/sdk-bridge.js +120 -32
  134. package/lib/sdk-message-processor.js +10 -6
  135. package/lib/server-home-capsule-creation.js +36 -0
  136. package/lib/server-home-chat-events.js +287 -0
  137. package/lib/server-home-chat.js +518 -0
  138. package/lib/server-home-clay-entry.js +65 -0
  139. package/lib/server-home-clay-session-links.js +26 -0
  140. package/lib/server-home-debate-planning.js +214 -0
  141. package/lib/server-home-debates.js +137 -0
  142. package/lib/server-home-mate-creation.js +261 -0
  143. package/lib/server-home-models.js +241 -0
  144. package/lib/server-home-preferences.js +61 -0
  145. package/lib/server-mates.js +37 -0
  146. package/lib/server-tools.js +377 -0
  147. package/lib/server.js +111 -59
  148. package/lib/session-pair-mcp-server.js +6 -6
  149. package/lib/sessions.js +18 -3
  150. package/lib/tool-capsule-source.js +142 -0
  151. package/lib/tool-control-mcp-server.js +126 -0
  152. package/lib/tool-llm.js +48 -0
  153. package/lib/tool-storage.js +77 -0
  154. package/lib/tool-ui-spec-advanced.js +82 -0
  155. package/lib/tool-ui-spec.js +261 -0
  156. package/lib/tools-registry.js +276 -0
  157. package/lib/users-home-dock-preferences.js +77 -0
  158. package/lib/users-home-surface-preferences.js +99 -0
  159. package/lib/users-preferences.js +4 -7
  160. package/lib/users.js +12 -0
  161. package/lib/whats-new-content.js +3 -3
  162. package/lib/workspace-assignment-service.js +420 -0
  163. package/lib/workspace-query-mcp-server.js +154 -0
  164. package/lib/workspace-query-service.js +492 -0
  165. package/lib/ws-schema.js +86 -12
  166. package/lib/yoke/adapters/acp.js +2 -1
  167. package/lib/yoke/adapters/antigravity.js +2 -1
  168. package/lib/yoke/adapters/claude-worker.js +11 -4
  169. package/lib/yoke/adapters/claude.js +73 -43
  170. package/lib/yoke/adapters/codex.js +37 -6
  171. package/lib/yoke/adapters/kiro.js +2 -1
  172. package/lib/yoke/claude-user-input.js +38 -0
  173. package/lib/yoke/codex-user-input.js +64 -0
  174. package/lib/yoke/complete-once.js +164 -0
  175. package/lib/yoke/index.js +5 -1
  176. package/lib/yoke/interface.js +19 -0
  177. package/lib/yoke/mcp-bridge-server.js +2 -6
  178. package/lib/yoke/user-input.js +254 -0
  179. package/package.json +2 -1
  180. package/lib/public/css/home-chat.css +0 -380
  181. package/lib/public/modules/home-chat.js +0 -438
  182. package/lib/server-clay-home.js +0 -245
@@ -100,11 +100,11 @@ function attachSessionPair(ctx) {
100
100
  var response = token.response || "";
101
101
  var text;
102
102
  if (token.interrupted) {
103
- text = "[Worker execution interrupted] The user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
103
+ text = "[Split Worker execution interrupted] The user interrupted the Split Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
104
104
  } else {
105
- text = "A Worker task delegated through send_to_partner has finished.\n\n" +
105
+ text = "A Split Worker task delegated through send_to_partner has finished.\n\n" +
106
106
  "Original task:\n" + token.message + "\n\n" +
107
- (failure ? "Worker error:\n" + failure : "Worker result:\n" + (response || "(No text response was recorded.)")) +
107
+ (failure ? "Split Worker error:\n" + failure : "Split Worker result:\n" + (response || "(No text response was recorded.)")) +
108
108
  "\n\nReview the result, verify it as needed, and continue the task.";
109
109
  }
110
110
  sm.sendAndRecord(caller, {
@@ -361,7 +361,7 @@ function attachSessionPair(ctx) {
361
361
  var driver;
362
362
  var groupName;
363
363
  if (Number.isInteger(driverSpec.sessionId)) {
364
- // "Add Worker" on an existing session: the current session becomes
364
+ // "Add Split Worker" on an existing session: the current session becomes
365
365
  // the Driver with its full conversation context intact.
366
366
  driver = sm.sessions.get(driverSpec.sessionId);
367
367
  if (!driver) throw new Error("driver session not found");
@@ -378,7 +378,7 @@ function attachSessionPair(ctx) {
378
378
  }
379
379
  var workerEffort = yoke.clampEffort(workerVendor, workerSpec.effort || (sm.currentEffortByVendor && sm.currentEffortByVendor[workerVendor]) || sm.currentEffort || "medium") || null;
380
380
  var worker = sm.createSessionRaw({ ownerId: ownerId, vendor: workerVendor, model: workerSpec.model || null, effort: workerEffort });
381
- worker.title = "Worker · " + ((yoke.getVendorInfo(workerVendor) || {}).displayName || workerVendor);
381
+ worker.title = "Split Worker · " + ((yoke.getVendorInfo(workerVendor) || {}).displayName || workerVendor);
382
382
  if (workerSpec.effort) worker.loopSettings = { effort: workerSpec.effort };
383
383
  var result = store.create(ws, {
384
384
  members: [driver.localId, worker.localId],
@@ -393,7 +393,7 @@ function attachSessionPair(ctx) {
393
393
  function createWorkerForDriver(driver, args) {
394
394
  if (ctx.isMate) throw new Error("Pair sessions are only available in projects");
395
395
  var installed = sm.installedVendors || [];
396
- if (installed.length === 0) throw new Error("no coding agent is installed for a Worker session");
396
+ if (installed.length === 0) throw new Error("no coding agent is installed for a Split Worker session");
397
397
  var vendor = typeof args.workerVendor === "string" ? args.workerVendor.trim() : "";
398
398
  if (vendor) validateVendor(vendor);
399
399
  if (!vendor) {
@@ -468,9 +468,9 @@ function attachSessionPair(ctx) {
468
468
  var group = store.groupForMember(session.localId);
469
469
  var pairPrompt = "";
470
470
  if (group && group.pair && group.pair.driverId === session.localId) {
471
- pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner, read_partner, interrupt_partner, and close_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. The human may also message or stop the Worker directly; treat that intervention as authoritative. Use close_partner immediately when the human asks to close, dismiss, or remove the Worker pane; it preserves the session history. A completed Worker turn leaves the Worker session available for more work. Use interrupt_partner when execution must stop or change direction but the pair should remain open; its work is partial and unverified until you review it. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If the Worker is no longer available, keep the work in the visible Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background session. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
471
+ pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner, read_partner, interrupt_partner, and close_partner are provided directly to you. Infer the user's intended target from conversational and UI context. References to the user-visible paired or split pane, its session, its activity or status, or a collaborator the user opened resolve to Clay's Split Worker and the appropriate partner tool. Internal Sub-agents are a distinct execution mechanism, not a lexical category; use them only when the user clearly intends internal or background parallel delegation rather than the visible paired session. When ambiguous and a visible pair exists, prefer the visible Split Worker. If the ambiguity would materially change where work runs, ask a concise clarification instead of guessing. Use send_to_partner to delegate concrete bounded work to the Split Worker. The human may also message or stop the Split Worker directly; treat that intervention as authoritative. Use close_partner immediately when the human asks to close, dismiss, or remove the Split Worker pane; it preserves the session history. A completed Split Worker turn leaves the Split Worker session available for more work. Use interrupt_partner when execution must stop or change direction but the pair should remain open; its work is partial and unverified until you review it. If review or user feedback requires corrections to the Split Worker's implementation, delegate a follow-up turn to that Split Worker instead of editing the Split Worker-owned files yourself. If the Split Worker is no longer available, keep the work in the visible Split Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background Sub-agent. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
472
472
  } else if (!group && !ctx.isMate) {
473
- pairPrompt = "When the user asks you to open or use a visible Worker, call send_to_partner directly. Clay will create the paired Worker session and open it in the right pane automatically before delivering the task. Do not ask the user to start split mode and do not use a background sub-agent as a substitute for a visible Worker.";
473
+ pairPrompt = "Infer the user's intended target from conversational and UI context. References to a user-visible paired or split pane, its session, its activity or status, or a collaborator the user wants opened resolve to Clay's Split Worker and its partner tools. Internal Sub-agents are a distinct execution mechanism, not a lexical category; use them only when the user clearly intends internal or background parallel delegation rather than a visible paired session. When ambiguity would materially change where work runs, ask a concise clarification instead of guessing. When the user intends a visible collaborator, call send_to_partner directly; Clay will create the paired Split Worker session and open it in the right pane automatically before delivering the task. Do not ask the user to start split mode and do not use a background Sub-agent as a substitute for a visible Split Worker.";
474
474
  }
475
475
  return [pairPrompt, workerProposal.getSystemPrompt(session)].filter(Boolean).join("\n\n");
476
476
  }
@@ -75,6 +75,7 @@ function attachSessions(ctx) {
75
75
  var sendToSessionOthers = ctx.sendToSessionOthers;
76
76
  var opts = ctx.opts;
77
77
  var usersModule = ctx.usersModule;
78
+ var getProjectAccess = ctx.getProjectAccess || function () { return { visibility: "public" }; };
78
79
  var userPresence = ctx.userPresence;
79
80
  var pushModule = ctx.pushModule;
80
81
  var getSessionForWs = ctx.getSessionForWs;
@@ -85,6 +86,60 @@ function attachSessions(ctx) {
85
86
  var onProcessingChanged = ctx.onProcessingChanged;
86
87
  var broadcastPresence = ctx.broadcastPresence;
87
88
  var adapter = ctx.adapter;
89
+
90
+ function ownsSearchSession(ws, session) {
91
+ if (!session) return false;
92
+ if (usersModule && typeof usersModule.isMultiUser === "function" && usersModule.isMultiUser()) {
93
+ return !!(ws && ws._clayUser && usersModule.canAccessSession(ws._clayUser.id, session, getProjectAccess()));
94
+ }
95
+ return !session.ownerId;
96
+ }
97
+
98
+ function respondToAskUser(session, msg) {
99
+ if (!session || !msg || !msg.toolId) return false;
100
+ var toolId = msg.toolId;
101
+ var answers = msg.answers || {};
102
+ var pending = session.pendingAskUser && session.pendingAskUser[toolId];
103
+ if (!pending) return false;
104
+ if (pending.mode === "yoke") {
105
+ try { yoke.userInput.normalizeAnswers(pending.request.questions, answers); }
106
+ catch (e) { return false; }
107
+ }
108
+ delete session.pendingAskUser[toolId];
109
+ sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId, answers: answers });
110
+ if (pending.mode === "yoke" && typeof pending.respond === "function") {
111
+ pending.respond(answers);
112
+ } else if (pending.mode === "mcp") {
113
+ var answerText = formatAskUserAnswerAsMessage(pending.input, answers);
114
+ var userMsg = { type: "user_message", text: answerText, askUserAnswer: true };
115
+ session.history.push(userMsg);
116
+ sm.appendToSessionFile(session, userMsg);
117
+ sendToSession(session.localId, userMsg);
118
+ if (!session.isProcessing) {
119
+ session.isProcessing = true;
120
+ onProcessingChanged();
121
+ session.sentToolResults = {};
122
+ sendToSession(session.localId, { type: "status", status: "processing" });
123
+ if (!session.queryInstance && !session.worker) sdk.startQuery(session, answerText, undefined, ensureProjectAccessForSession(session));
124
+ else sdk.pushMessage(session, answerText);
125
+ } else {
126
+ sdk.pushMessage(session, answerText);
127
+ }
128
+ } else {
129
+ pending.resolve({ behavior: "deny", message: formatAskUserAnswerAsMessage(pending.input, answers) });
130
+ }
131
+ return true;
132
+ }
133
+
134
+ function cancelPendingAskUser(session, reason) {
135
+ var pendingMap = session && session.pendingAskUser ? session.pendingAskUser : {};
136
+ Object.keys(pendingMap).forEach(function (toolId) {
137
+ var pending = pendingMap[toolId];
138
+ if (pending && pending.respond && typeof pending.respond.cancel === "function") pending.respond.cancel(reason || "Cancelled");
139
+ else if (pending && typeof pending.resolve === "function") pending.resolve({ behavior: "deny", message: reason || "Cancelled" });
140
+ });
141
+ if (session) session.pendingAskUser = {};
142
+ }
88
143
  var getProjectList = ctx.getProjectList;
89
144
  var getProjectCount = ctx.getProjectCount;
90
145
  var getScheduleCount = ctx.getScheduleCount;
@@ -821,14 +876,19 @@ function attachSessions(ctx) {
821
876
  }
822
877
 
823
878
  if (msg.type === "search_sessions") {
824
- var results = sm.searchSessions(msg.query || "");
879
+ var results = sm.searchSessions(msg.query || "", function (session) {
880
+ return ownsSearchSession(ws, session);
881
+ });
825
882
  sendTo(ws, { type: "search_results", query: msg.query || "", results: results });
826
883
  return true;
827
884
  }
828
885
 
829
886
  if (msg.type === "search_session_content") {
830
887
  var targetSession = msg.id ? sm.sessions.get(msg.id) : getSessionForWs(ws);
831
- if (!targetSession) return true;
888
+ if (!ownsSearchSession(ws, targetSession)) {
889
+ sendTo(ws, { type: "search_content_results", query: msg.query || "", sessionId: msg.id || null, hits: [], total: 0 });
890
+ return true;
891
+ }
832
892
  var contentResults = sm.searchSessionContent(targetSession.localId, msg.query || "");
833
893
  var searchResp = { type: "search_content_results", query: msg.query || "", sessionId: targetSession.localId, hits: contentResults.hits, total: contentResults.total };
834
894
  if (msg.source) searchResp.source = msg.source;
@@ -1256,7 +1316,7 @@ function attachSessions(ctx) {
1256
1316
  session.blocks = {};
1257
1317
  session.sentToolResults = {};
1258
1318
  session.pendingPermissions = {};
1259
- session.pendingAskUser = {};
1319
+ cancelPendingAskUser(session, "Conversation rewound");
1260
1320
  session.isProcessing = false;
1261
1321
  onProcessingChanged();
1262
1322
 
@@ -1328,53 +1388,7 @@ function attachSessions(ctx) {
1328
1388
 
1329
1389
  if (msg.type === "ask_user_response") {
1330
1390
  var session = getSessionForWs(ws);
1331
- if (!session) return true;
1332
- var toolId = msg.toolId;
1333
- var answers = msg.answers || {};
1334
- var pending = session.pendingAskUser[toolId];
1335
- if (!pending) return true;
1336
- delete session.pendingAskUser[toolId];
1337
- sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId, answers: answers });
1338
-
1339
- if (pending.mode === "mcp") {
1340
- // Stateless MCP path: the tool already returned. Inject the user's
1341
- // answer as a new user message so the conversation continues
1342
- // naturally on the next turn. This matches how the mate would see
1343
- // any other user input.
1344
- var answerText = formatAskUserAnswerAsMessage(pending.input, answers);
1345
- var userMsg = { type: "user_message", text: answerText };
1346
- session.history.push(userMsg);
1347
- sm.appendToSessionFile(session, userMsg);
1348
- sendToSession(session.localId, userMsg);
1349
-
1350
- if (!session.isProcessing) {
1351
- session.isProcessing = true;
1352
- onProcessingChanged();
1353
- session.sentToolResults = {};
1354
- sendToSession(session.localId, { type: "status", status: "processing" });
1355
- if (!session.queryInstance && !session.worker) {
1356
- sdk.startQuery(session, answerText, undefined, ensureProjectAccessForSession(session));
1357
- } else {
1358
- sdk.pushMessage(session, answerText);
1359
- }
1360
- } else {
1361
- // Turn is still running; queue for the next turn.
1362
- sdk.pushMessage(session, answerText);
1363
- }
1364
- } else {
1365
- // Claude native AskUserQuestion path (built-in tool, intercepted via
1366
- // canUseTool). In headless SDK mode the built-in can't render its own
1367
- // answer UI: resolving "allow" lets it return an EMPTY result, so the
1368
- // model continues the same turn with no answer ("I don't see an
1369
- // answer"). Injected user messages also lose the race against that
1370
- // empty result. Instead, deliver the answer as the tool_result by
1371
- // denying with the formatted answer text — the SDK surfaces a deny
1372
- // `message` to the model as the tool result, so the model receives the
1373
- // user's choice in the same turn. (updatedInput.answers is not read by
1374
- // the 0.3.x built-in, which is what caused answers to be dropped.)
1375
- var nativeAnswerText = formatAskUserAnswerAsMessage(pending.input, answers);
1376
- pending.resolve({ behavior: "deny", message: nativeAnswerText });
1377
- }
1391
+ if (session) respondToAskUser(session, msg);
1378
1392
  return true;
1379
1393
  }
1380
1394
 
@@ -1443,7 +1457,7 @@ function attachSessions(ctx) {
1443
1457
  session.isProcessing = false;
1444
1458
  onProcessingChanged();
1445
1459
  session.pendingPermissions = {};
1446
- session.pendingAskUser = {};
1460
+ cancelPendingAskUser(session, "Conversation restarted");
1447
1461
  sm.broadcastSessionList();
1448
1462
  setImmediate(function () {
1449
1463
  if (session.abortController) {
@@ -2011,6 +2025,7 @@ function attachSessions(ctx) {
2011
2025
  return {
2012
2026
  handleSessionsMessage: handleSessionsMessage,
2013
2027
  resolveSessionForView: resolveSessionForView,
2028
+ respondToAskUser: respondToAskUser,
2014
2029
  };
2015
2030
  }
2016
2031
 
@@ -167,16 +167,16 @@ function attachWorkerProposal(ctx) {
167
167
  }
168
168
 
169
169
  async function propose(args, session) {
170
- if (!isEligible(session)) return toolResult({ error: "Worker suggestions are only available in an unpaired Fable session." });
171
- if (hasPendingProposal(session)) return toolResult({ error: "A Worker suggestion is already awaiting a decision." });
170
+ if (!isEligible(session)) return toolResult({ error: "Split Worker suggestions are only available in an unpaired Fable session." });
171
+ if (hasPendingProposal(session)) return toolResult({ error: "A Split Worker suggestion is already awaiting a decision." });
172
172
  var summary = typeof args.summary === "string" ? args.summary.trim() : "";
173
173
  var plan = typeof args.plan === "string" ? args.plan.trim() : "";
174
174
  var task = typeof args.message === "string" ? args.message.trim() : "";
175
175
  if (!summary || !plan || !task) return toolResult({ error: "summary, plan, and message are required." });
176
- if (task.length > MAX_TASK_CHARS) return toolResult({ error: "The Worker task is too long." });
176
+ if (task.length > MAX_TASK_CHARS) return toolResult({ error: "The Split Worker task is too long." });
177
177
  await ensureModelCatalogs();
178
178
  var options = proposalOptions();
179
- if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Worker session." });
179
+ if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Split Worker session." });
180
180
  var recommendation = chooseRecommendation(args, session, options);
181
181
  var autoApprove = skipPermissionsEnabled(session);
182
182
  var proposal = {
@@ -200,17 +200,17 @@ function attachWorkerProposal(ctx) {
200
200
  effort: recommendation.effort,
201
201
  autoApproved: true,
202
202
  }, autoApprovalWs(session));
203
- if (!accepted.ok) return toolResult({ error: accepted.error || "Could not start the Worker." });
203
+ if (!accepted.ok) return toolResult({ error: accepted.error || "Could not start the Split Worker." });
204
204
  return toolResult({
205
205
  status: "running",
206
206
  proposalId: proposal.proposalId,
207
- instruction: "The Worker was auto-approved and started because skip permissions is enabled. Its result will return for review.",
207
+ instruction: "The Split Worker was auto-approved and started because skip permissions is enabled. Its result will return for review.",
208
208
  });
209
209
  }
210
210
  return toolResult({
211
211
  status: "posted",
212
212
  proposalId: proposal.proposalId,
213
- instruction: "The Worker suggestion is visible in the chat. End this turn now and wait for the user's decision.",
213
+ instruction: "The Split Worker suggestion is visible in the chat. End this turn now and wait for the user's decision.",
214
214
  });
215
215
  }
216
216
 
@@ -231,10 +231,10 @@ function attachWorkerProposal(ctx) {
231
231
 
232
232
  function parsePartnerResult(result) {
233
233
  if (!result || result.isError || !result.content || !result.content[0]) {
234
- return { status: "error", error: "Worker execution failed." };
234
+ return { status: "error", error: "Split Worker execution failed." };
235
235
  }
236
236
  try { return JSON.parse(result.content[0].text); }
237
- catch (e) { return { status: "error", error: result.content[0].text || "Worker execution failed." }; }
237
+ catch (e) { return { status: "error", error: result.content[0].text || "Split Worker execution failed." }; }
238
238
  }
239
239
 
240
240
  async function runWorker(session, proposal) {
@@ -247,13 +247,13 @@ function attachWorkerProposal(ctx) {
247
247
  });
248
248
  var followup;
249
249
  if (result.status === "complete") {
250
- followup = "[Worker execution completed]\nReview and verify the Worker's result. The Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Worker-owned files yourself. If that Worker is no longer available, keep the work in the visible Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background session.\n\n" + (result.response || "The Worker completed without a text summary.");
250
+ followup = "[Split Worker execution completed]\nReview and verify the Split Worker's result. The Split Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Split Worker-owned files yourself. If that Split Worker is no longer available, keep the work in the visible Split Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background Sub-agent.\n\n" + (result.response || "The Split Worker completed without a text summary.");
251
251
  } else if (result.status === "interrupted") {
252
- followup = "[Worker execution interrupted]\nThe user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
252
+ followup = "[Split Worker execution interrupted]\nThe user interrupted the Split Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
253
253
  } else if (result.status === "running") {
254
- followup = "[Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
254
+ followup = "[Split Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
255
255
  } else {
256
- followup = "[Worker execution failed]\nInspect the failure and delegate a narrower follow-up to the Worker when implementation work remains.\n\n" + (result.error || result.response || "Unknown Worker error.");
256
+ followup = "[Split Worker execution failed]\nInspect the failure and delegate a narrower follow-up to the Split Worker when implementation work remains.\n\n" + (result.error || result.response || "Unknown Split Worker error.");
257
257
  }
258
258
  await resumeDriver(session, followup);
259
259
  }
@@ -272,8 +272,8 @@ function attachWorkerProposal(ctx) {
272
272
  var options = proposal.options || proposalOptions();
273
273
  var vendor = msg.vendor || proposal.recommendedVendor;
274
274
  var model = msg.model || "";
275
- if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Worker vendor is not installed");
276
- if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Worker model is unavailable");
275
+ if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Split Worker vendor is not installed");
276
+ if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Split Worker model is unavailable");
277
277
  var effort = yoke.clampEffort(vendor, msg.effort || proposal.recommendedEffort || "medium") || "";
278
278
  var startingPatch = { status: "starting", selectedVendor: vendor, selectedModel: model, selectedEffort: effort };
279
279
  if (msg.autoApproved) startingPatch.autoApproved = true;
@@ -288,7 +288,7 @@ function attachWorkerProposal(ctx) {
288
288
  else ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
289
289
  runWorker(session, proposal).catch(function (err) {
290
290
  updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
291
- resumeDriver(session, "[Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
291
+ resumeDriver(session, "[Split Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
292
292
  });
293
293
  return { ok: true, status: "running", group: created.group };
294
294
  } catch (err) {
@@ -300,11 +300,11 @@ function attachWorkerProposal(ctx) {
300
300
  async function respondToProposal(ws, msg) {
301
301
  var session = sessionForResponse(ws);
302
302
  var proposal = findProposal(session, msg.proposalId);
303
- if (!proposal) throw new Error("Worker suggestion not found");
304
- if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
303
+ if (!proposal) throw new Error("Split Worker suggestion not found");
304
+ if (proposal.status !== "pending") throw new Error("Split Worker suggestion has already been resolved");
305
305
  if (!msg.accepted) {
306
306
  updateProposal(session, proposal, { status: "declined" });
307
- await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
307
+ await resumeDriver(session, "[Split Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
308
308
  return { ok: true, status: "declined" };
309
309
  }
310
310
  return acceptProposal(session, proposal, msg, ws);
@@ -322,13 +322,13 @@ function attachWorkerProposal(ctx) {
322
322
  if (!isEligible(session)) return [];
323
323
  return [{
324
324
  name: "propose_worker",
325
- description: "After making a concise plan for an implementation-heavy task, suggest that the user run the execution with another model in a visible Worker session. Use this only before substantial execution begins, never for small edits, explanations, or tasks the user asked you to keep in this session.",
325
+ description: "After making a concise plan for an implementation-heavy task, suggest that the user run the execution with another model in a visible Split Worker session. Use this only before substantial execution begins, never for small edits, explanations, or tasks the user asked you to keep in this session.",
326
326
  inputSchema: buildShape({
327
- summary: { type: "string", description: "One short sentence explaining why a Worker is useful for this task." },
327
+ summary: { type: "string", description: "One short sentence explaining why a Split Worker is useful for this task." },
328
328
  plan: { type: "string", description: "A concise numbered implementation plan to show in the approval card." },
329
- message: { type: "string", description: "Complete execution instructions for the Worker, including scope, constraints, validation, and expected result." },
330
- recommendedVendor: { type: "string", description: "Optional installed vendor id for the Worker." },
331
- recommendedModel: { type: "string", description: "Optional exact Worker model id. Omit when uncertain." },
329
+ message: { type: "string", description: "Complete execution instructions for the Split Worker, including scope, constraints, validation, and expected result." },
330
+ recommendedVendor: { type: "string", description: "Optional installed vendor id for the Split Worker." },
331
+ recommendedModel: { type: "string", description: "Optional exact Split Worker model id. Omit when uncertain." },
332
332
  recommendedEffort: { type: "string", description: "Optional reasoning effort: minimal, low, medium, high, xhigh, or max." },
333
333
  }, ["summary", "plan", "message"]),
334
334
  handler: function (args) { return propose(args || {}, session); },
@@ -337,7 +337,7 @@ function attachWorkerProposal(ctx) {
337
337
 
338
338
  function getSystemPrompt(session) {
339
339
  if (!isEligible(session)) return "";
340
- return "You can introduce Clay's Driver/Worker workflow with propose_worker. For an implementation-heavy task that is likely to consume substantial execution time, first reason about the work and form a concise plan. Before editing files or running substantial execution, call propose_worker once with that plan and a complete Worker instruction, recommending another suitable execution model. Use it conservatively: do not suggest a Worker for questions, investigations, tiny fixes, or when the user explicitly wants you to execute here. After posting a proposal, end the turn and wait. If accepted, the Worker runs visibly and its result returns to you for review.";
340
+ return "You can introduce Clay's Driver/Split Worker workflow with propose_worker. For an implementation-heavy task that is likely to consume substantial execution time, first reason about the work and form a concise plan. Before editing files or running substantial execution, call propose_worker once with that plan and a complete Split Worker instruction, recommending another suitable execution model. Use it conservatively: do not suggest a Split Worker for questions, investigations, tiny fixes, or when the user explicitly wants you to execute here. After posting a proposal, end the turn and wait. If accepted, the Split Worker runs visibly and its result returns to you for review.";
341
341
  }
342
342
 
343
343
  return {
@@ -0,0 +1,144 @@
1
+ var workspaceMcp = require("./workspace-query-mcp-server");
2
+ var clayHistoryMcp = require("./clay-history-mcp-server");
3
+
4
+ function attachProjectWorkspaceQuery(ctx) {
5
+ var service = ctx.service;
6
+ var sm = ctx.sm;
7
+ var projectSlug = ctx.projectSlug;
8
+ var getProjectOwnerId = ctx.getProjectOwnerId || function () { return null; };
9
+ var isMate = ctx.isMate;
10
+ var mateId = ctx.mateId;
11
+
12
+ function binding(session) {
13
+ if (!service || isMate !== true || !mateId) return null;
14
+ if (session && (!sm || !sm.sessions || sm.sessions.get(session.localId) !== session)) return null;
15
+ if (session && (session.homeDebatePlanning === true || session.debateSetupMode === true || session._mentionInProgress === true || session.spawn)) return null;
16
+ return service.bindSource({
17
+ projectSlug: projectSlug,
18
+ projectOwnerId: getProjectOwnerId(),
19
+ isMate: true,
20
+ mateId: mateId,
21
+ session: session || null,
22
+ });
23
+ }
24
+
25
+ function getToolDefs(session) {
26
+ var bound = session ? binding(session) : null;
27
+ if (session && !bound) return [];
28
+ return workspaceMcp.getToolDefs(bound, !!(bound && bound.isClay));
29
+ }
30
+
31
+ function createMcpServer(adapter, session) {
32
+ if (!service || isMate !== true) return null;
33
+ var bound = session ? binding(session) : null;
34
+ var projectBound = binding(null);
35
+ if (!projectBound) return null;
36
+ return workspaceMcp.createMcpServer(adapter, bound, projectBound.isClay);
37
+ }
38
+
39
+ function createHistoryMcpServer(adapter, session) {
40
+ if (!adapter || typeof adapter.createToolServer !== "function") return null;
41
+ var bound = session ? binding(session) : null;
42
+ var projectBound = binding(null);
43
+ if (!projectBound || projectBound.isClay !== true) return null;
44
+ return adapter.createToolServer({
45
+ name: "clay-history",
46
+ version: "2.0.0",
47
+ tools: clayHistoryMcp.getToolDefs({ workspace: bound }),
48
+ });
49
+ }
50
+
51
+ function isAuthoritativeClay() {
52
+ var bound = binding(null);
53
+ return !!(bound && bound.isClay === true);
54
+ }
55
+
56
+ function getBridgeTools(session, normalizeSchema) {
57
+ if (!session) return [];
58
+ var defs = getToolDefs(session);
59
+ var tools = [];
60
+ for (var i = 0; i < defs.length; i++) {
61
+ tools.push({
62
+ server: "clay-workspace",
63
+ name: defs[i].name,
64
+ description: defs[i].description || defs[i].name,
65
+ inputSchema: normalizeSchema(defs[i].inputSchema),
66
+ });
67
+ }
68
+ return tools;
69
+ }
70
+
71
+ function getDynamicToolDefs(session) {
72
+ return getToolDefs(session).map(function (tool) {
73
+ tool.permissionName = "mcp__clay-workspace__" + tool.name;
74
+ return tool;
75
+ });
76
+ }
77
+
78
+ function getHistoryDynamicToolDefs(session) {
79
+ var bound = session ? binding(session) : null;
80
+ if (!bound || bound.isClay !== true) return [];
81
+ return clayHistoryMcp.getToolDefs({ workspace: bound }).map(function (tool) {
82
+ tool.permissionName = "mcp__clay-history__" + tool.name;
83
+ return tool;
84
+ });
85
+ }
86
+
87
+ function getHistoryBridgeTools(session, normalizeSchema) {
88
+ if (!session) return [];
89
+ var bound = binding(session);
90
+ if (!bound || bound.isClay !== true) return [];
91
+ var defs = clayHistoryMcp.getToolDefs({ workspace: bound });
92
+ var tools = [];
93
+ for (var i = 0; i < defs.length; i++) {
94
+ tools.push({
95
+ server: "clay-history",
96
+ name: defs[i].name,
97
+ description: defs[i].description || defs[i].name,
98
+ inputSchema: normalizeSchema(defs[i].inputSchema),
99
+ });
100
+ }
101
+ return tools;
102
+ }
103
+
104
+ function callBridgeTool(session, toolName, args) {
105
+ if (!session) return Promise.reject(new Error("Workspace tools require a valid Clay session."));
106
+ var defs = getToolDefs(session);
107
+ for (var i = 0; i < defs.length; i++) {
108
+ if (defs[i].name === toolName) return Promise.resolve(defs[i].handler(args || {}));
109
+ }
110
+ return Promise.reject(new Error("Workspace tool not found: " + toolName));
111
+ }
112
+
113
+ function callHistoryBridgeTool(session, toolName, args) {
114
+ var bound = session ? binding(session) : null;
115
+ if (!bound || bound.isClay !== true) return Promise.reject(new Error("Clay history requires a valid builtin Clay session."));
116
+ var defs = clayHistoryMcp.getToolDefs({ workspace: bound });
117
+ for (var i = 0; i < defs.length; i++) {
118
+ if (defs[i].name === toolName) return Promise.resolve(defs[i].handler(args || {}));
119
+ }
120
+ return Promise.reject(new Error("Clay history tool not found: " + toolName));
121
+ }
122
+
123
+ function getMemorySessions(session, excludeSource) {
124
+ var bound = session && service && typeof service.bindProjectSession === "function"
125
+ ? service.bindProjectSession({ projectSlug: projectSlug, session: session })
126
+ : binding(null);
127
+ return bound ? bound.getMemorySessions(excludeSource) : [];
128
+ }
129
+
130
+ return {
131
+ createMcpServer: createMcpServer,
132
+ createHistoryMcpServer: createHistoryMcpServer,
133
+ isAuthoritativeClay: isAuthoritativeClay,
134
+ getBridgeTools: getBridgeTools,
135
+ getHistoryBridgeTools: getHistoryBridgeTools,
136
+ getDynamicToolDefs: getDynamicToolDefs,
137
+ getHistoryDynamicToolDefs: getHistoryDynamicToolDefs,
138
+ callBridgeTool: callBridgeTool,
139
+ callHistoryBridgeTool: callHistoryBridgeTool,
140
+ getMemorySessions: getMemorySessions,
141
+ };
142
+ }
143
+
144
+ module.exports = { attachProjectWorkspaceQuery: attachProjectWorkspaceQuery };