clay-server 3.8.1 → 4.0.0-beta.2

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 (189) 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-connection.js +4 -0
  16. package/lib/project-debate-proposal.js +133 -17
  17. package/lib/project-debate.js +214 -110
  18. package/lib/project-delegated-follow-up.js +84 -0
  19. package/lib/project-delegated-session.js +53 -0
  20. package/lib/project-filesystem.js +7 -9
  21. package/lib/project-knowledge.js +1 -0
  22. package/lib/project-mate-creation-proposal.js +120 -0
  23. package/lib/project-memory.js +14 -7
  24. package/lib/project-models.js +157 -32
  25. package/lib/project-session-notes.js +2 -2
  26. package/lib/project-session-pair.js +8 -8
  27. package/lib/project-sessions.js +66 -51
  28. package/lib/project-vendor-login.js +397 -0
  29. package/lib/project-worker-proposal.js +25 -25
  30. package/lib/project-workspace-query.js +144 -0
  31. package/lib/project.js +349 -102
  32. package/lib/public/app.js +85 -42
  33. package/lib/public/css/avatar-imprints.css +0 -1
  34. package/lib/public/css/base.css +4 -0
  35. package/lib/public/css/capsule-ui.css +389 -0
  36. package/lib/public/css/command-palette.css +306 -6
  37. package/lib/public/css/home-capsule-library.css +243 -0
  38. package/lib/public/css/home-capsule-source.css +192 -0
  39. package/lib/public/css/home-debate-live.css +146 -0
  40. package/lib/public/css/home-debate-planning.css +125 -0
  41. package/lib/public/css/home-debates-archive.css +281 -0
  42. package/lib/public/css/home-hub.css +896 -405
  43. package/lib/public/css/home-mate-creation.css +34 -0
  44. package/lib/public/css/home-mate-model-picker.css +131 -0
  45. package/lib/public/css/home-mate-settings.css +344 -0
  46. package/lib/public/css/home-session-actions.css +265 -0
  47. package/lib/public/css/home-sidebar.css +667 -0
  48. package/lib/public/css/icon-strip.css +1 -2
  49. package/lib/public/css/mates.css +7 -34
  50. package/lib/public/css/pane.css +1 -1
  51. package/lib/public/css/playbook.css +0 -80
  52. package/lib/public/css/sticky-notes.css +53 -117
  53. package/lib/public/css/tui-attention.css +0 -44
  54. package/lib/public/css/workspace-assignment.css +45 -0
  55. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  56. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  57. package/lib/public/index.html +93 -74
  58. package/lib/public/modules/app-connection.js +10 -1
  59. package/lib/public/modules/app-dm.js +43 -160
  60. package/lib/public/modules/app-home-hub.js +328 -585
  61. package/lib/public/modules/app-message-cards.js +242 -0
  62. package/lib/public/modules/app-message-router.js +84 -0
  63. package/lib/public/modules/app-messages.js +102 -37
  64. package/lib/public/modules/app-notifications.js +8 -119
  65. package/lib/public/modules/app-projects.js +9 -1
  66. package/lib/public/modules/app-rendering.js +21 -602
  67. package/lib/public/modules/avatar-imprint.js +11 -8
  68. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  69. package/lib/public/modules/chat-render-runtime.js +198 -0
  70. package/lib/public/modules/command-palette.js +225 -436
  71. package/lib/public/modules/dm-render.js +82 -0
  72. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  73. package/lib/public/modules/home-capsule-library.js +146 -0
  74. package/lib/public/modules/home-capsule-source.js +383 -0
  75. package/lib/public/modules/home-chat-identity.js +35 -0
  76. package/lib/public/modules/home-chat-scroll.js +77 -0
  77. package/lib/public/modules/home-chat-stream-state.js +34 -0
  78. package/lib/public/modules/home-composer-focus.js +25 -0
  79. package/lib/public/modules/home-conversations-sheet.js +179 -0
  80. package/lib/public/modules/home-debate-activity.js +48 -0
  81. package/lib/public/modules/home-debate-controls.js +295 -0
  82. package/lib/public/modules/home-debate-launch.js +41 -0
  83. package/lib/public/modules/home-debate-live.js +284 -0
  84. package/lib/public/modules/home-debate-models.js +73 -0
  85. package/lib/public/modules/home-debate-planning.js +335 -0
  86. package/lib/public/modules/home-debates-archive.js +279 -0
  87. package/lib/public/modules/home-dock-resize.js +74 -0
  88. package/lib/public/modules/home-dock.js +446 -0
  89. package/lib/public/modules/home-mate-chat.js +496 -0
  90. package/lib/public/modules/home-mate-creation.js +109 -0
  91. package/lib/public/modules/home-mate-model-picker.js +281 -0
  92. package/lib/public/modules/home-mate-selection.js +38 -0
  93. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  94. package/lib/public/modules/home-mate-settings.js +383 -0
  95. package/lib/public/modules/home-session-actions.js +373 -0
  96. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  97. package/lib/public/modules/home-shell.js +9 -0
  98. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  99. package/lib/public/modules/home-sidebar.js +158 -0
  100. package/lib/public/modules/home-sub-surface.js +19 -0
  101. package/lib/public/modules/home-surface-boot.js +68 -0
  102. package/lib/public/modules/home-surface.js +141 -0
  103. package/lib/public/modules/home-tools.js +271 -0
  104. package/lib/public/modules/markdown.js +50 -2
  105. package/lib/public/modules/mate-management.js +86 -0
  106. package/lib/public/modules/mate-wizard.js +0 -7
  107. package/lib/public/modules/mention.js +17 -19
  108. package/lib/public/modules/pane-bridge.js +9 -0
  109. package/lib/public/modules/profile.js +4 -5
  110. package/lib/public/modules/project-settings.js +34 -28
  111. package/lib/public/modules/project-switcher.js +11 -5
  112. package/lib/public/modules/project-workspace-assignment.js +23 -0
  113. package/lib/public/modules/search-clay-chat.js +402 -0
  114. package/lib/public/modules/server-settings.js +14 -12
  115. package/lib/public/modules/session-actions.js +1 -1
  116. package/lib/public/modules/sidebar-mates.js +56 -552
  117. package/lib/public/modules/sidebar-mobile.js +7 -64
  118. package/lib/public/modules/sidebar-presence.js +37 -0
  119. package/lib/public/modules/split-pair-ui.js +13 -13
  120. package/lib/public/modules/split-view.js +7 -0
  121. package/lib/public/modules/sticky-notes.js +7 -6
  122. package/lib/public/modules/tool-input-composition.js +48 -0
  123. package/lib/public/modules/tool-llm-status.js +167 -0
  124. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  125. package/lib/public/modules/tool-renderer-chart.js +158 -0
  126. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  127. package/lib/public/modules/tool-renderer.js +464 -0
  128. package/lib/public/modules/tool-runtime.js +309 -0
  129. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  130. package/lib/public/modules/tool-ui-tree.js +21 -0
  131. package/lib/public/modules/tui-attention.js +10 -0
  132. package/lib/public/modules/user-settings.js +16 -23
  133. package/lib/public/modules/vendor-login.js +287 -0
  134. package/lib/public/modules/worker-proposal.js +2 -2
  135. package/lib/public/modules/workspace-assignment-card.js +149 -0
  136. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  137. package/lib/public/style.css +13 -2
  138. package/lib/runtime-env.js +65 -0
  139. package/lib/sdk-bridge.js +120 -32
  140. package/lib/sdk-message-processor.js +10 -6
  141. package/lib/server-home-capsule-creation.js +36 -0
  142. package/lib/server-home-chat-events.js +287 -0
  143. package/lib/server-home-chat.js +518 -0
  144. package/lib/server-home-clay-entry.js +65 -0
  145. package/lib/server-home-clay-session-links.js +26 -0
  146. package/lib/server-home-debate-planning.js +214 -0
  147. package/lib/server-home-debates.js +137 -0
  148. package/lib/server-home-mate-creation.js +261 -0
  149. package/lib/server-home-models.js +241 -0
  150. package/lib/server-home-preferences.js +61 -0
  151. package/lib/server-mates.js +37 -0
  152. package/lib/server-tools.js +377 -0
  153. package/lib/server.js +111 -59
  154. package/lib/session-pair-mcp-server.js +6 -6
  155. package/lib/sessions.js +18 -3
  156. package/lib/tool-capsule-source.js +142 -0
  157. package/lib/tool-control-mcp-server.js +126 -0
  158. package/lib/tool-llm.js +48 -0
  159. package/lib/tool-storage.js +77 -0
  160. package/lib/tool-ui-spec-advanced.js +82 -0
  161. package/lib/tool-ui-spec.js +261 -0
  162. package/lib/tools-registry.js +286 -0
  163. package/lib/users-home-dock-preferences.js +77 -0
  164. package/lib/users-home-surface-preferences.js +99 -0
  165. package/lib/users-preferences.js +4 -7
  166. package/lib/users.js +12 -0
  167. package/lib/whats-new-content.js +3 -3
  168. package/lib/workspace-assignment-service.js +420 -0
  169. package/lib/workspace-query-mcp-server.js +154 -0
  170. package/lib/workspace-query-service.js +492 -0
  171. package/lib/ws-schema.js +93 -12
  172. package/lib/yoke/adapters/acp.js +2 -1
  173. package/lib/yoke/adapters/antigravity.js +2 -1
  174. package/lib/yoke/adapters/claude-worker.js +11 -4
  175. package/lib/yoke/adapters/claude.js +73 -43
  176. package/lib/yoke/adapters/codex.js +40 -10
  177. package/lib/yoke/adapters/kiro.js +2 -1
  178. package/lib/yoke/claude-user-input.js +38 -0
  179. package/lib/yoke/codex-app-server.js +20 -2
  180. package/lib/yoke/codex-user-input.js +64 -0
  181. package/lib/yoke/complete-once.js +164 -0
  182. package/lib/yoke/index.js +5 -1
  183. package/lib/yoke/interface.js +19 -0
  184. package/lib/yoke/mcp-bridge-server.js +2 -6
  185. package/lib/yoke/user-input.js +254 -0
  186. package/package.json +3 -2
  187. package/lib/public/css/home-chat.css +0 -380
  188. package/lib/public/modules/home-chat.js +0 -438
  189. package/lib/server-clay-home.js +0 -245
@@ -0,0 +1,377 @@
1
+ var toolsRegistry = require("./tools-registry");
2
+ var toolStorage = require("./tool-storage");
3
+ var crypto = require("crypto");
4
+ var toolLlm = require("./tool-llm");
5
+
6
+ function attachTools(deps) {
7
+ var users = deps.users;
8
+ var projects = deps.projects;
9
+ var serverLogic = Object.create(null);
10
+ var controlTimeoutMs = deps.controlTimeoutMs || 15000;
11
+ var llmControlTimeoutMs = deps.llmControlTimeoutMs || 90000;
12
+ var pendingControl = Object.create(null);
13
+
14
+ function contextFor(userId) {
15
+ var multiUser = users.isMultiUser();
16
+ var linuxUser = null;
17
+ if (multiUser) {
18
+ var user = users.findUserById(userId);
19
+ if (user && user.linuxUser) linuxUser = user.linuxUser;
20
+ }
21
+ return { userId: userId, multiUser: multiUser, linuxUser: linuxUser };
22
+ }
23
+
24
+ function send(ws, payload) {
25
+ if (ws.readyState !== undefined && ws.readyState !== 1) return;
26
+ ws.send(JSON.stringify(payload));
27
+ }
28
+
29
+ function broadcastToUser(userId, payload) {
30
+ var sent = new Set();
31
+ projects.forEach(function (projectContext) {
32
+ projectContext.forEachClient(function (otherWs) {
33
+ if (sent.has(otherWs)) return;
34
+ if (users.isMultiUser() && (!otherWs._clayUser || otherWs._clayUser.id !== userId)) return;
35
+ sent.add(otherWs);
36
+ send(otherWs, payload);
37
+ });
38
+ });
39
+ }
40
+
41
+ function sendError(ws, msg, error) {
42
+ send(ws, {
43
+ type: "tools_error",
44
+ requestType: msg.type,
45
+ toolId: msg.toolId || (msg.manifest && msg.manifest.id) || null,
46
+ seq: msg.seq,
47
+ requestId: msg.requestId,
48
+ message: error && error.message ? error.message : "Tool operation failed.",
49
+ });
50
+ }
51
+
52
+ function run(ws, msg, operation) {
53
+ Promise.resolve().then(operation).catch(function (error) { sendError(ws, msg, error); });
54
+ }
55
+
56
+ function fullToolList(ctx) {
57
+ var manifests = toolsRegistry.listTools(ctx);
58
+ var tools = [];
59
+ for (var i = 0; i < manifests.length; i++) {
60
+ if (manifests[i].error) {
61
+ tools.push(manifests[i]);
62
+ continue;
63
+ }
64
+ var tool = toolsRegistry.getTool(ctx, manifests[i].id);
65
+ if (tool) tools.push(tool);
66
+ }
67
+ return tools;
68
+ }
69
+
70
+ function installedManifests(userId) {
71
+ var ctx = contextFor(userId);
72
+ return toolsRegistry.listTools(ctx).filter(function (manifest) { return !manifest.error; });
73
+ }
74
+
75
+ function refreshMateCatalogs(userId) {
76
+ projects.forEach(function (projectContext) {
77
+ try {
78
+ if (!projectContext || typeof projectContext.getStatus !== "function" || typeof projectContext.refreshCapsuleCatalog !== "function") return;
79
+ var status = projectContext.getStatus();
80
+ if (!status || status.isMate !== true) return;
81
+ if (users.isMultiUser() && status.projectOwnerId !== userId) return;
82
+ projectContext.refreshCapsuleCatalog();
83
+ } catch (error) {
84
+ console.error("[tools] Could not refresh a Mate Capsule catalog:", error && error.message ? error.message : error);
85
+ }
86
+ });
87
+ }
88
+
89
+ function socketUserId(ws) {
90
+ if (!users.isMultiUser()) return "default";
91
+ return ws._clayUser ? ws._clayUser.id : null;
92
+ }
93
+
94
+ function latestHomeSocket(userId) {
95
+ var found = null;
96
+ var foundAt = -1;
97
+ var seen = new Set();
98
+ projects.forEach(function (projectContext) {
99
+ projectContext.forEachClient(function (ws) {
100
+ if (seen.has(ws) || socketUserId(ws) !== userId || !ws._homeChatTap) return;
101
+ seen.add(ws);
102
+ if (ws.readyState !== undefined && ws.readyState !== 1) return;
103
+ var openedAt = ws._homeChatTap.openedAt || 0;
104
+ if (!found || openedAt >= foundAt) {
105
+ found = ws;
106
+ foundAt = openedAt;
107
+ }
108
+ });
109
+ });
110
+ return found;
111
+ }
112
+
113
+ function requestBrowserControl(userId, mateId, toolId, kind, payload, timeoutMs) {
114
+ var ws = latestHomeSocket(userId);
115
+ if (!ws) return Promise.reject(new Error("The user's home screen is not open."));
116
+ var requestId = "toolctl_" + crypto.randomUUID();
117
+ return new Promise(function (resolve, reject) {
118
+ var timer = setTimeout(function () {
119
+ delete pendingControl[requestId];
120
+ reject(new Error("The tool control request timed out because the home screen did not respond."));
121
+ }, timeoutMs || controlTimeoutMs);
122
+ pendingControl[requestId] = {
123
+ ws: ws,
124
+ userId: userId,
125
+ resolve: resolve,
126
+ reject: reject,
127
+ timer: timer,
128
+ };
129
+ send(ws, {
130
+ type: "tool_control_request",
131
+ requestId: requestId,
132
+ toolId: toolId,
133
+ kind: kind,
134
+ payload: payload || {},
135
+ callerId: mateId,
136
+ });
137
+ });
138
+ }
139
+
140
+ function controlForMate(userId, mateId, toolId, kind, payload) {
141
+ payload = payload || {};
142
+ var ctx = contextFor(userId);
143
+ toolsRegistry.listTools(ctx);
144
+ var installed = toolsRegistry.getTool(ctx, toolId);
145
+ if (!installed) return Promise.reject(new Error("Tool is not installed."));
146
+ if (installed.manifest.runtime === "server") {
147
+ var adapter = serverLogic[toolId];
148
+ if (!adapter) return Promise.reject(new Error("No trusted server runtime is registered for this capsule."));
149
+ var context = { userId: userId, callerId: mateId };
150
+ if (kind === "snapshot") return Promise.resolve(adapter.snapshot(context));
151
+ if (kind === "act") return Promise.resolve(adapter.act(context, payload.actionId, payload.args || {}));
152
+ if (kind === "set" && adapter.set) return Promise.resolve(adapter.set(context, payload.controlId, payload.value));
153
+ return Promise.reject(new Error("The server-runtime capsule does not support '" + kind + "'."));
154
+ }
155
+ var permissions = installed.manifest.permissions || [];
156
+ var timeoutMs = kind === "act" && permissions.indexOf("llm") !== -1 ? llmControlTimeoutMs : controlTimeoutMs;
157
+ return requestBrowserControl(userId, mateId, toolId, kind, payload || {}, timeoutMs);
158
+ }
159
+
160
+ function handleControlResponse(ws, msg, userId) {
161
+ var pending = pendingControl[msg.requestId];
162
+ if (!pending || pending.ws !== ws || pending.userId !== userId) return;
163
+ clearTimeout(pending.timer);
164
+ delete pendingControl[msg.requestId];
165
+ if (msg.error) pending.reject(new Error(msg.error));
166
+ else pending.resolve(msg.data || {});
167
+ }
168
+
169
+ function storageOperation(ctx, msg) {
170
+ if (!toolsRegistry.getTool(ctx, msg.toolId)) throw new Error("Tool is not installed.");
171
+ var storage = toolStorage.createToolStorage(ctx, msg.toolId);
172
+ var args = msg.args || {};
173
+ if (msg.op === "list") return storage.list();
174
+ if (msg.op === "get") return storage.get(args.id);
175
+ if (msg.op === "put") return storage.put(args.doc);
176
+ if (msg.op === "delete") return storage.delete(args.id);
177
+ if (msg.op === "query") return storage.query(args.query || {});
178
+ throw new Error("Unknown storage operation '" + msg.op + "'.");
179
+ }
180
+
181
+ function projectForSocket(ws) {
182
+ var found = null;
183
+ projects.forEach(function (projectContext) {
184
+ if (!found && projectContext.clients && projectContext.clients.has(ws)) found = projectContext;
185
+ });
186
+ return found;
187
+ }
188
+
189
+ async function llmOperation(ws, ctx, userId, msg) {
190
+ var tool = toolsRegistry.getTool(ctx, msg.toolId);
191
+ if (!tool) throw new Error("Tool is not installed.");
192
+ var permissions = tool.manifest.permissions || [];
193
+ if (permissions.indexOf("llm") === -1) throw new Error("This capsule does not have the llm permission.");
194
+ var args = toolLlm.validateArgs(msg.args);
195
+ var projectContext = projectForSocket(ws);
196
+ if (!projectContext || typeof projectContext.completeToolLlm !== "function") throw new Error("No project LLM runtime is available.");
197
+ console.debug("[tool-llm] toolId=" + msg.toolId + " callerId=" + (msg.callerId || "user"));
198
+ return projectContext.completeToolLlm(ws, args);
199
+ }
200
+
201
+ async function llmConfiguration(ws, alias) {
202
+ var projectContext = projectForSocket(ws);
203
+ if (!projectContext || typeof projectContext.getToolLlmConfig !== "function") {
204
+ return { status: "error", vendor: "", vendorName: "", model: "", modelName: "", error: "No project model configuration is available." };
205
+ }
206
+ try {
207
+ return await projectContext.getToolLlmConfig(ws, alias);
208
+ } catch (error) {
209
+ return { status: "error", vendor: "", vendorName: "", model: "", modelName: "", error: error && error.message ? error.message : "Could not load the model configuration." };
210
+ }
211
+ }
212
+
213
+ function installForMate(userId, input) {
214
+ var installed = toolsRegistry.installTool(contextFor(userId), input);
215
+ refreshMateCatalogs(userId);
216
+ broadcastToUser(userId, { type: "tool_installed", tool: installed });
217
+ return installed;
218
+ }
219
+
220
+ function sourceForMate(userId, toolId) {
221
+ var ctx = contextFor(userId);
222
+ var installed = toolsRegistry.getTool(ctx, toolId);
223
+ if (!installed) throw new Error("Tool not found.");
224
+ if (installed.manifest.runtime !== "worker") throw new Error("Server-managed Capsule source is not available to Mates.");
225
+ if (!installed.metadata || installed.metadata.mateEditingAllowed !== true) {
226
+ throw new Error("The user has not allowed Mate source access for this Capsule.");
227
+ }
228
+ return toolsRegistry.getToolSource(ctx, toolId);
229
+ }
230
+
231
+ function updateForMate(userId, toolId, input) {
232
+ var ctx = contextFor(userId);
233
+ var installed = toolsRegistry.getTool(ctx, toolId);
234
+ if (!installed) throw new Error("Tool not found.");
235
+ if (!installed.metadata || installed.metadata.mateEditingAllowed !== true) {
236
+ throw new Error("The user has not allowed Mate editing for this Capsule.");
237
+ }
238
+ var updated = toolsRegistry.updateTool(ctx, toolId, input);
239
+ refreshMateCatalogs(userId);
240
+ broadcastToUser(userId, { type: "tool_installed", tool: updated });
241
+ var source = toolsRegistry.getToolSource(ctx, toolId);
242
+ return Object.assign({}, updated, { revision: source.revision });
243
+ }
244
+
245
+ function setMateAccess(userId, toolId, allowed, requestId) {
246
+ var ctx = contextFor(userId);
247
+ var tool = toolsRegistry.getTool(ctx, toolId);
248
+ if (!tool) throw new Error("Tool not found.");
249
+ if (tool.manifest.runtime !== "worker" && allowed === true) {
250
+ throw new Error("Server-managed Capsules cannot be edited by Mates.");
251
+ }
252
+ var metadata = toolsRegistry.setMateEditingAllowed(ctx, toolId, allowed);
253
+ broadcastToUser(userId, { type: "tool_mate_access_state", toolId: toolId, requestId: requestId || null, ok: true, metadata: metadata });
254
+ return metadata;
255
+ }
256
+
257
+ function removeForMate(userId, toolId) {
258
+ var removed = toolsRegistry.removeTool(contextFor(userId), toolId);
259
+ if (!removed) throw new Error("Tool not found.");
260
+ refreshMateCatalogs(userId);
261
+ broadcastToUser(userId, { type: "tool_removed", toolId: toolId });
262
+ return { removed: true, toolId: toolId };
263
+ }
264
+
265
+ function handleMessage(ws, msg) {
266
+ var messageTypes = ["tools_list", "tool_get", "tool_install", "tool_remove", "tool_storage_op", "tool_llm_op", "tool_llm_config_get", "tool_control_response", "tool_source_get", "tool_mate_access_set"];
267
+ if (!msg || messageTypes.indexOf(msg.type) === -1) return false;
268
+ var userId;
269
+ if (users.isMultiUser()) {
270
+ if (!ws._clayUser) return false;
271
+ userId = ws._clayUser.id;
272
+ } else {
273
+ userId = "default";
274
+ }
275
+ var ctx = contextFor(userId);
276
+
277
+ if (msg.type === "tool_control_response") {
278
+ handleControlResponse(ws, msg, userId);
279
+ return true;
280
+ }
281
+
282
+ if (msg.type === "tools_list") {
283
+ run(ws, msg, function () {
284
+ send(ws, { type: "tools_state", tools: fullToolList(ctx) });
285
+ });
286
+ return true;
287
+ }
288
+ if (msg.type === "tool_get") {
289
+ run(ws, msg, function () {
290
+ var tool = toolsRegistry.getTool(ctx, msg.toolId);
291
+ if (!tool) throw new Error("Tool not found.");
292
+ send(ws, { type: "tools_state", tools: [tool], requestedToolId: msg.toolId });
293
+ });
294
+ return true;
295
+ }
296
+ if (msg.type === "tool_source_get") {
297
+ Promise.resolve().then(function () {
298
+ return toolsRegistry.getToolSource(ctx, msg.toolId);
299
+ }).then(function (source) {
300
+ send(ws, Object.assign({ type: "tool_source_state", toolId: msg.toolId, requestId: msg.requestId, ok: true }, source));
301
+ }).catch(function (error) {
302
+ send(ws, { type: "tool_source_state", toolId: msg.toolId, requestId: msg.requestId, ok: false, error: error && error.message ? error.message : "Could not read Capsule source." });
303
+ });
304
+ return true;
305
+ }
306
+ if (msg.type === "tool_mate_access_set") {
307
+ Promise.resolve().then(function () {
308
+ if (typeof msg.allowed !== "boolean") throw new Error("Mate editing permission must be true or false.");
309
+ return setMateAccess(userId, msg.toolId, msg.allowed, msg.requestId);
310
+ }).catch(function (error) {
311
+ send(ws, { type: "tool_mate_access_state", toolId: msg.toolId, requestId: msg.requestId, ok: false, error: error && error.message ? error.message : "Could not update Mate editing permission." });
312
+ });
313
+ return true;
314
+ }
315
+ if (msg.type === "tool_install") {
316
+ run(ws, msg, function () {
317
+ installForMate(userId, {
318
+ manifest: msg.manifest,
319
+ logicSource: msg.logicSource,
320
+ uiTree: msg.uiTree,
321
+ });
322
+ });
323
+ return true;
324
+ }
325
+ if (msg.type === "tool_remove") {
326
+ run(ws, msg, function () {
327
+ removeForMate(userId, msg.toolId);
328
+ });
329
+ return true;
330
+ }
331
+ if (msg.type === "tool_storage_op") {
332
+ Promise.resolve().then(function () {
333
+ return storageOperation(ctx, msg);
334
+ }).then(function (data) {
335
+ send(ws, { type: "tool_storage_result", toolId: msg.toolId, op: msg.op, seq: msg.seq, data: data });
336
+ }).catch(function (error) {
337
+ send(ws, {
338
+ type: "tool_storage_result",
339
+ toolId: msg.toolId,
340
+ op: msg.op,
341
+ seq: msg.seq,
342
+ error: error && error.message ? error.message : "Tool storage operation failed.",
343
+ });
344
+ });
345
+ return true;
346
+ }
347
+ if (msg.type === "tool_llm_op") {
348
+ run(ws, msg, async function () {
349
+ var data = await llmOperation(ws, ctx, userId, msg);
350
+ send(ws, { type: "tool_llm_result", toolId: msg.toolId, requestId: msg.requestId, data: data });
351
+ });
352
+ return true;
353
+ }
354
+ if (msg.type === "tool_llm_config_get") {
355
+ run(ws, msg, async function () {
356
+ var alias = toolLlm.MODEL_ALIASES.indexOf(msg.alias) !== -1 ? msg.alias : "standard";
357
+ var state = await llmConfiguration(ws, alias);
358
+ send(ws, Object.assign({ type: "tool_llm_config_state", requestId: msg.requestId || null, alias: alias }, state));
359
+ });
360
+ return true;
361
+ }
362
+ return false;
363
+ }
364
+
365
+ return {
366
+ handleMessage: handleMessage,
367
+ installedManifests: installedManifests,
368
+ controlForMate: controlForMate,
369
+ installForMate: installForMate,
370
+ sourceForMate: sourceForMate,
371
+ updateForMate: updateForMate,
372
+ setMateAccess: setMateAccess,
373
+ removeForMate: removeForMate,
374
+ };
375
+ }
376
+
377
+ module.exports = { attachTools: attachTools };
package/lib/server.js CHANGED
@@ -13,10 +13,16 @@ 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");
20
26
  var serverEmail = require("./server-email");
21
27
  var serverGlobalWs = require("./server-global-ws");
22
28
  var { parseWsRequestUrl } = require("./ws-request");
@@ -291,6 +297,27 @@ function createServer(opts) {
291
297
 
292
298
  // --- Project registry ---
293
299
  var projects = new Map(); // slug → projectContext
300
+ var assignmentService = attachWorkspaceAssignmentService({
301
+ storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
302
+ getProjects: function () { return projects; },
303
+ isMultiUser: function () { return users.isMultiUser(); },
304
+ sessionRef: workspaceQueryModule.sessionRef,
305
+ });
306
+ var workspaceQueryService = attachWorkspaceQueryService({
307
+ getProjects: function () { return projects; },
308
+ isMultiUser: function () { return users.isMultiUser(); },
309
+ canAccessProject: function (userId, status) {
310
+ return users.canAccessProject(userId, {
311
+ ownerId: status && status.projectOwnerId || null,
312
+ visibility: status && status.visibility || "public",
313
+ allowedUsers: status && status.allowedUsers || [],
314
+ });
315
+ },
316
+ resolveMate: function (userId, mateId) {
317
+ return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
318
+ },
319
+ assignmentService: assignmentService,
320
+ });
294
321
 
295
322
  // --- Admin module ---
296
323
  var admin = serverAdmin.attachAdmin({
@@ -355,7 +382,7 @@ function createServer(opts) {
355
382
  // origin. External sites still cannot frame Clay; frame-ancestors below
356
383
  // is the modern equivalent for browsers that honor CSP.
357
384
  "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;",
385
+ "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
386
  };
360
387
  if (tlsOptions) {
361
388
  securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
@@ -655,7 +682,7 @@ function createServer(opts) {
655
682
  // --- Skills routes (delegated to server-skills) ---
656
683
  if (skills.handleRequest(req, res, fullUrl)) return;
657
684
 
658
- // Root path — redirect to first accessible project
685
+ // Root path — render home while keeping the last accessible project connected.
659
686
  if (fullUrl === "/" && req.method === "GET") {
660
687
  if (!isRequestAuthed(req)) {
661
688
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -665,9 +692,23 @@ function createServer(opts) {
665
692
  if (projects.size > 0) {
666
693
  var targetSlug = null;
667
694
  var reqUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
695
+ var homePreference = typeof users.getHomeSurfacePreference === "function"
696
+ ? users.getHomeSurfacePreference(reqUser ? reqUser.id : "default")
697
+ : null;
698
+ var preferredContextSlug = homePreference && homePreference.projectSlug;
699
+ if (preferredContextSlug && projects.has(preferredContextSlug)) {
700
+ if (reqUser && onGetProjectAccess) {
701
+ var preferredAccess = onGetProjectAccess(preferredContextSlug);
702
+ if (preferredAccess && !preferredAccess.error && users.canAccessProject(reqUser.id, preferredAccess)) {
703
+ targetSlug = preferredContextSlug;
704
+ }
705
+ } else {
706
+ targetSlug = preferredContextSlug;
707
+ }
708
+ }
668
709
  // Check for last-visited project cookie
669
710
  var lastProject = parseCookies(req)["clay_last_project"];
670
- if (lastProject && projects.has(lastProject)) {
711
+ if (!targetSlug && lastProject && projects.has(lastProject)) {
671
712
  if (reqUser && onGetProjectAccess) {
672
713
  var lpAccess = onGetProjectAccess(lastProject);
673
714
  if (lpAccess && !lpAccess.error && users.canAccessProject(reqUser.id, lpAccess)) {
@@ -692,8 +733,15 @@ function createServer(opts) {
692
733
  });
693
734
  }
694
735
  if (targetSlug) {
695
- res.writeHead(302, { "Location": "/p/" + targetSlug + "/" });
696
- res.end();
736
+ try {
737
+ var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
738
+ homeHtml = homeHtml.replace("<body>", '<body data-home-project-slug="' + targetSlug + '">');
739
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
740
+ res.end(homeHtml);
741
+ } catch (e) {
742
+ res.writeHead(500, { "Content-Type": "text/plain" });
743
+ res.end("App shell missing.");
744
+ }
697
745
  return;
698
746
  }
699
747
  }
@@ -1028,10 +1076,9 @@ function createServer(opts) {
1028
1076
  unreadMap[wsSlug] = 0;
1029
1077
  }
1030
1078
  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.
1079
+ // Tear down the embedded mate chat subscription on socket close.
1033
1080
  ws.on("close", function () {
1034
- try { clayHomeHandler.handleDisconnection(ws); } catch (e) {}
1081
+ try { homeChatHandler.handleDisconnection(ws); } catch (e) {}
1035
1082
  });
1036
1083
  });
1037
1084
  });
@@ -1119,8 +1166,20 @@ function createServer(opts) {
1119
1166
  projectOwnerId: projectOwnerId || null,
1120
1167
  worktreeMeta: worktreeMeta || null,
1121
1168
  isMate: extra.isMate || false,
1169
+ mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
1122
1170
  mateDisplayName: extra.mateDisplayName || "",
1123
1171
  isHostAgent: !!extra.isHostAgent,
1172
+ createReadyMate: function (ws, userId, definition) {
1173
+ if (!mateHandler || typeof mateHandler.createReadyMateFromInterview !== "function") throw new Error("Mate creation is unavailable.");
1174
+ return mateHandler.createReadyMateFromInterview(ws, userId, definition);
1175
+ },
1176
+ workspaceQueryService: workspaceQueryService,
1177
+ getProjectAccess: function () {
1178
+ return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
1179
+ },
1180
+ shouldSuppressResponseNotification: function (session) {
1181
+ return shouldSuppressHomeResponse(projects, slug, session);
1182
+ },
1124
1183
  pushModule: pushModule,
1125
1184
  debug: debug,
1126
1185
  dangerouslySkipPermissions: dangerouslySkipPermissions,
@@ -1143,52 +1202,6 @@ function createServer(opts) {
1143
1202
  });
1144
1203
  return list;
1145
1204
  },
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
1205
  getHubSchedules: function () {
1193
1206
  var allSchedules = [];
1194
1207
  projects.forEach(function (ctx, s) {
@@ -1277,6 +1290,29 @@ function createServer(opts) {
1277
1290
  onSetProjectDefaultMode: onSetProjectDefaultMode,
1278
1291
  onGetProjectLastVendor: onGetProjectLastVendor,
1279
1292
  onSetProjectLastVendor: onSetProjectLastVendor,
1293
+ listControllableTools: function (userId) {
1294
+ return toolsHandler ? toolsHandler.installedManifests(userId) : [];
1295
+ },
1296
+ controlToolForMate: function (userId, mateId, toolId, kind, payload) {
1297
+ if (!toolsHandler) return Promise.reject(new Error("Tool control is unavailable."));
1298
+ return toolsHandler.controlForMate(userId, mateId, toolId, kind, payload);
1299
+ },
1300
+ installToolForMate: function (userId, input) {
1301
+ if (!toolsHandler) return Promise.reject(new Error("Tool installation is unavailable."));
1302
+ return toolsHandler.installForMate(userId, input);
1303
+ },
1304
+ sourceToolForMate: function (userId, toolId) {
1305
+ if (!toolsHandler) return Promise.reject(new Error("Capsule source access is unavailable."));
1306
+ return toolsHandler.sourceForMate(userId, toolId);
1307
+ },
1308
+ updateToolForMate: function (userId, toolId, input) {
1309
+ if (!toolsHandler) return Promise.reject(new Error("Capsule source updates are unavailable."));
1310
+ return toolsHandler.updateForMate(userId, toolId, input);
1311
+ },
1312
+ removeToolForMate: function (userId, toolId) {
1313
+ if (!toolsHandler) return Promise.reject(new Error("Tool removal is unavailable."));
1314
+ return toolsHandler.removeForMate(userId, toolId);
1315
+ },
1280
1316
  onGetProjectMcpServers: onGetProjectMcpServers,
1281
1317
  onSetProjectMcpServers: onSetProjectMcpServers,
1282
1318
  onGetDaemonConfig: onGetDaemonConfig,
@@ -1295,6 +1331,7 @@ function createServer(opts) {
1295
1331
  isUserOnline: isUserOnline,
1296
1332
  });
1297
1333
  projects.set(slug, ctx);
1334
+ assignmentService.reconcileProject(slug);
1298
1335
  // ctx.warmup() is now deferred to the first websocket connection into
1299
1336
  // this project (see project-connection.js handleConnection). Warming
1300
1337
  // every project at startup spawned a CodexAppServer and an mcp-bridge
@@ -1318,12 +1355,24 @@ function createServer(opts) {
1318
1355
  // --- Email account handler (per-user email account management) ---
1319
1356
  var emailHandler = serverEmail.attachEmail({ users: users });
1320
1357
 
1321
- // --- Clay home chat handler (host agent chat embedded in home hub) ---
1322
- var clayHomeHandler = serverClayHome.attachClayHome({
1358
+ // --- Embedded mate chat handler ---
1359
+ var homeChatHandler = serverHomeChat.attachHomeChat({
1323
1360
  users: users,
1324
1361
  mates: mates,
1325
1362
  projects: projects,
1326
1363
  addProject: addProject,
1364
+ workspaceQueryService: workspaceQueryService,
1365
+ });
1366
+
1367
+ var homePreferencesHandler = serverHomePreferences.attachHomePreferences({
1368
+ users: users,
1369
+ projects: projects,
1370
+ });
1371
+
1372
+ // --- User-installed home tools handler ---
1373
+ var toolsHandler = serverTools.attachTools({
1374
+ users: users,
1375
+ projects: projects,
1327
1376
  });
1328
1377
 
1329
1378
  // --- Mate handler ---
@@ -1333,11 +1382,14 @@ function createServer(opts) {
1333
1382
  if (mateHandler) mateHandler.scheduleRegistryRefresh();
1334
1383
  }
1335
1384
 
1336
- function handleDmMessage(ws, msg) {
1385
+ function handleDmMessage(ws, msg, routedProjectSlug) {
1386
+ if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return;
1337
1387
  if (dmHandler.handleMessage(ws, msg)) return;
1338
1388
  if (mateHandler && mateHandler.handleMessage(ws, msg)) return;
1339
1389
  if (emailHandler.handleMessage(ws, msg)) return;
1340
- if (clayHomeHandler.handleMessage(ws, msg)) return;
1390
+ if (homeChatHandler.handleMessage(ws, msg)) return;
1391
+ if (homePreferencesHandler.handleMessage(ws, msg)) return;
1392
+ if (toolsHandler.handleMessage(ws, msg)) return;
1341
1393
  }
1342
1394
 
1343
1395
  function removeProject(slug) {