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
@@ -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;
@@ -143,12 +149,16 @@ function attachSessions(ctx) {
143
149
  return !session.ownerId;
144
150
  }
145
151
 
146
- function respondToAskUser(session, msg) {
152
+ function respondToAskUser(session, msg, ws) {
147
153
  if (!session || !msg || !msg.toolId) return false;
148
154
  var toolId = msg.toolId;
149
155
  var answers = msg.answers || {};
150
156
  var pending = session.pendingAskUser && session.pendingAskUser[toolId];
151
157
  if (!pending) return false;
158
+ if (pending.request && ["mcp-import", "product-control"].indexOf(pending.request.source) >= 0 &&
159
+ (typeof pending.request.authorizeResponse !== "function" || !pending.request.authorizeResponse(ws))) return false;
160
+ if (pending.request && ["work-primary", "mate-schedule"].indexOf(pending.request.source) >= 0 && usersModule.isMultiUser() &&
161
+ (!ws || !ws._clayUser || ws._clayUser.id !== session.ownerId)) return false;
152
162
  if (pending.mode === "yoke") {
153
163
  try { yoke.userInput.normalizeAnswers(pending.request.questions, answers); }
154
164
  catch (e) { return false; }
@@ -209,6 +219,10 @@ function attachSessions(ctx) {
209
219
  var setLatestVersion = ctx.setLatestVersion;
210
220
  var loadContextSources = ctx.loadContextSources;
211
221
 
222
+ var sessionHistory = require("./project-session-history-actions").attachSessionHistoryActions(Object.assign({}, ctx, {
223
+ resolveSessionHome: resolveSessionHome, cancelPendingAskUser: cancelPendingAskUser,
224
+ }));
225
+
212
226
  function canAccessProjectSlug(ws, targetSlug) {
213
227
  if (!targetSlug) return false;
214
228
  if (!usersModule || typeof usersModule.isMultiUser !== "function" || !usersModule.isMultiUser()) return true;
@@ -436,7 +450,7 @@ function attachSessions(ctx) {
436
450
  // Idempotent: if the PTY is already gone and history is populated, this
437
451
  // is a no-op (avoids re-reading jsonl and re-writing the session file on
438
452
  // every click while the user is reading the session in GUI mode).
439
- function prepareTuiSessionForGuiView(session) {
453
+ function prepareTuiSessionForGuiView(session, options) {
440
454
  if (!session || session.cliSessionId == null) return;
441
455
  var cliSess;
442
456
  try { cliSess = require("./cli-sessions"); } catch (e) { return; }
@@ -463,7 +477,7 @@ function attachSessions(ctx) {
463
477
  try { tm.close(session.terminalId); } catch (e) {}
464
478
  }
465
479
  session.terminalId = null;
466
- try { sm.saveSessionFile(session); } catch (e) {}
480
+ if (!options || !options.deferSave) { try { sm.saveSessionFile(session); } catch (e) {} }
467
481
  }
468
482
 
469
483
  // Resolve how a session should be presented to a viewer WITHOUT spawning a
@@ -764,11 +778,10 @@ function attachSessions(ctx) {
764
778
  sendTo(ws, { type: "error", text: "Cannot transfer ownership of home directory projects." });
765
779
  return true;
766
780
  }
767
- var projectOwnerId = getProjectOwnerId();
768
- var isAdmin = ws._clayUser && ws._clayUser.role === "admin";
781
+ var projectOwnerId = getProjectAccess().ownerId;
769
782
  var isProjectOwner = ws._clayUser && projectOwnerId && ws._clayUser.id === projectOwnerId;
770
- if (!ws._clayUser || (!isAdmin && !isProjectOwner)) {
771
- 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." });
772
785
  return true;
773
786
  }
774
787
  var targetUser = msg.userId ? usersModule.findUserById(msg.userId) : null;
@@ -776,11 +789,11 @@ function attachSessions(ctx) {
776
789
  sendTo(ws, { type: "error", text: "User not found." });
777
790
  return true;
778
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; }
779
796
  setProjectOwnerId(targetUser.id);
780
- // Persist via daemon callback
781
- if (opts.onProjectOwnerChanged) {
782
- opts.onProjectOwnerChanged(slug, targetUser.id);
783
- }
784
797
  send({ type: "project_owner_changed", ownerId: targetUser.id, ownerName: targetUser.displayName || targetUser.username });
785
798
  return true;
786
799
  }
@@ -797,6 +810,7 @@ function attachSessions(ctx) {
797
810
  // broadcasts session_switched. A live runtime keeps its actual mode
798
811
  // for every viewer; idle sessions open in chat. Nothing is spawned here.
799
812
  var xmTarget = sm.sessions.get(msg.id);
813
+ if (usersModule.isMultiUser() && (!ws._clayUser || !usersModule.canAccessSession(ws._clayUser.id, xmTarget, getProjectAccess()))) return true;
800
814
  if (xmTarget && (!xmTarget.vendor || vendorSupportsTui(xmTarget.vendor))) {
801
815
  // Single source of truth: live runtime wins (tui stays tui, gui stays
802
816
  // gui); idle legacy sessions hydrate their transcript into chat.
@@ -935,19 +949,7 @@ function attachSessions(ctx) {
935
949
  var s = sm.sessions.get(msg.id);
936
950
  if (usersModule.isMultiUser() && (!ws._clayUser ||
937
951
  !usersModule.canAccessSession(ws._clayUser.id, s, getProjectAccess()))) return true;
938
- s.title = String(msg.title).substring(0, 100);
939
- s.titleProvisional = false;
940
- s.titleAutoGenerated = false;
941
- s.titleManuallySet = true;
942
- sm.saveSessionFile(s);
943
- sm.notifySessionRenamed(s.localId);
944
- sm.broadcastSessionList();
945
- // Sync title to SDK session
946
- if (s.cliSessionId) {
947
- adapter.renameSession(s.cliSessionId, s.title, { dir: cwd }).catch(function(e) {
948
- console.error("[project] SDK renameSession failed:", e.message);
949
- });
950
- }
952
+ sessionActions.rename(s, String(msg.title).substring(0, 100));
951
953
  }
952
954
  return true;
953
955
  }
@@ -1082,7 +1084,6 @@ function attachSessions(ctx) {
1082
1084
 
1083
1085
  if (msg.type === "stop") {
1084
1086
  var session = getSessionForWs(ws);
1085
- if (autonomousRun && session && session.autonomousRun) autonomousRun.finish(session, "stopped", "Stopped by the user.");
1086
1087
  stopSession(session, ws && ws._clayUser);
1087
1088
  return true;
1088
1089
  }
@@ -1183,38 +1184,14 @@ function attachSessions(ctx) {
1183
1184
 
1184
1185
  if (msg.type === "set_vendor" && msg.vendor) {
1185
1186
  var vendorSession = getSessionForWs(ws);
1186
- if (vendorSession) {
1187
- // Refuse to rebind vendor on a session that is already bound to a
1188
- // different CLI (cliSessionId is vendor-specific). This prevents a
1189
- // stale client-side vendor state from clobbering the persisted vendor
1190
- // on page reload / server restart.
1191
- var alreadyBound = vendorSession.cliSessionId && vendorSession.vendor && vendorSession.vendor !== msg.vendor;
1192
- if (alreadyBound) {
1193
- console.warn("[project] set_vendor ignored: session " + vendorSession.localId +
1194
- " is bound to '" + vendorSession.vendor + "', refused rebind to '" + msg.vendor + "'");
1195
- } else {
1196
- vendorSession.vendor = msg.vendor;
1197
- if (vendorSession.permissionMode === "auto" && msg.vendor !== "claude") {
1198
- vendorSession.permissionMode = "default";
1199
- vendorSession.effectivePermissionMode = null;
1200
- }
1201
- if (vendorSession.vendor !== msg.vendor) vendorSession.model = "";
1202
- sm.saveSessionFile(vendorSession);
1203
- sm.broadcastSessionList();
1204
- }
1205
- }
1206
- if (msg.vendor) {
1207
- var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
1208
- sendTo(ws, {
1209
- type: "model_info",
1210
- model: "",
1211
- models: vendorModels,
1212
- vendor: msg.vendor, sessionId: vendorSession ? vendorSession.localId : null,
1213
- availableVendors: sm.availableVendors || [],
1214
- installedVendors: sm.installedVendors || [],
1215
- });
1216
- 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 });
1217
1193
  }
1194
+ catch (error) { sendTo(ws, { type: "error", text: error.message }); }
1218
1195
  return true;
1219
1196
  }
1220
1197
 
@@ -1282,15 +1259,7 @@ function attachSessions(ctx) {
1282
1259
  if (!valid.ok) { permissionResult(false, valid.error, true); return true; }
1283
1260
  (async function() {
1284
1261
  try {
1285
- if (session.permissionMode === "bypassPermissions" && valid.mode !== "bypassPermissions" && autonomousRun) {
1286
- await autonomousRun.stopForPermissionChange(session);
1287
- }
1288
- await fullAccessService.setMode(session, valid.mode);
1289
- var state = permissionModes.clientState(session, sm.currentPermissionMode, sm.defaultVendor);
1290
- sm.sendToSession(session, { type: "config_state", sessionId: session.localId, vendor: session.vendor || sm.defaultVendor || "claude", mode: state.requestedPermissionMode,
1291
- effectivePermissionMode: state.effectivePermissionMode, permissionRequestId: requestId,
1292
- permissionCapabilities: state.permissionCapabilities, mcpPermissionModeOverrides: state.mcpPermissionModeOverrides,
1293
- effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1262
+ await sessionActions.permission(session, valid.mode, requestId);
1294
1263
  permissionResult(true, null, true);
1295
1264
  } catch (error) {
1296
1265
  permissionResult(false, "Failed to change permission mode: " + (error.message || error), true);
@@ -1360,10 +1329,9 @@ function attachSessions(ctx) {
1360
1329
  sendTo(ws, { type: "error", text: "This effort level is not supported by the selected vendor." });
1361
1330
  return true;
1362
1331
  }
1363
- sdk.setEffort(session, sessionEffort).catch(function (e) {
1332
+ sessionActions.effort(session, sessionEffort).catch(function (e) {
1364
1333
  sendTo(ws, { type: "error", text: "Failed to change reasoning effort: " + (e.message || e) });
1365
1334
  });
1366
- 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 });
1367
1335
  }
1368
1336
  return true;
1369
1337
  }
@@ -1440,92 +1408,9 @@ function attachSessions(ctx) {
1440
1408
  sendTo(ws, { type: "rewind_error", text: "Rewind already in progress." });
1441
1409
  return true;
1442
1410
  }
1443
- session._rewindInProgress = true;
1444
- var mode = msg.mode || "both";
1445
-
1446
- (async function () {
1447
- try {
1448
- // File restoration (delegated to adapter via sdk-bridge)
1449
- if (mode !== "chat") {
1450
- await sdk.rewindExecuteFiles(session, msg.uuid);
1451
- }
1452
-
1453
- // Conversation rollback (skip for files-only mode)
1454
- if (mode !== "files") {
1455
- var targetIdx = -1;
1456
- for (var i = 0; i < session.messageUUIDs.length; i++) {
1457
- if (session.messageUUIDs[i].uuid === msg.uuid) {
1458
- targetIdx = i;
1459
- break;
1460
- }
1461
- }
1462
-
1463
- // Codex's thread/revert cuts at a turn id, not a count. Read the
1464
- // target entry's turnId BEFORE trimming local history below.
1465
- var hasTurnsToRollBack = false;
1466
- var targetTurnId = null;
1467
- if (targetIdx >= 0) {
1468
- targetTurnId = session.messageUUIDs[targetIdx].turnId || null;
1469
- for (var ri = targetIdx; ri < session.messageUUIDs.length; ri++) {
1470
- if (session.messageUUIDs[ri].type === "user") { hasTurnsToRollBack = true; break; }
1471
- }
1472
- }
1473
-
1474
- // Notify adapter of conversation rollback (e.g. Codex thread/revert)
1475
- // BEFORE trimming local history below. Deliberately not caught
1476
- // here: a failed or impossible (missing turnId) provider-side
1477
- // rollback must abort the whole rewind via the outer catch, never
1478
- // leave local history trimmed ahead of the real provider thread.
1479
- if (hasTurnsToRollBack) {
1480
- await sdk.rollbackConversation(session, targetTurnId);
1481
- }
1482
-
1483
- if (targetIdx >= 0) {
1484
- var trimTo = session.messageUUIDs[targetIdx].historyIndex;
1485
- for (var k = trimTo - 1; k >= 0; k--) {
1486
- if (session.history[k].type === "user_message") {
1487
- trimTo = k;
1488
- break;
1489
- }
1490
- }
1491
- session.history = session.history.slice(0, trimTo);
1492
- session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
1493
- // Reset digest checkpoint if it points past the trimmed history
1494
- if (typeof session._dmLastDigestedIndex === "number" && session._dmLastDigestedIndex > trimTo) {
1495
- session._dmLastDigestedIndex = trimTo;
1496
- }
1497
- }
1498
-
1499
- var kept = session.messageUUIDs;
1500
- session.lastRewindUuid = kept.length > 0 ? kept[kept.length - 1].uuid : null;
1501
- }
1502
-
1503
- if (session.abortController) {
1504
- try { session.abortController.abort(); } catch (e) {}
1505
- }
1506
- if (session.messageQueue) {
1507
- try { session.messageQueue.end(); } catch (e) {}
1508
- }
1509
- session.queryInstance = null;
1510
- session.messageQueue = null;
1511
- session.abortController = null;
1512
- session.blocks = {};
1513
- session.sentToolResults = {};
1514
- session.pendingPermissions = {};
1515
- cancelPendingAskUser(session, "Conversation rewound");
1516
- session.isProcessing = false;
1517
- onProcessingChanged();
1518
-
1519
- sm.saveSessionFile(session);
1520
- sm.switchSession(session.localId, ws, hydrateImageRefs);
1521
- sm.sendAndRecord(session, { type: "rewind_complete", mode: mode });
1522
- sm.broadcastSessionList();
1523
- } catch (err) {
1524
- sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + err.message });
1525
- } finally {
1526
- session._rewindInProgress = false;
1527
- }
1528
- })();
1411
+ sessionHistory.rewind(session, msg.uuid, msg.mode || "both", ws, function () {}).catch(function (error) {
1412
+ sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + error.message });
1413
+ });
1529
1414
  return true;
1530
1415
  }
1531
1416
 
@@ -1535,69 +1420,17 @@ function attachSessions(ctx) {
1535
1420
  sendTo(ws, { type: "error", text: "Cannot fork: no CLI session" });
1536
1421
  return true;
1537
1422
  }
1538
- var forkTitle = (session.title || "New Session") + " (fork)";
1539
-
1540
- sdk.forkSession(session, msg.uuid).then(function(result) {
1541
- if (result.useLocalHistory) {
1542
- // Copy local history up to the target UUID
1543
- var targetIdx = -1;
1544
- for (var fi = 0; fi < session.messageUUIDs.length; fi++) {
1545
- if (session.messageUUIDs[fi].uuid === msg.uuid) { targetIdx = fi; break; }
1546
- }
1547
- var forkHistory = [];
1548
- if (targetIdx >= 0) {
1549
- var trimTo = session.messageUUIDs[targetIdx].historyIndex;
1550
- forkHistory = session.history.slice(0, trimTo);
1551
- } else {
1552
- forkHistory = session.history.slice();
1553
- }
1554
- var forked = sm.createSession({
1555
- vendor: session.vendor,
1556
- ownerId: session.ownerId || null,
1557
- userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
1558
- sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1559
- }, ws);
1560
- forked.cliSessionId = result.sessionId;
1561
- forked.title = forkTitle;
1562
- forked.history = forkHistory;
1563
- forked.messageUUIDs = [];
1564
- for (var hi = 0; hi < forkHistory.length; hi++) {
1565
- if (forkHistory[hi].type === "message_uuid") {
1566
- forked.messageUUIDs.push({ uuid: forkHistory[hi].uuid, type: forkHistory[hi].messageType, historyIndex: hi, turnId: forkHistory[hi].turnId || null });
1567
- }
1568
- }
1569
- sm.saveSessionFile(forked);
1570
- sm.switchSession(forked.localId, ws, hydrateImageRefs);
1571
- sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
1572
- sm.broadcastSessionList();
1573
- } else {
1574
- // Read history from CLI session files
1575
- var cliSess = require("./cli-sessions");
1576
- return cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId).then(function(history) {
1577
- var forked = sm.resumeSession(result.sessionId, {
1578
- history: history,
1579
- title: forkTitle,
1580
- vendor: session.vendor,
1581
- ownerId: session.ownerId || null,
1582
- userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
1583
- sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1584
- sessionVisibilityExplicit: session.sessionVisibilityExplicit === true,
1585
- }, ws);
1586
- if (forked) {
1587
- ws._clayActiveSession = forked.localId;
1588
- sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
1589
- }
1590
- });
1591
- }
1592
- }).catch(function(e) {
1593
- sendTo(ws, { type: "error", text: "Fork failed: " + (e.message || e) });
1423
+ sessionHistory.fork(session, msg.uuid, ws, function () {}).then(function (forked) {
1424
+ if (forked) sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
1425
+ }).catch(function (error) {
1426
+ sendTo(ws, { type: "error", text: "Fork failed: " + error.message });
1594
1427
  });
1595
1428
  return true;
1596
1429
  }
1597
1430
 
1598
1431
  if (msg.type === "ask_user_response") {
1599
1432
  var session = getSessionForWs(ws);
1600
- if (session) respondToAskUser(session, msg);
1433
+ if (session) respondToAskUser(session, msg, ws);
1601
1434
  return true;
1602
1435
  }
1603
1436
 
@@ -1805,14 +1638,6 @@ function attachSessions(ctx) {
1805
1638
  if (msg.type === "browse_dir") {
1806
1639
  var rawPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
1807
1640
  var absTarget = path.resolve(rawPath);
1808
- // Multi-user mode: non-admins can only browse their home directory
1809
- if (osUsers && osUsers.length > 0 && ws._clayUser && ws._clayUser.role !== "admin") {
1810
- var browseHome = ws._clayUser.linuxUser ? "/home/" + ws._clayUser.linuxUser : null;
1811
- if (!browseHome || (absTarget !== browseHome && (absTarget + "/").indexOf(browseHome + "/") !== 0)) {
1812
- sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: [], error: "Access restricted to your home directory" });
1813
- return true;
1814
- }
1815
- }
1816
1641
  var parentDir, prefix;
1817
1642
  try {
1818
1643
  var stat = fs.statSync(absTarget);
@@ -1829,12 +1654,17 @@ function attachSessions(ctx) {
1829
1654
  parentDir = path.dirname(absTarget);
1830
1655
  prefix = path.basename(absTarget).toLowerCase();
1831
1656
  }
1657
+ if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, parentDir)) {
1658
+ sendTo(ws, { type: "browse_dir_result", entries: [], error: "Directory access is not permitted" });
1659
+ return true;
1660
+ }
1832
1661
  try {
1833
1662
  var dirItems = fs.readdirSync(parentDir, { withFileTypes: true });
1834
1663
  var dirEntries = [];
1835
1664
  for (var di = 0; di < dirItems.length; di++) {
1836
1665
  var d = dirItems[di];
1837
1666
  if (!d.isDirectory()) continue;
1667
+ if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, path.join(parentDir, d.name))) continue;
1838
1668
  if (d.name.charAt(0) === ".") continue;
1839
1669
  if (IGNORED_DIRS.has(d.name)) continue;
1840
1670
  if (prefix && !d.name.toLowerCase().startsWith(prefix)) continue;
@@ -1852,17 +1682,9 @@ function attachSessions(ctx) {
1852
1682
  if (msg.type === "add_project") {
1853
1683
  var addPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
1854
1684
  var addAbs = path.resolve(addPath);
1855
- // Multi-user mode: normal users restricted to their home directory
1856
- if (osUsers && osUsers.length > 0 && ws._clayUser && ws._clayUser.role !== "admin") {
1857
- if (!ws._clayUser.linuxUser) {
1858
- sendTo(ws, { type: "add_project_result", ok: false, error: "No Linux user assigned" });
1859
- return true;
1860
- }
1861
- var userHome = "/home/" + ws._clayUser.linuxUser;
1862
- if (addAbs !== userHome && (addAbs + "/").indexOf(userHome + "/") !== 0) {
1863
- sendTo(ws, { type: "add_project_result", ok: false, error: "Path not allowed. You can only add directories under " + userHome });
1864
- return true;
1865
- }
1685
+ if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, addAbs)) {
1686
+ sendTo(ws, { type: "add_project_result", ok: false, error: "Directory access is not permitted" });
1687
+ return true;
1866
1688
  }
1867
1689
  try {
1868
1690
  var addStat = fs.statSync(addAbs);
@@ -1876,7 +1698,7 @@ function attachSessions(ctx) {
1876
1698
  }
1877
1699
  if (typeof opts.onAddProject === "function") {
1878
1700
  var result = opts.onAddProject(addAbs, ws._clayUser);
1879
- sendTo(ws, { type: "add_project_result", ok: result.ok, slug: result.slug, error: result.error, existing: result.existing });
1701
+ sendTo(ws, Object.assign({}, result, { type: "add_project_result" }));
1880
1702
  } else {
1881
1703
  sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1882
1704
  }
@@ -1901,7 +1723,7 @@ function attachSessions(ctx) {
1901
1723
  }
1902
1724
  if (typeof opts.onCreateProject === "function") {
1903
1725
  var createResult = opts.onCreateProject(createName, ws._clayUser);
1904
- sendTo(ws, { type: "add_project_result", ok: createResult.ok, slug: createResult.slug, error: createResult.error });
1726
+ sendTo(ws, Object.assign({}, createResult, { type: "add_project_result" }));
1905
1727
  } else {
1906
1728
  sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1907
1729
  }
@@ -1991,10 +1813,10 @@ function attachSessions(ctx) {
1991
1813
  moveAllSchedulesToProject(removeSlug, msg.moveTasksTo);
1992
1814
  }
1993
1815
  if (typeof opts.onRemoveProject === "function") {
1994
- // Send result before removing so the WS is still open
1995
- sendTo(ws, { type: "remove_project_result", ok: true, slug: removeSlug });
1996
1816
  var removeUserId = ws._clayUser ? ws._clayUser.id : null;
1997
- opts.onRemoveProject(removeSlug, removeUserId);
1817
+ Promise.resolve().then(function () { return opts.onRemoveProject(removeSlug, removeUserId); }).then(function (result) {
1818
+ sendTo(ws, { type: "remove_project_result", ok: !!result && result.ok === true, slug: removeSlug, error: result && result.error });
1819
+ }, function (error) { sendTo(ws, { type: "remove_project_result", ok: false, slug: removeSlug, error: error.message }); });
1998
1820
  } else {
1999
1821
  sendTo(ws, { type: "remove_project_result", ok: false, error: "Not supported" });
2000
1822
  }
@@ -2040,7 +1862,7 @@ function attachSessions(ctx) {
2040
1862
  }
2041
1863
  if (typeof opts.onSetProjectTitle === "function") {
2042
1864
  var titleResult = opts.onSetProjectTitle(msg.slug, msg.title || null);
2043
- sendTo(ws, { type: "set_project_title_result", ok: titleResult.ok, slug: msg.slug, error: titleResult.error });
1865
+ sendTo(ws, Object.assign({}, titleResult, { type: "set_project_title_result", slug: msg.slug }));
2044
1866
  } else {
2045
1867
  sendTo(ws, { type: "set_project_title_result", ok: false, error: "Not supported" });
2046
1868
  }
@@ -2194,6 +2016,11 @@ function attachSessions(ctx) {
2194
2016
  return handleSessionsMessage(ws, { type: "permission_response", requestId: msg.requestId, decision: msg.decision });
2195
2017
  },
2196
2018
  stopHomeSession: stopSession,
2019
+ sessionActions: sessionActions,
2020
+ sessionHistoryActions: sessionHistory,
2021
+ sessionVendorActions: sessionVendor,
2022
+ sessionRuntimeActions: sessionRuntime,
2023
+ deleteProductControlPlan: function (plan) { sessionDelete.executeOwnedDeletion(plan); },
2197
2024
  };
2198
2025
  }
2199
2026