clay-server 5.2.0-beta.3 → 5.2.0-beta.5

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 (339) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +19 -16
  3. package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
  4. package/lib/canonical-clay-record-authority.js +15 -0
  5. package/lib/canonical-clay-scheduling-authority.js +19 -0
  6. package/lib/claude-hook-installer.js +28 -0
  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-workspace-controls.js +83 -0
  23. package/lib/clay-workspace-input.js +17 -0
  24. package/lib/clay-workspace-receipts.js +26 -0
  25. package/lib/daemon-project-access.js +11 -0
  26. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  27. package/lib/daemon-project-lifecycle.js +262 -0
  28. package/lib/daemon-projects.js +1 -1
  29. package/lib/daemon.js +73 -221
  30. package/lib/issues-service.js +29 -15
  31. package/lib/knowledge-db-import.js +5 -4
  32. package/lib/knowledge-db-service.js +9 -6
  33. package/lib/knowledge-document-index.js +2 -1
  34. package/lib/knowledge-file-store.js +7 -1
  35. package/lib/knowledge-product-operations.js +46 -0
  36. package/lib/mate-avatar-catalog.js +12 -0
  37. package/lib/mate-avatar-migration.js +41 -0
  38. package/lib/mate-creation-mcp-server.js +2 -0
  39. package/lib/mate-knowledge-canonical-access.js +10 -0
  40. package/lib/mate-knowledge-service.js +7 -4
  41. package/lib/mate-lifecycle-service.js +94 -0
  42. package/lib/mate-message-delivery.js +43 -0
  43. package/lib/mate-ready-creation.js +9 -1
  44. package/lib/mate-schedule-input.js +29 -0
  45. package/lib/mate-schedule-lifecycle.js +151 -0
  46. package/lib/mate-scheduling.js +103 -0
  47. package/lib/mates.js +18 -6
  48. package/lib/mcp-agent-tools.js +117 -0
  49. package/lib/mcp-connection-store.js +119 -15
  50. package/lib/mcp-import-credentials.js +31 -0
  51. package/lib/mcp-local-transport.js +13 -0
  52. package/lib/mcp-oauth.js +8 -6
  53. package/lib/mcp-query-bridge.js +30 -0
  54. package/lib/mcp-remote-connections.js +74 -20
  55. package/lib/mcp-source-adapters.js +91 -0
  56. package/lib/mcp-source-context.js +42 -0
  57. package/lib/mcp-source-discovery.js +169 -0
  58. package/lib/mcp-source-plugins.js +68 -0
  59. package/lib/mcp-source-restrictions.js +47 -0
  60. package/lib/mcp-tool-policy.js +34 -0
  61. package/lib/notes-checklist.js +50 -0
  62. package/lib/notes.js +49 -3
  63. package/lib/os-users.js +20 -5
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +8 -4
  66. package/lib/project-debate.js +1 -0
  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-knowledge.js +32 -18
  73. package/lib/project-lifecycle-operations.js +98 -0
  74. package/lib/project-lifecycle-process-witness.js +32 -0
  75. package/lib/project-lifecycle-runtime.js +50 -0
  76. package/lib/project-linux-access.js +22 -0
  77. package/lib/project-logs-canonical-clay.js +27 -0
  78. package/lib/project-logs-clay-query.js +16 -0
  79. package/lib/project-logs-schema.js +4 -2
  80. package/lib/project-logs-service.js +8 -7
  81. package/lib/project-logs.js +1 -0
  82. package/lib/project-loop.js +1 -0
  83. package/lib/project-mate-chat-control.js +55 -0
  84. package/lib/project-mate-creation-proposal.js +18 -3
  85. package/lib/project-mate-interaction.js +8 -0
  86. package/lib/project-mcp.js +54 -16
  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-pending-message-queue.js +1 -0
  92. package/lib/project-product-control.js +87 -0
  93. package/lib/project-product-session-queue.js +46 -0
  94. package/lib/project-push-access.js +28 -0
  95. package/lib/project-scheduled-message-queue.js +30 -0
  96. package/lib/project-scheduled-messages.js +63 -64
  97. package/lib/project-scheduled-task-control.js +83 -0
  98. package/lib/project-scheduled-tasks.js +13 -2
  99. package/lib/project-scheduling-access.js +10 -0
  100. package/lib/project-session-actions.js +65 -0
  101. package/lib/project-session-delete.js +8 -1
  102. package/lib/project-session-document.js +8 -3
  103. package/lib/project-session-github.js +63 -19
  104. package/lib/project-session-history-actions.js +165 -0
  105. package/lib/project-session-linear.js +14 -6
  106. package/lib/project-session-notes.js +7 -3
  107. package/lib/project-session-runtime-actions.js +63 -0
  108. package/lib/project-session-vendor.js +54 -0
  109. package/lib/project-sessions.js +68 -241
  110. package/lib/project-shared-browser.js +56 -12
  111. package/lib/project-user-message.js +30 -1
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +144 -37
  115. package/lib/public/app.js +5 -0
  116. package/lib/public/appearance-bootstrap.js +14 -0
  117. package/lib/public/css/clay-tool-activity.css +51 -0
  118. package/lib/public/css/clay-workspace.css +29 -0
  119. package/lib/public/css/home-debate-planning.css +2 -0
  120. package/lib/public/css/home-mate-creation.css +5 -0
  121. package/lib/public/css/mate-avatar-gallery.css +12 -0
  122. package/lib/public/css/mate-conversation.css +10 -5
  123. package/lib/public/css/overlays.css +4 -2
  124. package/lib/public/css/project-admin-overview.css +13 -0
  125. package/lib/public/css/session-folders.css +11 -0
  126. package/lib/public/css/session-github.css +46 -1
  127. package/lib/public/css/shared-browser.css +35 -1
  128. package/lib/public/css/sticky-notes.css +61 -1
  129. package/lib/public/index.html +11 -5
  130. package/lib/public/mate-avatars/B01.png +0 -0
  131. package/lib/public/mate-avatars/B02.png +0 -0
  132. package/lib/public/mate-avatars/B03.png +0 -0
  133. package/lib/public/mate-avatars/B04.png +0 -0
  134. package/lib/public/mate-avatars/B05.png +0 -0
  135. package/lib/public/mate-avatars/B06.png +0 -0
  136. package/lib/public/mate-avatars/B07.png +0 -0
  137. package/lib/public/mate-avatars/B08.png +0 -0
  138. package/lib/public/mate-avatars/B09.png +0 -0
  139. package/lib/public/mate-avatars/B10.png +0 -0
  140. package/lib/public/mate-avatars/B11.png +0 -0
  141. package/lib/public/mate-avatars/B12.png +0 -0
  142. package/lib/public/mate-avatars/B13.png +0 -0
  143. package/lib/public/mate-avatars/B14.png +0 -0
  144. package/lib/public/mate-avatars/B15.png +0 -0
  145. package/lib/public/mate-avatars/B16.png +0 -0
  146. package/lib/public/mate-avatars/B17.png +0 -0
  147. package/lib/public/mate-avatars/B18.png +0 -0
  148. package/lib/public/mate-avatars/B19.png +0 -0
  149. package/lib/public/mate-avatars/B20.png +0 -0
  150. package/lib/public/mate-avatars/B21.png +0 -0
  151. package/lib/public/mate-avatars/B22.png +0 -0
  152. package/lib/public/mate-avatars/B23.png +0 -0
  153. package/lib/public/mate-avatars/B24.png +0 -0
  154. package/lib/public/mate-avatars/B25.png +0 -0
  155. package/lib/public/mate-avatars/B26.png +0 -0
  156. package/lib/public/mate-avatars/B27.png +0 -0
  157. package/lib/public/mate-avatars/B28.png +0 -0
  158. package/lib/public/mate-avatars/B29.png +0 -0
  159. package/lib/public/mate-avatars/B30.png +0 -0
  160. package/lib/public/mate-avatars/B31.png +0 -0
  161. package/lib/public/mate-avatars/B32.png +0 -0
  162. package/lib/public/mate-avatars/B33.png +0 -0
  163. package/lib/public/mate-avatars/B34.png +0 -0
  164. package/lib/public/mate-avatars/B35.png +0 -0
  165. package/lib/public/mate-avatars/B36.png +0 -0
  166. package/lib/public/mate-avatars/B37.png +0 -0
  167. package/lib/public/mate-avatars/B38.png +0 -0
  168. package/lib/public/mate-avatars/B39.png +0 -0
  169. package/lib/public/mate-avatars/B40.png +0 -0
  170. package/lib/public/mate-avatars/B41.png +0 -0
  171. package/lib/public/mate-avatars/B42.png +0 -0
  172. package/lib/public/mate-avatars/B43.png +0 -0
  173. package/lib/public/mate-avatars/B44.png +0 -0
  174. package/lib/public/mate-avatars/B45.png +0 -0
  175. package/lib/public/mate-avatars/B46.png +0 -0
  176. package/lib/public/mate-avatars/B47.png +0 -0
  177. package/lib/public/mate-avatars/B48.png +0 -0
  178. package/lib/public/mate-avatars/C01.png +0 -0
  179. package/lib/public/mate-avatars/C02.png +0 -0
  180. package/lib/public/mate-avatars/C03.png +0 -0
  181. package/lib/public/mate-avatars/C04.png +0 -0
  182. package/lib/public/mate-avatars/C05.png +0 -0
  183. package/lib/public/mate-avatars/C06.png +0 -0
  184. package/lib/public/mate-avatars/C07.png +0 -0
  185. package/lib/public/mate-avatars/C08.png +0 -0
  186. package/lib/public/mate-avatars/C09.png +0 -0
  187. package/lib/public/mate-avatars/C10.png +0 -0
  188. package/lib/public/mate-avatars/C11.png +0 -0
  189. package/lib/public/mate-avatars/C12.png +0 -0
  190. package/lib/public/mate-avatars/C13.png +0 -0
  191. package/lib/public/mate-avatars/C14.png +0 -0
  192. package/lib/public/mate-avatars/C15.png +0 -0
  193. package/lib/public/mate-avatars/C16.png +0 -0
  194. package/lib/public/mate-avatars/C17.png +0 -0
  195. package/lib/public/mate-avatars/C18.png +0 -0
  196. package/lib/public/mate-avatars/C19.png +0 -0
  197. package/lib/public/mate-avatars/C20.png +0 -0
  198. package/lib/public/mate-avatars/C21.png +0 -0
  199. package/lib/public/mate-avatars/C22.png +0 -0
  200. package/lib/public/mate-avatars/C23.png +0 -0
  201. package/lib/public/mate-avatars/C24.png +0 -0
  202. package/lib/public/mate-avatars/C25.png +0 -0
  203. package/lib/public/mate-avatars/C26.png +0 -0
  204. package/lib/public/mate-avatars/C27.png +0 -0
  205. package/lib/public/mate-avatars/C28.png +0 -0
  206. package/lib/public/mate-avatars/C29.png +0 -0
  207. package/lib/public/mate-avatars/C30.png +0 -0
  208. package/lib/public/mate-avatars/C31.png +0 -0
  209. package/lib/public/mate-avatars/C32.png +0 -0
  210. package/lib/public/mate-avatars/C33.png +0 -0
  211. package/lib/public/mate-avatars/C34.png +0 -0
  212. package/lib/public/mate-avatars/C35.png +0 -0
  213. package/lib/public/mate-avatars/C36.png +0 -0
  214. package/lib/public/mate-avatars/C37.png +0 -0
  215. package/lib/public/mate-avatars/C38.png +0 -0
  216. package/lib/public/mate-avatars/C39.png +0 -0
  217. package/lib/public/mate-avatars/C40.png +0 -0
  218. package/lib/public/mate-avatars/C41.png +0 -0
  219. package/lib/public/mate-avatars/C42.png +0 -0
  220. package/lib/public/mate-avatars/C43.png +0 -0
  221. package/lib/public/mate-avatars/C44.png +0 -0
  222. package/lib/public/mate-avatars/C45.png +0 -0
  223. package/lib/public/mate-avatars/C46.png +0 -0
  224. package/lib/public/mate-avatars/C47.png +0 -0
  225. package/lib/public/mate-avatars/C48.png +0 -0
  226. package/lib/public/mate-avatars/catalog.json +482 -0
  227. package/lib/public/mate-avatars/lead-clay.png +0 -0
  228. package/lib/public/modules/admin-settings-projects.js +6 -2
  229. package/lib/public/modules/app-connection.js +8 -1
  230. package/lib/public/modules/app-message-router.js +4 -0
  231. package/lib/public/modules/app-messages.js +31 -5
  232. package/lib/public/modules/app-projects.js +5 -0
  233. package/lib/public/modules/app-rendering.js +1 -1
  234. package/lib/public/modules/avatar.js +15 -41
  235. package/lib/public/modules/chat-render-runtime.js +10 -1
  236. package/lib/public/modules/clay-browser-links.js +47 -0
  237. package/lib/public/modules/clay-log-links.js +1 -1
  238. package/lib/public/modules/clay-owner-navigation.js +27 -0
  239. package/lib/public/modules/clay-tool-activity.js +140 -0
  240. package/lib/public/modules/clay-tool-presentation.js +338 -0
  241. package/lib/public/modules/command-palette.js +2 -2
  242. package/lib/public/modules/debate.js +1 -0
  243. package/lib/public/modules/default-ai.js +11 -3
  244. package/lib/public/modules/history-replay-batch.js +5 -1
  245. package/lib/public/modules/home-debate-live.js +2 -0
  246. package/lib/public/modules/home-debate-planning.js +7 -7
  247. package/lib/public/modules/home-mate-avatar-editor.js +127 -208
  248. package/lib/public/modules/home-mate-chat.js +8 -6
  249. package/lib/public/modules/home-mate-creation.js +73 -1
  250. package/lib/public/modules/home-mate-settings.js +19 -12
  251. package/lib/public/modules/markdown.js +4 -0
  252. package/lib/public/modules/mate-avatar-catalog.js +97 -0
  253. package/lib/public/modules/mate-management.js +40 -7
  254. package/lib/public/modules/mcp-connection-view.js +31 -13
  255. package/lib/public/modules/mcp-discovery-view.js +31 -0
  256. package/lib/public/modules/mcp-ui.js +23 -4
  257. package/lib/public/modules/mention.js +3 -0
  258. package/lib/public/modules/pane-browser-bridge.js +20 -0
  259. package/lib/public/modules/personal-settings-dialog.js +7 -1
  260. package/lib/public/modules/profile.js +1 -238
  261. package/lib/public/modules/project-access-recovery.js +17 -0
  262. package/lib/public/modules/project-admin-overview.js +21 -0
  263. package/lib/public/modules/project-mate-navigation.js +99 -14
  264. package/lib/public/modules/search-clay-chat.js +16 -2
  265. package/lib/public/modules/session-folder-layout.js +1 -1
  266. package/lib/public/modules/session-folder-view-menu.js +9 -0
  267. package/lib/public/modules/session-github.js +85 -14
  268. package/lib/public/modules/session-work-primary.js +51 -0
  269. package/lib/public/modules/shared-browser-settings.js +112 -0
  270. package/lib/public/modules/shared-browser-tabs.js +13 -3
  271. package/lib/public/modules/shared-browser.js +27 -6
  272. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  273. package/lib/public/modules/split-view.js +2 -0
  274. package/lib/public/modules/sticky-note-markdown.js +72 -6
  275. package/lib/public/modules/sticky-notes-actions.js +30 -0
  276. package/lib/public/modules/sticky-notes-browser.js +9 -2
  277. package/lib/public/modules/sticky-notes-card.js +23 -7
  278. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  279. package/lib/public/modules/sticky-notes-editor.js +108 -44
  280. package/lib/public/modules/sticky-notes-shared.js +50 -2
  281. package/lib/public/modules/sticky-notes-state.js +66 -0
  282. package/lib/public/modules/sticky-notes.js +34 -51
  283. package/lib/public/modules/theme.js +8 -1
  284. package/lib/public/modules/thinking-lifecycle.js +33 -3
  285. package/lib/public/modules/thinking-view.js +23 -1
  286. package/lib/public/modules/tools.js +36 -7
  287. package/lib/public/modules/user-settings-appearance.js +10 -3
  288. package/lib/public/modules/user-settings-editors.js +1 -1
  289. package/lib/public/modules/user-settings-layout.js +11 -2
  290. package/lib/public/modules/user-settings-preferences.js +6 -2
  291. package/lib/public/modules/user-settings-profile.js +12 -2
  292. package/lib/public/modules/user-settings.js +3 -2
  293. package/lib/public/style.css +3 -0
  294. package/lib/public/sw.js +3 -1
  295. package/lib/scheduled-task-records.js +3 -1
  296. package/lib/scheduler.js +2 -0
  297. package/lib/sdk-bridge.js +228 -23
  298. package/lib/sdk-message-processor.js +2 -0
  299. package/lib/sdk-response-recovery.js +155 -0
  300. package/lib/server-admin.js +63 -16
  301. package/lib/server-appearance-settings.js +13 -1
  302. package/lib/server-dm.js +2 -1
  303. package/lib/server-global-ws.js +10 -20
  304. package/lib/server-home-chat-events.js +18 -4
  305. package/lib/server-home-chat.js +12 -16
  306. package/lib/server-home-mate-creation.js +1 -0
  307. package/lib/server-home-mcp-consent.js +14 -0
  308. package/lib/server-mates.js +25 -62
  309. package/lib/server-owner-ui.js +77 -0
  310. package/lib/server-path-access.js +38 -0
  311. package/lib/server-project-removal.js +22 -0
  312. package/lib/server-settings.js +4 -1
  313. package/lib/server.js +144 -41
  314. package/lib/session-config-transition.js +45 -0
  315. package/lib/session-folders.js +7 -2
  316. package/lib/session-github.js +9 -2
  317. package/lib/session-visibility.js +5 -2
  318. package/lib/session-work-primary.js +50 -0
  319. package/lib/sessions.js +13 -0
  320. package/lib/shared-browser-cookie-sync.js +32 -0
  321. package/lib/shared-browser-cookies.js +79 -0
  322. package/lib/shared-browser-runtime.js +25 -9
  323. package/lib/shared-browser-storage-sync.js +43 -0
  324. package/lib/shared-browser-storage.js +101 -0
  325. package/lib/shared-browser-tools.js +2 -1
  326. package/lib/shared-browser-worker.js +46 -10
  327. package/lib/terminal-manager.js +52 -0
  328. package/lib/users-permissions.js +2 -8
  329. package/lib/users.js +4 -0
  330. package/lib/workspace-query-access.js +4 -1
  331. package/lib/ws-schema.js +8 -2
  332. package/lib/yoke/acp-agent-profiles.js +2 -2
  333. package/lib/yoke/adapters/claude.js +47 -8
  334. package/lib/yoke/adapters/codex.js +52 -111
  335. package/lib/yoke/claude-worker-startup.js +28 -0
  336. package/lib/yoke/codex-idle-recovery.js +81 -1
  337. package/lib/yoke/codex-recovery-admission.js +164 -0
  338. package/lib/yoke/session-tool-transport.js +3 -0
  339. package/package.json +5 -1
@@ -74,12 +74,26 @@ function attachMates(ctx) {
74
74
  var mateSlug = "mate-" + mate.id;
75
75
  var mateName = (mate.profile && mate.profile.displayName) || mate.name || "Mate";
76
76
  var registered = false;
77
+ var boundScheduleJob = null;
77
78
  var favorites;
78
79
  try {
79
80
  registered = addProject(mateDir, mateSlug, mateName, null, mate.createdBy || effectiveUserId, null, { isMate: true, mateDisplayName: mateName }) !== false;
80
81
  if (!registered) throw new Error("The Mate project could not be registered.");
82
+ var intent = mate.seedData && mate.seedData.scheduleIntent;
83
+ if (intent && intent.message && intent.scheduleRule) {
84
+ var targetProject = projects.get(mateSlug);
85
+ var targetManager = targetProject && targetProject.getSessionManager();
86
+ if (!targetProject || !targetManager || typeof targetProject.bindMateSchedule !== "function") throw new Error("The Mate schedule could not be bound to the new Mate.");
87
+ var targetSession = targetManager.createSessionRaw({ ownerId: users.isMultiUser() ? effectiveUserId : null, vendor: mate.vendor, model: mate.model });
88
+ boundScheduleJob = targetProject.bindMateSchedule(targetSession, intent, "creation-" + mate.id);
89
+ if (!boundScheduleJob) throw new Error("The Mate schedule could not be persisted.");
90
+ mates.updateMate(mateCtx, mate.id, { seedData: Object.assign({}, mate.seedData, { scheduleIntent: null }) });
91
+ }
81
92
  favorites = users.addDmFavorite(effectiveUserId, mate.id);
82
93
  } catch (error) {
94
+ if (targetSession && targetSession.mateSchedules && targetProject && typeof targetProject.cancelMateSchedule === "function") {
95
+ try { targetSession.mateSchedules.forEach(function (record) { targetProject.cancelMateSchedule(targetSession, record); }); } catch (cancelError) {}
96
+ }
83
97
  if (registered) try { removeProject(mateSlug); } catch (removeError) {}
84
98
  try { mates.deleteMate(mateCtx, mate.id); } catch (deleteError) {}
85
99
  throw error;
@@ -97,6 +111,10 @@ function attachMates(ctx) {
97
111
  return mate;
98
112
  }
99
113
 
114
+ var lifecycle = require("./mate-lifecycle-service").attachMateLifecycleService({
115
+ users: users, mates: mates, projects: projects, removeProject: removeProject, refresh: refreshTeamSections,
116
+ });
117
+
100
118
  // --- Mate message handlers ---
101
119
 
102
120
  function handleMessage(ws, msg) {
@@ -168,7 +186,7 @@ function attachMates(ctx) {
168
186
  for (var mi = 0; mi < mateList.length; mi++) {
169
187
  var m = mateList[mi];
170
188
  var mSlug = "mate-" + m.id;
171
- if (!projects.has(mSlug)) {
189
+ if (!m.lifecycleDeletion && !projects.has(mSlug)) {
172
190
  var mDir = mates.getMateDir(mateCtx5, m.id);
173
191
  fs.mkdirSync(mDir, { recursive: true });
174
192
  var mName = (m.profile && m.profile.displayName) || m.name || "New Mate";
@@ -200,45 +218,9 @@ function attachMates(ctx) {
200
218
 
201
219
  if (msg.type === "mate_delete") {
202
220
  if (!msg.mateId) return true;
203
- var mateCtx6 = mates.buildMateCtx(userId);
204
- // Track deleted built-in mate key so it doesn't auto-recreate
205
- var mateToDelete = mates.getMate(mateCtx6, msg.mateId);
206
- if (mateToDelete && mateToDelete.builtinKey) {
207
- users.addDeletedBuiltinKey(userId, mateToDelete.builtinKey);
208
- }
209
- var result = mates.deleteMate(mateCtx6, msg.mateId);
210
- if (result.error) {
211
- ws.send(JSON.stringify({ type: "mate_error", error: result.error }));
212
- } else {
213
- removeProject("mate-" + msg.mateId);
214
- // Build updated available builtins list
215
- var builtinDefs3 = require("./builtin-mates");
216
- var missingKeys3 = mates.getMissingBuiltinKeys(mateCtx6);
217
- var availableBuiltins3 = [];
218
- for (var abk3 = 0; abk3 < missingKeys3.length; abk3++) {
219
- var bDef3 = builtinDefs3.getBuiltinByKey(missingKeys3[abk3]);
220
- if (bDef3) {
221
- availableBuiltins3.push({
222
- key: bDef3.key,
223
- displayName: bDef3.displayName,
224
- bio: bDef3.bio,
225
- avatarCustom: bDef3.avatarCustom || "",
226
- avatarStyle: bDef3.avatarStyle || "imprint",
227
- avatarColor: bDef3.avatarColor || "",
228
- });
229
- }
230
- }
231
- ws.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
232
- // Broadcast to all clients so strips update
233
- projects.forEach(function (pCtx) {
234
- pCtx.forEachClient(function (otherWs) {
235
- if (otherWs === ws) return;
236
- if (users.isMultiUser() && (!otherWs._clayUser || otherWs._clayUser.id !== userId)) return;
237
- if (otherWs.readyState !== 1) return;
238
- otherWs.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
239
- });
240
- });
241
- }
221
+ lifecycle.remove(userId, msg.mateId, null, ws).then(function () {}, function (error) {
222
+ ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, error: error.message }));
223
+ });
242
224
  return true;
243
225
  }
244
226
 
@@ -303,28 +285,8 @@ function attachMates(ctx) {
303
285
  ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, error: "Use the Mate model selector to change this setting." }));
304
286
  return true;
305
287
  }
306
- var mateCtx7 = mates.buildMateCtx(userId);
307
- var updated = mates.updateMate(mateCtx7, msg.mateId, msg.updates);
308
- if (updated && updated.error) {
309
- ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: updated.error }));
310
- return true;
311
- }
312
- if (updated) {
313
- ws.send(JSON.stringify({ type: "mate_updated", mate: updated, requestId: msg.requestId }));
314
- // Broadcast update
315
- projects.forEach(function (pCtx) {
316
- pCtx.forEachClient(function (otherWs) {
317
- if (otherWs === ws) return;
318
- if (users.isMultiUser() && (!otherWs._clayUser || otherWs._clayUser.id !== userId)) return;
319
- if (otherWs.readyState !== 1) return;
320
- otherWs.send(JSON.stringify({ type: "mate_updated", mate: updated }));
321
- });
322
- });
323
- // Re-enforce team sections across all mate projects so roster stays current
324
- refreshTeamSections(mateCtx7);
325
- } else {
326
- ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: "Mate not found" }));
327
- }
288
+ try { lifecycle.update(userId, msg.mateId, msg.updates, ws, msg.requestId); }
289
+ catch (error) { ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: error.message })); }
328
290
  return true;
329
291
  }
330
292
 
@@ -333,6 +295,7 @@ function attachMates(ctx) {
333
295
 
334
296
  return {
335
297
  handleMessage: handleMessage,
298
+ lifecycle: lifecycle,
336
299
  scheduleRegistryRefresh: scheduleRegistryRefresh,
337
300
  createReadyMateFromInterview: createReadyMateFromInterview,
338
301
  };
@@ -0,0 +1,77 @@
1
+ var crypto = require('crypto');
2
+ // Only authenticated application sockets enumerated by the server are targets.
3
+ function attachOwnerUi(ctx) {
4
+ var identities = new WeakMap(), capabilities = new WeakMap(), deliveries = new Map();
5
+ function alive(ws, owner) {
6
+ return ws && ws.readyState === 1 && (!ws._clayAuthCheck || ws._clayAuthCheck()) &&
7
+ (ctx.isMultiUser() ? !!owner && ws._clayUser && ws._clayUser.id === owner && !!ctx.findUser(owner) : !ws._clayUser);
8
+ }
9
+ function sockets(owner) {
10
+ var all = new Map();
11
+ ctx.forEachClient(function (ws, slug) {
12
+ if (!alive(ws, owner) || all.has(ws)) return;
13
+ if (!identities.has(ws)) identities.set(ws, 'viewer:' + crypto.randomUUID());
14
+ all.set(ws, { ws: ws, ref: identities.get(ws), slug: slug || null, sessionId: ws._clayActiveSession || null,
15
+ supported: capabilities.get(ws) === 1 && !ws._clayPane });
16
+ });
17
+ return Array.from(all.values());
18
+ }
19
+ function get(owner, ref) { return sockets(owner).find(function (item) { return item.ref === ref; }); }
20
+ function inspect(owner, ref) {
21
+ var viewer = get(owner, ref);
22
+ if (!viewer) throw new Error('The exact owner UI connection is absent or revoked.');
23
+ return viewer;
24
+ }
25
+ function check(owner, original) {
26
+ var live = inspect(owner, original.ref);
27
+ if (live.ws !== original.ws || live.slug !== original.slug || live.sessionId !== original.sessionId || live.supported !== original.supported) throw new Error('The selected viewer context changed. Inspect again.');
28
+ return live;
29
+ }
30
+ function deliver(owner, original, target, revalidate) {
31
+ revalidate(); var viewer = check(owner, original);
32
+ if (!viewer.supported) return { status: 'unsupported', delivered: false, acknowledged: false, visible: 'unknown' };
33
+ if (deliveries.size >= 100) {
34
+ deliveries.forEach(function (item, key) { if (Date.now() - item.created > 300000) deliveries.delete(key); });
35
+ if (deliveries.size >= 100) throw new Error('UI delivery capacity is full; wait for earlier attempts to expire.');
36
+ }
37
+ var id = 'navigation:' + crypto.randomUUID();
38
+ var record = { owner: owner, viewer: viewer, target: target, revalidate: revalidate, created: Date.now(), claimed: false,
39
+ result: { status: 'delivered', deliveryId: id, delivered: false, acknowledged: false, visible: 'unknown' } };
40
+ deliveries.set(id, record);
41
+ try {
42
+ viewer.ws.send(JSON.stringify({ type: 'clay_navigation_offer', deliveryId: id }));
43
+ if (!alive(viewer.ws, owner)) throw new Error('Viewer authorization or connection ended during delivery.');
44
+ record.result.delivered = true;
45
+ } catch (error) { record.result.status = 'failed'; record.result.error = error.message; record.result.retrySafe = false; }
46
+ return Object.assign({}, record.result);
47
+ }
48
+ function read(owner, id) {
49
+ var record = deliveries.get(id);
50
+ if (!record || record.owner !== owner) throw new Error('No delivery exists for this owner.');
51
+ record.revalidate();
52
+ return Object.assign({}, record.result);
53
+ }
54
+ function handleMessage(ws, msg) {
55
+ if (['clay_ui_ready', 'clay_navigation_claim', 'clay_navigation_ack'].indexOf(msg.type) < 0) return false;
56
+ var owner = ctx.isMultiUser() ? ws._clayUser && ws._clayUser.id : null;
57
+ if (!sockets(owner).some(function (item) { return item.ws === ws; })) return true;
58
+ if (msg.type === 'clay_ui_ready') { if (msg.version === 1) capabilities.set(ws, 1); return true; }
59
+ var record = deliveries.get(msg.deliveryId);
60
+ if (!record || record.viewer.ws !== ws || record.owner !== owner || Date.now() - record.created > 30000) return true;
61
+ try {
62
+ record.revalidate();
63
+ if (msg.type === 'clay_navigation_claim') {
64
+ if (record.claimed) return true;
65
+ check(owner, record.viewer); record.claimed = true;
66
+ ws.send(JSON.stringify({ type: 'clay_navigation_apply', deliveryId: msg.deliveryId, target: record.target }));
67
+ } else if (record.claimed && !record.result.acknowledged && ['accepted', 'refused', 'failed'].indexOf(msg.status) >= 0) {
68
+ record.result.acknowledged = true;
69
+ // Accepted only confirms invocation of the existing navigator, never rendering.
70
+ record.result.clientStatus = msg.status;
71
+ }
72
+ } catch (error) { record.result.status = 'failed'; record.result.error = error.message; record.result.retrySafe = false; }
73
+ return true;
74
+ }
75
+ return { list: sockets, inspect: inspect, check: check, deliver: deliver, read: read, handleMessage: handleMessage };
76
+ }
77
+ module.exports = { attachOwnerUi: attachOwnerUi };
@@ -0,0 +1,38 @@
1
+ // Directory pickers and project registration must not become content-access
2
+ // bypasses through another spelling, symlink or nested directory.
3
+ var fs = require("fs");
4
+ var path = require("path");
5
+ function inside(root, target) {
6
+ var relative = path.relative(root, target);
7
+ return relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
8
+ }
9
+ function attachPathAccess(ctx) {
10
+ return function (userId, requested) {
11
+ if (!ctx.users.isMultiUser()) return true;
12
+ var user = userId && ctx.users.findUserById(userId);
13
+ if (!user) return false;
14
+ var target;
15
+ try { target = fs.realpathSync(requested); } catch (error) { return false; }
16
+ var allowed = false;
17
+ var denied = false;
18
+ ctx.projects.forEach(function (project, slug) {
19
+ var status = project.getStatus();
20
+ var root;
21
+ try { root = fs.realpathSync(status.path); } catch (error) { return; }
22
+ if (inside(root, target) || inside(target, root)) {
23
+ if (!ctx.canAccess(userId, slug)) denied = true;
24
+ else if (inside(root, target)) allowed = true;
25
+ }
26
+ });
27
+ if (denied) return false;
28
+ if (ctx.osUsers) {
29
+ if (!user.linuxUser) return false;
30
+ try {
31
+ var identity = ctx.resolveIdentity(user.linuxUser);
32
+ return inside(fs.realpathSync(identity.home), target);
33
+ } catch (error) { return false; }
34
+ }
35
+ return allowed || ctx.users.getAllUsers().length === 1;
36
+ };
37
+ }
38
+ module.exports = { attachPathAccess: attachPathAccess };
@@ -0,0 +1,22 @@
1
+ // Shared runtime removal used by the server callback and lifecycle controls.
2
+ function removeProjectRuntime(projects, slug, awaitCompletion, registryRefresh) {
3
+ var project = projects.get(slug);
4
+ if (!project) return false;
5
+ var wasMate = project.getStatus().isMate;
6
+ var shutdownResult = project.destroy();
7
+ if (!awaitCompletion) projects.delete(slug);
8
+ if (shutdownResult && typeof shutdownResult.catch === 'function') {
9
+ shutdownResult.catch(function (error) {
10
+ console.error('[server] Project destroy failed for ' + slug + ':', error && error.message ? error.message : error);
11
+ });
12
+ }
13
+ if (!wasMate && registryRefresh) registryRefresh();
14
+ if (awaitCompletion) return Promise.resolve(shutdownResult).then(function (result) {
15
+ if (result !== true) throw new Error('Project runtime shutdown did not complete successfully: ' + slug);
16
+ if (projects.get(slug) !== project) throw new Error('Project runtime was replaced during shutdown: ' + slug);
17
+ projects.delete(slug);
18
+ return true;
19
+ });
20
+ return true;
21
+ }
22
+ module.exports = { removeProjectRuntime: removeProjectRuntime };
@@ -755,7 +755,10 @@ function attachSettings(ctx) {
755
755
  return false;
756
756
  }
757
757
 
758
- return { handleRequest: handleRequest };
758
+ return {
759
+ handleRequest: handleRequest,
760
+ getInitialAppearance: appearanceSettings.getInitialAppearance
761
+ };
759
762
  }
760
763
 
761
764
  module.exports = { attachSettings: attachSettings };
package/lib/server.js CHANGED
@@ -219,6 +219,14 @@ function createServer(opts) {
219
219
  var isRequestAuthed = auth.isRequestAuthed;
220
220
  var parseCookies = auth.parseCookies;
221
221
 
222
+ var projectAdminOverview = require("./project-admin-overview");
223
+
224
+ function administrativeMetadata(userId, slug) {
225
+ var user = userId && users.findUserById(userId);
226
+ var context = projects.get(slug);
227
+ return projectAdminOverview.metadataFor(user, context && context.getStatus(), onGetProjectAccess && onGetProjectAccess(slug), users);
228
+ }
229
+
222
230
  function canUserAccessSlug(userId, slug) {
223
231
  if (!userId || !slug) return false;
224
232
  var project = projects && projects.get(slug);
@@ -237,6 +245,13 @@ function createServer(opts) {
237
245
  return users.canAccessProject(userId, access) === true;
238
246
  }
239
247
 
248
+ var projects = new Map(); // slug → projectContext
249
+ var lifecycleRuntime = require("./project-lifecycle-runtime").attachLifecycleRuntime({ projects: projects });
250
+ var canUsePath = require("./server-path-access").attachPathAccess({
251
+ users: users, projects: projects, canAccess: canUserAccessSlug, osUsers: osUsers,
252
+ resolveIdentity: require("./os-users").resolveOsUserInfo,
253
+ });
254
+
240
255
  var realVersion = require("../package.json").version;
241
256
  var currentVersion = debug ? "0.0.9" : realVersion;
242
257
 
@@ -341,7 +356,6 @@ function createServer(opts) {
341
356
  var caContent = caPath ? (function () { try { return fs.readFileSync(caPath); } catch (e) { return null; } })() : null;
342
357
 
343
358
  // --- Project registry ---
344
- var projects = new Map(); // slug → projectContext
345
359
  var assignmentService = attachWorkspaceAssignmentService({
346
360
  storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
347
361
  getProjects: function () { return projects; },
@@ -363,6 +377,29 @@ function createServer(opts) {
363
377
  },
364
378
  assignmentService: assignmentService,
365
379
  });
380
+ var ownerUiService = require('./server-owner-ui').attachOwnerUi({
381
+ isMultiUser: users.isMultiUser, findUser: users.findUserById,
382
+ forEachClient: function (visit) {
383
+ if (globalWs) globalWs.forEachClient(function (ws) { visit(ws, null); });
384
+ projects.forEach(function (project, slug) { project.forEachClient(function (ws) { visit(ws, slug); }); });
385
+ }
386
+ });
387
+ var clayProductControl = require("./clay-product-control").attachClayProductControl({
388
+ getOwnerUi: function () { return ownerUiService; },
389
+ getNotifications: function () { return _globalNotifications; },
390
+ getRecordServices: function () { return { issues: issuesService, logs: projectLogsService }; },
391
+ projectLifecycle: opts.projectLifecycle || null,
392
+ getMateLifecycle: function () { return mateHandler && mateHandler.lifecycle; },
393
+ getMateDir: function (ownerId, mateId) { return mates.getMateDir(mates.buildMateCtx(users.isMultiUser() ? ownerId : null), mateId); },
394
+ lifecyclePermissions: function (user) { return users.getEffectivePermissions(user, osUsers); },
395
+ canUsePath: canUsePath,
396
+ getProjects: function () { return projects; },
397
+ isMultiUser: function () { return users.isMultiUser(); },
398
+ onGetProjectAccess: onGetProjectAccess,
399
+ canAccessProject: function (userId, access) { return users.canAccessProject(userId, access) === true; },
400
+ findUser: function (userId) { return users.findUserById(userId); },
401
+ resolveMate: function (userId, mateId) { return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId); },
402
+ });
366
403
  // Project access is resolved from the authoritative project record, not from
367
404
  // getStatus(), which carries no visibility or allowedUsers fields.
368
405
  var projectLogsService = attachProjectLogsService({
@@ -409,6 +446,7 @@ function createServer(opts) {
409
446
  // Mate Knowledge reads the synchronized Knowledge records. The Mate list is
410
447
  // derived from the owning user alone, so a binding can never span users.
411
448
  var mateKnowledgeService = attachMateKnowledgeService({
449
+ findUser: function (userId) { return users.findUserById(userId); },
412
450
  getProjects: function () { return projects; },
413
451
  isMultiUser: function () { return users.isMultiUser(); },
414
452
  resolveMate: function (userId, mateId) {
@@ -491,6 +529,41 @@ function createServer(opts) {
491
529
  },
492
530
  });
493
531
 
532
+ function injectAppearanceSeed(html, appearance) {
533
+ var validId = function (value) {
534
+ return typeof value === "string" && /^[a-zA-Z0-9_-]{1,120}$/.test(value) ? value : null;
535
+ };
536
+ var mode = appearance && ["light", "dark"].indexOf(appearance.mode) >= 0 ? appearance.mode : null;
537
+ var seed = appearance && appearance.configured === true ? {
538
+ configured: true,
539
+ theme: validId(appearance.theme),
540
+ mode: mode,
541
+ lightTheme: validId(appearance.lightTheme),
542
+ darkTheme: validId(appearance.darkTheme),
543
+ } : null;
544
+ var payload = JSON.stringify(seed).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
545
+ var script = '<script>window.__clayAppearanceSeed=' + payload + ';</script>';
546
+ return html.replace('<script src="appearance-bootstrap.js"></script>', script + '<script src="appearance-bootstrap.js"></script>');
547
+ }
548
+
549
+ function sendAppShell(req, res, slug) {
550
+ try {
551
+ var shell = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
552
+ if (slug) shell = injectRootProjectSlug(shell, slug);
553
+ var appearance = null;
554
+ try { appearance = settings.getInitialAppearance(req); } catch (e) {}
555
+ shell = injectAppearanceSeed(shell, appearance);
556
+ res.writeHead(200, {
557
+ "Content-Type": "text/html; charset=utf-8",
558
+ "Cache-Control": "private, no-store",
559
+ });
560
+ res.end(shell);
561
+ } catch (e) {
562
+ res.writeHead(500, { "Content-Type": "text/plain" });
563
+ res.end("App shell missing.");
564
+ }
565
+ }
566
+
494
567
  var palette = serverPalette.attachPalette({
495
568
  users: users,
496
569
  projects: projects,
@@ -512,7 +585,28 @@ function createServer(opts) {
512
585
  broadcastAll: function (msg) { broadcastAll(msg); },
513
586
  sendToUser: function (userId, msg) { sendToUser(userId, msg); },
514
587
  pushModule: pushModule,
588
+ forEachClient: forEachAppClient,
589
+ getUsers: function () { return users.getAllUsers(); },
590
+ canView: function (ws, notification) { return canViewNotification(ws && ws._clayUser && ws._clayUser.id, notification); },
591
+ canViewPrincipal: function (principal, notification) { return canViewNotification(principal.userId, notification); },
515
592
  });
593
+ function canViewNotification(userId, notification) {
594
+ if (!users.isMultiUser()) return true;
595
+ if (!userId || !users.findUserById(userId)) return false;
596
+ if (notification.targetUserId && notification.targetUserId !== userId) return false;
597
+ if (notification.slug) {
598
+ if (!canUserAccessSlug(userId, notification.slug)) return false;
599
+ if (notification.sessionId) {
600
+ var project = projects.get(notification.slug);
601
+ var session = project && project.getSessionManager().sessions.get(notification.sessionId);
602
+ var status = project && project.getStatus();
603
+ var access = status && status.isMate ? { isMate: true, visibility: "private", ownerId: status.projectOwnerId } : onGetProjectAccess(notification.slug);
604
+ if (!session || !users.canAccessSession(userId, session, access)) return false;
605
+ }
606
+ return !notification.ownerId || notification.ownerId === userId || !!notification.sessionId;
607
+ }
608
+ return notification.targetUserId === userId || notification.ownerId === userId;
609
+ }
516
610
 
517
611
  // --- Security headers ---
518
612
  var securityHeaders = {
@@ -910,15 +1004,7 @@ function createServer(opts) {
910
1004
  });
911
1005
  }
912
1006
  if (targetSlug) {
913
- try {
914
- var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
915
- homeHtml = injectRootProjectSlug(homeHtml, targetSlug);
916
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
917
- res.end(homeHtml);
918
- } catch (e) {
919
- res.writeHead(500, { "Content-Type": "text/plain" });
920
- res.end("App shell missing.");
921
- }
1007
+ sendAppShell(req, res, targetSlug);
922
1008
  return;
923
1009
  }
924
1010
  }
@@ -936,9 +1022,7 @@ function createServer(opts) {
936
1022
  return;
937
1023
  }
938
1024
  }
939
- if (serveStatic("/index.html", res)) return;
940
- res.writeHead(500, { "Content-Type": "text/plain" });
941
- res.end("App shell missing.");
1025
+ sendAppShell(req, res, null);
942
1026
  return;
943
1027
  }
944
1028
 
@@ -955,8 +1039,8 @@ function createServer(opts) {
955
1039
  var projectList = [];
956
1040
  var infoUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
957
1041
  projects.forEach(function (ctx, slug) {
958
- if (users.isMultiUser() && (!infoUser || !canUserAccessSlug(infoUser.id, slug))) return;
959
- projectList.push({ slug: slug, project: ctx.project });
1042
+ if (users.isMultiUser() && (!infoUser || (!canUserAccessSlug(infoUser.id, slug) && !administrativeMetadata(infoUser.id, slug)))) return;
1043
+ projectList.push({ slug: slug, project: ctx.project, contentAccess: !users.isMultiUser() || canUserAccessSlug(infoUser.id, slug) });
960
1044
  });
961
1045
  res.writeHead(200, { "Content-Type": "application/json" });
962
1046
  res.end(JSON.stringify({ projects: projectList, version: currentVersion, authenticated: true }));
@@ -965,6 +1049,10 @@ function createServer(opts) {
965
1049
 
966
1050
  // Static files (favicon, manifest, icons, sw.js, mate avatars, etc.)
967
1051
  if (!fullUrl.includes("..") && !fullUrl.startsWith("/p/") && !fullUrl.startsWith("/api/")) {
1052
+ if (fullUrl === "/index.html") {
1053
+ sendAppShell(req, res, null);
1054
+ return;
1055
+ }
968
1056
  if (serveStatic(fullUrl, res)) return;
969
1057
  }
970
1058
 
@@ -1028,8 +1116,14 @@ function createServer(opts) {
1028
1116
  if (users.isMultiUser() && !isMcpBridgeAuthenticated) {
1029
1117
  var httpUser = getMultiUserFromReq(req);
1030
1118
  if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
1031
- res.writeHead(302, { "Location": "/" });
1032
- res.end();
1119
+ var overview = httpUser && administrativeMetadata(httpUser.id, slug);
1120
+ if (overview && req.method === "GET" && (projectUrlForAuth === "/" || projectUrlForAuth === "/index.html")) {
1121
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
1122
+ res.end(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)));
1123
+ return;
1124
+ }
1125
+ res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
1126
+ res.end(JSON.stringify({ error: "Project content access is not permitted" }));
1033
1127
  return;
1034
1128
  }
1035
1129
  }
@@ -1054,6 +1148,10 @@ function createServer(opts) {
1054
1148
 
1055
1149
  // Static files (same assets for all projects)
1056
1150
  if (req.method === "GET") {
1151
+ if (projectUrl === "/" || projectUrl === "/index.html") {
1152
+ sendAppShell(req, res, slug);
1153
+ return;
1154
+ }
1057
1155
  if (serveStatic(projectUrl, res)) return;
1058
1156
  }
1059
1157
 
@@ -1134,6 +1232,7 @@ function createServer(opts) {
1134
1232
  var globalWs = serverGlobalWs.attachGlobalWs({
1135
1233
  osUsers: osUsers,
1136
1234
  usersModule: users,
1235
+ canUsePath: canUsePath,
1137
1236
  onAddProject: onAddProject,
1138
1237
  onCreateProject: onCreateProject,
1139
1238
  onCloneProject: onCloneProject,
@@ -1274,17 +1373,10 @@ function createServer(opts) {
1274
1373
  if (processingUpdateTimer) clearTimeout(processingUpdateTimer);
1275
1374
  processingUpdateTimer = setTimeout(function () {
1276
1375
  processingUpdateTimer = null;
1277
- var allProjectsList = getProjects();
1278
1376
  // Always send per-client to include cross-project unread counts
1279
1377
  projects.forEach(function (ctx, projSlug) {
1280
1378
  ctx.forEachClient(function (ws) {
1281
- var filtered = allProjectsList;
1282
- if (users.isMultiUser() && onGetProjectAccess) {
1283
- var wsUser = ws._clayUser;
1284
- filtered = wsUser ? allProjectsList.filter(function (p) {
1285
- return canUserAccessSlug(wsUser.id, p.slug);
1286
- }) : [];
1287
- }
1379
+ var filtered = getVisibleProjects(ws._clayUser ? ws._clayUser.id : null);
1288
1380
  // Attach per-project unread counts for this client
1289
1381
  var unreadMap = getCrossProjectUnread(ws);
1290
1382
  var projectsWithUnread = filtered.map(function (p) {
@@ -1334,6 +1426,10 @@ function createServer(opts) {
1334
1426
  function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
1335
1427
  if (projects.has(slug)) return false;
1336
1428
  var extra = extraOpts || {};
1429
+ if (extra.isMate) {
1430
+ var registeredMate = mates.getMate(mates.buildMateCtx(users.isMultiUser() ? projectOwnerId : null), extra.mateId || path.basename(cwd));
1431
+ if (registeredMate && registeredMate.lifecycleDeletion) return false;
1432
+ }
1337
1433
  var ctx = createProjectContext({
1338
1434
  cwd: cwd,
1339
1435
  slug: slug,
@@ -1353,6 +1449,8 @@ function createServer(opts) {
1353
1449
  return mateHandler.createReadyMateFromInterview(ws, userId, definition);
1354
1450
  },
1355
1451
  workspaceQueryService: workspaceQueryService,
1452
+ clayProductControl: clayProductControl,
1453
+ resumeLifecycleProject: function (userId) { return opts.projectLifecycle ? opts.projectLifecycle.resumeRecovered(slug, userId) : true; },
1356
1454
  projectLogsService: projectLogsService,
1357
1455
  issuesService: issuesService,
1358
1456
  linearStorage: linear.storage,
@@ -1374,13 +1472,19 @@ function createServer(opts) {
1374
1472
  return !!(access && !access.error && users.canAccessProject(userId, access));
1375
1473
  },
1376
1474
  getProjectAccess: function () {
1377
- if (extra.isMate) return { isMate: true, visibility: "private", ownerId: projectOwnerId || null, allowedUsers: [] };
1475
+ if (extra.isMate) {
1476
+ var mate = mates.getMate(mates.buildMateCtx(users.isMultiUser() ? projectOwnerId : null), extra.mateId || path.basename(cwd));
1477
+ return { isMate: true, visibility: "private", ownerId: projectOwnerId || null, allowedUsers: [], lifecycleExecutionPaused: !mate || !!mate.lifecycleDeletion };
1478
+ }
1378
1479
  return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
1379
1480
  },
1380
1481
  shouldSuppressResponseNotification: function (session) {
1381
1482
  return shouldSuppressHomeResponse(projects, slug, session);
1382
1483
  },
1383
- pushModule: pushModule,
1484
+ pushModule: require("./project-push-access").scopedPush(pushModule, {
1485
+ slug: slug, users: users, getProject: function (target) { return projects.get(target); },
1486
+ canAccessProject: canUserAccessSlug, getAccess: onGetProjectAccess,
1487
+ }),
1384
1488
  debug: debug,
1385
1489
  dangerouslySkipPermissions: dangerouslySkipPermissions,
1386
1490
  osUsers: osUsers,
@@ -1412,6 +1516,7 @@ function createServer(opts) {
1412
1516
  var status = ctx.getStatus();
1413
1517
  var recs = ctx.getSchedules();
1414
1518
  for (var i = 0; i < recs.length; i++) {
1519
+ if (users.isMultiUser() && recs[i].ownerId !== userId) continue;
1415
1520
  // Shallow-copy full record and augment with project metadata
1416
1521
  var copy = {};
1417
1522
  var keys = Object.keys(recs[i]);
@@ -1469,6 +1574,7 @@ function createServer(opts) {
1469
1574
  return ctx.getSchedules().length;
1470
1575
  },
1471
1576
  canAccessProjectSlug: canUserAccessSlug,
1577
+ canUsePath: canUsePath,
1472
1578
  onPresenceChange: broadcastPresenceChange,
1473
1579
  onProcessingChanged: broadcastProcessingChange,
1474
1580
  onSessionDone: function () { onSessionDone(slug); },
@@ -1622,6 +1728,7 @@ function createServer(opts) {
1622
1728
  }
1623
1729
 
1624
1730
  function handleDmMessage(ws, msg, routedProjectSlug) {
1731
+ if (ownerUiService.handleMessage(ws, msg)) return true;
1625
1732
  if (cursorSharingService && cursorSharingService.handleMessage(ws, msg)) return true;
1626
1733
  if (defaultVendorService && defaultVendorService.handleMessage(ws, msg, routedProjectSlug)) return true;
1627
1734
  if (contextViewService && contextViewService.handleMessage(ws, msg)) return true;
@@ -1636,19 +1743,8 @@ function createServer(opts) {
1636
1743
  return false;
1637
1744
  }
1638
1745
 
1639
- function removeProject(slug) {
1640
- var ctx = projects.get(slug);
1641
- if (!ctx) return false;
1642
- var wasMate = ctx.getStatus().isMate;
1643
- var shutdownResult = ctx.destroy();
1644
- projects.delete(slug);
1645
- if (shutdownResult && typeof shutdownResult.catch === "function") {
1646
- shutdownResult.catch(function(err) {
1647
- console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
1648
- });
1649
- }
1650
- if (!wasMate) scheduleRegistryRefresh();
1651
- return true;
1746
+ function removeProject(slug, awaitCompletion) {
1747
+ return require('./server-project-removal').removeProjectRuntime(projects, slug, awaitCompletion, scheduleRegistryRefresh);
1652
1748
  }
1653
1749
 
1654
1750
  // Now that addProject and removeProject are defined, initialize mateHandler
@@ -1734,7 +1830,11 @@ function createServer(opts) {
1734
1830
  function getVisibleProjects(userId) {
1735
1831
  var visible = [];
1736
1832
  projects.forEach(function (ctx, slug) {
1737
- if (users.isMultiUser() && (!userId || !canUserAccessSlug(userId, slug))) return;
1833
+ if (users.isMultiUser() && (!userId || !canUserAccessSlug(userId, slug))) {
1834
+ var metadata = administrativeMetadata(userId, slug);
1835
+ if (metadata) visible.push(metadata);
1836
+ return;
1837
+ }
1738
1838
  var status = ctx.getStatus();
1739
1839
  if (!status.isMate && onGetProjectAccess) {
1740
1840
  var access = onGetProjectAccess(slug);
@@ -1933,6 +2033,7 @@ function createServer(opts) {
1933
2033
  speech.destroy();
1934
2034
  var shutdowns = [];
1935
2035
  shutdowns.push(durableSchedulerLifecycle.shutdown());
2036
+ shutdowns.push(lifecycleRuntime.shutdown());
1936
2037
  projects.forEach(function (ctx, slug) {
1937
2038
  console.log("[server] Destroying project:", slug);
1938
2039
  var result = ctx.destroy();
@@ -2000,6 +2101,8 @@ function createServer(opts) {
2000
2101
  isTLS: !!tlsOptions,
2001
2102
  addProject: addProject,
2002
2103
  removeProject: removeProject,
2104
+ getProjectLifecycleTargets: lifecycleRuntime.targets,
2105
+ closeProjectLifecycle: lifecycleRuntime.close,
2003
2106
  getProjects: getProjects,
2004
2107
  reorderProjects: reorderProjects,
2005
2108
  setProjectTitle: setProjectTitle,