synabun 2.0.10 → 2.1.0

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 (517) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +17 -8
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +1 -1
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +228 -0
  24. package/lib/virustotal.mjs +192 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  42. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  44. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  45. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  46. package/mcp-server/dist/services/neural-interface.js +67 -11
  47. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  48. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  49. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  50. package/mcp-server/dist/services/server-instructions.js +20 -62
  51. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  52. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  53. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  54. package/mcp-server/dist/services/tool-catalog.js +339 -0
  55. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  56. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  60. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  61. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  62. package/mcp-server/dist/services/tool-list.js +334 -0
  63. package/mcp-server/dist/services/tool-list.js.map +1 -0
  64. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  66. package/mcp-server/dist/services/tool-runtime.js +400 -0
  67. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  68. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  70. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  71. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  72. package/mcp-server/dist/tools/agents.d.ts +10 -10
  73. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  74. package/mcp-server/dist/tools/agents.js +22 -13
  75. package/mcp-server/dist/tools/agents.js.map +1 -1
  76. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  77. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  78. package/mcp-server/dist/tools/image-tools.js +1 -1
  79. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  80. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  81. package/mcp-server/dist/tools/skill-tools.js +639 -0
  82. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  83. package/mcp-server/dist/tools/skill.d.ts +7 -0
  84. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  85. package/mcp-server/dist/tools/skill.js +11 -0
  86. package/mcp-server/dist/tools/skill.js.map +1 -0
  87. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  89. package/mcp-server/dist/tools/tool-groups.js +147 -0
  90. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  91. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  93. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  94. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  97. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  98. package/mcp-server/package-lock.json +2 -2
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/src/http.ts +75 -12
  101. package/mcp-server/src/index.ts +147 -154
  102. package/mcp-server/src/preload.ts +12 -12
  103. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  104. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  105. package/mcp-server/src/services/neural-interface.ts +79 -20
  106. package/mcp-server/src/services/server-instructions.ts +27 -63
  107. package/mcp-server/src/services/tool-catalog.ts +403 -0
  108. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  109. package/mcp-server/src/services/tool-list.ts +410 -0
  110. package/mcp-server/src/services/tool-runtime.ts +457 -0
  111. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  112. package/mcp-server/src/tools/agents.ts +22 -14
  113. package/mcp-server/src/tools/image-tools.ts +1 -1
  114. package/mcp-server/src/tools/skill-tools.ts +589 -0
  115. package/mcp-server/src/tools/skill.ts +13 -0
  116. package/mcp-server/src/tools/tool-groups.ts +164 -0
  117. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  118. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  119. package/neural-interface/i18n/en.json +3463 -200
  120. package/neural-interface/i18n/pt-BR.json +3411 -151
  121. package/neural-interface/lib/assistant-api.js +127 -28
  122. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  123. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  124. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  125. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  126. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  127. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  128. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  129. package/neural-interface/lib/assistant-catalog.js +6 -7
  130. package/neural-interface/lib/assistant-dispatch.js +871 -156
  131. package/neural-interface/lib/assistant-envelope.js +3 -1
  132. package/neural-interface/lib/assistant-persona.js +80 -11
  133. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  134. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  135. package/neural-interface/lib/assistant-route-gate.js +6 -4
  136. package/neural-interface/lib/assistant-router.js +13 -8
  137. package/neural-interface/lib/assistant-runtime.js +56 -18
  138. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  139. package/neural-interface/lib/assistant-tools.js +51 -0
  140. package/neural-interface/lib/backup-service.js +620 -3
  141. package/neural-interface/lib/backup-zip-reader.js +10 -5
  142. package/neural-interface/lib/backup-zip-writer.js +257 -0
  143. package/neural-interface/lib/browser-execution.js +10 -2
  144. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  145. package/neural-interface/lib/browser-network-guard.js +284 -0
  146. package/neural-interface/lib/browser-policy-roots.js +64 -0
  147. package/neural-interface/lib/browser-tool-policy.js +186 -31
  148. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  149. package/neural-interface/lib/claude-hooks-status.js +103 -0
  150. package/neural-interface/lib/claude-temporary.js +5 -11
  151. package/neural-interface/lib/cli-install.js +18 -138
  152. package/neural-interface/lib/cli-resolver.js +4 -1
  153. package/neural-interface/lib/cli-update-planner.js +32 -31
  154. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  155. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  156. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  157. package/neural-interface/lib/confined-fs.js +30 -3
  158. package/neural-interface/lib/crash-notice-api.js +98 -0
  159. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  160. package/neural-interface/lib/exec-loop-command.js +4 -8
  161. package/neural-interface/lib/http-guards.js +8 -2
  162. package/neural-interface/lib/install-details.js +4 -1
  163. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  164. package/neural-interface/lib/mcp-client-config.js +0 -29
  165. package/neural-interface/lib/mcp-external-servers.js +323 -0
  166. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  167. package/neural-interface/lib/morelogin.js +30 -0
  168. package/neural-interface/lib/native-loop-providers.js +547 -47
  169. package/neural-interface/lib/native-loop-runtime.js +268 -41
  170. package/neural-interface/lib/opencode-config.js +557 -0
  171. package/neural-interface/lib/opencode-db-schema.js +17 -34
  172. package/neural-interface/lib/opencode-history.js +48 -218
  173. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  174. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  175. package/neural-interface/lib/opencode-protocol.js +249 -112
  176. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  177. package/neural-interface/lib/opencode-serve-records.js +39 -21
  178. package/neural-interface/lib/opencode-v2-client.js +48 -153
  179. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  180. package/neural-interface/lib/opencode-worker-data.js +27 -0
  181. package/neural-interface/lib/opencode2-client.js +73 -37
  182. package/neural-interface/lib/opencode2-proxy.js +23 -63
  183. package/neural-interface/lib/opencode2-translate.js +51 -2
  184. package/neural-interface/lib/owner-authority.js +249 -0
  185. package/neural-interface/lib/page-session.js +275 -0
  186. package/neural-interface/lib/peer-identity.js +763 -0
  187. package/neural-interface/lib/request-gate.js +17 -2
  188. package/neural-interface/lib/rulesets/api.js +1 -1
  189. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  190. package/neural-interface/lib/rulesets/installer.js +92 -27
  191. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  192. package/neural-interface/lib/session-cache-builder.js +17 -30
  193. package/neural-interface/lib/session-title-generator.js +58 -36
  194. package/neural-interface/lib/skills/api-v2.js +495 -0
  195. package/neural-interface/lib/skills/bundle.js +418 -0
  196. package/neural-interface/lib/skills/catalog.js +294 -0
  197. package/neural-interface/lib/skills/creation-api.js +191 -0
  198. package/neural-interface/lib/skills/creation-brief.js +247 -0
  199. package/neural-interface/lib/skills/creation-sources.js +267 -0
  200. package/neural-interface/lib/skills/creation.js +843 -0
  201. package/neural-interface/lib/skills/deploy.js +564 -0
  202. package/neural-interface/lib/skills/digest.js +119 -0
  203. package/neural-interface/lib/skills/discover.js +111 -0
  204. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  205. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  206. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  207. package/neural-interface/lib/skills/eval-trace.js +119 -0
  208. package/neural-interface/lib/skills/evals-api.js +212 -0
  209. package/neural-interface/lib/skills/evals.js +1573 -0
  210. package/neural-interface/lib/skills/export.js +200 -0
  211. package/neural-interface/lib/skills/frontmatter.js +64 -0
  212. package/neural-interface/lib/skills/health.js +75 -0
  213. package/neural-interface/lib/skills/json-edit.js +165 -0
  214. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  215. package/neural-interface/lib/skills/known-bridges.js +96 -0
  216. package/neural-interface/lib/skills/library.js +823 -0
  217. package/neural-interface/lib/skills/local-store.js +21 -0
  218. package/neural-interface/lib/skills/migration.js +433 -0
  219. package/neural-interface/lib/skills/operations.js +45 -0
  220. package/neural-interface/lib/skills/ownership.js +427 -0
  221. package/neural-interface/lib/skills/packages.js +88 -0
  222. package/neural-interface/lib/skills/reviews.js +212 -0
  223. package/neural-interface/lib/skills/revision.js +80 -0
  224. package/neural-interface/lib/skills/roots.js +262 -0
  225. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  226. package/neural-interface/lib/skills/scan-assets.js +252 -0
  227. package/neural-interface/lib/skills/scan-context.js +26 -0
  228. package/neural-interface/lib/skills/scan-shell.js +324 -0
  229. package/neural-interface/lib/skills/scan.js +364 -0
  230. package/neural-interface/lib/skills/scanners.js +54 -0
  231. package/neural-interface/lib/skills/service.js +522 -0
  232. package/neural-interface/lib/skills/telemetry.js +113 -0
  233. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  234. package/neural-interface/lib/tool-lists.js +124 -0
  235. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  236. package/neural-interface/lib/worker-confinement.js +1192 -0
  237. package/neural-interface/lib/worker-guard-setting.js +56 -0
  238. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  239. package/neural-interface/lib/worker-relay-ports.js +227 -0
  240. package/neural-interface/lib/worker-resume.js +229 -0
  241. package/neural-interface/lib/worker-sandbox.js +299 -0
  242. package/neural-interface/package-lock.json +18 -12
  243. package/neural-interface/package.json +2 -2
  244. package/neural-interface/public/index.html +3 -1
  245. package/neural-interface/public/index2d.html +19 -11
  246. package/neural-interface/public/onboarding.html +2 -2
  247. package/neural-interface/public/shared/account-menu.js +8 -5
  248. package/neural-interface/public/shared/agent-runtime-options.js +35 -19
  249. package/neural-interface/public/shared/api.js +1527 -1336
  250. package/neural-interface/public/shared/app-frame.js +192 -0
  251. package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
  252. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  253. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  254. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  255. package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
  256. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  257. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  258. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  259. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  260. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  261. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  262. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  263. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  264. package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
  265. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  266. package/neural-interface/public/shared/assistant/asst-render.js +401 -77
  267. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  268. package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
  269. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  270. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  271. package/neural-interface/public/shared/assistant/asst-state.js +11 -15
  272. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  273. package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
  274. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
  275. package/neural-interface/public/shared/automation-context.js +5 -2
  276. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  277. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  278. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  279. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  280. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  281. package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
  282. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  283. package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
  284. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  285. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  286. package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
  287. package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
  288. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  289. package/neural-interface/public/shared/cli-install-view.js +4 -8
  290. package/neural-interface/public/shared/cli-status.js +2 -2
  291. package/neural-interface/public/shared/constants.js +2 -2
  292. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  293. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  294. package/neural-interface/public/shared/context-menu-model.js +366 -0
  295. package/neural-interface/public/shared/context-menu.js +372 -0
  296. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  297. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  298. package/neural-interface/public/shared/desktop-update-view.js +20 -0
  299. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  300. package/neural-interface/public/shared/html-shell.js +9 -11
  301. package/neural-interface/public/shared/i18n.js +2 -0
  302. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  303. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  304. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  305. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  306. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  307. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  308. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  309. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  310. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  311. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  312. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  313. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
  314. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  315. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  316. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
  317. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  318. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  319. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  320. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  321. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  322. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  323. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  324. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
  325. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  326. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
  327. package/neural-interface/public/shared/page-session.js +96 -0
  328. package/neural-interface/public/shared/registry.js +5 -3
  329. package/neural-interface/public/shared/retired-2d.js +3 -0
  330. package/neural-interface/public/shared/schedules-view.js +195 -61
  331. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  332. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  333. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  334. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  335. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  336. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  337. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  338. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  339. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  340. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  341. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  342. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  343. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  344. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  345. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  346. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  347. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  348. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  349. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  350. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  351. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  352. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  353. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  354. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  355. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  356. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  357. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  358. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  359. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  360. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  361. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  362. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  363. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  364. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  365. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  366. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  367. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  368. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  369. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  370. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  371. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  372. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  373. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  374. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  375. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  376. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  377. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  378. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  379. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  380. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  381. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  382. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  383. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  384. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  385. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  386. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  387. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  388. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  389. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  390. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  391. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  392. package/neural-interface/public/shared/styles.css +1525 -1779
  393. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  394. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  395. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  396. package/neural-interface/public/shared/tool-list-view.js +112 -0
  397. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  398. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  399. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  400. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  401. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  402. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  403. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  404. package/neural-interface/public/shared/tour/tour.css +144 -0
  405. package/neural-interface/public/shared/ui-automation-studio.js +91 -128
  406. package/neural-interface/public/shared/ui-claude-panel.js +77 -5
  407. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  408. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  409. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  410. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  411. package/neural-interface/public/shared/ui-detail.js +15 -3
  412. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  413. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  414. package/neural-interface/public/shared/ui-menubar.js +8 -18
  415. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  416. package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
  417. package/neural-interface/public/shared/ui-navbar.js +4 -24
  418. package/neural-interface/public/shared/ui-panels.js +96 -65
  419. package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
  420. package/neural-interface/public/shared/ui-sessions.js +5 -3
  421. package/neural-interface/public/shared/ui-settings.js +594 -462
  422. package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
  423. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  424. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  425. package/neural-interface/public/shared/ui-skills.js +33 -2830
  426. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  427. package/neural-interface/public/shared/ui-sync.js +15 -5
  428. package/neural-interface/public/shared/ui-terminal.js +124 -180
  429. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  430. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  431. package/neural-interface/public/shared/ui-trash.js +6 -4
  432. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  433. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  434. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  435. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  436. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  437. package/neural-interface/public/shared/ui-update.js +38 -2
  438. package/neural-interface/public/shared/ui-whiteboard.js +311 -58
  439. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  440. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  441. package/neural-interface/public/shared/view-variant.js +66 -0
  442. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  443. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  444. package/neural-interface/public/shared/worker-end.js +10 -3
  445. package/neural-interface/public/shared/worker-restart.js +12 -0
  446. package/neural-interface/public/sidepanel-session.html +1 -1
  447. package/neural-interface/public/variant/2d/background.js +2 -1
  448. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  449. package/neural-interface/public/variant/2d/graph.js +42 -20
  450. package/neural-interface/public/variant/2d/hulls.js +14 -9
  451. package/neural-interface/public/variant/2d/lasso.js +11 -7
  452. package/neural-interface/public/variant/2d/main.js +32 -1
  453. package/neural-interface/public/variant/2d/minimap.js +11 -13
  454. package/neural-interface/public/variant/3d/main.js +69 -6
  455. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  456. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  457. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  458. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  459. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  460. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  461. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  462. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  463. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  464. package/neural-interface/scripts/build-vendor.mjs +32 -0
  465. package/neural-interface/server.js +3934 -2454
  466. package/neural-interface/templates/rulesets/core.md +1 -1
  467. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  468. package/neural-interface/templates/rulesets/manifest.json +8 -8
  469. package/npm-shrinkwrap.json +18 -12
  470. package/package.json +16 -3
  471. package/postinstall.js +9 -4
  472. package/preuninstall.js +1 -1
  473. package/setup.js +97 -322
  474. package/skills/skill-forge/SKILL.md +177 -0
  475. package/skills/skill-forge/evals/evals.json +54 -0
  476. package/skills/skill-forge/references/package.md +105 -0
  477. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  478. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  479. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  480. package/skills/synabun/SKILL.md +10 -7
  481. package/skills/synabun/modules/audit.md +1 -5
  482. package/skills/synabun/modules/changelog.md +1 -5
  483. package/skills/synabun/modules/idea.md +1 -1
  484. package/skills/synabun/modules/memorize.md +6 -9
  485. package/skills/synabun/modules/schedule.md +4 -4
  486. package/mcp-server/dist/services/profiles.d.ts +0 -120
  487. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  488. package/mcp-server/dist/services/profiles.js +0 -387
  489. package/mcp-server/dist/services/profiles.js.map +0 -1
  490. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  491. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  492. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  493. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  494. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  495. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  496. package/mcp-server/dist/tools/leonardo.js +0 -21
  497. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  498. package/mcp-server/dist/tools/profile.d.ts +0 -17
  499. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  500. package/mcp-server/dist/tools/profile.js +0 -114
  501. package/mcp-server/dist/tools/profile.js.map +0 -1
  502. package/mcp-server/src/services/profiles.ts +0 -428
  503. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  504. package/mcp-server/src/tools/leonardo.ts +0 -30
  505. package/mcp-server/src/tools/profile.ts +0 -122
  506. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  507. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  508. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  509. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  510. package/skills/leonardo/SKILL.md +0 -168
  511. package/skills/leonardo/icon.svg +0 -1
  512. package/skills/leonardo/modules/browser-guide.md +0 -378
  513. package/skills/leonardo/modules/image-prompter.md +0 -283
  514. package/skills/leonardo/modules/model-advisor.md +0 -100
  515. package/skills/leonardo/modules/prompt-library.md +0 -110
  516. package/skills/leonardo/modules/style-guide.md +0 -186
  517. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -7,6 +7,7 @@
7
7
  // composer attachments, Claude/Codex account management, and the sidepanel tab registry.
8
8
  // Mounted from server.js with `app.use('/api/assistant', createAssistantApi(deps))`.
9
9
 
10
+ import { dispatchInput } from './assistant-tools.js';
10
11
  import { Router } from 'express';
11
12
  import { DispatchError } from './assistant-dispatch.js';
12
13
  import { openServable } from './assistant-media.js';
@@ -42,7 +43,7 @@ const NO_CATALOG = Object.freeze({});
42
43
  function sendError(res, error, fallbackStatus = 500) {
43
44
  if (error instanceof DispatchError || (error && error.code && error.status)) {
44
45
  const status = Number(error.status) || fallbackStatus;
45
- const body = typeof error.toJSON === 'function' ? error.toJSON() : { code: error.code, message: error.message };
46
+ const body = typeof error.toJSON === 'function' ? error.toJSON() : { code: error.code, message: error.message, ...(error.problems ? { problems: error.problems } : {}) };
46
47
  return res.status(status).json({ ok: false, error: body.message, ...body });
47
48
  }
48
49
  return res.status(fallbackStatus).json({ ok: false, error: error?.message || String(error), code: error?.code || 'INTERNAL' });
@@ -96,7 +97,7 @@ export function slimRunView(run) {
96
97
  * @param {object} [deps.runtime] createAssistantRuntime() (sessions; optional until wired)
97
98
  * @param {object} [deps.claudeAccounts] createClaudeAccounts()
98
99
  * @param {object} [deps.codexAccounts] createCodexAccounts() (lib/codex-accounts.js)
99
- * @param {Function} [deps.buildCatalog] async () => catalog object (models/accounts/projects/profiles)
100
+ * @param {Function} [deps.buildCatalog] async () => catalog object (models/accounts/projects/tools)
100
101
  * @param {Function} [deps.createLoginTerminal] async ({ env, cwd, profile, accountId }) => { terminalSessionId, profile }
101
102
  * @param {Function} [deps.claudeSignIn] async (accountId) => { sessionId, status, error } — Claude Code's own
102
103
  * `auth login` in a SynaBun terminal under the account's config directory (lib/provider-panel-terminal-auth.js)
@@ -153,6 +154,11 @@ export function createAssistantApi({
153
154
  port = 3344,
154
155
  // The Admin sandbox setting (lib/agent-sandbox-setting.js): one global value for every agent SynaBun starts.
155
156
  adminSandbox = null,
157
+ // Protect workers (lib/worker-guard-setting.js): what capability read-only / workspace means for every worker.
158
+ workerGuard = null,
159
+ // The page secret (lib/page-session.js): a request without an agent pin is the owner's page only when it carries
160
+ // this boot's cookie; a local process that merely sends no headers is nobody (never 'ui').
161
+ pageSession = null,
156
162
  } = {}) {
157
163
  if (!dispatcher) throw new Error('createAssistantApi requires a dispatcher');
158
164
  const router = Router();
@@ -164,20 +170,31 @@ export function createAssistantApi({
164
170
  next();
165
171
  });
166
172
 
167
- // Resolve who is calling: the UI (no pin), an assistant brain (assistant-<uuid>
168
- // pin), or a dispatched worker (run id pin → forbidden for orchestration).
173
+ // Resolve who is calling: the UI (the page secret cookie, no pin), a dispatched worker (run id pin →
174
+ // forbidden for orchestration), or nobody known. A brain is NOT decided here: a pin only records which
175
+ // session a request *claims* to be (for attribution), never that the request is that session's brain.
176
+ // Brain authority is the per-launch credential alone (brainCaller) — the final pre-restart review
177
+ // (run f283e53b) showed a forged `X-Synabun-Terminal: assistant-*`, and equally a real but non-secret
178
+ // session id (a worker's environment and its task text both carry one), would otherwise pass as the brain.
169
179
  function callerContext(req) {
170
180
  const pin = String(req.get('x-synabun-terminal') || req.get('x-synabun-assistant') || '').trim();
171
181
  const bodySession = req.body?.assistantSessionId || req.body?.assistant_session_id || req.query?.assistantSessionId || null;
172
- if (!pin) return { kind: 'ui', assistantSessionId: bodySession ? String(bodySession) : null };
182
+ if (!pin) {
183
+ // The page: this boot's page secret cookie (the browser sends it by itself). Without a page session wired
184
+ // (a test, an older host) every pinless request is the page, as before.
185
+ if (pageSession && typeof pageSession.verify === 'function' && !pageSession.verify(req)) return { kind: 'unknown', assistantSessionId: bodySession ? String(bodySession) : null, pin: '', pageless: true };
186
+ return { kind: 'ui', assistantSessionId: bodySession ? String(bodySession) : null };
187
+ }
173
188
  if (dispatcher.get(pin)) return { kind: 'worker', forbidden: true, pin };
174
- // A brain's pin resolves to its session (Claude pins the session id; the
175
- // OpenCode brain pins assistant-oc-<24>, which must never be taken as an id).
189
+ // The session a request claims, for attribution once a credential has proven it a brain: a Claude brain's
190
+ // pin resolves to its session; the OpenCode brain pins assistant-oc-<24> (never taken as an id) and names
191
+ // the session in the body. The claim grants nothing on its own.
176
192
  const resolved = runtime?.resolveTerminal?.(pin) || null;
177
- if (resolved) return { kind: 'assistant', assistantSessionId: resolved, pin };
178
- if (/^assistant-oc-/.test(pin)) return { kind: 'assistant', assistantSessionId: bodySession ? String(bodySession) : null, pin };
179
- if (/^assistant-/.test(pin)) return { kind: 'assistant', assistantSessionId: pin, pin };
180
- return { kind: 'unknown', assistantSessionId: bodySession ? String(bodySession) : null, pin };
193
+ const claimed = resolved
194
+ || (/^assistant-oc-/.test(pin) ? (bodySession ? String(bodySession) : null)
195
+ : /^assistant-/.test(pin) ? pin
196
+ : (bodySession ? String(bodySession) : null));
197
+ return { kind: 'unknown', assistantSessionId: claimed, pin };
181
198
  }
182
199
  /**
183
200
  * The plan mode of the brain making this call: 'read-only' (a remote session
@@ -212,20 +229,56 @@ export function createAssistantApi({
212
229
  function orchestrationGuard(req, res, next) {
213
230
  const caller = callerContext(req);
214
231
  if (caller.forbidden) return res.status(403).json({ ok: false, code: 'AGENTS_NOT_PERMITTED', error: 'Dispatched workers cannot orchestrate other agents.' });
215
- req.assistantCaller = caller;
216
- next();
232
+ // The user's own page (this boot's page secret cookie): the only caller that orchestrates without being a brain.
233
+ if (caller.kind === 'ui') { req.assistantCaller = caller; return next(); }
234
+ // The only other caller allowed is an Assistant brain, and only its per-launch credential proves it (brainCaller):
235
+ // never a pin, never a session id, which a worker's environment and its task text both carry. This is the forged-
236
+ // and known-pin bypass the final pre-restart review found (run f283e53b, assistant-api.js:189): an
237
+ // `X-Synabun-Terminal: assistant-*` header, or a real live session id, used to pass here as the brain.
238
+ const brain = brainCaller(req);
239
+ if (brain) { req.assistantCaller = brain; return next(); }
240
+ return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Orchestration is the user\'s (from SynaBun\'s own page) and the Assistant\'s brain\'s: this request is neither (no page secret, no brain launch credential a live session knows).' });
217
241
  }
218
242
  /**
219
- * The caller when it is an assistant brain, else null. Brains pin their
220
- * session (Claude) or assistant-oc-<24> (OpenCode); the Codex brain's MCP
221
- * child pins codex-sp-<uuid>, which no session resolves, but it sends
222
- * X-Synabun-Role: assistant. The UI, workers and other sessions never count.
243
+ * The caller when it is an assistant brain, else null. The one proof is the per-launch credential
244
+ * (X-Synabun-Brain-Token, SYNABUN_BRAIN_TOKEN in the brain's own environment only, minted per brain launch and
245
+ * resolved by runtime.sessionForBrainCredential over the live brains): a pin or a session id names nobody here,
246
+ * because any local process can send a header and a session id is not secret (a worker carries its own, and ids
247
+ * appear in task text). The credential's session is authoritative, whatever pin or body the request carries. The
248
+ * UI, workers and other sessions never count.
223
249
  */
224
250
  function brainCaller(req) {
251
+ if (req.assistantCaller?.credentialed) return req.assistantCaller;
252
+ if (String(req.get('x-synabun-role') || '').trim().toLowerCase() !== 'assistant') return null;
253
+ let session = null;
254
+ try { session = runtime?.sessionForBrainCredential?.(req.get('x-synabun-brain-token')) || null; } catch { session = null; }
255
+ if (!session) return null;
225
256
  const caller = req.assistantCaller || callerContext(req);
226
- if (caller.kind === 'assistant') return caller;
227
- if (caller.kind === 'unknown' && String(req.get('x-synabun-role') || '').trim().toLowerCase() === 'assistant') return caller;
228
- return null;
257
+ return { ...caller, kind: 'assistant', assistantSessionId: String(session), credentialed: true };
258
+ }
259
+ /**
260
+ * Who may READ a run (GET /runs/:id, /result, /transcript): the owner's page (ui), a credential-verified
261
+ * Assistant brain (its session's runs, as today), or the worker of that very run — a worker's pin is its own
262
+ * run id, so it reads only itself, never another run. Everyone else (a pinless local process with no page
263
+ * secret, a worker naming another run) is refused. Listing and deletion are owner-or-brain only (never a worker):
264
+ * the list is not "its own run", and a finished run is cleared by the owner or the brain that owns its session.
265
+ */
266
+ function runReadAllowed(req, runId) {
267
+ const caller = callerContext(req);
268
+ if (caller.kind === 'ui') return true;
269
+ const brain = brainCaller(req);
270
+ if (brain) {
271
+ // A brain reads only its OWN session's runs (like DELETE): another session's run is not readable through the
272
+ // credential. A run with no session, or a sessionless brain, falls through as before (nothing to scope to).
273
+ const runSession = dispatcher.get(runId)?.assistantSessionId || null;
274
+ if (runSession && brain.assistantSessionId && String(runSession) !== String(brain.assistantSessionId)) return false;
275
+ return true;
276
+ }
277
+ if (caller.kind === 'worker' && caller.pin && String(caller.pin) === String(runId)) return true;
278
+ return false;
279
+ }
280
+ function refuseRunRead(res) {
281
+ return res.status(403).json({ ok: false, code: 'RUN_FORBIDDEN', error: 'This run is readable by the owner (from SynaBun\'s own page), its own worker and the Assistant brain of its session only.' });
229
282
  }
230
283
  /** A remote session whose workers only a person may answer (strict worker approvals below autonomous). */
231
284
  function humanApprovalRequired(assistantSessionId) {
@@ -363,9 +416,8 @@ export function createAssistantApi({
363
416
  try {
364
417
  const body = req.body || {};
365
418
  const spec = {
366
- ...body,
419
+ ...dispatchInput(body),
367
420
  accountId: body.accountId || body.account_id || body.codexAccountId || body.claudeAccountId || null,
368
- mcpProfile: body.mcpProfile || body.mcp_profile,
369
421
  permissionPolicy: body.permissionPolicy || body.permission_policy,
370
422
  maxMinutes: body.maxMinutes ?? body.max_minutes,
371
423
  budgetUsd: body.budgetUsd ?? body.budget_usd,
@@ -724,18 +776,25 @@ export function createAssistantApi({
724
776
  });
725
777
 
726
778
  router.get('/runs', (req, res) => {
779
+ // Listing is owner-or-brain only: a worker reads its own run by id, never the roster of every run.
780
+ const caller = callerContext(req);
781
+ const brain = brainCaller(req);
782
+ if (caller.kind !== 'ui' && !brain) return refuseRunRead(res);
727
783
  try {
728
784
  const active = req.query.active === '1' || req.query.activeOnly === '1';
785
+ // A brain sees only its OWN session's runs (its session id forced over whatever the query asks); the owner's
786
+ // page may list any session.
787
+ const scopedSession = (brain && caller.kind !== 'ui') ? (brain.assistantSessionId || null) : (req.query.assistantSessionId || null);
729
788
  res.json({
730
789
  ok: true,
731
790
  runs: dispatcher.list({
732
791
  activeOnly: active,
733
- assistantSessionId: req.query.assistantSessionId || null,
792
+ assistantSessionId: scopedSession,
734
793
  workflowId: req.query.workflowId || null,
735
794
  status: req.query.status || null,
736
795
  limit: clampInt(req.query.limit, 1, 500, 100),
737
796
  }),
738
- totals: dispatcher.totals({ assistantSessionId: req.query.assistantSessionId || null, workflowId: req.query.workflowId || null }),
797
+ totals: dispatcher.totals({ assistantSessionId: scopedSession, workflowId: req.query.workflowId || null }),
739
798
  });
740
799
  } catch (error) { sendError(res, error); }
741
800
  });
@@ -768,12 +827,19 @@ export function createAssistantApi({
768
827
  if (!assistantSessionId && !workflowId && req.assistantCaller.kind !== 'ui') {
769
828
  return res.status(400).json({ ok: false, code: 'SCOPE_REQUIRED', error: 'Pass assistant_session_id (your ASSISTANT_SESSION_ID) or workflow_id to stop runs.' });
770
829
  }
771
- const result = await dispatcher.killAll({ assistantSessionId, workflowId, reason: body.reason || 'kill_all' });
830
+ const reason = body.reason || 'kill_all';
831
+ const result = await dispatcher.killAll({ assistantSessionId, workflowId, reason });
832
+ // Who stopped every run, when: the records only say `kill_all` (2026-10-24 had to be traced by elimination).
833
+ const caller = req.assistantCaller.kind === 'ui' ? 'the page' : `the brain of ${req.assistantCaller.assistantSessionId || 'an unknown session'}`;
834
+ const scope = [assistantSessionId ? `session ${assistantSessionId}` : null, workflowId ? `workflow ${workflowId}` : null].filter(Boolean).join(', ') || 'every session';
835
+ const stopped = Array.isArray(result?.stopped) ? result.stopped : [];
836
+ log('kill-all', `${new Date(now()).toISOString()} ${caller} stopped ${stopped.length} run(s) of ${scope} (${reason})${stopped.length ? `: ${stopped.join(', ')}` : ''}`);
772
837
  res.json(result);
773
838
  } catch (error) { sendError(res, error); }
774
839
  });
775
840
 
776
841
  router.get('/runs/:runId', (req, res) => {
842
+ if (!runReadAllowed(req, req.params.runId)) return refuseRunRead(res);
777
843
  const run = dispatcher.get(req.params.runId);
778
844
  if (!run) return res.status(404).json({ ok: false, code: 'RUN_NOT_FOUND', error: `Unknown run ${req.params.runId}` });
779
845
  const brain = brainCaller(req);
@@ -781,6 +847,7 @@ export function createAssistantApi({
781
847
  res.json({ ok: true, run: brain ? slimRunView(run) : run });
782
848
  });
783
849
  router.get('/runs/:runId/result', (req, res) => {
850
+ if (!runReadAllowed(req, req.params.runId)) return refuseRunRead(res);
784
851
  try {
785
852
  const out = dispatcher.transcript(req.params.runId, { format: 'result' });
786
853
  if (out?.result) {
@@ -791,6 +858,7 @@ export function createAssistantApi({
791
858
  } catch (error) { sendError(res, error); }
792
859
  });
793
860
  router.get('/runs/:runId/transcript', (req, res) => {
861
+ if (!runReadAllowed(req, req.params.runId)) return refuseRunRead(res);
794
862
  try {
795
863
  const format = req.query.format || 'tail';
796
864
  const out = dispatcher.transcript(req.params.runId, {
@@ -811,6 +879,7 @@ export function createAssistantApi({
811
879
  // path from the request; owner-only like everything here. sendFile answers
812
880
  // Range requests, so a video seeks.
813
881
  router.get('/runs/:runId/media/:n', async (req, res) => {
882
+ if (!runReadAllowed(req, req.params.runId)) return refuseRunRead(res);
814
883
  const notFound = () => res.status(404).json({ ok: false, code: 'MEDIA_NOT_FOUND', error: `No media ${req.params.n} for run ${req.params.runId}` });
815
884
  if (!/^\d{1,4}$/.test(String(req.params.n))) return notFound();
816
885
  let file = null;
@@ -900,10 +969,23 @@ export function createAssistantApi({
900
969
  router.post('/runs/:runId/stop', orchestrationGuard, async (req, res) => {
901
970
  try { res.json(await dispatcher.stop(req.params.runId, req.body?.reason || (req.assistantCaller.kind === 'ui' ? 'user' : 'assistant'))); } catch (error) { sendError(res, error); }
902
971
  });
903
- // Finished runs are cleared from the agents tray by the user, never by an agent.
972
+ // Finished runs are cleared from the agents tray by the owner (the page) or the Assistant brain of the run's
973
+ // own session; never by a worker (even its own run). A brain's delete is scoped to its session's runs.
904
974
  router.delete('/runs/:runId', async (req, res) => {
905
- if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Finished runs are removed from the assistant panel only.' });
906
- try { res.json(await dispatcher.remove(req.params.runId, { assistantSessionId: req.query.assistantSessionId || req.body?.assistantSessionId || null })); } catch (error) { sendError(res, error); }
975
+ const caller = callerContext(req);
976
+ const brain = brainCaller(req);
977
+ if (caller.kind !== 'ui' && !brain) return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Finished runs are removed by the owner (from SynaBun\'s own page) or the Assistant brain of the run\'s session.' });
978
+ if (brain && caller.kind !== 'ui') {
979
+ // A brain removes only the runs of its own session: another session's run is left untouched.
980
+ const runSession = dispatcher.get(req.params.runId)?.assistantSessionId || null;
981
+ if (runSession && brain.assistantSessionId && String(runSession) !== String(brain.assistantSessionId)) {
982
+ return res.status(403).json({ ok: false, code: 'RUN_NOT_IN_SESSION', error: `Run ${req.params.runId} belongs to another assistant session` });
983
+ }
984
+ }
985
+ const assistantSessionId = (brain && caller.kind !== 'ui')
986
+ ? (brain.assistantSessionId || null)
987
+ : (req.query.assistantSessionId || req.body?.assistantSessionId || null);
988
+ try { res.json(await dispatcher.remove(req.params.runId, { assistantSessionId })); } catch (error) { sendError(res, error); }
907
989
  });
908
990
  router.post('/runs/clear-finished', async (req, res) => {
909
991
  if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Finished runs are removed from the assistant panel only.' });
@@ -1263,6 +1345,23 @@ export function createAssistantApi({
1263
1345
  try { res.json({ ok: true, ...adminSandbox.set(value) }); } catch (error) { sendError(res, error); }
1264
1346
  });
1265
1347
 
1348
+ // ── Protect workers (lib/worker-guard-setting.js) ──────────────────────────
1349
+ // What capability read-only / workspace means for every worker the dispatcher starts
1350
+ // (lib/worker-confinement.js): enforce (default), warn, off. The user's setting: an agent cannot change it.
1351
+ router.get('/worker-guard', (req, res) => {
1352
+ if (!workerGuard) return res.status(503).json({ ok: false, code: 'WORKER_GUARD_UNAVAILABLE', error: 'The Protect workers setting is not available on this server' });
1353
+ res.json({ ok: true, ...workerGuard.view() });
1354
+ });
1355
+ router.put('/worker-guard', requireJson, (req, res) => {
1356
+ if (!workerGuard) return res.status(503).json({ ok: false, code: 'WORKER_GUARD_UNAVAILABLE', error: 'The Protect workers setting is not available on this server' });
1357
+ if (callerContext(req).kind !== 'ui' || req.get('x-synabun-desktop-grant') || req.get('x-synabun-role')) {
1358
+ return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the user can change Protect workers.' });
1359
+ }
1360
+ const value = req.body?.mode;
1361
+ if (typeof value !== 'string' || !workerGuard.view().modes.includes(value)) return res.status(400).json({ ok: false, code: 'INVALID_VALUE', error: `mode must be one of ${workerGuard.view().modes.join(', ')}.` });
1362
+ try { res.json({ ok: true, ...workerGuard.set(value) }); } catch (error) { sendError(res, error); }
1363
+ });
1364
+
1266
1365
  // ── sidepanel tab registry (UI heartbeat) ──────────────────────────────────
1267
1366
  function listTabs() {
1268
1367
  const cutoff = now() - tabTtlMs;
@@ -123,10 +123,11 @@ export function createClaudeBrain({ session, sink, deps = {}, persona = '', hook
123
123
  'X-Synabun-Role': 'assistant', 'X-Synabun-Terminal': session.id, 'X-Synabun-Memory-Session': session.id,
124
124
  ...(deps.desktopGrant ? { 'X-Synabun-Desktop-Grant': deps.desktopGrant } : {}),
125
125
  ...mcpHeaders,
126
- // A WhatsApp conversation's brain credential (whatsapp_send_file). The header names the variable and
126
+ // This brain launch's credential (assistant-runtime.js mintBrainCredential). The header names the variable and
127
127
  // the CLI expands it from its own environment (below): the MCP config is on its command line, which every
128
128
  // account of the computer can read, an environment only this user's processes. The in-process MCP server
129
- // keeps the value for the request it arrived with, and sends it on the file route alone. Last, so nothing replaces it.
129
+ // keeps the value for the request it arrived with, and sends it only to the routes that ask who the brain is
130
+ // (whatsapp_send_file, the Skills Service). Last, so nothing replaces it.
130
131
  ...(deps.brainCredential ? { 'X-Synabun-Brain-Token': '${SYNABUN_BRAIN_TOKEN}' } : {}),
131
132
  },
132
133
  // A remote (WhatsApp) session never pre-approves computer use: its PreToolUse hook refuses, asks or allows each call.
@@ -143,7 +144,7 @@ export function createClaudeBrain({ session, sink, deps = {}, persona = '', hook
143
144
  ...(typeof deps.claudeAccountEnv === 'function' ? deps.claudeAccountEnv(brain.accountId) : {}),
144
145
  SYNABUN_ASSISTANT_SESSION: String(session.id),
145
146
  ...(deps.userContextThreads === true ? { SYNABUN_ASSISTANT_THREADS: '1' } : {}),
146
- // The value of the X-Synabun-Brain-Token header above (a WhatsApp conversation's brain only).
147
+ // The value of the X-Synabun-Brain-Token header above.
147
148
  ...(deps.brainCredential ? { SYNABUN_BRAIN_TOKEN: deps.brainCredential } : {}),
148
149
  },
149
150
  maxBudgetUsd: Number(deps.maxBudgetUsd) > 0 ? Number(deps.maxBudgetUsd) : undefined,
@@ -6,7 +6,9 @@
6
6
  // PreToolUse payload from stdin and asks lib/browser-tool-policy.js here, with
7
7
  // no server round trip; a refusal becomes permissionDecision "deny" with the
8
8
  // reason. Script paths resolve against the payload's cwd, else
9
- // SYNABUN_BROWSER_POLICY_CWD (the run's project). An unreadable payload allows
9
+ // SYNABUN_BROWSER_POLICY_CWD (the run's project); SYNABUN_BROWSER_POLICY_ROOTS
10
+ // (a JSON list, written as the run starts) names the registered code projects
11
+ // its test runners may also run in. An unreadable payload allows
10
12
  // the call: the policy is a speed bump, not a sandbox. A policy module that
11
13
  // cannot load fails closed (logged on stderr): shell commands, web search and
12
14
  // every MCP tool but SynaBun's are denied with that reason.
@@ -25,6 +27,14 @@ function unloadedDenial(tool) {
25
27
  return `SynaBun browser policy: ${tool} was refused — SynaBun's browser policy could not load (${loadError?.message || 'browserToolDenial is missing'}), so no command, web search or other server's tool can be checked for a browser launch. SynaBun's own tools still work. Stop and report this`;
26
28
  }
27
29
 
30
+ /** The registered code projects from the env (a JSON list of paths); anything unreadable is none. */
31
+ function policyRoots() {
32
+ try {
33
+ const list = JSON.parse(process.env.SYNABUN_BROWSER_POLICY_ROOTS || '[]');
34
+ return Array.isArray(list) ? list.filter((path) => typeof path === 'string') : [];
35
+ } catch { return []; }
36
+ }
37
+
28
38
  let raw = '';
29
39
  try { for await (const chunk of process.stdin) raw += chunk; } catch {}
30
40
  let payload = {};
@@ -35,7 +45,7 @@ try {
35
45
  const input = payload.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
36
46
  const cwd = (typeof payload.cwd === 'string' && payload.cwd) || process.env.SYNABUN_BROWSER_POLICY_CWD || process.cwd();
37
47
  if (typeof browserToolDenial !== 'function') process.stderr.write(`[SynaBun] browser policy could not load (${loadError?.message || 'browserToolDenial is missing'}); denying shell, web search and other servers' tools\n`);
38
- const reason = !tool ? null : typeof browserToolDenial === 'function' ? browserToolDenial(tool, input, { host: 'codex', cwd }) : unloadedDenial(tool);
48
+ const reason = !tool ? null : typeof browserToolDenial === 'function' ? browserToolDenial(tool, input, { host: 'codex', cwd, roots: policyRoots() }) : unloadedDenial(tool);
39
49
  // Codex prints "Command blocked by PreToolUse hook: <reason>. Command: <cmd>", so
40
50
  // the reason goes without its own final period.
41
51
  if (reason) process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason.replace(/[.\s]+$/, '') } }));
@@ -4,9 +4,19 @@
4
4
  // tool may run; a refusal becomes permissionDecision "deny" with the reason.
5
5
  // The call's whole tool_input travels (the remote denylist reads paths from
6
6
  // it), marked inputComplete; one too big to send goes as its command/action
7
- // only, marked incomplete. Any failure (no env, server unreachable, bad reply)
8
- // allows the call — except in a WhatsApp session (SYNABUN_ROUTE_GATE_REMOTE=1),
9
- // where a call SynaBun could not check is denied.
7
+ // only, marked incomplete. No env or no answer at all (the server unreachable
8
+ // or slow) allows the call — except in a WhatsApp session
9
+ // (SYNABUN_ROUTE_GATE_REMOTE=1), where a call SynaBun could not check is
10
+ // denied. An answer that is not a decision (the check itself refused, such as
11
+ // 403 PEER_REFUSED, an error status, a body without `allow`) denies the call in
12
+ // every session and says what SynaBun answered: a refusal never passes for an
13
+ // allow.
14
+ //
15
+ // The request carries the session's pin (X-Synabun-Assistant): this hook runs
16
+ // under the brain's Codex app-server, an AI agent's process tree, which
17
+ // SynaBun's owner gate (lib/owner-authority.js) refuses unless the call says
18
+ // it is an agent's own. The per-session token in the body is what the route
19
+ // itself checks.
10
20
 
11
21
  const url = process.env.SYNABUN_ROUTE_GATE_URL;
12
22
  const session = process.env.SYNABUN_ROUTE_GATE_SESSION;
@@ -14,6 +24,7 @@ const token = process.env.SYNABUN_ROUTE_GATE_TOKEN;
14
24
  const remote = process.env.SYNABUN_ROUTE_GATE_REMOTE === '1';
15
25
  const MAX_INPUT_BYTES = 256 * 1024;
16
26
  const UNCHECKED = 'SynaBun could not check this call, so a WhatsApp session does not run it.';
27
+ const PIN_HEADER = 'x-synabun-assistant';
17
28
 
18
29
  let raw = '';
19
30
  try { for await (const chunk of process.stdin) raw += chunk; } catch {}
@@ -37,20 +48,32 @@ function callArguments(toolInput) {
37
48
  function deny(reason) {
38
49
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: String(reason).replace(/[.\s]+$/, '') } }));
39
50
  }
51
+ /** The refusal of a call whose check got an answer that is not a decision: what SynaBun answered, in short. */
52
+ function notADecision(status, body) {
53
+ const code = body && typeof body === 'object' && typeof body.code === 'string' ? ` ${body.code.slice(0, 64)}` : '';
54
+ const said = body && typeof body === 'object' && typeof body.error === 'string' ? `: ${body.error.slice(0, 300)}` : '';
55
+ return `SynaBun could not check this call (its route gate answered ${status}${code}${said}), so it did not run.`;
56
+ }
40
57
 
41
58
  if (url && session && token) {
59
+ let response = null;
42
60
  try {
43
- const response = await fetch(url, {
61
+ response = await fetch(url, {
44
62
  method: 'POST',
45
- headers: { 'content-type': 'application/json' },
63
+ // The session id as a header value: printable ASCII only (fetch refuses anything else before sending).
64
+ headers: { 'content-type': 'application/json', [PIN_HEADER]: String(session).replace(/[^\x21-\x7e]/g, '').slice(0, 200) },
46
65
  body: JSON.stringify({ session, token, host: 'codex', tool: String(input.tool_name || input.toolName || input.tool || ''), agent: input.agent_id || null, ...callArguments(input.tool_input) }),
47
66
  signal: AbortSignal.timeout(5000),
48
67
  });
49
- const decision = await response.json();
50
- if (decision && decision.allow === false) deny(decision.reason || 'SynaBun refused this call.');
51
- else if (remote && !(decision && decision.allow === true)) deny(UNCHECKED);
52
- } catch {
68
+ } catch { response = null; }
69
+ if (!response) {
53
70
  if (remote) deny(UNCHECKED);
71
+ } else {
72
+ let decision = null;
73
+ try { decision = await response.json(); } catch { decision = null; }
74
+ if (response.ok && decision && decision.allow === true) { /* allowed: Codex runs the call */ }
75
+ else if (response.ok && decision && decision.allow === false) deny(decision.reason || 'SynaBun refused this call.');
76
+ else deny(notADecision(response.status, decision));
54
77
  }
55
78
  }
56
79
  process.exit(0);
@@ -14,6 +14,8 @@ import { createCodexEnvelopeTranslator, codexControlResponse } from '../assistan
14
14
  import { baseModelId, contextVariantOf } from '../assistant-catalog.js';
15
15
  import { codexPlanPermission, codexReadOnlyPermission, planDenyMessage, readOnlyDenyMessage } from '../assistant-plan-permissions.js';
16
16
  import { planModeInstructions } from '../assistant-persona.js';
17
+ import { shouldAutoAcceptCodexMcpToolApproval } from '../codex-mcp-approvals.js';
18
+ import { codexMcpToolApproval } from '../../public/shared/cdx/cdx-protocol.js';
17
19
 
18
20
  const READY_TIMEOUT_MS = 90_000;
19
21
  // Approvals the Codex sidepanel's Auto-accept answers on its own (cdx-requests.js).
@@ -83,7 +85,7 @@ export function createCodexBrain({ session, sink, deps = {}, persona = '' } = {}
83
85
  }
84
86
  if (planned) {
85
87
  const method = packet.request?.brain_native?.method || packet.request?.method || '';
86
- const tool = packet.request?.brain_native?.params?._meta?.tool_name || packet.request?.tool_name || 'request';
88
+ const tool = codexMcpToolApproval(packet.request?.brain_native?.params)?.fullName || packet.request?.tool_name || 'request';
87
89
  const response = planned === 'allow'
88
90
  ? { behavior: 'allow', ...(!remoteReadOnly && approvalMode === 'auto' ? { always: true } : {}) }
89
91
  : { behavior: 'deny', decline: true, message: remoteReadOnly ? readOnlyDenyMessage(tool) : planDenyMessage(tool, { host: 'codex' }) };
@@ -91,7 +93,9 @@ export function createCodexBrain({ session, sink, deps = {}, persona = '' } = {}
91
93
  sink.send({ type: 'stderr', text: `${planMode ? 'Plan mode' : 'Read-only (WhatsApp)'}: ${planned === 'allow' ? 'allowed' : 'declined'} ${String(method).split('/').slice(1, -1).join('/') || 'request'} ${tool}${planned === 'allow' ? '' : remoteReadOnly ? ' (read-only)' : ' (no code changes)'}` });
92
94
  return;
93
95
  }
94
- if (packet.type === 'control_request' && !planMode && !remoteReadOnly && approvalMode === 'auto' && AUTO_ACCEPT_METHODS.has(packet.request?.brain_native?.method || packet.request?.method)) {
96
+ const method = packet.request?.brain_native?.method || packet.request?.method;
97
+ const autoMcp = method === 'mcpServer/elicitation/request' && shouldAutoAcceptCodexMcpToolApproval(packet.request?.brain_native?.params, { autoAccept: approvalMode === 'auto', role: 'assistant' });
98
+ if (packet.type === 'control_request' && !planMode && !remoteReadOnly && (autoMcp || (approvalMode === 'auto' && AUTO_ACCEPT_METHODS.has(method)))) {
95
99
  // Auto-accept: approve at once (for the session when offered), as the Codex sidepanel does.
96
100
  send({ type: 'server_request_response', ...codexControlResponse(packet, { behavior: 'allow', always: true }) });
97
101
  sink.send({ type: 'stderr', text: `Auto-accepted: ${String(packet.request?.brain_native?.method || packet.request?.method).split('/').slice(1, -1).join('/')}` });
@@ -119,8 +123,8 @@ export function createCodexBrain({ session, sink, deps = {}, persona = '' } = {}
119
123
  });
120
124
  const socket = createVirtualSocket({
121
125
  name: `codex-brain-${session.id}`,
122
- // A WhatsApp conversation's brain credential rides on the socket object, never in a message: the skin
123
- // handler gives it to this app-server's SynaBun MCP child through its environment (whatsapp_send_file).
126
+ // The brain's launch credential rides on the socket object, never in a message: the skin handler gives it to
127
+ // this app-server's SynaBun MCP child through its environment (whatsapp_send_file, the Skills Service).
124
128
  credential: deps.brainCredential || null,
125
129
  onSend: (packet) => {
126
130
  if (disposed || !packet || typeof packet !== 'object') return;
@@ -159,7 +163,7 @@ export function createCodexBrain({ session, sink, deps = {}, persona = '' } = {}
159
163
  readyResolve = () => { clearTimeout(timer); resolve(true); };
160
164
  });
161
165
  send({
162
- type: 'bootstrap', windowId, accountId: brain.accountId || 'default', mcpProfile: brain.mcpProfile || null,
166
+ type: 'bootstrap', windowId, accountId: brain.accountId || 'default',
163
167
  cwd: brain.cwd || deps.PACKAGE_ROOT || undefined, threadId: threadId || null, role: 'assistant',
164
168
  // Unlocks the capability-gated `computer` MCP group for this brain only.
165
169
  desktopGrant: deps.desktopGrant || null,