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
@@ -0,0 +1,51 @@
1
+ // This projection is an allowlist. Never add runtime/session/path objects here.
2
+ function projectMetadata(status, access, users) {
3
+ if (!status || status.isMate || !access || access.error) return null;
4
+ var owner = access.ownerId && users.findUserById(access.ownerId);
5
+ return {
6
+ slug: status.slug,
7
+ project: status.title || status.project || status.slug,
8
+ title: status.title || status.project || status.slug,
9
+ projectOwnerId: owner ? owner.id : null,
10
+ ownerName: owner ? (owner.displayName || owner.username) : "Ownership needs recovery",
11
+ visibility: access.visibility,
12
+ contentAccess: false,
13
+ administrativeAccess: true,
14
+ ownershipRecovery: access.ownershipRecovery === "required" || !owner,
15
+ runtimeHealth: "Registered",
16
+ };
17
+ }
18
+
19
+ function metadataFor(user, status, access, users) {
20
+ if (!user || !users.findUserById(user.id) || user.role !== "admin" || !status || status.isMate || /^mate-/.test(status.slug)) return null;
21
+ return projectMetadata(status, access, users);
22
+ }
23
+
24
+ function escape(value) {
25
+ return String(value || "").replace(/[&<>"']/g, function (character) {
26
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[character];
27
+ });
28
+ }
29
+
30
+ function page(metadata, projects) {
31
+ var privacyText = metadata.ownershipRecovery
32
+ ? "Content remains restricted because no verified owner is available. Ask the server operator to verify the account’s OS UID and real home/project mapping, then run the documented ownership repair."
33
+ : "Conversations, files and terminals are private. Ask the owner to share project access.";
34
+ var accessText = metadata.ownershipRecovery
35
+ ? "Ownership needs recovery. The server operator must independently verify or repair the authoritative account-to-OS mapping before ownership repair can succeed. Ambiguous records stay restricted; administrator status cannot claim this project. See the Project ownership and administrator access guide."
36
+ : "Only the project owner can change ownership, visibility and sharing. Administrator status does not grant access to private content.";
37
+ var strip = projects.filter(function (project) { return !project.isMate; }).map(function (project) {
38
+ return '<a class="icon-strip-item' + (project.slug === metadata.slug ? ' active' : '') + '" href="/p/' + encodeURIComponent(project.slug) + '/" title="' + escape(project.title || project.project) + '"><span class="icon-strip-name">' + escape((project.title || project.project || project.slug).substring(0, 2)) + '</span></a>';
39
+ }).join("");
40
+ return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' + escape(metadata.title) + ' · Clay</title>' +
41
+ '<script src="/appearance-bootstrap.js"></script><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/css/project-admin-overview.css"></head><body>' +
42
+ '<div id="layout"><header id="top-bar"><a href="/" class="top-bar-brand-link" aria-label="Clay home"><span class="top-bar-brand-wordmark"></span></a></header>' +
43
+ '<div id="layout-body"><nav id="icon-strip" aria-label="Projects"><div class="icon-strip-projects">' + strip + '</div></nav>' +
44
+ '<main id="main-area" class="project-admin-overview"><div class="project-admin-canvas"><h1>' + escape(metadata.title) + '</h1>' +
45
+ '<div role="tablist" aria-label="Project information"><button type="button" role="tab" aria-selected="true" id="overview-tab" aria-controls="overview-panel">Overview</button><button type="button" role="tab" aria-selected="false" id="access-tab" aria-controls="access-panel">Access</button></div>' +
46
+ '<section id="overview-panel" role="tabpanel" aria-labelledby="overview-tab"><dl><dt>Owner</dt><dd>' + escape(metadata.ownerName) + '</dd><dt>Visibility</dt><dd>' + escape(metadata.visibility) + '</dd><dt>Status</dt><dd>' + (metadata.ownershipRecovery ? 'Ownership needs recovery' : escape(metadata.runtimeHealth)) + '</dd><dt>Your access</dt><dd>Administrative metadata only</dd></dl><p>' + escape(privacyText) + '</p><p class="project-admin-secondary">Resource usage is unavailable for this project.</p></section>' +
47
+ '<section id="access-panel" role="tabpanel" aria-labelledby="access-tab" hidden><h2>Administrative metadata only</h2><p>' + escape(accessText) + '</p></section></div></main></div></div>' +
48
+ '<script type="module" src="/modules/project-admin-overview.js"></script></body></html>';
49
+ }
50
+
51
+ module.exports = { metadataFor: metadataFor, page: page };
@@ -25,7 +25,7 @@ function attachAskUser(ctx) {
25
25
  respond.cancel("Autonomous mode. Make your own decision.");
26
26
  return;
27
27
  }
28
- var questions = request.source === "mcp-import" ? request.questions : planningQuestions(boundSession, request.questions);
28
+ var questions = ["mcp-import", "product-control"].indexOf(request.source) >= 0 ? request.questions : planningQuestions(boundSession, request.questions);
29
29
  var input = { questions: questions };
30
30
  if (request.source === 'mcp-import') input.source = 'mcp-import';
31
31
  if (!boundSession.pendingAskUser) boundSession.pendingAskUser = {};
@@ -40,6 +40,8 @@ function attachAskUser(ctx) {
40
40
  boundSession.pendingAskUser[request.id] = pending;
41
41
  if (respond && typeof respond.onSettle === "function") {
42
42
  respond.onSettle(function (result) {
43
+ if (request.source === 'product-control' && result.status !== 'submitted') ctx.record(boundSession, { type: 'product_control_question_expired', toolId: request.id, error: 'This exact change review expired or was cancelled. No change was authorized.' });
44
+ if (request.source === 'mate-schedule' && result.status !== 'submitted') ctx.record(boundSession, { type: 'mate_schedule_question_expired', toolId: request.id, error: 'This schedule review expired. Request a new review.' });
43
45
  if (request.source === 'mcp-import' && result.status !== 'submitted') ctx.record(boundSession, { type: 'mcp_import_consent_expired', toolId: request.id, source: 'mcp-import', error: 'Import consent expired or was cancelled. No connection was imported.' });
44
46
  if (boundSession.pendingAskUser && boundSession.pendingAskUser[request.id] === pending) delete boundSession.pendingAskUser[request.id];
45
47
  });
@@ -48,7 +50,7 @@ function attachAskUser(ctx) {
48
50
  ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input, source: request.source });
49
51
  }
50
52
  var router = ctx.getWorkerQuestionRouter && ctx.getWorkerQuestionRouter();
51
- if (router && request.source !== "mcp-import") router.routeQuestion(boundSession, request, respond, function (answers, driverId) {
53
+ if (router && request.source !== "mcp-import" && request.source !== "mate-schedule" && request.source !== "product-control") router.routeQuestion(boundSession, request, respond, function (answers, driverId) {
52
54
  var displayAnswers = {};
53
55
  request.questions.forEach(function (question, index) { displayAnswers[index] = answers[question.id].join(", "); });
54
56
  ctx.record(boundSession, { type: "ask_user_answered", toolId: request.id, answers: displayAnswers, answeredByDriver: driverId });
@@ -3,7 +3,7 @@ var path = require("path");
3
3
  var usersModule = require("./users");
4
4
  var userPresence = require("./user-presence");
5
5
  var emailAccounts = require("./email-accounts");
6
- var { getCodexConfig } = require("./codex-defaults");
6
+ var providerSettings = require("./yoke/settings");
7
7
  var yoke = require("./yoke");
8
8
  var permissionModes = require("./session-permission-mode");
9
9
  var sessionProvenance = require("./session-provenance");
@@ -217,7 +217,7 @@ function attachConnection(ctx) {
217
217
  sm.saveSessionFile(restoredActive);
218
218
  }
219
219
  var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
220
- sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: yoke.VENDOR_REGISTRY, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
220
+ sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: require("./yoke/vendor-registry").publicInfo(), isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
221
221
  if (typeof sendCursorSharingState === "function") sendCursorSharingState(ws);
222
222
  // Update notifications are pushed on a scheduled interval (see
223
223
  // scheduleUpdateBroadcast). We no longer push on connect to avoid
@@ -234,7 +234,7 @@ function attachConnection(ctx) {
234
234
  var initialPermissionState = permissionModes.clientState(restoredActive, sm.currentPermissionMode, sm.defaultVendor);
235
235
  sendTo(ws, { type: "config_state", model: initialModelInfo.model, vendor: initialVendor, sessionId: restoredActive ? restoredActive.localId : null, mode: initialPermissionState.requestedPermissionMode, effectivePermissionMode: initialPermissionState.effectivePermissionMode, permissionCapabilities: initialPermissionState.permissionCapabilities, mcpPermissionModeOverrides: initialPermissionState.mcpPermissionModeOverrides, effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
236
236
  sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
237
- sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
237
+ providerSettings.messages(sm).forEach(function(message) { sendTo(ws, message); });
238
238
  sendTo(ws, { type: "term_list", terminals: tm.list() });
239
239
  // Any login flow already running in this project, so a reconnecting client
240
240
  // re-attaches to the existing login terminal instead of starting another.
@@ -301,12 +301,13 @@ function attachConnection(ctx) {
301
301
  }
302
302
  sendTo(ws, { type: "history_meta", total: total, from: fromIndex });
303
303
  for (var i = fromIndex; i < total; i++) {
304
- sendTo(ws, Object.assign({}, hydrateImageRefs(active.history[i]), { _historyIndex: i }));
304
+ sendTo(ws, Object.assign({}, hydrateImageRefs(require("./yoke/usage").clientEvent(active.history[i], active.vendor || "claude")), { _historyIndex: i }));
305
305
  }
306
- var _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
306
+ var _lastContext = null, _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
307
307
  for (var _ri = total - 1; _ri >= 0; _ri--) {
308
308
  if (active.history[_ri].type === "result") {
309
- var _r = active.history[_ri];
309
+ var _r = require("./yoke/usage").clientEvent(active.history[_ri], active.vendor || "claude");
310
+ _lastContext = _r.context;
310
311
  _lastUsage = _r.usage || null;
311
312
  _lastModelUsage = _r.modelUsage || null;
312
313
  _lastCost = _r.cost != null ? _r.cost : null;
@@ -314,7 +315,7 @@ function attachConnection(ctx) {
314
315
  break;
315
316
  }
316
317
  }
317
- sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, contextUsage: active.lastContextUsage || null });
318
+ sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, context: _lastContext, contextUsage: require("./yoke/usage").context(active.lastContextUsage, active.vendor || "claude") });
318
319
  if (scheduledMessages) scheduledMessages.sendState(active, ws);
319
320
  if (pairResultRecovery) pairResultRecovery.sendState(active, ws);
320
321
 
@@ -12,7 +12,7 @@ function unsafeReason(session, isPaired) {
12
12
  if (session.homeDebatePlanning || session.homeDebatePhase || session.debateSetupMode || session.debateState || session._debate) return "Debate sessions cannot receive project follow-ups.";
13
13
  if (session.loop || session.spawn || session.handoff || session.assignment || session.singleTurn || session._pairDelegation || isPaired) return "Orchestrated sessions cannot receive project follow-ups.";
14
14
  if (session.dangerouslySkipPermissions || session.permissionMode === "bypassPermissions") return "Full-access sessions cannot receive project follow-ups.";
15
- if (session.destroying || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
15
+ if (session.destroying || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._pendingMessageDrainPaused || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
16
16
  return "";
17
17
  }
18
18
 
@@ -36,6 +36,7 @@ function attachDelegatedFollowUp(ctx) {
36
36
  var paired = !!(ctx.isPairedSession && ctx.isPairedSession(session));
37
37
  var reason = unsafeReason(session, paired);
38
38
  if (reason) throw new Error(reason);
39
+ if (ctx.isPendingQueuePaused && ctx.isPendingQueuePaused(session)) throw new Error("The Driver queue is stopped; explicit owner resume is required.");
39
40
  if (!ctx.canResumeVendor(session.vendor)) throw new Error("Target session provider cannot safely resume this conversation.");
40
41
  return { session: session, sessionRef: targetSessionRef, title: session.title || "Existing session" };
41
42
  }
@@ -0,0 +1,65 @@
1
+ // Reserve a new project directory before running Git or changing ownership.
2
+ // Filesystem identity checks also keep failed attempts from deleting targets
3
+ // replaced by another actor. Hostile OS/root mutation is outside this boundary.
4
+ var fs = require("fs");
5
+ var path = require("path");
6
+
7
+ function canonical(candidate) {
8
+ var current = path.resolve(candidate);
9
+ var missing = [];
10
+ while (true) {
11
+ try { return path.join.apply(path, [fs.realpathSync(current)].concat(missing)); }
12
+ catch (error) {
13
+ if (error.code !== "ENOENT") throw error;
14
+ // A dangling symlink is an existing object, not a missing directory.
15
+ try { fs.lstatSync(current); throw new Error("A project path contains a dangling symlink."); }
16
+ catch (statError) { if (statError.code !== "ENOENT") throw statError; }
17
+ var parent = path.dirname(current);
18
+ if (parent === current) throw error;
19
+ missing.unshift(path.basename(current));
20
+ current = parent;
21
+ }
22
+ }
23
+ }
24
+
25
+ function inside(root, candidate) {
26
+ var relative = path.relative(root, candidate);
27
+ return relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
28
+ }
29
+
30
+ function rejectIntersection(target, projects, userId) {
31
+ (projects || []).forEach(function (project) {
32
+ var root = canonical(project.path);
33
+ if (target === root || inside(target, root) || (project.ownerId !== userId && inside(root, target))) {
34
+ throw new Error("The destination intersects a registered project.");
35
+ }
36
+ });
37
+ }
38
+
39
+ function claim(options) {
40
+ var slug = options.slug;
41
+ if (!slug || slug === "." || slug === ".." || path.basename(slug) !== slug) throw new Error("Invalid project directory name.");
42
+ var base = canonical(options.baseDir);
43
+ var target = path.join(base, slug);
44
+ rejectIntersection(target, options.projects, options.userId);
45
+ fs.mkdirSync(base, { recursive: true });
46
+ if (fs.realpathSync(options.baseDir) !== base) throw new Error("The project base directory changed.");
47
+ rejectIntersection(target, options.projects, options.userId);
48
+ // EEXIST rejects directories, files, symlinks and concurrent claims alike.
49
+ fs.mkdirSync(target, { mode: 0o700 });
50
+ var identity = fs.lstatSync(target);
51
+ function assertOwned() {
52
+ var current = fs.lstatSync(target);
53
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== identity.dev || current.ino !== identity.ino || fs.realpathSync(base) !== base) {
54
+ throw new Error("The claimed project directory changed.");
55
+ }
56
+ }
57
+ function cleanup() {
58
+ try { assertOwned(); } catch (error) { return false; }
59
+ fs.rmSync(target, { recursive: true });
60
+ return true;
61
+ }
62
+ return { path: target, assertOwned: assertOwned, cleanup: cleanup };
63
+ }
64
+
65
+ module.exports = { claim: claim, canonical: canonical };
@@ -0,0 +1,61 @@
1
+ // Shared file-viewer effects. Authority and scope are derived by each caller.
2
+ var fs = require('fs'), path = require('path'), crypto = require('crypto');
3
+ var resolve = require('./project-file-path').resolveFilePath;
4
+ function hash(content) { return crypto.createHash('sha256').update(content).digest('hex'); }
5
+ function attachFileOperations(ctx) {
6
+ function list(scope, requested) {
7
+ var dir = resolve(ctx.cwd, requested || '.', scope);
8
+ var items = scope.identity ? ctx.fsAsUser('list', { dir: dir }, scope.identity) : fs.readdirSync(dir, { withFileTypes: true }).map(function (item) { return { name: item.name, isDir: item.isDirectory() }; });
9
+ return items.filter(function (item) { return !item.isDir || !ctx.IGNORED_DIRS.has(item.name); }).map(function (item) { return { name: item.name, type: item.isDir ? 'dir' : 'file', path: path.relative(ctx.cwd, path.join(dir, item.name)).split(path.sep).join('/') }; });
10
+ }
11
+ function read(scope, requested) {
12
+ var file = resolve(ctx.cwd, requested, scope), identity = scope.identity;
13
+ var stat = identity ? ctx.fsAsUser('stat', { file: file }, identity) : fs.statSync(file);
14
+ if (stat.size > ctx.FS_MAX_SIZE) return { binary: true, size: stat.size, error: 'File too large (' + (stat.size / 1024 / 1024).toFixed(1) + ' MB)' };
15
+ var ext = path.extname(file).toLowerCase();
16
+ if (ctx.BINARY_EXTS.has(ext)) {
17
+ var binary = { binary: true, size: stat.size };
18
+ if (ctx.IMAGE_EXTS.has(ext)) binary.imageUrl = 'api/file?path=' + encodeURIComponent(requested);
19
+ return binary;
20
+ }
21
+ return { content: identity ? ctx.fsAsUser('read', { file: file, readContent: true }, identity).content : fs.readFileSync(file, 'utf8'), size: stat.size };
22
+ }
23
+ function snapshot(scope, requested) {
24
+ var file = resolve(ctx.cwd, requested, scope), stat = fs.lstatSync(file);
25
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 65536) throw new Error('Select a regular, singly linked text file of at most 64 KB.');
26
+ var result = read(scope, requested);
27
+ if (result.binary || typeof result.content !== 'string' || result.content.indexOf('\0') >= 0) throw new Error('Binary files are unavailable for canonical editing.');
28
+ return { dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid, gid: stat.gid, size: stat.size, mtime: stat.mtimeMs, hash: hash(result.content) };
29
+ }
30
+ function write(scope, requested, content, control) {
31
+ var file = resolve(ctx.cwd, requested, scope), attempted = false;
32
+ if (!control) {
33
+ if (scope.identity) ctx.fsAsUser('write', { file: file, content: content }, scope.identity);
34
+ else fs.writeFileSync(file, content, 'utf8');
35
+ return { ok: true };
36
+ }
37
+ if (typeof fs.constants.O_NOFOLLOW !== 'number') throw new Error('Canonical editing requires no-follow file descriptors on this platform.');
38
+ control.revalidate();
39
+ if (JSON.stringify(snapshot(scope, requested)) !== JSON.stringify(control.expected)) throw new Error('The exact file revision changed.');
40
+ try {
41
+ attempted = true;
42
+ if (scope.identity) ctx.fsAsUser('write_exact', { file: file, content: content, expected: control.expected, root: fs.realpathSync(ctx.cwd) }, scope.identity);
43
+ else {
44
+ var fd = fs.openSync(file, fs.constants.O_RDWR | fs.constants.O_NOFOLLOW);
45
+ try {
46
+ var stat = fs.fstatSync(fd), expected = control.expected;
47
+ if (!stat.isFile() || stat.nlink !== 1 || stat.dev !== expected.dev || stat.ino !== expected.ino || stat.mode !== expected.mode || stat.uid !== expected.uid || stat.gid !== expected.gid || stat.size !== expected.size || stat.mtimeMs !== expected.mtime || hash(fs.readFileSync(fd, 'utf8')) !== expected.hash) throw new Error('The open file revision changed.');
48
+ control.revalidate();
49
+ fs.ftruncateSync(fd, 0);
50
+ var bytes = Buffer.from(content, 'utf8'), offset = 0;
51
+ while (offset < bytes.length) { var written = fs.writeSync(fd, bytes, offset, bytes.length - offset, offset); if (!written) throw new Error('File write made no progress.'); offset += written; }
52
+ fs.fsyncSync(fd);
53
+ } finally { fs.closeSync(fd); }
54
+ }
55
+ control.revalidate();
56
+ return { status: 'completed', persisted: true, path: requested };
57
+ } catch (error) { error.productControlResult = { persisted: attempted ? 'unknown' : false, retrySafe: false, error: error.message }; throw error; }
58
+ }
59
+ return { list: list, read: read, write: write, snapshot: snapshot };
60
+ }
61
+ module.exports = { attachFileOperations: attachFileOperations };
@@ -37,6 +37,7 @@ function attachFilesystem(ctx) {
37
37
 
38
38
  var access = ctx.requestAccess || attachRequestAccess(ctx);
39
39
  var fileHistory = attachFileHistory(ctx, access);
40
+ var operations = require("./project-file-operations").attachFileOperations(ctx);
40
41
 
41
42
  function handleFilesystemMessage(ws, msg) {
42
43
  var fileScope;
@@ -57,40 +58,11 @@ function attachFilesystem(ctx) {
57
58
 
58
59
  // --- fs_list ---
59
60
  if (msg.type === "fs_list") {
60
- var fsListIdentity = fileScope.identity;
61
- var fsDir = fileTarget;
62
61
  try {
63
- var fsListUserInfo = fsListIdentity;
64
- var entries = [];
65
- if (fsListUserInfo) {
66
- // Run as target OS user to respect Linux file permissions
67
- var rawEntries = fsAsUser("list", { dir: fsDir }, fsListUserInfo);
68
- for (var fi = 0; fi < rawEntries.length; fi++) {
69
- var re = rawEntries[fi];
70
- if (re.isDir && IGNORED_DIRS.has(re.name)) continue;
71
- entries.push({
72
- name: re.name,
73
- type: re.isDir ? "dir" : "file",
74
- path: path.relative(cwd, path.join(fsDir, re.name)).split(path.sep).join("/"),
75
- });
76
- }
77
- } else {
78
- var items = fs.readdirSync(fsDir, { withFileTypes: true });
79
- for (var fi = 0; fi < items.length; fi++) {
80
- var item = items[fi];
81
- if (item.isDirectory() && IGNORED_DIRS.has(item.name)) continue;
82
- entries.push({
83
- name: item.name,
84
- type: item.isDirectory() ? "dir" : "file",
85
- path: path.relative(cwd, path.join(fsDir, item.name)).split(path.sep).join("/"),
86
- });
87
- }
88
- }
89
- sendTo(ws, { type: "fs_list_result", path: msg.path || ".", entries: entries });
90
- // Auto-watch the directory for changes
91
- startDirWatch(ws, msg.path || ".");
62
+ sendTo(ws, { type: 'fs_list_result', path: msg.path || '.', entries: operations.list(fileScope, msg.path || '.') });
63
+ startDirWatch(ws, msg.path || '.');
92
64
  } catch (e) {
93
- sendTo(ws, { type: "fs_list_result", path: msg.path, entries: [], error: fileError(e).message, errorCode: fileError(e).code });
65
+ sendTo(ws, { type: 'fs_list_result', path: msg.path, entries: [], error: fileError(e).message, errorCode: fileError(e).code });
94
66
  }
95
67
  return true;
96
68
  }
@@ -143,62 +115,15 @@ function attachFilesystem(ctx) {
143
115
 
144
116
  // --- fs_read ---
145
117
  if (msg.type === "fs_read") {
146
- var fsReadIdentity = fileScope.identity;
147
- var fsFile = fileTarget;
148
- try {
149
- var fsReadUserInfo = fsReadIdentity;
150
- var ext = path.extname(fsFile).toLowerCase();
151
- if (fsReadUserInfo) {
152
- // Run stat and read as target OS user
153
- var statResult = fsAsUser("stat", { file: fsFile }, fsReadUserInfo);
154
- if (statResult.size > FS_MAX_SIZE) {
155
- sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: statResult.size, error: "File too large (" + (statResult.size / 1024 / 1024).toFixed(1) + " MB)" });
156
- return true;
157
- }
158
- if (BINARY_EXTS.has(ext)) {
159
- var result = { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: statResult.size };
160
- if (IMAGE_EXTS.has(ext)) result.imageUrl = "api/file?path=" + encodeURIComponent(msg.path);
161
- sendTo(ws, result);
162
- return true;
163
- }
164
- var readResult = fsAsUser("read", { file: fsFile, readContent: true }, fsReadUserInfo);
165
- sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, content: readResult.content, size: statResult.size });
166
- } else {
167
- var stat = fs.statSync(fsFile);
168
- if (stat.size > FS_MAX_SIZE) {
169
- sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: stat.size, error: "File too large (" + (stat.size / 1024 / 1024).toFixed(1) + " MB)" });
170
- return true;
171
- }
172
- if (BINARY_EXTS.has(ext)) {
173
- var result = { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: stat.size };
174
- if (IMAGE_EXTS.has(ext)) result.imageUrl = "api/file?path=" + encodeURIComponent(msg.path);
175
- sendTo(ws, result);
176
- return true;
177
- }
178
- var content = fs.readFileSync(fsFile, "utf8");
179
- sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, content: content, size: stat.size });
180
- }
181
- } catch (e) {
182
- sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, error: fileError(e).message, errorCode: fileError(e).code });
183
- }
118
+ var metadata = { type: 'fs_read_result', path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId };
119
+ try { sendTo(ws, Object.assign(metadata, operations.read(fileScope, msg.path))); }
120
+ catch (e) { sendTo(ws, Object.assign(metadata, { error: fileError(e).message, errorCode: fileError(e).code })); }
184
121
  return true;
185
122
  }
186
123
 
187
- // --- fs_write ---
188
- if (msg.type === "fs_write") {
189
- var fsWriteIdentity = fileScope.identity;
190
- var fsWriteFile = fileTarget;
191
- try {
192
- var fsWriteUserInfo = fsWriteIdentity;
193
- if (fsWriteUserInfo) {
194
- fsAsUser("write", { file: fsWriteFile, content: msg.content || "" }, fsWriteUserInfo);
195
- } else {
196
- fs.writeFileSync(fsWriteFile, msg.content || "", "utf8");
197
- }
198
- sendTo(ws, { type: "fs_write_result", path: msg.path, ok: true });
199
- } catch (e) {
200
- sendTo(ws, { type: "fs_write_result", path: msg.path, ok: false, error: fileError(e).message, errorCode: fileError(e).code });
201
- }
124
+ if (msg.type === 'fs_write') {
125
+ try { sendTo(ws, Object.assign({ type: 'fs_write_result', path: msg.path }, operations.write(fileScope, msg.path, msg.content || ''))); }
126
+ catch (e) { sendTo(ws, { type: 'fs_write_result', path: msg.path, ok: false, error: fileError(e).message, errorCode: fileError(e).code }); }
202
127
  return true;
203
128
  }
204
129
 
@@ -239,8 +164,8 @@ function attachFilesystem(ctx) {
239
164
  var setResult;
240
165
  try { setResult = opts.onSetProjectEnv(targetSlug, msg.envrc || ""); }
241
166
  catch (error) { sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: error.message }); return true; }
242
- if (setResult.ok) onEnvironmentChanged();
243
- sendTo(ws, { type: "set_project_env_result", ok: setResult.ok, slug: targetSlug, requestId: msg.requestId, error: setResult.error, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
167
+ if (setResult.ok && !setResult.runtimeRefreshed) onEnvironmentChanged();
168
+ sendTo(ws, Object.assign({}, setResult, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined }));
244
169
  } else {
245
170
  sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: "Not supported" });
246
171
  }
@@ -297,6 +222,7 @@ function attachFilesystem(ctx) {
297
222
 
298
223
  return {
299
224
  handleFilesystemMessage: handleFilesystemMessage,
225
+ productOperations: operations,
300
226
  };
301
227
  }
302
228
 
@@ -16,7 +16,7 @@ function createFullAccessService(ctx) {
16
16
  enabled: session.permissionMode === "bypassPermissions" };
17
17
  }
18
18
  function persist(session) {
19
- sm.saveSessionFile(session);
19
+ if (sm.saveSessionFile(session) === false) throw new Error("Could not persist permission mode.");
20
20
  sm.broadcastSessionList();
21
21
  if (typeof sm.sendToSession === "function") sm.sendToSession(session, { type: "session_full_access_changed", id: session.localId,
22
22
  enabled: session.permissionMode === "bypassPermissions", permissionMode: session.permissionMode });
@@ -55,7 +55,28 @@ function createFullAccessService(ctx) {
55
55
  throw error;
56
56
  }
57
57
  }
58
- function setMode(session, mode) { return serialize(session, function () { return applyMode(session, mode); }); }
58
+ function setMode(session, mode, options) {
59
+ return serialize(session, function () {
60
+ if (!options || typeof options.revalidate !== "function") return applyMode(session, mode);
61
+ options.revalidate();
62
+ var observed = { revision: Number(session._permissionObservationRevision || 0), mode: session.effectivePermissionMode, reason: session.permissionModeFallbackReason };
63
+ return require("./session-config-transition").applySessionConfig({
64
+ sm: sm, session: session, revalidate: options.revalidate, method: "setPermissionMode", nativeValue: mode === "bypassPermissions" ? "default" : mode,
65
+ changes: function () {
66
+ var newer = Number(session._permissionObservationRevision || 0) !== observed.revision || session.effectivePermissionMode !== observed.mode || session.permissionModeFallbackReason !== observed.reason;
67
+ return { permissionMode: mode, permissionModeBeforeFullAccess: mode === "bypassPermissions" ? (session.permissionMode || sm.currentPermissionMode || "default") : null,
68
+ effectivePermissionMode: newer ? session.effectivePermissionMode : null, permissionModeFallbackReason: newer ? session.permissionModeFallbackReason : null };
69
+ },
70
+ publish: function () {
71
+ sm.broadcastSessionList();
72
+ if (typeof sm.sendToSession === "function") {
73
+ sm.sendToSession(session, { type: "session_full_access_changed", id: session.localId, enabled: mode === "bypassPermissions", permissionMode: mode });
74
+ sm.sendToSession(session, { type: "config_state", sessionId: session.localId, mode: mode, effectivePermissionMode: session.effectivePermissionMode || null, vendor: session.vendor || sm.defaultVendor || "claude" });
75
+ }
76
+ },
77
+ });
78
+ });
79
+ }
59
80
  function setEnabled(session, enabled) {
60
81
  return serialize(session, function () {
61
82
  return applyMode(session, enabled ? "bypassPermissions" : (session.permissionModeBeforeFullAccess || "default"));
@@ -419,9 +419,9 @@ function attachHTTP(ctx) {
419
419
  var uninstallUserInfo = getOsUserInfoForReq(req);
420
420
  var baseDir = scope === "global" ? (uninstallUserInfo ? uninstallUserInfo.home : require("./config").REAL_HOME) : cwd;
421
421
  if (osUsers && !uninstallUserInfo) throw new Error("A mapped OS user is required to uninstall skills");
422
- var skillDir = claySkillStorage.isClaySkill(skill) ? path.join(claySkillStorage.privateRoot(baseDir), skill) : path.join(baseDir, ".claude", "skills", skill);
422
+ var skillDir = claySkillStorage.isClaySkill(skill) ? path.join(claySkillStorage.privateRoot(baseDir), skill) : path.join(require("./yoke/source-locations").legacySkillRoot(baseDir), skill);
423
423
  // Safety: keep removal inside the selected skill storage root.
424
- var expectedParent = claySkillStorage.isClaySkill(skill) ? claySkillStorage.privateRoot(baseDir) : path.join(baseDir, ".claude", "skills");
424
+ var expectedParent = claySkillStorage.isClaySkill(skill) ? claySkillStorage.privateRoot(baseDir) : require("./yoke/source-locations").legacySkillRoot(baseDir);
425
425
  var resolved = path.resolve(skillDir);
426
426
  if (!resolved.startsWith(expectedParent + path.sep)) {
427
427
  res.writeHead(403, { "Content-Type": "application/json" });
@@ -528,8 +528,8 @@ function attachHTTP(ctx) {
528
528
  var sui = getOsUserInfoForReq(req);
529
529
  return sui ? sui.home : require("./config").REAL_HOME;
530
530
  })();
531
- var globalSkillsDir = path.join(skillUserHome, ".claude", "skills");
532
- var projectSkillsDir = path.join(cwd, ".claude", "skills");
531
+ var globalSkillsDir = require("./yoke/source-locations").legacySkillRoot(skillUserHome);
532
+ var projectSkillsDir = require("./yoke/source-locations").legacySkillRoot(cwd);
533
533
  var results = [];
534
534
  var pending = skills.length;
535
535
 
@@ -44,21 +44,30 @@ function attachKnowledge(ctx) {
44
44
  return { type: user ? "user" : "system", userId: user ? user.id : null, displayName: user ? (user.displayName || user.username) : null };
45
45
  }
46
46
 
47
- function mirror(ws, name) {
47
+ function mirrorActor(who, name) {
48
48
  if (!isMate) return { ok: true, names: [] };
49
- var summary = mateSync.syncMateSource({ mateDir: cwd, fileName: name, baseDir: ctx.knowledgeBaseDir, actor: actor(ws) });
49
+ var summary = mateSync.syncMateSource({ mateDir: cwd, fileName: name, baseDir: ctx.knowledgeBaseDir, actor: who });
50
50
  return { ok: !!summary && summary.failed === 0, names: [name], error: summary && summary.errors[0] ? summary.errors[0].message : "Knowledge index synchronization failed." };
51
51
  }
52
52
 
53
- function mirrorMany(ws, names) {
53
+ function mirror(ws, name) { return mirrorActor(actor(ws), name); }
54
+
55
+ function mirrorManyActor(who, names) {
54
56
  var failures = [];
55
57
  for (var i = 0; i < names.length; i++) {
56
- var mirrored = mirror(ws, names[i]);
58
+ var mirrored = mirrorActor(who, names[i]);
57
59
  if (!mirrored.ok) failures.push(mirrored.error);
58
60
  }
59
61
  return { ok: failures.length === 0, names: names.slice(), error: failures[0] || null };
60
62
  }
61
63
 
64
+ function mirrorMany(ws, names) { return mirrorManyActor(actor(ws), names); }
65
+
66
+ function controlMirror(who, names) {
67
+ try { var state = mirrorManyActor(who, names); return { indexSyncPending: !state.ok, syncNames: names, syncError: state.ok ? null : state.error }; }
68
+ catch (error) { return { indexSyncPending: true, syncNames: names, syncError: error.message }; }
69
+ }
70
+
62
71
  function addSyncState(payload, sync) {
63
72
  if (sync.ok) return payload;
64
73
  return Object.assign(payload, { indexSyncPending: true, syncNames: sync.names, syncError: sync.error });
@@ -188,6 +197,22 @@ function attachKnowledge(ctx) {
188
197
  return databaseReady.then(function () { return combineSearch(databases.listCached({})); });
189
198
  }
190
199
 
200
+ function renameDocument(msg, who) {
201
+ var beforeMoveState = documentIndex(); var beforeMove = beforeMoveState.documents; var moveTarget = findDocument(beforeMove, msg.from, msg.documentId); var linkChanges = moveTarget ? rewriteMoveLinks(beforeMove, moveTarget, msg.name) : [];
202
+ var renamed = files.rename(msg.from, msg.name, msg.expectedRevision || null); var linkUpdateFailures = []; var updatedLinks = 0;
203
+ var updatedDocuments = []; var mirrorNames = [msg.from, renamed.name];
204
+ for (var u = 0; u < linkChanges.length; u++) {
205
+ var change = linkChanges[u]; var writeName = change.id === renamed.id ? renamed.name : change.name; var expected = change.id === renamed.id ? renamed.revision : change.revision;
206
+ try { var rewritten = files.save(writeName, change.content, expected); updatedLinks += change.count; updatedDocuments.push(rewritten); mirrorNames.push(rewritten.name); }
207
+ catch (linkError) { linkUpdateFailures.push({ name: writeName, error: linkError.message }); }
208
+ }
209
+ renamed = files.read(renamed.name);
210
+ var uniqueMirrorNames = mirrorNames.filter(function (name, at) { return mirrorNames.indexOf(name) === at; }); var renameSync = mirrorManyActor(who, uniqueMirrorNames);
211
+ return addSyncState({ type: "knowledge_renamed", id: renamed.id, from: msg.from, name: renamed.name, revision: renamed.revision,
212
+ content: renamed.content, updatedLinks: updatedLinks, linkUpdateFailures: linkUpdateFailures,
213
+ updatedDocuments: updatedDocuments.map(function (item) { return { id: item.id, name: item.name, content: item.content, revision: item.revision }; }) }, renameSync);
214
+ }
215
+
191
216
  function handleKnowledgeMessage(ws, msg) {
192
217
  if (msg.type === "knowledge_list") {
193
218
  try { var listed = listKnowledgeItems(ws); if (listed && typeof listed.then === "function") listed.then(function (items) { response(ws, msg, { type: "knowledge_list", files: items, drafts: drafts.list(ws && ws._clayUser && ws._clayUser.id || "system") }); }).catch(function (error) { failure(ws, msg, error); }); else response(ws, msg, { type: "knowledge_list", files: listed, drafts: drafts.list(ws && ws._clayUser && ws._clayUser.id || "system") }); } catch (error) { failure(ws, msg, error); }
@@ -236,19 +261,7 @@ function attachKnowledge(ctx) {
236
261
  if (msg.type === "knowledge_rename") {
237
262
  if (!requireWrite(ws, msg)) return true;
238
263
  try {
239
- var beforeMoveState = documentIndex(); var beforeMove = beforeMoveState.documents; var moveTarget = findDocument(beforeMove, msg.from, msg.documentId); var linkChanges = moveTarget ? rewriteMoveLinks(beforeMove, moveTarget, msg.name) : [];
240
- var renamed = files.rename(msg.from, msg.name, msg.expectedRevision || null); var linkUpdateFailures = []; var updatedLinks = 0;
241
- var updatedDocuments = []; var mirrorNames = [msg.from, renamed.name];
242
- for (var u = 0; u < linkChanges.length; u++) {
243
- var change = linkChanges[u]; var writeName = change.id === renamed.id ? renamed.name : change.name; var expected = change.id === renamed.id ? renamed.revision : change.revision;
244
- try { var rewritten = files.save(writeName, change.content, expected); updatedLinks += change.count; updatedDocuments.push(rewritten); mirrorNames.push(rewritten.name); }
245
- catch (linkError) { linkUpdateFailures.push({ name: writeName, error: linkError.message }); }
246
- }
247
- renamed = files.read(renamed.name);
248
- var uniqueMirrorNames = mirrorNames.filter(function (name, at) { return mirrorNames.indexOf(name) === at; }); var renameSync = mirrorMany(ws, uniqueMirrorNames);
249
- response(ws, msg, addSyncState({ type: "knowledge_renamed", id: renamed.id, from: msg.from, name: renamed.name, revision: renamed.revision,
250
- content: renamed.content, updatedLinks: updatedLinks, linkUpdateFailures: linkUpdateFailures,
251
- updatedDocuments: updatedDocuments.map(function (item) { return { id: item.id, name: item.name, content: item.content, revision: item.revision }; }) }, renameSync));
264
+ response(ws, msg, renameDocument(msg, actor(ws)));
252
265
  sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
253
266
  } catch (error) { failure(ws, msg, error); }
254
267
  return true;
@@ -357,7 +370,8 @@ function attachKnowledge(ctx) {
357
370
  return false;
358
371
  }
359
372
 
360
- return { handleKnowledgeMessage: handleKnowledgeMessage, listKnowledgeFiles: listKnowledgeFiles, databases: databases };
373
+ var productOperations = require('./knowledge-product-operations').createKnowledgeProductOperations({ files: files, mirror: controlMirror, rename: renameDocument });
374
+ return { productOperations: productOperations, handleKnowledgeMessage: handleKnowledgeMessage, listKnowledgeFiles: listKnowledgeFiles, databases: databases };
361
375
  }
362
376
 
363
377
  module.exports = { attachKnowledge: attachKnowledge };