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,286 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+ var config = require("./config");
4
+ var toolUiSpec = require("./tool-ui-spec");
5
+ var toolCapsuleSource = require("./tool-capsule-source");
6
+
7
+ var CAPSULES_ROOT = path.join(__dirname, "capsules");
8
+ var SEED_MARKER = ".capsules-v7";
9
+ var REMOVED_BUILTIN_IDS = ["board", "translator", "scratchpad"];
10
+ var UI_NODE_TYPES = toolUiSpec.UI_NODE_TYPES;
11
+ var DISCOVERY_FIELD_MAX_LENGTH = 240;
12
+
13
+ function resolveToolsRoot(ctx) {
14
+ if (ctx && ctx.linuxUser) return path.join("/home", ctx.linuxUser, ".clay", "tools");
15
+ if (ctx && ctx.multiUser && ctx.userId) return path.join(config.CONFIG_DIR, "tools", ctx.userId);
16
+ return path.join(config.CONFIG_DIR, "tools");
17
+ }
18
+
19
+ function validateToolId(id) {
20
+ if (typeof id !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)) {
21
+ throw new Error("Tool ID must be a lowercase slug.");
22
+ }
23
+ }
24
+
25
+ function isTrustedServerRuntime(manifest) {
26
+ if (!manifest || manifest.runtime !== "server") return false;
27
+ var sourceManifest = path.join(CAPSULES_ROOT, manifest.id || "", "manifest.json");
28
+ try {
29
+ var shipped = JSON.parse(fs.readFileSync(sourceManifest, "utf8"));
30
+ return shipped.id === manifest.id && shipped.runtime === "server";
31
+ } catch (e) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ function filesMatch(first, second) {
37
+ try {
38
+ return fs.readFileSync(first).equals(fs.readFileSync(second));
39
+ } catch (e) {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ function manifestWithoutHydratedMetadata(manifest) {
45
+ var result = Object.assign({}, manifest);
46
+ delete result.modelAlias;
47
+ delete result.description;
48
+ delete result.useWhen;
49
+ return result;
50
+ }
51
+
52
+ function hydrateShippedManifestMetadata(directory, manifest) {
53
+ if (!manifest) return manifest;
54
+ var shippedDirectory = path.join(CAPSULES_ROOT, manifest.id || "");
55
+ var shippedManifestPath = path.join(shippedDirectory, "manifest.json");
56
+ try {
57
+ var shipped = JSON.parse(fs.readFileSync(shippedManifestPath, "utf8"));
58
+ if (shipped.id !== manifest.id) return manifest;
59
+ if (JSON.stringify(manifestWithoutHydratedMetadata(manifest)) !== JSON.stringify(manifestWithoutHydratedMetadata(shipped))) return manifest;
60
+ if (!filesMatch(path.join(directory, "ui.json"), path.join(shippedDirectory, "ui.json"))) return manifest;
61
+ if ((manifest.runtime || "worker") === "worker" && !filesMatch(path.join(directory, "logic.js"), path.join(shippedDirectory, "logic.js"))) return manifest;
62
+ var hydrated = Object.assign({}, manifest);
63
+ var fields = ["modelAlias", "description", "useWhen"];
64
+ for (var i = 0; i < fields.length; i++) {
65
+ if (hydrated[fields[i]] === undefined && shipped[fields[i]] !== undefined) hydrated[fields[i]] = shipped[fields[i]];
66
+ }
67
+ return hydrated;
68
+ } catch (e) {
69
+ return manifest;
70
+ }
71
+ }
72
+
73
+ function validateDiscoveryField(manifest, field, label) {
74
+ if (manifest[field] === undefined) return;
75
+ if (typeof manifest[field] !== "string" || !manifest[field].trim()) {
76
+ throw new Error("Tool " + label + " must be non-empty text.");
77
+ }
78
+ if (manifest[field] !== manifest[field].trim() || /[\u0000-\u001f\u007f]/.test(manifest[field])) {
79
+ throw new Error("Tool " + label + " must be a single trimmed line.");
80
+ }
81
+ if (manifest[field].length > DISCOVERY_FIELD_MAX_LENGTH) {
82
+ throw new Error("Tool " + label + " must be " + DISCOVERY_FIELD_MAX_LENGTH + " characters or fewer.");
83
+ }
84
+ }
85
+
86
+ function validateManifest(manifest, opts) {
87
+ opts = opts || {};
88
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
89
+ throw new Error("Tool manifest is required.");
90
+ }
91
+ validateToolId(manifest.id);
92
+ if (typeof manifest.name !== "string" || !manifest.name.trim()) {
93
+ throw new Error("Tool name is required.");
94
+ }
95
+ if (manifest.lucideIcon !== undefined && (typeof manifest.lucideIcon !== "string" || !/^[a-z0-9-]+$/.test(manifest.lucideIcon))) {
96
+ throw new Error("Tool icon must be a lowercase Lucide icon name.");
97
+ }
98
+ if (manifest.skills !== undefined && typeof manifest.skills !== "string") {
99
+ throw new Error("Tool skills must be markdown text.");
100
+ }
101
+ validateDiscoveryField(manifest, "description", "description");
102
+ validateDiscoveryField(manifest, "useWhen", "useWhen");
103
+ if (manifest.permissions !== undefined) {
104
+ if (!Array.isArray(manifest.permissions)) throw new Error("Tool permissions must be an array.");
105
+ for (var pi = 0; pi < manifest.permissions.length; pi++) {
106
+ if (manifest.permissions[pi] !== "llm") throw new Error("Unknown tool permission '" + manifest.permissions[pi] + "'.");
107
+ }
108
+ }
109
+ if (manifest.modelAlias !== undefined && ["fast", "standard", "deep"].indexOf(manifest.modelAlias) === -1) {
110
+ throw new Error("Tool modelAlias must be fast, standard, or deep.");
111
+ }
112
+ var runtime = manifest.runtime || "worker";
113
+ if (runtime !== "worker" && runtime !== "server") {
114
+ throw new Error("Tool runtime must be 'worker' or 'server'.");
115
+ }
116
+ if (runtime === "server" && !opts.allowServerRuntime) {
117
+ throw new Error("Server-runtime capsules cannot be installed over WebSocket.");
118
+ }
119
+ if (runtime === "server" && !isTrustedServerRuntime(manifest)) {
120
+ throw new Error("Server runtime is reserved for shipped built-in capsules.");
121
+ }
122
+ }
123
+
124
+ var validateUiNode = toolUiSpec.validateUiNode;
125
+ var validateUiTreeForManifest = toolUiSpec.validateUiTreeForManifest;
126
+
127
+ function toolDirectory(ctx, id) {
128
+ validateToolId(id);
129
+ return path.join(resolveToolsRoot(ctx), id);
130
+ }
131
+
132
+ function seedBuiltInCapsules(ctx) {
133
+ var root = resolveToolsRoot(ctx);
134
+ var marker = path.join(root, SEED_MARKER);
135
+ if (fs.existsSync(marker)) return;
136
+ fs.mkdirSync(root, { recursive: true });
137
+ for (var ri = 0; ri < REMOVED_BUILTIN_IDS.length; ri++) {
138
+ fs.rmSync(path.join(root, REMOVED_BUILTIN_IDS[ri]), { recursive: true, force: true });
139
+ }
140
+ // Clay ships no built-in Capsules any more, so lib/capsules/ is absent from a
141
+ // fresh checkout and from the npm tarball (neither git nor npm carries an
142
+ // empty directory). Seeding still owns the removed-built-in migration above
143
+ // and the marker below, so an absent root means "nothing to copy" rather than
144
+ // a failure. Anything other than a missing root is still a real error.
145
+ var entries = [];
146
+ try {
147
+ entries = fs.readdirSync(CAPSULES_ROOT, { withFileTypes: true });
148
+ } catch (e) {
149
+ if (e.code !== "ENOENT") throw e;
150
+ }
151
+ var seeded = [];
152
+ for (var i = 0; i < entries.length; i++) {
153
+ if (!entries[i].isDirectory()) continue;
154
+ var destination = path.join(root, entries[i].name);
155
+ if (!fs.existsSync(destination)) {
156
+ fs.cpSync(path.join(CAPSULES_ROOT, entries[i].name), destination, { recursive: true });
157
+ seeded.push(entries[i].name);
158
+ } else {
159
+ var sourceFiles = fs.readdirSync(path.join(CAPSULES_ROOT, entries[i].name));
160
+ for (var fi = 0; fi < sourceFiles.length; fi++) {
161
+ var sourceFile = path.join(CAPSULES_ROOT, entries[i].name, sourceFiles[fi]);
162
+ var destinationFile = path.join(destination, sourceFiles[fi]);
163
+ if (!fs.existsSync(destinationFile)) fs.copyFileSync(sourceFile, destinationFile);
164
+ }
165
+ }
166
+ }
167
+ fs.writeFileSync(marker, seeded.join("\n") + "\n", "utf8");
168
+ }
169
+
170
+ function getTool(ctx, id) {
171
+ var directory = toolDirectory(ctx, id);
172
+ var manifestPath = path.join(directory, "manifest.json");
173
+ var logicPath = path.join(directory, "logic.js");
174
+ var uiPath = path.join(directory, "ui.json");
175
+ if (!fs.existsSync(manifestPath) || !fs.existsSync(uiPath)) return null;
176
+ var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
177
+ var uiTree = JSON.parse(fs.readFileSync(uiPath, "utf8"));
178
+ validateManifest(manifest, { allowServerRuntime: true });
179
+ if (manifest.id !== id) throw new Error("Capsule folder name must match manifest ID.");
180
+ validateUiTreeForManifest(uiTree, manifest);
181
+ var runtime = manifest.runtime || "worker";
182
+ if (runtime === "worker" && !fs.existsSync(logicPath)) throw new Error("Worker capsule logic.js is required.");
183
+ manifest = hydrateShippedManifestMetadata(directory, manifest);
184
+ return {
185
+ manifest: Object.assign({}, manifest, { runtime: runtime }),
186
+ logicSource: runtime === "worker" ? fs.readFileSync(logicPath, "utf8") : null,
187
+ uiTree: uiTree,
188
+ metadata: toolCapsuleSource.sanitizedMetadata(resolveToolsRoot(ctx), id),
189
+ };
190
+ }
191
+
192
+ function listTools(ctx) {
193
+ seedBuiltInCapsules(ctx);
194
+ var root = resolveToolsRoot(ctx);
195
+ var entries = fs.readdirSync(root, { withFileTypes: true });
196
+ var manifests = [];
197
+ for (var i = 0; i < entries.length; i++) {
198
+ if (!entries[i].isDirectory()) continue;
199
+ if (entries[i].name.charAt(0) === ".") continue;
200
+ try {
201
+ var tool = getTool(ctx, entries[i].name);
202
+ if (tool) manifests.push(tool.manifest);
203
+ else manifests.push({ id: entries[i].name, error: "Capsule requires manifest.json and ui.json." });
204
+ } catch (e) {
205
+ manifests.push({ id: entries[i].name, error: e.message });
206
+ }
207
+ }
208
+ manifests.sort(function (a, b) { return (a.name || a.id).localeCompare(b.name || b.id); });
209
+ return manifests;
210
+ }
211
+
212
+ function installTool(ctx, input) {
213
+ if (!input || typeof input !== "object") throw new Error("Tool installation payload is required.");
214
+ validateManifest(input.manifest, { allowServerRuntime: false });
215
+ if (typeof input.logicSource !== "string" || !input.logicSource.trim()) {
216
+ throw new Error("Tool logic source is required.");
217
+ }
218
+ validateUiTreeForManifest(input.uiTree, input.manifest);
219
+ var manifest = Object.assign({}, input.manifest, { name: input.manifest.name.trim(), runtime: "worker" });
220
+ var directory = toolDirectory(ctx, manifest.id);
221
+ if (fs.existsSync(directory)) throw new Error("A Capsule with this ID already exists. Read its source and use the update tool instead.");
222
+ fs.mkdirSync(resolveToolsRoot(ctx), { recursive: true });
223
+ fs.mkdirSync(directory);
224
+ try {
225
+ fs.writeFileSync(path.join(directory, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
226
+ fs.writeFileSync(path.join(directory, "logic.js"), input.logicSource, "utf8");
227
+ fs.writeFileSync(path.join(directory, "ui.json"), JSON.stringify(input.uiTree, null, 2) + "\n", "utf8");
228
+ } catch (error) {
229
+ fs.rmSync(directory, { recursive: true, force: true });
230
+ throw error;
231
+ }
232
+ return getTool(ctx, manifest.id);
233
+ }
234
+
235
+ function getToolSource(ctx, id) {
236
+ var tool = getTool(ctx, id);
237
+ if (!tool) throw new Error("Tool not found.");
238
+ return toolCapsuleSource.readSource(toolDirectory(ctx, id));
239
+ }
240
+
241
+ function getToolMetadata(ctx, id) {
242
+ if (!getTool(ctx, id)) throw new Error("Tool not found.");
243
+ return toolCapsuleSource.sanitizedMetadata(resolveToolsRoot(ctx), id);
244
+ }
245
+
246
+ function setMateEditingAllowed(ctx, id, allowed) {
247
+ if (!getTool(ctx, id)) throw new Error("Tool not found.");
248
+ return toolCapsuleSource.setMateEditingAllowed(resolveToolsRoot(ctx), id, allowed);
249
+ }
250
+
251
+ function updateTool(ctx, id, input) {
252
+ var tool = getTool(ctx, id);
253
+ if (!tool) throw new Error("Tool not found.");
254
+ toolCapsuleSource.updateSource(toolDirectory(ctx, id), input, {
255
+ validateManifest: validateManifest,
256
+ validateUiNode: function (uiTree) { return validateUiTreeForManifest(uiTree, input.manifest); },
257
+ });
258
+ return getTool(ctx, id);
259
+ }
260
+
261
+ function removeTool(ctx, id) {
262
+ var directory = toolDirectory(ctx, id);
263
+ if (!fs.existsSync(directory)) return false;
264
+ fs.rmSync(directory, { recursive: true, force: true });
265
+ toolCapsuleSource.removeMetadata(resolveToolsRoot(ctx), id);
266
+ return true;
267
+ }
268
+
269
+ module.exports = {
270
+ CAPSULES_ROOT: CAPSULES_ROOT,
271
+ UI_NODE_TYPES: UI_NODE_TYPES,
272
+ resolveToolsRoot: resolveToolsRoot,
273
+ validateToolId: validateToolId,
274
+ validateManifest: validateManifest,
275
+ validateUiNode: validateUiNode,
276
+ validateUiTreeForManifest: validateUiTreeForManifest,
277
+ seedBuiltInCapsules: seedBuiltInCapsules,
278
+ listTools: listTools,
279
+ getTool: getTool,
280
+ installTool: installTool,
281
+ getToolSource: getToolSource,
282
+ getToolMetadata: getToolMetadata,
283
+ setMateEditingAllowed: setMateEditingAllowed,
284
+ updateTool: updateTool,
285
+ removeTool: removeTool,
286
+ };
@@ -0,0 +1,77 @@
1
+ // Per-user persistence for the home tool dock.
2
+
3
+ function attachHomeDockPreferences(deps) {
4
+ var loadUsers = deps.loadUsers;
5
+ var saveUsers = deps.saveUsers;
6
+
7
+ function defaults() {
8
+ return { dockOpen: false, dockFocus: false, dockWidth: null, activeToolId: null };
9
+ }
10
+
11
+ function normalize(value) {
12
+ var source = value && typeof value === "object" ? value : {};
13
+ var width = typeof source.dockWidth === "number" && Number.isFinite(source.dockWidth)
14
+ ? Math.max(420, Math.min(1600, Math.round(source.dockWidth)))
15
+ : null;
16
+ var toolId = typeof source.activeToolId === "string" && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(source.activeToolId)
17
+ ? source.activeToolId
18
+ : null;
19
+ return {
20
+ dockOpen: source.dockOpen === true,
21
+ dockFocus: source.dockOpen === true && source.dockFocus === true,
22
+ dockWidth: width,
23
+ activeToolId: toolId,
24
+ };
25
+ }
26
+
27
+ function loadSingleUserPreference() {
28
+ try {
29
+ var config = require("./config");
30
+ var current = config.loadConfig() || {};
31
+ return normalize(current.homeDockPreference);
32
+ } catch (e) {
33
+ return defaults();
34
+ }
35
+ }
36
+
37
+ function saveSingleUserPreference(preference) {
38
+ try {
39
+ var config = require("./config");
40
+ var current = config.loadConfig() || {};
41
+ config.saveConfig(Object.assign({}, current, { homeDockPreference: preference }));
42
+ return { ok: true, preference: preference };
43
+ } catch (e) {
44
+ return { error: "Unable to save home dock preference" };
45
+ }
46
+ }
47
+
48
+ function getHomeDockPreference(userId) {
49
+ var data = loadUsers();
50
+ for (var i = 0; i < data.users.length; i++) {
51
+ if (data.users[i].id === userId) return normalize(data.users[i].homeDockPreference);
52
+ }
53
+ if (userId === "default") return loadSingleUserPreference();
54
+ return defaults();
55
+ }
56
+
57
+ function setHomeDockPreference(userId, patch) {
58
+ var current = getHomeDockPreference(userId);
59
+ var next = normalize(Object.assign({}, current, patch || {}));
60
+ var data = loadUsers();
61
+ for (var i = 0; i < data.users.length; i++) {
62
+ if (data.users[i].id !== userId) continue;
63
+ data.users[i].homeDockPreference = next;
64
+ saveUsers(data);
65
+ return { ok: true, preference: next };
66
+ }
67
+ if (userId === "default") return saveSingleUserPreference(next);
68
+ return { error: "User not found" };
69
+ }
70
+
71
+ return {
72
+ getHomeDockPreference: getHomeDockPreference,
73
+ setHomeDockPreference: setHomeDockPreference,
74
+ };
75
+ }
76
+
77
+ module.exports = { attachHomeDockPreferences: attachHomeDockPreferences };
@@ -0,0 +1,99 @@
1
+ // Per-user persistence for durable Home conversation selections.
2
+
3
+ function attachHomeSurfacePreferences(deps) {
4
+ var loadUsers = deps.loadUsers;
5
+ var saveUsers = deps.saveUsers;
6
+
7
+ function defaults() {
8
+ return { surface: null, projectSlug: null, activeMateId: null, activeSessionByMate: {}, sidebarCollapsed: false, chatScope: "all", subSurface: "chat" };
9
+ }
10
+
11
+ function validId(value) {
12
+ return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(value);
13
+ }
14
+
15
+ function validSessionReference(value) {
16
+ return typeof value === "string" && value.length > 0 && value.length <= 256 && !/[\x00-\x1f]/.test(value);
17
+ }
18
+
19
+ function validProjectSlug(value) {
20
+ return typeof value === "string" && /^[a-z0-9][a-z0-9_-]{0,127}$/.test(value);
21
+ }
22
+
23
+ function normalize(value) {
24
+ var source = value && typeof value === "object" ? value : {};
25
+ var sessions = source.activeSessionByMate && typeof source.activeSessionByMate === "object" && !Array.isArray(source.activeSessionByMate)
26
+ ? source.activeSessionByMate
27
+ : {};
28
+ var normalizedSessions = {};
29
+ var mateIds = Object.keys(sessions);
30
+ for (var i = 0; i < mateIds.length; i++) {
31
+ if (!validId(mateIds[i]) || !validSessionReference(sessions[mateIds[i]])) continue;
32
+ normalizedSessions[mateIds[i]] = sessions[mateIds[i]];
33
+ }
34
+ return {
35
+ surface: source.surface === "home" || source.surface === "project" ? source.surface : null,
36
+ projectSlug: validProjectSlug(source.projectSlug) ? source.projectSlug : null,
37
+ activeMateId: validId(source.activeMateId) ? source.activeMateId : null,
38
+ activeSessionByMate: normalizedSessions,
39
+ sidebarCollapsed: source.sidebarCollapsed === true,
40
+ chatScope: source.chatScope === "current" ? "current" : "all",
41
+ subSurface: source.subSurface === "debates" ? "debates" : "chat",
42
+ };
43
+ }
44
+
45
+ function loadSingleUserPreference() {
46
+ try {
47
+ var config = require("./config");
48
+ var current = config.loadConfig() || {};
49
+ return normalize(current.homeSurfacePreference);
50
+ } catch (e) {
51
+ return defaults();
52
+ }
53
+ }
54
+
55
+ function saveSingleUserPreference(preference) {
56
+ try {
57
+ var config = require("./config");
58
+ var current = config.loadConfig() || {};
59
+ config.saveConfig(Object.assign({}, current, { homeSurfacePreference: preference }));
60
+ return { ok: true, preference: preference };
61
+ } catch (e) {
62
+ return { error: "Unable to save Home surface preference" };
63
+ }
64
+ }
65
+
66
+ function getHomeSurfacePreference(userId) {
67
+ var data = loadUsers();
68
+ for (var i = 0; i < data.users.length; i++) {
69
+ if (data.users[i].id === userId) return normalize(data.users[i].homeSurfacePreference);
70
+ }
71
+ if (userId === "default") return loadSingleUserPreference();
72
+ return defaults();
73
+ }
74
+
75
+ function setHomeSurfacePreference(userId, patch) {
76
+ var current = getHomeSurfacePreference(userId);
77
+ var merged = Object.assign({}, current, patch || {});
78
+ if (patch && patch.activeSessionByMate && typeof patch.activeSessionByMate === "object") {
79
+ merged.activeSessionByMate = Object.assign({}, current.activeSessionByMate, patch.activeSessionByMate);
80
+ }
81
+ var next = normalize(merged);
82
+ var data = loadUsers();
83
+ for (var i = 0; i < data.users.length; i++) {
84
+ if (data.users[i].id !== userId) continue;
85
+ data.users[i].homeSurfacePreference = next;
86
+ saveUsers(data);
87
+ return { ok: true, preference: next };
88
+ }
89
+ if (userId === "default") return saveSingleUserPreference(next);
90
+ return { error: "User not found" };
91
+ }
92
+
93
+ return {
94
+ getHomeSurfacePreference: getHomeSurfacePreference,
95
+ setHomeSurfacePreference: setHomeSurfacePreference,
96
+ };
97
+ }
98
+
99
+ module.exports = { attachHomeSurfacePreferences: attachHomeSurfacePreferences };
@@ -334,14 +334,11 @@ function attachPreferences(deps) {
334
334
  return { error: "User not found" };
335
335
  }
336
336
 
337
- // --- Per-user Mates UI toggle ---
337
+ // --- Per-user project Mate DM shortcut toggle ---
338
338
  //
339
- // When disabled, the entire Mates surface (sidebar avatars, DM "Create a
340
- // Mate" entry, home-hub mates strip) is hidden for this user; multi-user
341
- // and the rest of the sidebar are unaffected. Default is OFF: Mates is
342
- // opt-in, not opt-out. Stored as `matesEnabled`; only the literal `true`
343
- // enables it, so brand-new users (no field set) get the default-off
344
- // experience and must turn Mates on explicitly in settings.
339
+ // `matesEnabled` is retained as the persisted field for compatibility.
340
+ // It now controls only project-local Mate DM shortcuts. The broader Mate
341
+ // capability surface is always available. Default remains off.
345
342
 
346
343
  function getMatesEnabled(userId) {
347
344
  var data = loadUsers();
package/lib/users.js CHANGED
@@ -6,6 +6,8 @@ var { CONFIG_DIR } = require("./config");
6
6
  var { attachAuth } = require("./users-auth");
7
7
  var { DEFAULT_PERMISSIONS, ALL_PERMISSIONS, attachPermissions } = require("./users-permissions");
8
8
  var { attachPreferences } = require("./users-preferences");
9
+ var { attachHomeDockPreferences } = require("./users-home-dock-preferences");
10
+ var { attachHomeSurfacePreferences } = require("./users-home-surface-preferences");
9
11
 
10
12
  var USERS_FILE = path.join(CONFIG_DIR, "users.json");
11
13
 
@@ -394,6 +396,8 @@ function removeExpiredInvites() {
394
396
  var auth = attachAuth({ loadUsers: loadUsers, saveUsers: saveUsers, findAdmin: findAdmin });
395
397
  var permissions = attachPermissions({ loadUsers: loadUsers, saveUsers: saveUsers, findUserById: findUserById });
396
398
  var preferences = attachPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
399
+ var homeDockPreferences = attachHomeDockPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
400
+ var homeSurfacePreferences = attachHomeSurfacePreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
397
401
 
398
402
  // Alias auth functions
399
403
  var isMultiUser = auth.isMultiUser;
@@ -442,6 +446,10 @@ var markWhatsNewSeen = preferences.markWhatsNewSeen;
442
446
  var getTerminalFont = preferences.getTerminalFont;
443
447
  var setTerminalFont = preferences.setTerminalFont;
444
448
  var setMateOnboarded = preferences.setMateOnboarded;
449
+ var getHomeDockPreference = homeDockPreferences.getHomeDockPreference;
450
+ var setHomeDockPreference = homeDockPreferences.setHomeDockPreference;
451
+ var getHomeSurfacePreference = homeSurfacePreferences.getHomeSurfacePreference;
452
+ var setHomeSurfacePreference = homeSurfacePreferences.setHomeSurfacePreference;
445
453
 
446
454
  module.exports = {
447
455
  USERS_FILE: USERS_FILE,
@@ -511,6 +519,10 @@ module.exports = {
511
519
  markWhatsNewSeen: markWhatsNewSeen,
512
520
  getTerminalFont: getTerminalFont,
513
521
  setTerminalFont: setTerminalFont,
522
+ getHomeDockPreference: getHomeDockPreference,
523
+ setHomeDockPreference: setHomeDockPreference,
524
+ getHomeSurfacePreference: getHomeSurfacePreference,
525
+ setHomeSurfacePreference: setHomeSurfacePreference,
514
526
  getDeletedBuiltinKeys: getDeletedBuiltinKeys,
515
527
  addDeletedBuiltinKey: addDeletedBuiltinKey,
516
528
  removeDeletedBuiltinKey: removeDeletedBuiltinKey,
@@ -70,13 +70,13 @@ var ENTRIES = [
70
70
  title: "GUI is back as the default",
71
71
  publishedAt: "2026-07-01",
72
72
  image: null,
73
- summary: "Anthropic reversed the June 15 billing split, so SDK usage is no longer billed separately. Clay opens Claude sessions in the GUI chat again by default. Mates is now off by default as it winds down.",
73
+ summary: "Anthropic reversed the June 15 billing split, so SDK usage is no longer billed separately. Clay opens Claude sessions in the GUI chat again by default. Project Mate DM shortcuts are hidden by default.",
74
74
  body: '' +
75
75
  '<p><strong>Good news: Anthropic walked back the June 15 billing change.</strong> The planned Interactive vs. Programmatic split (which would have charged Claude Agent SDK usage at full API rates) is off the table, so the reason we moved everyone to the TUI is gone.</p>' +
76
76
  '<h3>GUI is the default again</h3>' +
77
77
  '<p>New Claude sessions open in the <strong>GUI chat</strong> (SDK) by default, the way they did before. The embedded <code>claude</code> <strong>TUI</strong> is still there whenever you want it &mdash; toggle <strong>"Open Claude as terminal (TUI)"</strong> in Settings.</p>' +
78
- '<h3>Mates is off by default</h3>' +
79
- '<p>As announced, <strong>Mates</strong> is no longer a first-class Clay feature. It is now <strong>off by default</strong> so Clay stays focused as a developer tool. Your existing Mates and their knowledge files remain on disk &mdash; if you still use them, re-enable Mates in <strong>Settings</strong>.</p>' +
78
+ '<h3>Project Mate DM shortcuts are optional</h3>' +
79
+ '<p>Mate DM shortcuts inside project workspaces are hidden by default and can be shown in <strong>Settings</strong>. Mates themselves remain available in Clay Home, together with their conversations, memory, debates, and project collaboration.</p>' +
80
80
  '<p>Clay only gets sharper from here.</p>',
81
81
  },
82
82
  ];