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
@@ -0,0 +1,110 @@
1
+ // Session-bound `clay-knowledge` wiring for Mate projects.
2
+ //
3
+ // Mate Knowledge is available only inside a Mate project. A non-Mate Project
4
+ // Driver session gets nothing here and continues to use `clay-logs`. Which
5
+ // tool set a Mate receives is decided by the server Mate registry through the
6
+ // service binder, never by a project flag or a tool argument.
7
+
8
+ var knowledgeMcp = require("./mate-knowledge-mcp-server");
9
+
10
+ var SYSTEM_PROMPT_LABEL = "--- Your Knowledge (durable personal and expertise context; manage via clay-knowledge tools) ---";
11
+ var CLAY_PROMPT_LABEL = "--- Mate Knowledge coordination (via clay-knowledge tools) ---";
12
+
13
+ function attachProjectMateKnowledge(ctx) {
14
+ var service = ctx.service;
15
+ var sm = ctx.sm;
16
+ var slug = ctx.projectSlug;
17
+ var isMate = ctx.isMate === true;
18
+ var mateId = ctx.mateId || null;
19
+ var getProjectOwnerId = ctx.getProjectOwnerId || function () { return null; };
20
+
21
+ function binding(session) {
22
+ if (!service || !isMate || !mateId) return null;
23
+ if (session && (!sm || !sm.sessions || sm.sessions.get(session.localId) !== session)) return null;
24
+ return service.bind({
25
+ projectSlug: slug,
26
+ projectOwnerId: getProjectOwnerId(),
27
+ isMate: true,
28
+ mateId: mateId,
29
+ session: session || null,
30
+ });
31
+ }
32
+
33
+ // Decided once from the project-level binding so a static descriptor and a
34
+ // session-bound server always advertise the same tool set.
35
+ function includeClay() {
36
+ var projectBound = binding(null);
37
+ return !!(projectBound && projectBound.isClay === true);
38
+ }
39
+
40
+ function getToolDefs(session) {
41
+ if (!service || !isMate) return [];
42
+ var bound = session ? binding(session) : null;
43
+ if (session && !bound) return [];
44
+ return knowledgeMcp.getToolDefs(bound, includeClay());
45
+ }
46
+
47
+ function createMcpServer(adapter, session) {
48
+ if (!service || !isMate) return null;
49
+ if (!binding(null)) return null;
50
+ var bound = session ? binding(session) : null;
51
+ // A session that cannot bind still gets the fail-closed descriptor rather
52
+ // than a silently missing server.
53
+ return knowledgeMcp.createMcpServer(adapter, bound, includeClay());
54
+ }
55
+
56
+ function getDynamicToolDefs(session) {
57
+ return getToolDefs(session).map(function (tool) {
58
+ tool.permissionName = "mcp__clay-knowledge__" + tool.name;
59
+ return tool;
60
+ });
61
+ }
62
+
63
+ function getBridgeTools(session, normalizeSchema) {
64
+ if (!session) return [];
65
+ var defs = getToolDefs(session);
66
+ var tools = [];
67
+ for (var i = 0; i < defs.length; i++) {
68
+ tools.push({
69
+ server: "clay-knowledge",
70
+ name: defs[i].name,
71
+ description: defs[i].description || defs[i].name,
72
+ inputSchema: normalizeSchema(defs[i].inputSchema),
73
+ });
74
+ }
75
+ return tools;
76
+ }
77
+
78
+ function callBridgeTool(session, toolName, args) {
79
+ if (!session) return Promise.reject(new Error("Knowledge tools require a valid Mate session."));
80
+ var defs = getToolDefs(session);
81
+ for (var i = 0; i < defs.length; i++) {
82
+ if (defs[i].name === toolName) return Promise.resolve(defs[i].handler(args || {}));
83
+ }
84
+ return Promise.reject(new Error("Knowledge tool not found: " + toolName));
85
+ }
86
+
87
+ function getSystemPrompt(session) {
88
+ if (!service || !isMate) return "";
89
+ var bound = session ? binding(session) : binding(null);
90
+ if (!bound) return "";
91
+ return bound.isClay
92
+ ? CLAY_PROMPT_LABEL + "\n" + knowledgeMcp.CLAY_CONTRACT
93
+ : SYSTEM_PROMPT_LABEL + "\n" + knowledgeMcp.MATE_CONTRACT;
94
+ }
95
+
96
+ return {
97
+ createMcpServer: createMcpServer,
98
+ getToolDefs: getToolDefs,
99
+ getDynamicToolDefs: getDynamicToolDefs,
100
+ getBridgeTools: getBridgeTools,
101
+ callBridgeTool: callBridgeTool,
102
+ getSystemPrompt: getSystemPrompt,
103
+ };
104
+ }
105
+
106
+ module.exports = {
107
+ SYSTEM_PROMPT_LABEL: SYSTEM_PROMPT_LABEL,
108
+ CLAY_PROMPT_LABEL: CLAY_PROMPT_LABEL,
109
+ attachProjectMateKnowledge: attachProjectMateKnowledge,
110
+ };
@@ -1,5 +1,17 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
+ var mateSync = require("./mate-knowledge-sync");
4
+
5
+ // Mirror a Mate-owned durable memory source into the Clay-wide Knowledge
6
+ // backend after the legacy write has already succeeded. Reconciling the source
7
+ // covers writes, rewrites, and removals with one call.
8
+ function mirrorMateSource(mateDir, fileName, actor) {
9
+ mateSync.syncQuietly({
10
+ mateDir: mateDir,
11
+ fileName: fileName,
12
+ actor: actor || { type: "agent" },
13
+ });
14
+ }
3
15
 
4
16
  function attachMemory(ctx) {
5
17
  var cwd = ctx.cwd;
@@ -8,7 +20,8 @@ function attachMemory(ctx) {
8
20
  var sendTo = ctx.sendTo;
9
21
  var matesModule = ctx.matesModule;
10
22
  var sessionSearch = ctx.sessionSearch;
11
- var getAllProjectSessions = ctx.getAllProjectSessions;
23
+ var getWorkspaceMemorySessions = ctx.getWorkspaceMemorySessions || function () { return []; };
24
+ var getOwnedCurrentSessions = ctx.getOwnedCurrentSessions || function () { return []; };
12
25
  var projectOwnerId = ctx.projectOwnerId;
13
26
  var handleMessage = ctx.handleMessage;
14
27
 
@@ -166,7 +179,7 @@ function attachMemory(ctx) {
166
179
  var timelineEntries = [];
167
180
 
168
181
  // Own sessions
169
- sm.sessions.forEach(function (s) {
182
+ getOwnedCurrentSessions().forEach(function (s) {
170
183
  if (s.hidden || !s.history || s.history.length === 0) return;
171
184
  timelineEntries.push({
172
185
  title: s.title || "New Session",
@@ -176,7 +189,7 @@ function attachMemory(ctx) {
176
189
  });
177
190
 
178
191
  // Cross-project sessions
179
- var crossForTimeline = getAllProjectSessions();
192
+ var crossForTimeline = getWorkspaceMemorySessions();
180
193
  for (var cti = 0; cti < crossForTimeline.length; cti++) {
181
194
  var cs = crossForTimeline[cti];
182
195
  timelineEntries.push({
@@ -209,7 +222,7 @@ function attachMemory(ctx) {
209
222
  try {
210
223
  // Collect mate's own sessions
211
224
  var mateSessions = [];
212
- sm.sessions.forEach(function (s) {
225
+ getOwnedCurrentSessions().forEach(function (s) {
213
226
  if (!s.hidden && s.history && s.history.length > 0) {
214
227
  mateSessions.push(s);
215
228
  }
@@ -218,7 +231,7 @@ function attachMemory(ctx) {
218
231
  // globalSearch: also collect sessions from all other projects + knowledge files
219
232
  var knowledgeFiles = [];
220
233
  if (hasGlobalSearch) {
221
- var crossSessions = getAllProjectSessions();
234
+ var crossSessions = getWorkspaceMemorySessions();
222
235
  for (var cs = 0; cs < crossSessions.length; cs++) {
223
236
  mateSessions.push(crossSessions[cs]);
224
237
  }
@@ -466,6 +479,7 @@ function attachMemory(ctx) {
466
479
  }
467
480
  fs.mkdirSync(knowledgeDir, { recursive: true });
468
481
  fs.writeFileSync(summaryFile, cleaned + "\n", "utf8");
482
+ mirrorMateSource(path.dirname(knowledgeDir), "memory-summary.md");
469
483
  console.log("[memory-summary] Updated memory-summary.md for mate " + mateId);
470
484
  } catch (e) {
471
485
  console.error("[memory-summary] Failed to write memory-summary.md for mate " + mateId + ":", e.message);
@@ -572,6 +586,7 @@ function attachMemory(ctx) {
572
586
  }
573
587
  fs.mkdirSync(knowledgeDir, { recursive: true });
574
588
  fs.writeFileSync(summaryFile, cleaned + "\n", "utf8");
589
+ mirrorMateSource(path.dirname(knowledgeDir), "memory-summary.md");
575
590
  console.log("[memory-summary] Generated initial memory-summary.md for mate " + mateId + " from " + digestsText.length + " digests");
576
591
  } catch (e) {
577
592
  console.error("[memory-summary] Failed to write initial memory-summary.md for mate " + mateId + ":", e.message);
@@ -595,7 +610,7 @@ function attachMemory(ctx) {
595
610
 
596
611
  // --- Message handlers for memory management UI ---
597
612
 
598
- function handleMemoryList(ws) {
613
+ function readMemoryState() {
599
614
  var digestFile = path.join(cwd, "knowledge", "session-digests.jsonl");
600
615
  var summaryFile = path.join(cwd, "knowledge", "memory-summary.md");
601
616
  var entries = [];
@@ -620,7 +635,12 @@ function attachMemory(ctx) {
620
635
  } catch (e) {}
621
636
  // Return newest first
622
637
  entries.reverse();
623
- sendTo(ws, { type: "memory_list", entries: entries, summary: summary });
638
+ return { entries: entries, summary: summary };
639
+ }
640
+
641
+ function handleMemoryList(ws) {
642
+ var state = readMemoryState();
643
+ sendTo(ws, { type: "memory_list", entries: state.entries, summary: state.summary });
624
644
  }
625
645
 
626
646
  function handleMemorySearch(ws, msg) {
@@ -660,6 +680,14 @@ function attachMemory(ctx) {
660
680
  } else {
661
681
  fs.writeFileSync(digestFile, lines.join("\n") + "\n");
662
682
  }
683
+ // Reconciling the journal after the rewrite tombstones the removed
684
+ // line, so a deleted memory does not stay active in the new backend.
685
+ var deleteUser = (ws && ws._clayUser) || null;
686
+ mirrorMateSource(cwd, "session-digests.jsonl", {
687
+ type: deleteUser ? "user" : "system",
688
+ userId: deleteUser ? deleteUser.id : null,
689
+ displayName: deleteUser ? (deleteUser.displayName || deleteUser.username) : null,
690
+ });
663
691
  }
664
692
  } catch (e) {}
665
693
  sendTo(ws, { type: "memory_deleted", index: msg.index });
@@ -671,6 +699,7 @@ function attachMemory(ctx) {
671
699
  gateMemory: gateMemory,
672
700
  updateMemorySummary: updateMemorySummary,
673
701
  initMemorySummary: initMemorySummary,
702
+ readMemoryState: readMemoryState,
674
703
  handleMemoryList: handleMemoryList,
675
704
  handleMemorySearch: handleMemorySearch,
676
705
  handleMemoryDelete: handleMemoryDelete,
@@ -12,6 +12,46 @@ function modelEntryMatches(entry, value) {
12
12
  return entry.value === value || entry.id === value || entry.resolvedModel === value;
13
13
  }
14
14
 
15
+ function normalizeCatalogModels(models) {
16
+ var normalized = [];
17
+ models = models || [];
18
+ for (var i = 0; i < models.length; i++) {
19
+ var value = modelEntryValue(models[i]);
20
+ if (!value) continue;
21
+ var displayName = typeof models[i] === "string" ? models[i] : (models[i].displayName || models[i].name || value);
22
+ var searchParts = [value, displayName];
23
+ if (typeof models[i] === "object") {
24
+ searchParts.push(models[i].id || "");
25
+ searchParts.push(models[i].resolvedModel || "");
26
+ }
27
+ normalized.push({ value: value, displayName: displayName, search: searchParts.join(" ").toLowerCase(), source: models[i] });
28
+ }
29
+ return normalized;
30
+ }
31
+
32
+ function selectCatalogModel(models, defaultModel, alias) {
33
+ var entries = normalizeCatalogModels(models);
34
+ if (entries.length === 0) return null;
35
+ var configured = null;
36
+ for (var i = 0; i < entries.length; i++) {
37
+ if (modelEntryMatches(entries[i].source, defaultModel)) {
38
+ configured = entries[i];
39
+ break;
40
+ }
41
+ }
42
+ alias = alias === "fast" || alias === "deep" ? alias : "standard";
43
+ if (alias === "standard" && configured) return configured;
44
+ var matcher = alias === "fast"
45
+ ? /haiku|mini|flash|fast|lite|small|luna/
46
+ : alias === "deep"
47
+ ? /opus|\bpro\b|max|deep|large|\bsol\b|reason/
48
+ : /sonnet|standard|balanced|terra/;
49
+ for (var mi = 0; mi < entries.length; mi++) {
50
+ if (matcher.test(entries[mi].search)) return entries[mi];
51
+ }
52
+ return configured || entries[0];
53
+ }
54
+
15
55
  function attachModels(ctx) {
16
56
  var cwd = ctx.cwd;
17
57
  var slug = ctx.slug;
@@ -25,26 +65,36 @@ function attachModels(ctx) {
25
65
  var serverTls = ctx.serverTls;
26
66
  var serverAuthToken = ctx.serverAuthToken;
27
67
 
28
- async function loadVendorModels(ws, msg) {
29
- var vendor = msg.vendor || "";
30
- var requestId = msg.requestId || null;
31
- var loadError = null;
32
- var vendorAdapter = null;
33
- var session = getSessionForWs(ws);
68
+ function getVendorAvailability() {
69
+ var available = (sm.availableVendors || []).slice();
70
+ var installed = (sm.installedVendors || []).slice();
71
+ var ids = available.slice();
72
+ for (var i = 0; i < installed.length; i++) {
73
+ if (ids.indexOf(installed[i]) === -1) ids.push(installed[i]);
74
+ }
75
+ return ids.map(function (vendor) {
76
+ var info = yoke.getVendorInfo(vendor);
77
+ return {
78
+ id: vendor,
79
+ displayName: info && info.displayName ? info.displayName : vendor,
80
+ installed: installed.indexOf(vendor) !== -1,
81
+ };
82
+ });
83
+ }
34
84
 
35
- if (!vendor) {
36
- sendTo(ws, {
37
- type: "model_info",
38
- vendor: vendor,
39
- requestId: requestId,
40
- modelStatus: "error",
41
- error: "No vendor was selected.",
42
- model: "",
43
- models: [],
44
- sessionId: session ? session.localId : null,
45
- });
46
- return;
85
+ function modelDisplayName(models, value) {
86
+ for (var i = 0; i < models.length; i++) {
87
+ if (!modelEntryMatches(models[i], value)) continue;
88
+ return typeof models[i] === "string" ? models[i] : (models[i].displayName || modelEntryValue(models[i]));
47
89
  }
90
+ return value;
91
+ }
92
+
93
+ async function getVendorCatalog(ws, vendor) {
94
+ var loadError = null;
95
+ var loadedDefaultModel = "";
96
+ var vendorAdapter = null;
97
+ if (!vendor) return { vendor: "", models: [], capabilities: {}, status: "error", error: "No vendor was selected." };
48
98
 
49
99
  try {
50
100
  var modelLinuxUser = getLinuxUserForWs(ws);
@@ -75,6 +125,7 @@ function attachModels(ctx) {
75
125
  });
76
126
  sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
77
127
  sm.capabilitiesByVendor[vendor] = readyResult.capabilities || {};
128
+ loadedDefaultModel = readyResult.defaultModel || "";
78
129
  sm.modelsByVendor = sm.modelsByVendor || {};
79
130
  if (Array.isArray(readyResult.models) && readyResult.models.length > 0) {
80
131
  sm.modelsByVendor[vendor] = readyResult.models;
@@ -110,6 +161,84 @@ function attachModels(ctx) {
110
161
  }
111
162
 
112
163
  var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[vendor]) || [];
164
+ var vendorInfo = yoke.getVendorInfo(vendor);
165
+ var displayName = vendorInfo ? vendorInfo.displayName : vendor;
166
+ var status = vendorModels.length > 0 ? "ready" : (loadError ? "error" : "empty");
167
+ var defaultModel = "";
168
+ var preferredModels = [
169
+ (sm.defaultModelByVendor && sm.defaultModelByVendor[vendor]) || "",
170
+ loadedDefaultModel,
171
+ ];
172
+ for (var preferredIndex = 0; preferredIndex < preferredModels.length && !defaultModel; preferredIndex++) {
173
+ for (var modelIndex = 0; modelIndex < vendorModels.length; modelIndex++) {
174
+ if (!modelEntryMatches(vendorModels[modelIndex], preferredModels[preferredIndex])) continue;
175
+ defaultModel = modelEntryValue(vendorModels[modelIndex]);
176
+ break;
177
+ }
178
+ }
179
+ var errorText = "";
180
+ if (status === "error") {
181
+ errorText = "Could not load " + displayName + " models: " + (loadError.message || loadError);
182
+ } else if (status === "empty") {
183
+ errorText = displayName + " returned no models. Check CLI authentication and retry.";
184
+ }
185
+ return {
186
+ vendor: vendor,
187
+ models: vendorModels,
188
+ defaultModel: defaultModel,
189
+ capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[vendor]) || {},
190
+ availableVendors: sm.availableVendors || [],
191
+ installedVendors: sm.installedVendors || [],
192
+ status: status,
193
+ error: errorText,
194
+ };
195
+ }
196
+
197
+ async function resolveConfiguredModel(ws, alias) {
198
+ var availability = getVendorAvailability();
199
+ var candidates = [];
200
+ var preferredVendor = sm.defaultVendor || "";
201
+ if (preferredVendor) candidates.push(preferredVendor);
202
+ for (var i = 0; i < availability.length; i++) {
203
+ if (candidates.indexOf(availability[i].id) === -1) candidates.push(availability[i].id);
204
+ }
205
+ var errors = [];
206
+ for (var ci = 0; ci < candidates.length; ci++) {
207
+ var catalog = await getVendorCatalog(ws, candidates[ci]);
208
+ var models = catalog.models || [];
209
+ var selected = selectCatalogModel(models, catalog.defaultModel, alias);
210
+ if (catalog.status !== "ready" || !selected) {
211
+ if (catalog.error) errors.push(catalog.error);
212
+ continue;
213
+ }
214
+ var info = yoke.getVendorInfo(catalog.vendor);
215
+ return {
216
+ status: "ready",
217
+ vendor: catalog.vendor,
218
+ vendorName: info && info.displayName ? info.displayName : catalog.vendor,
219
+ alias: alias === "fast" || alias === "deep" ? alias : "standard",
220
+ model: selected.value,
221
+ modelName: selected.displayName || modelDisplayName(models, selected.value),
222
+ error: "",
223
+ };
224
+ }
225
+ return {
226
+ status: "error",
227
+ vendor: preferredVendor,
228
+ vendorName: preferredVendor ? ((yoke.getVendorInfo(preferredVendor) || {}).displayName || preferredVendor) : "",
229
+ model: "",
230
+ modelName: "",
231
+ alias: alias === "fast" || alias === "deep" ? alias : "standard",
232
+ error: errors[0] || "No configured model is available. Sign in to an installed model provider CLI, then retry.",
233
+ };
234
+ }
235
+
236
+ async function loadVendorModels(ws, msg) {
237
+ var vendor = msg.vendor || "";
238
+ var requestId = msg.requestId || null;
239
+ var session = getSessionForWs(ws);
240
+ var catalog = await getVendorCatalog(ws, vendor);
241
+ var vendorModels = catalog.models;
113
242
  var modelToSend = modelEntryValue(vendorModels[0]);
114
243
  var preferredModel = (session && session.vendor === vendor && session.model) || (sm.defaultModelByVendor && sm.defaultModelByVendor[vendor]) || "";
115
244
  if (preferredModel) {
@@ -120,27 +249,18 @@ function attachModels(ctx) {
120
249
  }
121
250
  }
122
251
  }
123
- var vendorInfo = yoke.getVendorInfo(vendor);
124
- var displayName = vendorInfo ? vendorInfo.displayName : vendor;
125
- var status = vendorModels.length > 0 ? "ready" : (loadError ? "error" : "empty");
126
- var errorText = "";
127
- if (status === "error") {
128
- errorText = "Could not load " + displayName + " models: " + (loadError.message || loadError);
129
- } else if (status === "empty") {
130
- errorText = displayName + " returned no models. Check CLI authentication and retry.";
131
- }
132
252
  sendTo(ws, {
133
253
  type: "model_info",
134
254
  model: modelToSend,
135
255
  models: vendorModels,
136
256
  vendor: vendor,
137
257
  sessionId: session ? session.localId : null,
138
- capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[vendor]) || {},
139
- availableVendors: sm.availableVendors || [],
140
- installedVendors: sm.installedVendors || [],
258
+ capabilities: catalog.capabilities,
259
+ availableVendors: catalog.availableVendors,
260
+ installedVendors: catalog.installedVendors,
141
261
  requestId: requestId,
142
- modelStatus: status,
143
- error: errorText,
262
+ modelStatus: catalog.status,
263
+ error: catalog.error,
144
264
  });
145
265
  }
146
266
 
@@ -208,6 +328,9 @@ function attachModels(ctx) {
208
328
 
209
329
  return {
210
330
  handleMessage: handleMessage,
331
+ getVendorCatalog: getVendorCatalog,
332
+ getVendorAvailability: getVendorAvailability,
333
+ resolveConfiguredModel: resolveConfiguredModel,
211
334
  loadVendorModels: loadVendorModels,
212
335
  selectModel: selectModel,
213
336
  };
@@ -217,4 +340,6 @@ module.exports = {
217
340
  attachModels: attachModels,
218
341
  modelEntryMatches: modelEntryMatches,
219
342
  modelEntryValue: modelEntryValue,
343
+ normalizeCatalogModels: normalizeCatalogModels,
344
+ selectCatalogModel: selectCatalogModel,
220
345
  };
@@ -7,7 +7,7 @@ var MAX_NOTE_PROMPT_CHARS = 800;
7
7
  var NOTES_LABEL = "--- Project sticky notes (cross-session work memory; manage via clay-notes tools) ---";
8
8
  var NOTES_HINT = "- More notes are available; call list_notes for the rest.";
9
9
  var PROACTIVE_POLICY = sessionNotesMcp.MEMORY_CONTRACT + " If an injected preview is relevant or truncated, call list_notes before acting or writing so you do not create a duplicate.";
10
- var NOTE_COLORS = ["yellow", "blue", "green", "pink", "orange", "purple"];
10
+ var NOTE_COLORS = ["purple", "green", "yellow", "blue", "pink", "orange"];
11
11
 
12
12
  function toolResult(value) {
13
13
  return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
@@ -150,7 +150,7 @@ function attachSessionNotes(ctx) {
150
150
  var placement = autoPlacement(notes);
151
151
  var created = nm.create({
152
152
  text: text,
153
- color: color || "yellow",
153
+ color: color || "purple",
154
154
  x: placement.x,
155
155
  y: placement.y,
156
156
  origin: { sessionId: caller.localId, vendor: caller.vendor || "claude" },
@@ -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
  }