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
@@ -2,7 +2,7 @@ var serverSettingsMessages = require("./project-server-settings");
2
2
  var fs = require("fs");
3
3
  var path = require("path");
4
4
  var { execFileSync } = require("child_process");
5
- var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
5
+ var providerSettings = require("./yoke/settings");
6
6
  var updateSnooze = require("./update-snooze");
7
7
  var yoke = require("./yoke");
8
8
  var sessionVisibility = require("./session-visibility");
@@ -118,14 +118,20 @@ function attachSessions(ctx) {
118
118
  var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
119
119
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
120
120
  var hydrateSkillCatalog = ctx.hydrateSkillCatalog || function () { return []; };
121
+ var sessionActions = require("./project-session-actions").attachSessionActions(ctx);
122
+ var sessionVendor = require("./project-session-vendor").attachSessionVendor(ctx);
123
+ var sessionRuntime = require("./project-session-runtime-actions").attachSessionRuntimeActions({ sm: sm, tm: tm, prepareGui: prepareTuiSessionForGuiView, isPendingQueuePaused: ctx.isPendingQueuePaused,
124
+ publishGui: function (session) {
125
+ clients.forEach(function (ws) {
126
+ if (ws.readyState !== 1 || ws._clayActiveSession !== session.localId) return;
127
+ if (usersModule.isMultiUser() && (!ws._clayUser || !usersModule.findUserById(ws._clayUser.id) || !usersModule.canAccessProject(ws._clayUser.id, getProjectAccess()) || !usersModule.canAccessSession(ws._clayUser.id, session, getProjectAccess()))) return;
128
+ sm.switchSession(session.localId, ws, ctx.hydrateImageRefs);
129
+ });
130
+ },
131
+ });
121
132
  function stopSession(session, actor) {
122
133
  if (!session) return false;
123
- session._pendingMessageDrainPaused = true;
124
- if (typeof ctx.onPendingQueuePause === "function") ctx.onPendingQueuePause(session, actor || null);
125
- if (!session.isProcessing && !session._queryStarting) return true;
126
- if (typeof ctx.onHumanPairStop === "function") ctx.onHumanPairStop(session);
127
- session.taskStopRequested = true;
128
- if (session.abortController) session.abortController.abort();
134
+ sessionActions.stop(session, actor);
129
135
  return true;
130
136
  }
131
137
  var hydrateImageRefs = ctx.hydrateImageRefs;
@@ -149,9 +155,9 @@ function attachSessions(ctx) {
149
155
  var answers = msg.answers || {};
150
156
  var pending = session.pendingAskUser && session.pendingAskUser[toolId];
151
157
  if (!pending) return false;
152
- if (pending.request && pending.request.source === "mcp-import" &&
158
+ if (pending.request && ["mcp-import", "product-control"].indexOf(pending.request.source) >= 0 &&
153
159
  (typeof pending.request.authorizeResponse !== "function" || !pending.request.authorizeResponse(ws))) return false;
154
- if (pending.request && pending.request.source === "work-primary" && usersModule.isMultiUser() &&
160
+ if (pending.request && ["work-primary", "mate-schedule"].indexOf(pending.request.source) >= 0 && usersModule.isMultiUser() &&
155
161
  (!ws || !ws._clayUser || ws._clayUser.id !== session.ownerId)) return false;
156
162
  if (pending.mode === "yoke") {
157
163
  try { yoke.userInput.normalizeAnswers(pending.request.questions, answers); }
@@ -213,6 +219,10 @@ function attachSessions(ctx) {
213
219
  var setLatestVersion = ctx.setLatestVersion;
214
220
  var loadContextSources = ctx.loadContextSources;
215
221
 
222
+ var sessionHistory = require("./project-session-history-actions").attachSessionHistoryActions(Object.assign({}, ctx, {
223
+ resolveSessionHome: resolveSessionHome, cancelPendingAskUser: cancelPendingAskUser,
224
+ }));
225
+
216
226
  function canAccessProjectSlug(ws, targetSlug) {
217
227
  if (!targetSlug) return false;
218
228
  if (!usersModule || typeof usersModule.isMultiUser !== "function" || !usersModule.isMultiUser()) return true;
@@ -248,7 +258,7 @@ function attachSessions(ctx) {
248
258
  if (!session || !session.cliSessionId) return;
249
259
  if (session._titleWatcherStop) return; // already watching
250
260
  var watcher;
251
- try { watcher = require("./claude-jsonl-watcher"); } catch (e) { return; }
261
+ try { watcher = require("./yoke/claude-jsonl-watcher"); } catch (e) { return; }
252
262
  var home = resolveSessionHome(session);
253
263
  var jsonlPath = watcher.jsonlPathFor(home, cwd, session.cliSessionId);
254
264
  if (!jsonlPath) return;
@@ -316,7 +326,7 @@ function attachSessions(ctx) {
316
326
  // message right away. The transcript is small enough that a full
317
327
  // re-send beats maintaining a delta protocol.
318
328
  try {
319
- var newIndex = require("./tui-transcript-index").readAssistantIndex(resolveSessionHome(s), cwd, s.cliSessionId);
329
+ var newIndex = require("./yoke/tui-transcript-index").readAssistantIndex(resolveSessionHome(s), cwd, s.cliSessionId);
320
330
  send({
321
331
  type: "tui_transcript_state",
322
332
  id: s.localId,
@@ -440,10 +450,10 @@ function attachSessions(ctx) {
440
450
  // Idempotent: if the PTY is already gone and history is populated, this
441
451
  // is a no-op (avoids re-reading jsonl and re-writing the session file on
442
452
  // every click while the user is reading the session in GUI mode).
443
- function prepareTuiSessionForGuiView(session) {
453
+ function prepareTuiSessionForGuiView(session, options) {
444
454
  if (!session || session.cliSessionId == null) return;
445
455
  var cliSess;
446
- try { cliSess = require("./cli-sessions"); } catch (e) { return; }
456
+ try { cliSess = require("./yoke/cli-sessions"); } catch (e) { return; }
447
457
  // Re-read whenever the jsonl has changed since the last hydrate (a TUI
448
458
  // turn appended messages), not just when history is empty. The earlier
449
459
  // "already hydrated" short-circuit kept stale history after a
@@ -467,7 +477,7 @@ function attachSessions(ctx) {
467
477
  try { tm.close(session.terminalId); } catch (e) {}
468
478
  }
469
479
  session.terminalId = null;
470
- try { sm.saveSessionFile(session); } catch (e) {}
480
+ if (!options || !options.deferSave) { try { sm.saveSessionFile(session); } catch (e) {} }
471
481
  }
472
482
 
473
483
  // Resolve how a session should be presented to a viewer WITHOUT spawning a
@@ -593,7 +603,7 @@ function attachSessions(ctx) {
593
603
  var from = sm.findTurnBoundary(session.history, Math.max(0, targetFrom));
594
604
  var to = before;
595
605
  var items = session.history.slice(from, to).map(function (item, index) {
596
- return Object.assign({}, hydrateImageRefs(item), { _historyIndex: from + index });
606
+ return Object.assign({}, hydrateImageRefs(require("./yoke/usage").clientEvent(item, session.vendor || "claude")), { _historyIndex: from + index });
597
607
  });
598
608
  sendTo(ws, {
599
609
  type: "history_prepend",
@@ -768,11 +778,10 @@ function attachSessions(ctx) {
768
778
  sendTo(ws, { type: "error", text: "Cannot transfer ownership of home directory projects." });
769
779
  return true;
770
780
  }
771
- var projectOwnerId = getProjectOwnerId();
772
- var isAdmin = ws._clayUser && ws._clayUser.role === "admin";
781
+ var projectOwnerId = getProjectAccess().ownerId;
773
782
  var isProjectOwner = ws._clayUser && projectOwnerId && ws._clayUser.id === projectOwnerId;
774
- if (!ws._clayUser || (!isAdmin && !isProjectOwner)) {
775
- sendTo(ws, { type: "error", text: "Only project owners or admins can transfer ownership." });
783
+ if (!ws._clayUser || !isProjectOwner) {
784
+ sendTo(ws, { type: "error", text: "Only project owners can transfer ownership." });
776
785
  return true;
777
786
  }
778
787
  var targetUser = msg.userId ? usersModule.findUserById(msg.userId) : null;
@@ -780,11 +789,11 @@ function attachSessions(ctx) {
780
789
  sendTo(ws, { type: "error", text: "User not found." });
781
790
  return true;
782
791
  }
792
+ // Persist before publishing runtime ownership.
793
+ if (!opts.onProjectOwnerChanged) return true;
794
+ var savedOwner = opts.onProjectOwnerChanged(slug, targetUser.id);
795
+ if (savedOwner && savedOwner.error) { sendTo(ws, { type: "error", text: savedOwner.error }); return true; }
783
796
  setProjectOwnerId(targetUser.id);
784
- // Persist via daemon callback
785
- if (opts.onProjectOwnerChanged) {
786
- opts.onProjectOwnerChanged(slug, targetUser.id);
787
- }
788
797
  send({ type: "project_owner_changed", ownerId: targetUser.id, ownerName: targetUser.displayName || targetUser.username });
789
798
  return true;
790
799
  }
@@ -801,6 +810,7 @@ function attachSessions(ctx) {
801
810
  // broadcasts session_switched. A live runtime keeps its actual mode
802
811
  // for every viewer; idle sessions open in chat. Nothing is spawned here.
803
812
  var xmTarget = sm.sessions.get(msg.id);
813
+ if (usersModule.isMultiUser() && (!ws._clayUser || !usersModule.canAccessSession(ws._clayUser.id, xmTarget, getProjectAccess()))) return true;
804
814
  if (xmTarget && (!xmTarget.vendor || vendorSupportsTui(xmTarget.vendor))) {
805
815
  // Single source of truth: live runtime wins (tui stays tui, gui stays
806
816
  // gui); idle legacy sessions hydrate their transcript into chat.
@@ -908,7 +918,7 @@ function attachSessions(ctx) {
908
918
  return true;
909
919
  }
910
920
  try {
911
- var tprIndex = require("./tui-transcript-index").readAssistantIndex(resolveSessionHome(tprSess), cwd, tprSess.cliSessionId);
921
+ var tprIndex = require("./yoke/tui-transcript-index").readAssistantIndex(resolveSessionHome(tprSess), cwd, tprSess.cliSessionId);
912
922
  sendTo(ws, {
913
923
  type: "tui_transcript_state",
914
924
  id: tprId,
@@ -939,19 +949,7 @@ function attachSessions(ctx) {
939
949
  var s = sm.sessions.get(msg.id);
940
950
  if (usersModule.isMultiUser() && (!ws._clayUser ||
941
951
  !usersModule.canAccessSession(ws._clayUser.id, s, getProjectAccess()))) return true;
942
- s.title = String(msg.title).substring(0, 100);
943
- s.titleProvisional = false;
944
- s.titleAutoGenerated = false;
945
- s.titleManuallySet = true;
946
- sm.saveSessionFile(s);
947
- sm.notifySessionRenamed(s.localId);
948
- sm.broadcastSessionList();
949
- // Sync title to SDK session
950
- if (s.cliSessionId) {
951
- adapter.renameSession(s.cliSessionId, s.title, { dir: cwd }).catch(function(e) {
952
- console.error("[project] SDK renameSession failed:", e.message);
953
- });
954
- }
952
+ sessionActions.rename(s, String(msg.title).substring(0, 100));
955
953
  }
956
954
  return true;
957
955
  }
@@ -1086,7 +1084,6 @@ function attachSessions(ctx) {
1086
1084
 
1087
1085
  if (msg.type === "stop") {
1088
1086
  var session = getSessionForWs(ws);
1089
- if (autonomousRun && session && session.autonomousRun) autonomousRun.finish(session, "stopped", "Stopped by the user.");
1090
1087
  stopSession(session, ws && ws._clayUser);
1091
1088
  return true;
1092
1089
  }
@@ -1102,14 +1099,14 @@ function attachSessions(ctx) {
1102
1099
  var pid = msg.pid;
1103
1100
  if (!pid || typeof pid !== "number") return true;
1104
1101
  // Verify target is actually a claude process before killing
1105
- if (!sdk.isClaudeProcess(pid)) {
1106
- console.error("[project] Refused to kill PID " + pid + ": not a claude process");
1107
- sendTo(ws, { type: "error", text: "Process " + pid + " is not a Claude process." });
1102
+ if (!sdk.isProviderProcess(pid, getSessionForWs(ws))) {
1103
+ console.error("[project] Refused to kill PID " + pid + ": not a verified provider process");
1104
+ sendTo(ws, { type: "error", text: "Process " + pid + " is not a verified provider process." });
1108
1105
  return true;
1109
1106
  }
1110
1107
  try {
1111
1108
  process.kill(pid, "SIGTERM");
1112
- console.log("[project] Sent SIGTERM to conflicting Claude process PID " + pid);
1109
+ console.log("[project] Sent SIGTERM to conflicting provider process PID " + pid);
1113
1110
  sendTo(ws, { type: "process_killed", pid: pid });
1114
1111
  } catch (e) {
1115
1112
  console.error("[project] Failed to kill PID " + pid + ":", e.message);
@@ -1187,38 +1184,14 @@ function attachSessions(ctx) {
1187
1184
 
1188
1185
  if (msg.type === "set_vendor" && msg.vendor) {
1189
1186
  var vendorSession = getSessionForWs(ws);
1190
- if (vendorSession) {
1191
- // Refuse to rebind vendor on a session that is already bound to a
1192
- // different CLI (cliSessionId is vendor-specific). This prevents a
1193
- // stale client-side vendor state from clobbering the persisted vendor
1194
- // on page reload / server restart.
1195
- var alreadyBound = vendorSession.cliSessionId && vendorSession.vendor && vendorSession.vendor !== msg.vendor;
1196
- if (alreadyBound) {
1197
- console.warn("[project] set_vendor ignored: session " + vendorSession.localId +
1198
- " is bound to '" + vendorSession.vendor + "', refused rebind to '" + msg.vendor + "'");
1199
- } else {
1200
- vendorSession.vendor = msg.vendor;
1201
- if (vendorSession.permissionMode === "auto" && msg.vendor !== "claude") {
1202
- vendorSession.permissionMode = "default";
1203
- vendorSession.effectivePermissionMode = null;
1204
- }
1205
- if (vendorSession.vendor !== msg.vendor) vendorSession.model = "";
1206
- sm.saveSessionFile(vendorSession);
1207
- sm.broadcastSessionList();
1208
- }
1209
- }
1210
- if (msg.vendor) {
1211
- var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
1212
- sendTo(ws, {
1213
- type: "model_info",
1214
- model: "",
1215
- models: vendorModels,
1216
- vendor: msg.vendor, sessionId: vendorSession ? vendorSession.localId : null,
1217
- availableVendors: sm.availableVendors || [],
1218
- installedVendors: sm.installedVendors || [],
1219
- });
1220
- send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
1187
+ if (!vendorSession) return true;
1188
+ // Re-selecting the provider of an established thread does not rebind it.
1189
+ if (vendorSession.cliSessionId && vendorSession.vendor === msg.vendor) return true;
1190
+ try {
1191
+ var vendorResult = sessionVendor.setVendor(vendorSession, msg.vendor);
1192
+ if (vendorResult.status === "failed") sendTo(ws, { type: "error", text: vendorResult.error });
1221
1193
  }
1194
+ catch (error) { sendTo(ws, { type: "error", text: error.message }); }
1222
1195
  return true;
1223
1196
  }
1224
1197
 
@@ -1286,15 +1259,7 @@ function attachSessions(ctx) {
1286
1259
  if (!valid.ok) { permissionResult(false, valid.error, true); return true; }
1287
1260
  (async function() {
1288
1261
  try {
1289
- if (session.permissionMode === "bypassPermissions" && valid.mode !== "bypassPermissions" && autonomousRun) {
1290
- await autonomousRun.stopForPermissionChange(session);
1291
- }
1292
- await fullAccessService.setMode(session, valid.mode);
1293
- var state = permissionModes.clientState(session, sm.currentPermissionMode, sm.defaultVendor);
1294
- sm.sendToSession(session, { type: "config_state", sessionId: session.localId, vendor: session.vendor || sm.defaultVendor || "claude", mode: state.requestedPermissionMode,
1295
- effectivePermissionMode: state.effectivePermissionMode, permissionRequestId: requestId,
1296
- permissionCapabilities: state.permissionCapabilities, mcpPermissionModeOverrides: state.mcpPermissionModeOverrides,
1297
- effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1262
+ await sessionActions.permission(session, valid.mode, requestId);
1298
1263
  permissionResult(true, null, true);
1299
1264
  } catch (error) {
1300
1265
  permissionResult(false, "Failed to change permission mode: " + (error.message || error), true);
@@ -1364,10 +1329,9 @@ function attachSessions(ctx) {
1364
1329
  sendTo(ws, { type: "error", text: "This effort level is not supported by the selected vendor." });
1365
1330
  return true;
1366
1331
  }
1367
- sdk.setEffort(session, sessionEffort).catch(function (e) {
1332
+ sessionActions.effort(session, sessionEffort).catch(function (e) {
1368
1333
  sendTo(ws, { type: "error", text: "Failed to change reasoning effort: " + (e.message || e) });
1369
1334
  });
1370
- sm.sendToSession(session, { type: "config_state", model: session.model || ((sm.defaultModelByVendor || {})[effortVendor]) || "", vendor: effortVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: sessionEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
1371
1335
  }
1372
1336
  return true;
1373
1337
  }
@@ -1403,20 +1367,12 @@ function attachSessions(ctx) {
1403
1367
  return true;
1404
1368
  }
1405
1369
 
1406
- // Codex-specific settings (stored on sessionManager, passed to adapter via adapterOptions)
1407
- if (msg.type === "set_codex_approval") {
1408
- sm.codexApproval = getCodexConfig({ codexApproval: msg.approval }).approval;
1409
- send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
1410
- return true;
1411
- }
1412
- if (msg.type === "set_codex_sandbox") {
1413
- sm.codexSandbox = msg.sandbox || CODEX_DEFAULTS.sandbox;
1414
- send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
1415
- return true;
1416
- }
1417
- if (msg.type === "set_codex_websearch") {
1418
- sm.codexWebSearch = msg.webSearch || CODEX_DEFAULTS.webSearch;
1419
- send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
1370
+ var providerChange;
1371
+ try { providerChange = providerSettings.change(msg, sm); }
1372
+ catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
1373
+ if (providerChange) {
1374
+ Object.keys(providerChange.patch).forEach(function(key) { sm[key] = providerChange.patch[key]; });
1375
+ providerChange.messages.forEach(function(message) { send(message); });
1420
1376
  return true;
1421
1377
  }
1422
1378
 
@@ -1444,92 +1400,9 @@ function attachSessions(ctx) {
1444
1400
  sendTo(ws, { type: "rewind_error", text: "Rewind already in progress." });
1445
1401
  return true;
1446
1402
  }
1447
- session._rewindInProgress = true;
1448
- var mode = msg.mode || "both";
1449
-
1450
- (async function () {
1451
- try {
1452
- // File restoration (delegated to adapter via sdk-bridge)
1453
- if (mode !== "chat") {
1454
- await sdk.rewindExecuteFiles(session, msg.uuid);
1455
- }
1456
-
1457
- // Conversation rollback (skip for files-only mode)
1458
- if (mode !== "files") {
1459
- var targetIdx = -1;
1460
- for (var i = 0; i < session.messageUUIDs.length; i++) {
1461
- if (session.messageUUIDs[i].uuid === msg.uuid) {
1462
- targetIdx = i;
1463
- break;
1464
- }
1465
- }
1466
-
1467
- // Codex's thread/revert cuts at a turn id, not a count. Read the
1468
- // target entry's turnId BEFORE trimming local history below.
1469
- var hasTurnsToRollBack = false;
1470
- var targetTurnId = null;
1471
- if (targetIdx >= 0) {
1472
- targetTurnId = session.messageUUIDs[targetIdx].turnId || null;
1473
- for (var ri = targetIdx; ri < session.messageUUIDs.length; ri++) {
1474
- if (session.messageUUIDs[ri].type === "user") { hasTurnsToRollBack = true; break; }
1475
- }
1476
- }
1477
-
1478
- // Notify adapter of conversation rollback (e.g. Codex thread/revert)
1479
- // BEFORE trimming local history below. Deliberately not caught
1480
- // here: a failed or impossible (missing turnId) provider-side
1481
- // rollback must abort the whole rewind via the outer catch, never
1482
- // leave local history trimmed ahead of the real provider thread.
1483
- if (hasTurnsToRollBack) {
1484
- await sdk.rollbackConversation(session, targetTurnId);
1485
- }
1486
-
1487
- if (targetIdx >= 0) {
1488
- var trimTo = session.messageUUIDs[targetIdx].historyIndex;
1489
- for (var k = trimTo - 1; k >= 0; k--) {
1490
- if (session.history[k].type === "user_message") {
1491
- trimTo = k;
1492
- break;
1493
- }
1494
- }
1495
- session.history = session.history.slice(0, trimTo);
1496
- session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
1497
- // Reset digest checkpoint if it points past the trimmed history
1498
- if (typeof session._dmLastDigestedIndex === "number" && session._dmLastDigestedIndex > trimTo) {
1499
- session._dmLastDigestedIndex = trimTo;
1500
- }
1501
- }
1502
-
1503
- var kept = session.messageUUIDs;
1504
- session.lastRewindUuid = kept.length > 0 ? kept[kept.length - 1].uuid : null;
1505
- }
1506
-
1507
- if (session.abortController) {
1508
- try { session.abortController.abort(); } catch (e) {}
1509
- }
1510
- if (session.messageQueue) {
1511
- try { session.messageQueue.end(); } catch (e) {}
1512
- }
1513
- session.queryInstance = null;
1514
- session.messageQueue = null;
1515
- session.abortController = null;
1516
- session.blocks = {};
1517
- session.sentToolResults = {};
1518
- session.pendingPermissions = {};
1519
- cancelPendingAskUser(session, "Conversation rewound");
1520
- session.isProcessing = false;
1521
- onProcessingChanged();
1522
-
1523
- sm.saveSessionFile(session);
1524
- sm.switchSession(session.localId, ws, hydrateImageRefs);
1525
- sm.sendAndRecord(session, { type: "rewind_complete", mode: mode });
1526
- sm.broadcastSessionList();
1527
- } catch (err) {
1528
- sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + err.message });
1529
- } finally {
1530
- session._rewindInProgress = false;
1531
- }
1532
- })();
1403
+ sessionHistory.rewind(session, msg.uuid, msg.mode || "both", ws, function () {}).catch(function (error) {
1404
+ sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + error.message });
1405
+ });
1533
1406
  return true;
1534
1407
  }
1535
1408
 
@@ -1539,62 +1412,10 @@ function attachSessions(ctx) {
1539
1412
  sendTo(ws, { type: "error", text: "Cannot fork: no CLI session" });
1540
1413
  return true;
1541
1414
  }
1542
- var forkTitle = (session.title || "New Session") + " (fork)";
1543
-
1544
- sdk.forkSession(session, msg.uuid).then(function(result) {
1545
- if (result.useLocalHistory) {
1546
- // Copy local history up to the target UUID
1547
- var targetIdx = -1;
1548
- for (var fi = 0; fi < session.messageUUIDs.length; fi++) {
1549
- if (session.messageUUIDs[fi].uuid === msg.uuid) { targetIdx = fi; break; }
1550
- }
1551
- var forkHistory = [];
1552
- if (targetIdx >= 0) {
1553
- var trimTo = session.messageUUIDs[targetIdx].historyIndex;
1554
- forkHistory = session.history.slice(0, trimTo);
1555
- } else {
1556
- forkHistory = session.history.slice();
1557
- }
1558
- var forked = sm.createSession({
1559
- vendor: session.vendor,
1560
- ownerId: session.ownerId || null,
1561
- userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
1562
- sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1563
- }, ws);
1564
- forked.cliSessionId = result.sessionId;
1565
- forked.title = forkTitle;
1566
- forked.history = forkHistory;
1567
- forked.messageUUIDs = [];
1568
- for (var hi = 0; hi < forkHistory.length; hi++) {
1569
- if (forkHistory[hi].type === "message_uuid") {
1570
- forked.messageUUIDs.push({ uuid: forkHistory[hi].uuid, type: forkHistory[hi].messageType, historyIndex: hi, turnId: forkHistory[hi].turnId || null });
1571
- }
1572
- }
1573
- sm.saveSessionFile(forked);
1574
- sm.switchSession(forked.localId, ws, hydrateImageRefs);
1575
- sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
1576
- sm.broadcastSessionList();
1577
- } else {
1578
- // Read history from CLI session files
1579
- var cliSess = require("./cli-sessions");
1580
- return cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId).then(function(history) {
1581
- var forked = sm.resumeSession(result.sessionId, {
1582
- history: history,
1583
- title: forkTitle,
1584
- vendor: session.vendor,
1585
- ownerId: session.ownerId || null,
1586
- userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
1587
- sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1588
- sessionVisibilityExplicit: session.sessionVisibilityExplicit === true,
1589
- }, ws);
1590
- if (forked) {
1591
- ws._clayActiveSession = forked.localId;
1592
- sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
1593
- }
1594
- });
1595
- }
1596
- }).catch(function(e) {
1597
- sendTo(ws, { type: "error", text: "Fork failed: " + (e.message || e) });
1415
+ sessionHistory.fork(session, msg.uuid, ws, function () {}).then(function (forked) {
1416
+ if (forked) sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
1417
+ }).catch(function (error) {
1418
+ sendTo(ws, { type: "error", text: "Fork failed: " + error.message });
1598
1419
  });
1599
1420
  return true;
1600
1421
  }
@@ -1809,14 +1630,6 @@ function attachSessions(ctx) {
1809
1630
  if (msg.type === "browse_dir") {
1810
1631
  var rawPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
1811
1632
  var absTarget = path.resolve(rawPath);
1812
- // Multi-user mode: non-admins can only browse their home directory
1813
- if (osUsers && osUsers.length > 0 && ws._clayUser && ws._clayUser.role !== "admin") {
1814
- var browseHome = ws._clayUser.linuxUser ? "/home/" + ws._clayUser.linuxUser : null;
1815
- if (!browseHome || (absTarget !== browseHome && (absTarget + "/").indexOf(browseHome + "/") !== 0)) {
1816
- sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: [], error: "Access restricted to your home directory" });
1817
- return true;
1818
- }
1819
- }
1820
1633
  var parentDir, prefix;
1821
1634
  try {
1822
1635
  var stat = fs.statSync(absTarget);
@@ -1833,12 +1646,17 @@ function attachSessions(ctx) {
1833
1646
  parentDir = path.dirname(absTarget);
1834
1647
  prefix = path.basename(absTarget).toLowerCase();
1835
1648
  }
1649
+ if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, parentDir)) {
1650
+ sendTo(ws, { type: "browse_dir_result", entries: [], error: "Directory access is not permitted" });
1651
+ return true;
1652
+ }
1836
1653
  try {
1837
1654
  var dirItems = fs.readdirSync(parentDir, { withFileTypes: true });
1838
1655
  var dirEntries = [];
1839
1656
  for (var di = 0; di < dirItems.length; di++) {
1840
1657
  var d = dirItems[di];
1841
1658
  if (!d.isDirectory()) continue;
1659
+ if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, path.join(parentDir, d.name))) continue;
1842
1660
  if (d.name.charAt(0) === ".") continue;
1843
1661
  if (IGNORED_DIRS.has(d.name)) continue;
1844
1662
  if (prefix && !d.name.toLowerCase().startsWith(prefix)) continue;
@@ -1856,17 +1674,9 @@ function attachSessions(ctx) {
1856
1674
  if (msg.type === "add_project") {
1857
1675
  var addPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
1858
1676
  var addAbs = path.resolve(addPath);
1859
- // Multi-user mode: normal users restricted to their home directory
1860
- if (osUsers && osUsers.length > 0 && ws._clayUser && ws._clayUser.role !== "admin") {
1861
- if (!ws._clayUser.linuxUser) {
1862
- sendTo(ws, { type: "add_project_result", ok: false, error: "No Linux user assigned" });
1863
- return true;
1864
- }
1865
- var userHome = "/home/" + ws._clayUser.linuxUser;
1866
- if (addAbs !== userHome && (addAbs + "/").indexOf(userHome + "/") !== 0) {
1867
- sendTo(ws, { type: "add_project_result", ok: false, error: "Path not allowed. You can only add directories under " + userHome });
1868
- return true;
1869
- }
1677
+ if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, addAbs)) {
1678
+ sendTo(ws, { type: "add_project_result", ok: false, error: "Directory access is not permitted" });
1679
+ return true;
1870
1680
  }
1871
1681
  try {
1872
1682
  var addStat = fs.statSync(addAbs);
@@ -1880,7 +1690,7 @@ function attachSessions(ctx) {
1880
1690
  }
1881
1691
  if (typeof opts.onAddProject === "function") {
1882
1692
  var result = opts.onAddProject(addAbs, ws._clayUser);
1883
- sendTo(ws, { type: "add_project_result", ok: result.ok, slug: result.slug, error: result.error, existing: result.existing });
1693
+ sendTo(ws, Object.assign({}, result, { type: "add_project_result" }));
1884
1694
  } else {
1885
1695
  sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1886
1696
  }
@@ -1905,7 +1715,7 @@ function attachSessions(ctx) {
1905
1715
  }
1906
1716
  if (typeof opts.onCreateProject === "function") {
1907
1717
  var createResult = opts.onCreateProject(createName, ws._clayUser);
1908
- sendTo(ws, { type: "add_project_result", ok: createResult.ok, slug: createResult.slug, error: createResult.error });
1718
+ sendTo(ws, Object.assign({}, createResult, { type: "add_project_result" }));
1909
1719
  } else {
1910
1720
  sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1911
1721
  }
@@ -1995,10 +1805,10 @@ function attachSessions(ctx) {
1995
1805
  moveAllSchedulesToProject(removeSlug, msg.moveTasksTo);
1996
1806
  }
1997
1807
  if (typeof opts.onRemoveProject === "function") {
1998
- // Send result before removing so the WS is still open
1999
- sendTo(ws, { type: "remove_project_result", ok: true, slug: removeSlug });
2000
1808
  var removeUserId = ws._clayUser ? ws._clayUser.id : null;
2001
- opts.onRemoveProject(removeSlug, removeUserId);
1809
+ Promise.resolve().then(function () { return opts.onRemoveProject(removeSlug, removeUserId); }).then(function (result) {
1810
+ sendTo(ws, { type: "remove_project_result", ok: !!result && result.ok === true, slug: removeSlug, error: result && result.error });
1811
+ }, function (error) { sendTo(ws, { type: "remove_project_result", ok: false, slug: removeSlug, error: error.message }); });
2002
1812
  } else {
2003
1813
  sendTo(ws, { type: "remove_project_result", ok: false, error: "Not supported" });
2004
1814
  }
@@ -2044,7 +1854,7 @@ function attachSessions(ctx) {
2044
1854
  }
2045
1855
  if (typeof opts.onSetProjectTitle === "function") {
2046
1856
  var titleResult = opts.onSetProjectTitle(msg.slug, msg.title || null);
2047
- sendTo(ws, { type: "set_project_title_result", ok: titleResult.ok, slug: msg.slug, error: titleResult.error });
1857
+ sendTo(ws, Object.assign({}, titleResult, { type: "set_project_title_result", slug: msg.slug }));
2048
1858
  } else {
2049
1859
  sendTo(ws, { type: "set_project_title_result", ok: false, error: "Not supported" });
2050
1860
  }
@@ -2106,7 +1916,7 @@ function attachSessions(ctx) {
2106
1916
  var galManaged = [];
2107
1917
  var galUser = [];
2108
1918
  try {
2109
- var galInstaller = require("./claude-hook-installer");
1919
+ var galInstaller = require("./yoke/claude-hook-installer");
2110
1920
  galManaged = galInstaller.CLAY_MANAGED_ALLOW || [];
2111
1921
  } catch (e) {}
2112
1922
  if (galUid) {
@@ -2132,7 +1942,7 @@ function attachSessions(ctx) {
2132
1942
  // home (OS-mode: getent passwd; single-user: os.homedir()).
2133
1943
  var salEffective = false;
2134
1944
  try {
2135
- var salInstaller = require("./claude-hook-installer");
1945
+ var salInstaller = require("./yoke/claude-hook-installer");
2136
1946
  var salHome = null;
2137
1947
  if (osUsers && ws._clayUser && ws._clayUser.linuxUser) {
2138
1948
  try {
@@ -2198,6 +2008,11 @@ function attachSessions(ctx) {
2198
2008
  return handleSessionsMessage(ws, { type: "permission_response", requestId: msg.requestId, decision: msg.decision });
2199
2009
  },
2200
2010
  stopHomeSession: stopSession,
2011
+ sessionActions: sessionActions,
2012
+ sessionHistoryActions: sessionHistory,
2013
+ sessionVendorActions: sessionVendor,
2014
+ sessionRuntimeActions: sessionRuntime,
2015
+ deleteProductControlPlan: function (plan) { sessionDelete.executeOwnedDeletion(plan); },
2201
2016
  };
2202
2017
  }
2203
2018