clay-server 5.2.0-beta.4 → 5.2.0-beta.6

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 (301) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +17 -14
  3. package/lib/canonical-clay-record-authority.js +15 -0
  4. package/lib/canonical-clay-scheduling-authority.js +19 -0
  5. package/lib/claude-hook-installer.js +1 -239
  6. package/lib/claude-jsonl-watcher.js +1 -204
  7. package/lib/clay-mate-chat-receipts.js +26 -0
  8. package/lib/clay-mate-chat.js +87 -0
  9. package/lib/clay-mate-knowledge-input.js +26 -0
  10. package/lib/clay-mate-knowledge.js +95 -0
  11. package/lib/clay-mate-lifecycle.js +95 -0
  12. package/lib/clay-product-control-input.js +33 -0
  13. package/lib/clay-product-control-tools.js +51 -0
  14. package/lib/clay-product-control.js +237 -0
  15. package/lib/clay-project-lifecycle.js +154 -0
  16. package/lib/clay-project-record-input.js +44 -0
  17. package/lib/clay-project-records.js +83 -0
  18. package/lib/clay-scheduling-input.js +21 -0
  19. package/lib/clay-scheduling-receipts.js +26 -0
  20. package/lib/clay-scheduling.js +119 -0
  21. package/lib/clay-screen-controls.js +96 -0
  22. package/lib/clay-skill-install.js +1 -33
  23. package/lib/clay-workspace-controls.js +83 -0
  24. package/lib/clay-workspace-input.js +17 -0
  25. package/lib/clay-workspace-receipts.js +26 -0
  26. package/lib/cli-sessions.js +1 -300
  27. package/lib/codex-defaults.js +1 -28
  28. package/lib/context-usage.js +3 -2
  29. package/lib/daemon-project-access.js +11 -0
  30. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  31. package/lib/daemon-project-lifecycle.js +262 -0
  32. package/lib/daemon-projects.js +1 -1
  33. package/lib/daemon.js +78 -276
  34. package/lib/default-ai-runtime.js +4 -8
  35. package/lib/email-mcp-server.js +1 -1
  36. package/lib/issues-service.js +29 -15
  37. package/lib/kiro-defaults.js +1 -22
  38. package/lib/knowledge-db-import.js +5 -4
  39. package/lib/knowledge-db-service.js +9 -6
  40. package/lib/knowledge-document-index.js +2 -1
  41. package/lib/knowledge-product-operations.js +46 -0
  42. package/lib/mate-creation-mcp-server.js +2 -0
  43. package/lib/mate-knowledge-canonical-access.js +10 -0
  44. package/lib/mate-knowledge-service.js +7 -4
  45. package/lib/mate-lifecycle-service.js +94 -0
  46. package/lib/mate-message-delivery.js +43 -0
  47. package/lib/mate-ready-creation.js +9 -1
  48. package/lib/mate-schedule-input.js +29 -0
  49. package/lib/mate-schedule-lifecycle.js +151 -0
  50. package/lib/mate-scheduling.js +103 -0
  51. package/lib/mates.js +4 -3
  52. package/lib/mcp-proxy.js +2 -32
  53. package/lib/mcp-query-bridge.js +1 -1
  54. package/lib/mcp-source-adapters.js +1 -91
  55. package/lib/mcp-source-context.js +1 -42
  56. package/lib/mcp-source-discovery.js +1 -169
  57. package/lib/mcp-source-plugins.js +1 -68
  58. package/lib/mcp-source-restrictions.js +1 -47
  59. package/lib/mcp-tool-policy.js +1 -26
  60. package/lib/notes-checklist.js +50 -0
  61. package/lib/notes.js +49 -3
  62. package/lib/os-user-diagnostics.js +2 -4
  63. package/lib/os-users.js +24 -70
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +4 -2
  66. package/lib/project-connection.js +8 -7
  67. package/lib/project-delegated-follow-up.js +2 -1
  68. package/lib/project-directory-claim.js +65 -0
  69. package/lib/project-file-operations.js +61 -0
  70. package/lib/project-filesystem.js +13 -87
  71. package/lib/project-full-access.js +23 -2
  72. package/lib/project-http.js +4 -4
  73. package/lib/project-knowledge.js +32 -18
  74. package/lib/project-lifecycle-operations.js +98 -0
  75. package/lib/project-lifecycle-process-witness.js +32 -0
  76. package/lib/project-lifecycle-runtime.js +50 -0
  77. package/lib/project-linux-access.js +22 -0
  78. package/lib/project-logs-canonical-clay.js +27 -0
  79. package/lib/project-logs-clay-query.js +16 -0
  80. package/lib/project-logs-schema.js +4 -2
  81. package/lib/project-logs-service.js +8 -7
  82. package/lib/project-logs.js +1 -0
  83. package/lib/project-loop.js +1 -0
  84. package/lib/project-mate-chat-control.js +55 -0
  85. package/lib/project-mate-creation-proposal.js +18 -3
  86. package/lib/project-mcp.js +2 -2
  87. package/lib/project-models.js +14 -31
  88. package/lib/project-notifications.js +74 -16
  89. package/lib/project-ownership.js +77 -0
  90. package/lib/project-pair-result-delivery.js +14 -3
  91. package/lib/project-pair-usage.js +12 -13
  92. package/lib/project-pending-message-queue.js +4 -1
  93. package/lib/project-product-control.js +87 -0
  94. package/lib/project-product-session-queue.js +46 -0
  95. package/lib/project-push-access.js +28 -0
  96. package/lib/project-scheduled-message-queue.js +30 -0
  97. package/lib/project-scheduled-messages.js +63 -64
  98. package/lib/project-scheduled-task-control.js +83 -0
  99. package/lib/project-scheduled-tasks.js +13 -2
  100. package/lib/project-scheduling-access.js +10 -0
  101. package/lib/project-session-actions.js +65 -0
  102. package/lib/project-session-delete.js +8 -1
  103. package/lib/project-session-github.js +47 -12
  104. package/lib/project-session-history-actions.js +167 -0
  105. package/lib/project-session-pair.js +1 -1
  106. package/lib/project-session-runtime-actions.js +64 -0
  107. package/lib/project-session-spawn.js +2 -3
  108. package/lib/project-session-vendor.js +55 -0
  109. package/lib/project-sessions.js +82 -267
  110. package/lib/project-user-message.js +31 -1
  111. package/lib/project-vendor-login.js +6 -36
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +95 -3
  115. package/lib/public/app.js +1 -0
  116. package/lib/public/css/clay-tool-activity.css +51 -0
  117. package/lib/public/css/home-mate-creation.css +5 -0
  118. package/lib/public/css/messages.css +6 -2
  119. package/lib/public/css/overlays.css +4 -2
  120. package/lib/public/css/project-admin-overview.css +13 -0
  121. package/lib/public/css/session-folders.css +11 -0
  122. package/lib/public/css/session-github.css +3 -0
  123. package/lib/public/css/sticky-notes.css +61 -1
  124. package/lib/public/index.html +1 -1
  125. package/lib/public/modules/admin-settings-projects.js +6 -2
  126. package/lib/public/modules/app-connection.js +8 -1
  127. package/lib/public/modules/app-favicon.js +11 -2
  128. package/lib/public/modules/app-header.js +4 -2
  129. package/lib/public/modules/app-loop-ui.js +2 -0
  130. package/lib/public/modules/app-message-router.js +1 -1
  131. package/lib/public/modules/app-messages.js +54 -8
  132. package/lib/public/modules/app-notifications.js +1 -2
  133. package/lib/public/modules/app-panels.js +61 -123
  134. package/lib/public/modules/app-projects.js +5 -0
  135. package/lib/public/modules/app-rate-limit.js +7 -27
  136. package/lib/public/modules/chat-render-runtime.js +3 -0
  137. package/lib/public/modules/clay-owner-navigation.js +27 -0
  138. package/lib/public/modules/clay-tool-activity.js +140 -0
  139. package/lib/public/modules/clay-tool-presentation.js +338 -0
  140. package/lib/public/modules/command-palette.js +1 -1
  141. package/lib/public/modules/default-ai.js +11 -3
  142. package/lib/public/modules/home-debate-planning.js +5 -5
  143. package/lib/public/modules/home-mate-chat.js +8 -6
  144. package/lib/public/modules/home-mate-creation.js +73 -1
  145. package/lib/public/modules/mcp-connection-view.js +1 -1
  146. package/lib/public/modules/permission-control.js +5 -4
  147. package/lib/public/modules/personal-settings-dialog.js +7 -1
  148. package/lib/public/modules/project-access-recovery.js +17 -0
  149. package/lib/public/modules/project-admin-overview.js +21 -0
  150. package/lib/public/modules/scheduler.js +2 -0
  151. package/lib/public/modules/session-create-form.js +10 -10
  152. package/lib/public/modules/session-folder-layout.js +1 -1
  153. package/lib/public/modules/session-folder-view-menu.js +9 -0
  154. package/lib/public/modules/session-github.js +65 -8
  155. package/lib/public/modules/settings-defaults.js +27 -28
  156. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  157. package/lib/public/modules/sticky-note-markdown.js +72 -6
  158. package/lib/public/modules/sticky-notes-actions.js +30 -0
  159. package/lib/public/modules/sticky-notes-browser.js +9 -2
  160. package/lib/public/modules/sticky-notes-card.js +23 -7
  161. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  162. package/lib/public/modules/sticky-notes-editor.js +108 -44
  163. package/lib/public/modules/sticky-notes-shared.js +50 -2
  164. package/lib/public/modules/sticky-notes-state.js +66 -0
  165. package/lib/public/modules/sticky-notes.js +34 -51
  166. package/lib/public/modules/thinking-lifecycle.js +33 -3
  167. package/lib/public/modules/thinking-view.js +23 -1
  168. package/lib/public/modules/tools.js +36 -7
  169. package/lib/public/modules/tui-grab.js +1 -2
  170. package/lib/public/modules/user-settings-appearance.js +10 -3
  171. package/lib/public/modules/user-settings-editors.js +1 -1
  172. package/lib/public/modules/user-settings-layout.js +11 -2
  173. package/lib/public/modules/user-settings-preferences.js +6 -2
  174. package/lib/public/modules/user-settings-profile.js +12 -2
  175. package/lib/public/modules/user-settings.js +3 -2
  176. package/lib/public/modules/vendor-login-browser.js +5 -3
  177. package/lib/public/modules/vendor-login.js +2 -2
  178. package/lib/public/modules/vendor-priority.js +15 -1
  179. package/lib/public/muse-avatar.svg +4 -0
  180. package/lib/public/style.css +2 -0
  181. package/lib/safe-bash-commands.js +3 -45
  182. package/lib/scheduled-task-records.js +3 -1
  183. package/lib/scheduler.js +2 -0
  184. package/lib/sdk-bridge.js +334 -403
  185. package/lib/sdk-foreground-admission.js +16 -0
  186. package/lib/sdk-mcp-descriptors.js +2 -37
  187. package/lib/sdk-message-processor.js +52 -17
  188. package/lib/sdk-quota-snapshot.js +1 -1
  189. package/lib/sdk-response-recovery.js +1 -0
  190. package/lib/sdk-worker.js +2 -677
  191. package/lib/server-admin.js +63 -16
  192. package/lib/server-dm.js +1 -1
  193. package/lib/server-global-ws.js +10 -20
  194. package/lib/server-home-chat-events.js +7 -3
  195. package/lib/server-home-chat.js +4 -16
  196. package/lib/server-home-mate-creation.js +1 -0
  197. package/lib/server-home-mcp-consent.js +3 -3
  198. package/lib/server-mates.js +25 -67
  199. package/lib/server-owner-ui.js +77 -0
  200. package/lib/server-path-access.js +38 -0
  201. package/lib/server-project-removal.js +22 -0
  202. package/lib/server.js +100 -30
  203. package/lib/session-config-transition.js +45 -0
  204. package/lib/session-folders.js +7 -2
  205. package/lib/session-fork-history.js +21 -0
  206. package/lib/session-github.js +9 -2
  207. package/lib/session-hygiene.js +3 -35
  208. package/lib/session-permission-mode.js +4 -3
  209. package/lib/session-skill-scope.js +11 -0
  210. package/lib/session-visibility.js +5 -2
  211. package/lib/sessions.js +27 -89
  212. package/lib/terminal-manager.js +52 -0
  213. package/lib/tool-llm.js +1 -1
  214. package/lib/tui-transcript-index.js +1 -132
  215. package/lib/users-permissions.js +2 -8
  216. package/lib/users.js +4 -0
  217. package/lib/vendor-login-browser.js +1 -11
  218. package/lib/workspace-query-access.js +4 -1
  219. package/lib/ws-schema.js +8 -2
  220. package/lib/yoke/acp-agent-profiles.js +5 -3
  221. package/lib/yoke/adapters/antigravity.js +3 -14
  222. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  223. package/lib/yoke/adapters/claude.js +54 -50
  224. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  225. package/lib/yoke/adapters/codex.js +53 -111
  226. package/lib/yoke/adapters/kiro.js +3 -1
  227. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  228. package/lib/yoke/adapters/muse.js +122 -0
  229. package/lib/yoke/auth-events.js +17 -0
  230. package/lib/yoke/claude-hook-installer.js +267 -0
  231. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  232. package/lib/yoke/claude-maintenance.js +55 -0
  233. package/lib/yoke/claude-permission-patterns.js +53 -0
  234. package/lib/yoke/claude-response-recovery.js +155 -0
  235. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  236. package/lib/yoke/claude-worker-startup.js +28 -0
  237. package/lib/yoke/clay-skill-install.js +33 -0
  238. package/lib/yoke/cli-sessions.js +300 -0
  239. package/lib/yoke/codex-app-server.js +7 -12
  240. package/lib/yoke/codex-idle-recovery.js +81 -1
  241. package/lib/yoke/codex-recovery-admission.js +164 -0
  242. package/lib/yoke/configuration.js +13 -0
  243. package/lib/yoke/discovery.js +70 -0
  244. package/lib/yoke/errors.js +8 -0
  245. package/lib/yoke/host-environment.js +7 -0
  246. package/lib/yoke/index.js +9 -51
  247. package/lib/yoke/interface.js +116 -0
  248. package/lib/yoke/kiro-acp-server.js +5 -5
  249. package/lib/yoke/legacy-sessions.js +89 -0
  250. package/lib/yoke/login-protocol.js +25 -0
  251. package/lib/yoke/maintenance.js +12 -0
  252. package/lib/yoke/mcp-proxy.js +32 -0
  253. package/lib/yoke/mcp-source-adapters.js +91 -0
  254. package/lib/yoke/mcp-source-context.js +42 -0
  255. package/lib/yoke/mcp-source-discovery.js +169 -0
  256. package/lib/yoke/mcp-source-plugins.js +68 -0
  257. package/lib/yoke/mcp-source-restrictions.js +47 -0
  258. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  259. package/lib/yoke/model-preferences.js +6 -0
  260. package/lib/yoke/muse-artifacts.js +36 -0
  261. package/lib/yoke/muse-controls.js +50 -0
  262. package/lib/yoke/muse-input.js +64 -0
  263. package/lib/yoke/muse-items.js +157 -0
  264. package/lib/yoke/muse-policy.js +52 -0
  265. package/lib/yoke/muse-query.js +428 -0
  266. package/lib/yoke/muse-runtime.js +211 -0
  267. package/lib/yoke/muse-services.js +79 -0
  268. package/lib/yoke/muse-skills.js +79 -0
  269. package/lib/yoke/muse-turns.js +71 -0
  270. package/lib/yoke/options/acp.js +2 -0
  271. package/lib/yoke/options/antigravity.js +2 -0
  272. package/lib/yoke/options/claude.js +21 -0
  273. package/lib/yoke/options/codex-defaults.js +29 -0
  274. package/lib/yoke/options/codex-settings.js +21 -0
  275. package/lib/yoke/options/codex.js +6 -0
  276. package/lib/yoke/options/kiro-defaults.js +22 -0
  277. package/lib/yoke/options/kiro.js +5 -0
  278. package/lib/yoke/options/muse.js +5 -0
  279. package/lib/yoke/provider-contract.js +5 -0
  280. package/lib/yoke/providers/antigravity-executable.js +8 -0
  281. package/lib/yoke/providers/claude-executable.js +40 -0
  282. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  283. package/lib/yoke/providers/claude-process.js +97 -0
  284. package/lib/yoke/providers/claude-provision.js +75 -0
  285. package/lib/yoke/providers/codex-errors.js +8 -0
  286. package/lib/yoke/providers/muse-executable.js +17 -0
  287. package/lib/yoke/provisioning.js +6 -0
  288. package/lib/yoke/query-options.js +10 -0
  289. package/lib/yoke/response-recovery.js +41 -0
  290. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  291. package/lib/yoke/sdk-server-factory.js +7 -0
  292. package/lib/yoke/session-history.js +8 -0
  293. package/lib/yoke/session-tool-transport.js +34 -13
  294. package/lib/yoke/settings.js +23 -0
  295. package/lib/yoke/source-locations.js +4 -0
  296. package/lib/yoke/title-query.js +18 -7
  297. package/lib/yoke/tui-transcript-index.js +132 -0
  298. package/lib/yoke/usage.js +48 -0
  299. package/lib/yoke/user-input.js +13 -0
  300. package/lib/yoke/vendor-registry.js +81 -1
  301. package/package.json +3 -2
@@ -146,7 +146,7 @@ function cleanupWorktreesForParent(relay, parentSlug) {
146
146
  function getFilteredRemovedProjects(config, userId) {
147
147
  if (!config.removedProjects || config.removedProjects.length === 0) return [];
148
148
  return config.removedProjects.filter(function (rp) {
149
- if (userId && rp.userId && rp.userId !== userId) return false;
149
+ if (userId && rp.userId !== userId) return false;
150
150
  if (!userId && rp.userId) return false;
151
151
  return fs.existsSync(rp.path);
152
152
  });
package/lib/daemon.js CHANGED
@@ -20,7 +20,7 @@ process.setMaxListeners(50);
20
20
 
21
21
  // Remove CLAUDECODE env var so the SDK can spawn Claude Code child processes
22
22
  // (prevents "cannot be launched inside another Claude Code session" error)
23
- delete process.env.CLAUDECODE;
23
+ require("./yoke/host-environment").prepare(process.env);
24
24
 
25
25
  var fs = require("fs");
26
26
  var path = require("path");
@@ -67,6 +67,8 @@ try {
67
67
  console.error("[daemon] ensureMultiUser threw (continuing):", e && e.message);
68
68
  }
69
69
 
70
+ config = require("./project-ownership").repair(config, usersModule.getAllUsers(), saveConfig, osUsersMod.resolveOsUserInfo);
71
+
70
72
  if (projectLogContext.initializeProjectKnowledge(config)) {
71
73
  saveConfig(config);
72
74
  console.log("[daemon] Assigned stable knowledge identities to existing projects");
@@ -187,7 +189,14 @@ function registerProjectWorktrees(project, requestUser) {
187
189
  }
188
190
  }
189
191
 
192
+ var projectLifecycle = require("./daemon-project-lifecycle").attachProjectLifecycle({
193
+ getConfig: function () { return config; }, setConfig: function (next) { config = next; }, saveConfig: saveConfig,
194
+ getRelay: function () { return relay; }, resolveIdentity: osUsersMod.resolveOsUserInfo,
195
+ findUser: usersModule.findUserById, registerWorktrees: registerProjectWorktrees,
196
+ });
197
+ var lifecycleCallbacks = require("./daemon-project-lifecycle-callbacks").attachLifecycleCallbacks({ service: projectLifecycle });
190
198
  var relay = createServer({
199
+ projectLifecycle: projectLifecycle,
191
200
  tlsOptions: tlsOptions,
192
201
  caPath: caRoot,
193
202
  builtinCert: config.builtinCert || false,
@@ -198,122 +207,11 @@ var relay = createServer({
198
207
  osUsers: config.osUsers || false,
199
208
  lanHost: lanIp ? lanIp + ":" + config.port : null,
200
209
  getRemovedProjects: function (userId) { return getFilteredRemovedProjects(config, userId); },
201
- onAddProject: function (absPath, wsUser) {
202
- // Check if already registered
203
- for (var j = 0; j < config.projects.length; j++) {
204
- if (config.projects[j].path === absPath) {
205
- return { ok: true, slug: config.projects[j].slug, existing: true };
206
- }
207
- }
208
- var slugs = config.projects.map(function (p) { return p.slug; });
209
- var slug = generateSlug(absPath, slugs);
210
- var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private" };
211
- projectEntry.projectKnowledgeId = rememberedProjectKnowledgeId(absPath);
212
- // The user who adds a project always becomes the owner
213
- if (wsUser && wsUser.id) {
214
- projectEntry.ownerId = wsUser.id;
215
- }
216
- config.projects.push(projectEntry);
217
- relay.addProject(absPath, slug, null, null, wsUser ? wsUser.id : null, null, projectRuntimeOptions(projectEntry));
218
- // Remove from removedProjects if present
219
- if (config.removedProjects) {
220
- config.removedProjects = config.removedProjects.filter(function (rp) { return rp.path !== absPath; });
221
- }
222
- saveConfig(config);
223
- try { syncClayrc(config.projects); } catch (e) {}
224
- console.log("[daemon] Added project (web):", slug, "→", absPath);
225
- // OS users mode: grant ACL to project owner
226
- if (config.osUsers) {
227
- var newProj = config.projects[config.projects.length - 1];
228
- if (newProj.ownerId) {
229
- var ownerUser = usersModule.findUserById(newProj.ownerId);
230
- if (ownerUser && ownerUser.linuxUser) {
231
- grantProjectAccess(absPath, ownerUser.linuxUser);
232
- }
233
- }
234
- }
235
- // Discover and register worktrees for the new project
236
- registerProjectWorktrees(projectEntry, wsUser);
237
- // Broadcast updated project list to all clients
238
- relay.broadcastAll({
239
- type: "projects_updated",
240
- projects: relay.getProjects(),
241
- projectCount: config.projects.length,
242
- });
243
- return { ok: true, slug: slug };
244
- },
245
- onCreateProject: function (projectName, wsUser) {
246
- console.log("[daemon] onCreateProject wsUser:", JSON.stringify(wsUser ? { id: wsUser.id, role: wsUser.role, username: wsUser.username, linuxUser: wsUser.linuxUser } : null));
247
- var os = require("os");
248
- var execFileSync = require("child_process").execFileSync;
249
- var baseDir;
250
- if (config.osUsers) {
251
- baseDir = "/var/clay/projects";
252
- } else {
253
- baseDir = config.projectsDir || path.join(REAL_HOME, "clay-projects");
254
- }
255
- try { fs.mkdirSync(baseDir, { recursive: true }); } catch (e) {}
256
- // Generate slug and deduplicate
257
- var slugs = config.projects.map(function (p) { return p.slug; });
258
- var slug = generateSlug(path.join(baseDir, projectName), slugs);
259
- var targetDir = path.join(baseDir, slug);
260
- try {
261
- fs.mkdirSync(targetDir, { recursive: true });
262
- // Run git init
263
- if (config.osUsers && wsUser) {
264
- var linuxUser = wsUser.linuxUser;
265
- if (linuxUser) {
266
- var uidGid = null;
267
- try {
268
- uidGid = {
269
- uid: parseInt(execFileSync("id", ["-u", linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10),
270
- gid: parseInt(execFileSync("id", ["-g", linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10),
271
- };
272
- } catch (e) {}
273
- if (uidGid) {
274
- fs.chmodSync(targetDir, 0o700);
275
- execFileSync("chown", ["-R", linuxUser + ":" + linuxUser, targetDir]);
276
- var gitInit = osUsersMod.wrapSpawnAsUser("git", ["init"], { cwd: targetDir, uid: uidGid.uid, gid: uidGid.gid, env: { PATH: "/usr/local/bin:/usr/bin:/bin" } });
277
- execFileSync(gitInit.command, gitInit.args, gitInit.options);
278
- } else {
279
- execFileSync("git", ["init"], { cwd: targetDir });
280
- }
281
- } else {
282
- execFileSync("git", ["init"], { cwd: targetDir });
283
- }
284
- } else {
285
- execFileSync("git", ["init"], { cwd: targetDir });
286
- }
287
- } catch (e) {
288
- try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (ce) {}
289
- return { ok: false, error: "Failed to create project: " + e.message };
290
- }
291
- // Register project - creator always becomes owner, default private
292
- var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now(), visibility: "private" };
293
- projectEntry.projectKnowledgeId = projectLogContext.createProjectKnowledgeId();
294
- if (wsUser && wsUser.id) {
295
- projectEntry.ownerId = wsUser.id;
296
- }
297
- relay.addProject(targetDir, slug, null, null, wsUser ? wsUser.id : null, null, projectRuntimeOptions(projectEntry));
298
- config.projects.push(projectEntry);
299
- saveConfig(config);
300
- try { syncClayrc(config.projects); } catch (e) {}
301
- console.log("[daemon] Created project:", slug, "→", targetDir, "entry:", JSON.stringify({ ownerId: projectEntry.ownerId, visibility: projectEntry.visibility }));
302
- // OS users mode: grant ACL
303
- if (config.osUsers && wsUser && wsUser.linuxUser) {
304
- console.log("[daemon] Granting ACL:", targetDir, "→", wsUser.linuxUser);
305
- grantProjectAccess(targetDir, wsUser.linuxUser);
306
- } else if (config.osUsers) {
307
- console.log("[daemon] Skipping ACL grant: osUsers=true but linuxUser=", wsUser && wsUser.linuxUser);
308
- }
309
- relay.broadcastAll({
310
- type: "projects_updated",
311
- projects: relay.getProjects(),
312
- projectCount: config.projects.length,
313
- });
314
- return { ok: true, slug: slug };
315
- },
210
+ onAddProject: lifecycleCallbacks.onAddProject,
211
+ onCreateProject: lifecycleCallbacks.onCreateProject,
316
212
  onCloneProject: function (cloneUrl, wsUser, callback) {
213
+ wsUser = wsUser && usersModule.findUserById(wsUser.id);
214
+ if (usersModule.isMultiUser() && !wsUser) { callback({ ok: false, error: "A valid account is required." }); return; }
317
215
  var os = require("os");
318
216
  var spawn = require("child_process").spawn;
319
217
  var execFileSync = require("child_process").execFileSync;
@@ -323,12 +221,15 @@ var relay = createServer({
323
221
  } else {
324
222
  baseDir = config.projectsDir || path.join(REAL_HOME, "clay-projects");
325
223
  }
326
- try { fs.mkdirSync(baseDir, { recursive: true }); } catch (e) {}
327
224
  // Derive slug from repo URL
328
225
  var repoName = cloneUrl.replace(/\.git$/, "").split("/").pop() || "project";
329
226
  var slugs = config.projects.map(function (p) { return p.slug; });
330
227
  var slug = generateSlug(path.join(baseDir, repoName), slugs);
331
- var targetDir = path.join(baseDir, slug);
228
+ var targetDir;
229
+ var directoryClaim;
230
+ var settled = false;
231
+ function finish(result) { if (settled) return; settled = true; callback(result); }
232
+ function cleanup() { if (directoryClaim) { try { directoryClaim.cleanup(); } catch (error) {} } }
332
233
  // Clone as user to use their git credentials
333
234
  var spawnOpts = { cwd: baseDir };
334
235
  if (config.osUsers && wsUser && wsUser.linuxUser) {
@@ -341,22 +242,23 @@ var relay = createServer({
341
242
  });
342
243
  } catch (e) {}
343
244
  }
344
- // Pre-create target directory as root and chown to user
345
- if (fs.existsSync(targetDir)) {
346
- callback({ ok: false, error: "Target directory already exists: " + targetDir });
245
+ try {
246
+ directoryClaim = require("./project-directory-claim").claim({ baseDir: baseDir, slug: slug, projects: config.projects, userId: wsUser && wsUser.id });
247
+ targetDir = directoryClaim.path;
248
+ directoryClaim.assertOwned();
249
+ if (config.osUsers && wsUser && wsUser.linuxUser && spawnOpts.uid != null) fs.chownSync(targetDir, spawnOpts.uid, spawnOpts.gid);
250
+ directoryClaim.assertOwned();
251
+ } catch (error) {
252
+ cleanup();
253
+ finish({ ok: false, error: "Failed to prepare project directory: " + error.message });
347
254
  return;
348
255
  }
349
- if (config.osUsers && wsUser && wsUser.linuxUser && spawnOpts.uid != null) {
350
- try {
351
- fs.mkdirSync(targetDir, { mode: 0o700 });
352
- fs.chownSync(targetDir, spawnOpts.uid, spawnOpts.gid);
353
- } catch (e) {
354
- callback({ ok: false, error: "Failed to prepare project directory: " + e.message });
355
- return;
356
- }
357
- }
358
- var cloneSpawn = osUsersMod.wrapSpawnAsUser("git", ["clone", cloneUrl, targetDir], spawnOpts);
359
- var proc = spawn(cloneSpawn.command, cloneSpawn.args, cloneSpawn.options);
256
+ var proc;
257
+ try {
258
+ directoryClaim.assertOwned();
259
+ var cloneSpawn = osUsersMod.wrapSpawnAsUser("git", ["clone", cloneUrl, targetDir], spawnOpts);
260
+ proc = spawn(cloneSpawn.command, cloneSpawn.args, cloneSpawn.options);
261
+ } catch (error) { cleanup(); finish({ ok: false, error: "Failed to start git clone: " + error.message }); return; }
360
262
  var stderrBuf = "";
361
263
  proc.stderr.on("data", function (chunk) { stderrBuf += chunk.toString(); });
362
264
  // 5 minute timeout
@@ -365,19 +267,23 @@ var relay = createServer({
365
267
  }, 5 * 60 * 1000);
366
268
  proc.on("close", function (code) {
367
269
  clearTimeout(cloneTimeout);
270
+ if (settled) return;
271
+ try { directoryClaim.assertOwned(); } catch (error) { finish({ ok: false, error: error.message }); return; }
368
272
  if (code !== 0) {
369
- try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (ce) {}
273
+ cleanup();
370
274
  var errMsg = stderrBuf.trim().split("\n").pop() || "Clone failed (exit code " + code + ")";
371
- callback({ ok: false, error: errMsg });
275
+ finish({ ok: false, error: errMsg });
372
276
  return;
373
277
  }
374
278
  // chown and restrict permissions if osUsers
375
279
  if (config.osUsers && wsUser && wsUser.linuxUser) {
376
280
  try {
281
+ directoryClaim.assertOwned();
377
282
  fs.chmodSync(targetDir, 0o700);
378
283
  execFileSync("chown", ["-R", wsUser.linuxUser + ":" + wsUser.linuxUser, targetDir]);
379
284
  } catch (e) {}
380
285
  }
286
+ if (usersModule.isMultiUser() && (!wsUser || !usersModule.findUserById(wsUser.id))) { finish({ ok: false, error: "The creating account is no longer available." }); return; }
381
287
  // Register project - creator always becomes owner
382
288
  // Creator always becomes owner, default private
383
289
  var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now(), visibility: "private" };
@@ -385,9 +291,11 @@ var relay = createServer({
385
291
  if (wsUser && wsUser.id) {
386
292
  projectEntry.ownerId = wsUser.id;
387
293
  }
388
- relay.addProject(targetDir, slug, null, null, wsUser ? wsUser.id : null, null, projectRuntimeOptions(projectEntry));
389
- config.projects.push(projectEntry);
390
- saveConfig(config);
294
+ try {
295
+ directoryClaim.assertOwned();
296
+ config = require("./project-ownership").register(config, projectEntry, usersModule.getAllUsers(), saveConfig, osUsersMod.resolveOsUserInfo);
297
+ } catch (error) { finish({ ok: false, error: "Failed to register project: " + error.message }); return; }
298
+ relay.addProject(targetDir, slug, null, null, projectEntry.ownerId || null, null, projectRuntimeOptions(projectEntry));
391
299
  try { syncClayrc(config.projects); } catch (e) {}
392
300
  console.log("[daemon] Cloned project:", slug, "→", targetDir);
393
301
  if (config.osUsers && wsUser && wsUser.linuxUser) {
@@ -398,12 +306,13 @@ var relay = createServer({
398
306
  projects: relay.getProjects(),
399
307
  projectCount: config.projects.length,
400
308
  });
401
- callback({ ok: true, slug: slug });
309
+ finish({ ok: true, slug: slug });
402
310
  });
403
311
  proc.on("error", function (err) {
404
312
  clearTimeout(cloneTimeout);
405
- try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (ce) {}
406
- callback({ ok: false, error: "Failed to start git clone: " + err.message });
313
+ if (settled) return;
314
+ cleanup();
315
+ finish({ ok: false, error: "Failed to start git clone: " + err.message });
407
316
  });
408
317
  },
409
318
  onRemoveProject: function (slug, userId) {
@@ -444,40 +353,7 @@ var relay = createServer({
444
353
  });
445
354
  return { ok: true };
446
355
  }
447
- var found = null;
448
- for (var j = 0; j < config.projects.length; j++) {
449
- if (config.projects[j].slug === slug) { found = config.projects[j]; break; }
450
- }
451
- if (!found) return { ok: false, error: "Project not found" };
452
- // Cascade remove worktrees belonging to this parent
453
- cleanupWorktreesForParent(relay, slug);
454
- // Save to removedProjects for re-add functionality
455
- if (!config.removedProjects) config.removedProjects = [];
456
- config.removedProjects.push({
457
- path: found.path,
458
- title: found.title || null,
459
- icon: found.icon || null,
460
- userId: userId || null,
461
- projectKnowledgeId: found.projectKnowledgeId || null,
462
- removedAt: Date.now(),
463
- });
464
- // Cap at 20 entries (oldest first)
465
- if (config.removedProjects.length > 20) {
466
- config.removedProjects = config.removedProjects.slice(config.removedProjects.length - 20);
467
- }
468
- relay.removeProject(slug);
469
- config.projects = config.projects.filter(function (p) { return p.slug !== slug; });
470
- saveConfig(config);
471
- // Remove from .clayrc so it doesn't appear in restore prompt
472
- if (found.path) { try { removeFromClayrc(found.path); } catch (e) {} }
473
- try { syncClayrc(config.projects); } catch (e) {}
474
- console.log("[daemon] Removed project (web):", slug);
475
- relay.broadcastAll({
476
- type: "projects_updated",
477
- projects: relay.getProjects(),
478
- projectCount: config.projects.length,
479
- });
480
- return { ok: true };
356
+ return lifecycleCallbacks.onRemoveProject(slug, userId);
481
357
  },
482
358
  onReorderProjects: function (slugs) {
483
359
  // Build a slug->project map from current projects
@@ -511,25 +387,8 @@ var relay = createServer({
511
387
  return { ok: true };
512
388
  },
513
389
  onSetProjectTitle: function (slug, newTitle) {
514
- relay.setProjectTitle(slug, newTitle);
515
- for (var ti = 0; ti < config.projects.length; ti++) {
516
- if (config.projects[ti].slug === slug) {
517
- if (newTitle) {
518
- config.projects[ti].title = newTitle;
519
- } else {
520
- delete config.projects[ti].title;
521
- }
522
- break;
523
- }
524
- }
525
- saveConfig(config);
526
- try { syncClayrc(config.projects); } catch (e) {}
527
- relay.broadcastAll({
528
- type: "projects_updated",
529
- projects: relay.getProjects(),
530
- projectCount: config.projects.length,
531
- });
532
- return { ok: true };
390
+ if (isWorktreeSlug(slug)) { relay.setProjectTitle(slug, newTitle); return { ok: true }; }
391
+ return lifecycleCallbacks.onSetProjectTitle(slug, newTitle);
533
392
  },
534
393
  onSetProjectIcon: function (slug, newIcon) {
535
394
  relay.setProjectIcon(slug, newIcon);
@@ -553,30 +412,28 @@ var relay = createServer({
553
412
  return { ok: true };
554
413
  },
555
414
  onSetProjectSessionVisibilityDefault: function (slug, visibility) {
556
- var result = daemonProjectAccess.setSessionVisibilityDefault(config, slug, visibility);
557
- if (result.error) return { ok: false, error: result.error };
558
- saveConfig(config);
559
- relay.broadcastAll({ type: "projects_updated", projects: relay.getProjects(), projectCount: config.projects.length });
560
- return result;
415
+ return projectLifecycle.update(slug, "session_visibility", visibility);
561
416
  },
562
417
  onProjectOwnerChanged: function (slug, ownerId) {
418
+ if (!config.osUsers) return projectLifecycle.update(slug, "owner", ownerId);
563
419
  if (slug.indexOf("mate-") === 0) return {error:"Mate ownership cannot be transferred."};
420
+ if (!ownerId || !usersModule.findUserById(ownerId)) return { error: "A valid owner is required." };
564
421
  console.log("[daemon] onProjectOwnerChanged:", slug, "→", ownerId);
422
+ var previousConfig = JSON.parse(JSON.stringify(config));
565
423
  var oldOwnerId = null;
566
424
  var projectIdx = -1;
567
425
  for (var oi = 0; oi < config.projects.length; oi++) {
568
426
  if (config.projects[oi].slug === slug) {
569
427
  oldOwnerId = config.projects[oi].ownerId || null;
570
428
  projectIdx = oi;
571
- if (ownerId) {
572
- config.projects[oi].ownerId = ownerId;
573
- } else {
574
- delete config.projects[oi].ownerId;
575
- }
429
+ config.projects[oi].ownerId = ownerId;
576
430
  break;
577
431
  }
578
432
  }
579
- saveConfig(config);
433
+ try { saveConfig(config); } catch (error) { config = previousConfig; throw error; }
434
+ relay.forEachProject(function (project, projectSlug) {
435
+ if (projectSlug === slug || daemonProjectAccess.parentSlugFor(projectSlug) === slug) project.setProjectOwner(ownerId);
436
+ });
580
437
  // OS users mode: revoke old owner ACL, grant new owner ACL
581
438
  if (config.osUsers && projectIdx >= 0) {
582
439
  var projectPath = config.projects[projectIdx].path;
@@ -615,18 +472,7 @@ var relay = createServer({
615
472
  return { envrc: "" };
616
473
  },
617
474
  onSetProjectEnv: function (slug, envrc) {
618
- for (var ei = 0; ei < config.projects.length; ei++) {
619
- if (config.projects[ei].slug === slug) {
620
- if (envrc) {
621
- config.projects[ei].envrc = envrc;
622
- } else {
623
- delete config.projects[ei].envrc;
624
- }
625
- saveConfig(config);
626
- return { ok: true };
627
- }
628
- }
629
- return { ok: false, error: "Project not found" };
475
+ return lifecycleCallbacks.onSetProjectEnv(slug, envrc);
630
476
  },
631
477
  onGetSharedEnv: function () {
632
478
  return { envrc: config.sharedEnv || "" };
@@ -927,6 +773,7 @@ var relay = createServer({
927
773
  spawnAndRestart();
928
774
  },
929
775
  onSetProjectVisibility: function (slug, visibility) {
776
+ if (!config.osUsers) return projectLifecycle.update(slug, "visibility", visibility);
930
777
  if (slug.indexOf("mate-") === 0) return {error:"Mates cannot be shared."};
931
778
  for (var i = 0; i < config.projects.length; i++) {
932
779
  if (config.projects[i].slug === slug) {
@@ -962,6 +809,7 @@ var relay = createServer({
962
809
  return { error: "Project not found" };
963
810
  },
964
811
  onSetProjectAllowedUsers: function (slug, allowedUsers) {
812
+ if (!config.osUsers && !daemonProjectAccess.parentSlugFor(slug)) return projectLifecycle.update(slug, "allowed_users", allowedUsers);
965
813
  var accessBefore = daemonProjectAccess.getProjectAccess(config, slug, config.osUsers);
966
814
  var result = daemonProjectAccess.setAllowedUsers(config, slug, allowedUsers);
967
815
  if (result.error) return result;
@@ -1062,8 +910,9 @@ var relay = createServer({
1062
910
  // process may implement the optional shutdownIfIdle(ms); adapters without one
1063
911
  // (Claude, which runs in-process or under sdk-worker) are simply skipped.
1064
912
  // The CLAY_CODEX_* names are kept as aliases for backwards compatibility.
1065
- var IDLE_MS = Number(process.env.CLAY_ADAPTER_IDLE_MS) || Number(process.env.CLAY_CODEX_IDLE_MS) || 5 * 60 * 1000;
1066
- var REAP_MS = Number(process.env.CLAY_ADAPTER_REAPER_MS) || Number(process.env.CLAY_CODEX_REAPER_MS) || 60 * 1000;
913
+ var adapterReaperConfig = require("./yoke/host-environment").reaper(process.env);
914
+ var IDLE_MS = adapterReaperConfig.idleMs;
915
+ var REAP_MS = adapterReaperConfig.reapMs;
1067
916
  var reaperHandle = setInterval(function () {
1068
917
  if (!relay || typeof relay.forEachProject !== "function") return;
1069
918
  relay.forEachProject(function (ctx) {
@@ -1131,11 +980,11 @@ var ipc = createIPCServer(socketPath(), function (msg) {
1131
980
  }
1132
981
  var slugs = config.projects.map(function (p) { return p.slug; });
1133
982
  var slug = generateSlug(absPath, slugs);
1134
- var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private" };
983
+ var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private", ownershipRecovery: "required" };
1135
984
  projectEntry.projectKnowledgeId = rememberedProjectKnowledgeId(absPath);
1136
- relay.addProject(absPath, slug, null, null, null, null, projectRuntimeOptions(projectEntry));
1137
- config.projects.push(projectEntry);
1138
- saveConfig(config);
985
+ config = require("./project-ownership").register(config, projectEntry, usersModule.getAllUsers(), saveConfig, osUsersMod.resolveOsUserInfo);
986
+ var registeredProject = config.projects[config.projects.length - 1];
987
+ relay.addProject(absPath, slug, null, null, registeredProject.ownerId || null, null, projectRuntimeOptions(registeredProject));
1139
988
  try { syncClayrc(config.projects); } catch (e) {}
1140
989
  console.log("[daemon] Added project:", slug, "→", absPath);
1141
990
  // Discover and register worktrees for the new project
@@ -1367,6 +1216,10 @@ var ipc = createIPCServer(socketPath(), function (msg) {
1367
1216
  var projects = config.projects || [];
1368
1217
  for (var i = 0; i < projects.length; i++) {
1369
1218
  var p = projects[i];
1219
+ if (!projectLifecycle.canRestore(p)) {
1220
+ console.log("[daemon] Project removal needs recovery; runtime remains stopped:", p.slug);
1221
+ continue;
1222
+ }
1370
1223
  if (fs.existsSync(p.path)) {
1371
1224
  console.log("[daemon] Adding project:", p.slug, "→", p.path);
1372
1225
  relay.addProject(p.path, p.slug, p.title, p.icon, p.ownerId, null, projectRuntimeOptions(p));
@@ -1413,7 +1266,7 @@ if (usersModule.isMultiUser()) {
1413
1266
  var mateDir = mates.getMateDir(mateCtx, m.id);
1414
1267
  var mateSlug = "mate-" + m.id;
1415
1268
  var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
1416
- if (fs.existsSync(mateDir)) {
1269
+ if (!m.lifecycleDeletion && fs.existsSync(mateDir)) {
1417
1270
  var mateDef = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
1418
1271
  var isHost = !!(mateDef && mateDef.hostAgent);
1419
1272
  console.log("[daemon] Adding mate project:", mateSlug + (isHost ? " (host agent)" : ""));
@@ -1430,7 +1283,7 @@ if (usersModule.isMultiUser()) {
1430
1283
  var mateDir = mates.getMateDir(mateCtx, m.id);
1431
1284
  var mateSlug = "mate-" + m.id;
1432
1285
  var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
1433
- if (fs.existsSync(mateDir)) {
1286
+ if (!m.lifecycleDeletion && fs.existsSync(mateDir)) {
1434
1287
  var mateDef2 = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
1435
1288
  var isHost2 = !!(mateDef2 && mateDef2.hostAgent);
1436
1289
  console.log("[daemon] Adding mate project:", mateSlug + (isHost2 ? " (host agent)" : ""));
@@ -1467,58 +1320,7 @@ try { syncClayrc(config.projects); } catch (e) {}
1467
1320
  // Multi-user OS-isolated installs have per-user ~/.claude trees that the
1468
1321
  // daemon process can't traverse; the inline cleanup in claude-worker.js
1469
1322
  // covers those.
1470
- function cleanupSdkWarmupSessions() {
1471
- var sessionHygiene = require("./session-hygiene");
1472
- var projectsRoot = path.join(REAL_HOME, ".claude", "projects");
1473
- var projectDirs;
1474
- try { projectDirs = fs.readdirSync(projectsRoot, { withFileTypes: true }); }
1475
- catch (e) { return; }
1476
-
1477
- var deleted = 0;
1478
- var nowMs = Date.now();
1479
- var MAX_BYTES = 64 * 1024;
1480
- var MIN_AGE_MS = 60 * 1000;
1481
-
1482
- for (var di = 0; di < projectDirs.length; di++) {
1483
- if (!projectDirs[di].isDirectory()) continue;
1484
- var projDir = path.join(projectsRoot, projectDirs[di].name);
1485
- var files;
1486
- try { files = fs.readdirSync(projDir); } catch (e) { continue; }
1487
-
1488
- for (var fi = 0; fi < files.length; fi++) {
1489
- var f = files[fi];
1490
- if (!f.endsWith(".jsonl")) continue;
1491
- var fp = path.join(projDir, f);
1492
-
1493
- var st;
1494
- try { st = fs.statSync(fp); } catch (e) { continue; }
1495
- if (st.size >= MAX_BYTES) continue;
1496
- if (nowMs - st.mtimeMs < MIN_AGE_MS) continue;
1497
-
1498
- var raw;
1499
- try { raw = fs.readFileSync(fp, "utf8"); } catch (e) { continue; }
1500
- var lines = raw.split("\n");
1501
- var events = [];
1502
- var parseOk = true;
1503
- for (var li = 0; li < lines.length; li++) {
1504
- var line = lines[li];
1505
- if (!line) continue;
1506
- var ev;
1507
- try { ev = JSON.parse(line); } catch (e) { parseOk = false; break; }
1508
- if (!ev || typeof ev !== "object") continue;
1509
- events.push(ev);
1510
- }
1511
- if (!parseOk) continue;
1512
- if (!sessionHygiene.isClaudeWarmupTranscript(events)) continue;
1513
-
1514
- try { fs.unlinkSync(fp); deleted++; } catch (e) {}
1515
- }
1516
- }
1517
-
1518
- if (deleted > 0) {
1519
- console.log("[daemon] Removed " + deleted + " stale SDK warmup session file(s)");
1520
- }
1521
- }
1323
+ function cleanupSdkWarmupSessions() { return require("./yoke/maintenance").cleanup(REAL_HOME); }
1522
1324
 
1523
1325
  // --- Start listening (with retry for port-in-use during update handoff) ---
1524
1326
  var listenRetries = 0;
@@ -3,11 +3,7 @@
3
3
  var modelEntryMatches = require("./model-entry").modelEntryMatches;
4
4
  var modelEntryValue = require("./model-entry").modelEntryValue;
5
5
 
6
- var KNOWN_VENDORS = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
7
- var VENDOR_FAMILY_ORDER = {
8
- claude: ["opus", "sonnet", "haiku"],
9
- codex: ["astra", "sol", "gpt", "o"],
10
- };
6
+ var providerInfo = require("./yoke/vendor-registry").getVendorInfo;
11
7
 
12
8
  function text(entry) {
13
9
  if (typeof entry === "string") return entry;
@@ -18,10 +14,10 @@ function selectTopModel(vendor, catalog) {
18
14
  var models = catalog && Array.isArray(catalog.models) ? catalog.models : [];
19
15
  if (!models.length) return "";
20
16
  var defaultModel = catalog.defaultModel;
21
- if (KNOWN_VENDORS.indexOf(vendor) === -1 && defaultModel) {
17
+ if (!providerInfo(vendor) && defaultModel) {
22
18
  for (var di = 0; di < models.length; di++) if (modelEntryMatches(models[di], defaultModel)) return modelEntryValue(models[di]);
23
19
  }
24
- var familyOrder = VENDOR_FAMILY_ORDER[vendor] || [];
20
+ var familyOrder = (providerInfo(vendor) || {}).preferredModelFamilies || [];
25
21
  if (familyOrder.length) {
26
22
  for (var fi = 0; fi < familyOrder.length; fi++) {
27
23
  for (var mi = 0; mi < models.length; mi++) if (new RegExp("(^|[^a-z])" + familyOrder[fi] + "([^a-z]|$)", "i").test(text(models[mi]))) return modelEntryValue(models[mi]);
@@ -114,4 +110,4 @@ function createDefaultAiRuntimeResolver(deps) {
114
110
  };
115
111
  }
116
112
 
117
- module.exports = { resolveDefaultAiRuntime: resolveDefaultAiRuntime, createDefaultAiRuntimeResolver: createDefaultAiRuntimeResolver, selectTopModel: selectTopModel, exactModel: exactModel, resolveEffort: resolveEffort, KNOWN_VENDORS: KNOWN_VENDORS, VENDOR_FAMILY_ORDER: VENDOR_FAMILY_ORDER };
113
+ module.exports = { resolveDefaultAiRuntime: resolveDefaultAiRuntime, createDefaultAiRuntimeResolver: createDefaultAiRuntimeResolver, selectTopModel: selectTopModel, exactModel: exactModel, resolveEffort: resolveEffort, KNOWN_VENDORS: Object.keys(require("./yoke/vendor-registry").VENDOR_REGISTRY), VENDOR_FAMILY_ORDER: require("./yoke/model-preferences").families() };
@@ -37,7 +37,7 @@ function buildShape(props, required) {
37
37
  // appendAuditLog(entry) -> void
38
38
  function create(deps) {
39
39
  var sdk;
40
- try { sdk = require("@anthropic-ai/claude-agent-sdk"); } catch (e) {
40
+ try { sdk = require("./yoke/sdk-server-factory").load(); } catch (e) {
41
41
  console.error("[email-mcp] Failed to load SDK:", e.message);
42
42
  return null;
43
43
  }