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
@@ -0,0 +1,56 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Protect workers: the one global value
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // DATA_HOME/data/worker-guard.json ({ mode, updatedAt }), read live: one value for every worker the
6
+ // Assistant's dispatcher starts with capability read-only or workspace (lib/worker-confinement.js says what
7
+ // each mode means; capability full is never guarded). No file, or one that cannot be read, is the default,
8
+ // enforce. Only the UI changes it (PUT /api/assistant/worker-guard answers UI_ONLY to agents); a change
9
+ // reaches every window as `agents:worker-guard-changed`. A run keeps the mode it started with.
10
+
11
+ import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+ import { WORKER_GUARD_DEFAULT, WORKER_GUARD_MODES, normalizeWorkerGuardMode } from './worker-confinement.js';
14
+
15
+ export const WORKER_GUARD_EVENT = 'agents:worker-guard-changed';
16
+
17
+ export function createWorkerGuardSetting({ path, broadcast = () => {}, log = () => {}, now = Date.now } = {}) {
18
+ if (!path) throw new Error('createWorkerGuardSetting requires a path');
19
+ const listeners = new Set();
20
+ let cache = null; // { mtimeMs, value }
21
+
22
+ function read() {
23
+ try {
24
+ const stat = statSync(path);
25
+ if (cache && cache.mtimeMs === stat.mtimeMs) return cache.value;
26
+ const data = JSON.parse(readFileSync(path, 'utf8'));
27
+ const value = normalizeWorkerGuardMode(data?.mode);
28
+ cache = { mtimeMs: stat.mtimeMs, value };
29
+ return value;
30
+ } catch {
31
+ return WORKER_GUARD_DEFAULT;
32
+ }
33
+ }
34
+ /** What the UI shows: the mode in effect, the modes, the default. */
35
+ function view() {
36
+ return { mode: read(), modes: [...WORKER_GUARD_MODES], defaultMode: WORKER_GUARD_DEFAULT };
37
+ }
38
+ function set(value) {
39
+ const text = String(value ?? '').trim().toLowerCase();
40
+ if (!WORKER_GUARD_MODES.includes(text)) throw Object.assign(new Error(`mode must be one of ${WORKER_GUARD_MODES.join(', ')}.`), { code: 'INVALID_VALUE', status: 400 });
41
+ const before = read();
42
+ mkdirSync(dirname(path), { recursive: true });
43
+ const tmp = `${path}.${process.pid}.tmp`;
44
+ writeFileSync(tmp, JSON.stringify({ mode: text, updatedAt: new Date(now()).toISOString() }, null, 2) + '\n', 'utf8');
45
+ renameSync(tmp, path);
46
+ cache = null;
47
+ const changed = text !== before;
48
+ if (changed) log('worker-guard', `protect workers: ${text}`);
49
+ try { broadcast({ type: WORKER_GUARD_EVENT, ...view() }); } catch { /* nobody listening */ }
50
+ if (changed) for (const listener of [...listeners]) { try { listener(text); } catch { /* the listener's own failure */ } }
51
+ return view();
52
+ }
53
+ function subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }
54
+
55
+ return { mode: read, view, set, subscribe, path };
56
+ }
@@ -0,0 +1,175 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — the worker-only MCP channel (a second loopback listener for sandboxed workers)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A guarded worker on macOS runs under a Seatbelt profile (lib/worker-sandbox.js) that blocks outbound connections
6
+ // to SynaBun's control port (NEURAL_PORT). That also blocks the worker's own SynaBun MCP client, so a guarded
7
+ // worker is given this instead: a second HTTP listener, bound to 127.0.0.1 on a port of its own, which the profile
8
+ // leaves open and which serves ONLY the MCP endpoint — never the owner's API, never the page, never a run's data.
9
+ //
10
+ // Each worker reaches it at a per-run path, /w/<token>/mcp, where <token> is an unguessable id this channel minted
11
+ // for that run. The channel pins every such request to that run (X-Synabun-Terminal = the run id, from the token,
12
+ // not from anything the worker sends) and strips the headers that carry authority — the brain launch credential,
13
+ // the assistant role, the owner's page cookie, and the from-outside markers — so a worker can never be read as the
14
+ // owner or as a brain here, whatever it sends. What stays is the run's own routing (its browser session / tab, its
15
+ // project, its memory session) and its own per-run desktop grant (a capability token minted for that run, needed by
16
+ // a computer-use worker). The MCP tools it then has are the worker-safe ones: memory, browser, loop, cards — the
17
+ // role-gated agents / skills groups are never advertised, because the role header is stripped.
18
+ //
19
+ // The channel carries a SECOND createMcpRoutes() instance (its own sessions), sharing the same in-process services
20
+ // and database as the main /mcp mount. A tool call that fetches back into the Neural Interface (a browser op) is
21
+ // made by the Neural Interface process itself to its own loopback port — never by the worker — so it is outside the
22
+ // worker's sandbox and works normally.
23
+ //
24
+ // Only an enforced guard on macOS uses this channel; a full worker, a warn/off run, or any non-macOS host keeps the
25
+ // main /mcp mount unchanged. If the channel cannot start, the caller falls back to the main mount and says so (the
26
+ // worker is then not OS-isolated): the live tree must never lose worker dispatch to a channel that failed to bind.
27
+
28
+ import express from 'express';
29
+ import net from 'node:net';
30
+ import { randomUUID } from 'node:crypto';
31
+
32
+ // The only Neural-Interface API a sandboxed worker's stdio MCP child (OpenCode) may reach through the channel: the
33
+ // browser tools, which it drives over HTTP to NEURAL_INTERFACE_URL. Everything else it does (memory) is direct DB
34
+ // I/O in its own process and never touches the channel. The owner's API (/api/assistant, /api/page-session, …) is
35
+ // NEVER proxied — the channel is not a way back to the control surface.
36
+ const PROXY_PREFIXES = Object.freeze(['/browser/', '/browser']);
37
+
38
+ const freePort = () => new Promise((done, fail) => {
39
+ const s = net.createServer();
40
+ s.on('error', fail);
41
+ s.listen(0, '127.0.0.1', () => { const { port } = s.address(); s.close(() => done(port)); });
42
+ });
43
+
44
+ // Headers a worker's request to the channel must never carry into the MCP server: owner / brain authority and the
45
+ // from-outside markers. The run's routing pins and its own desktop grant are kept.
46
+ const STRIPPED_HEADERS = Object.freeze([
47
+ 'x-synabun-tools', 'x-synabun-brain-token', 'x-synabun-assistant', 'x-synabun-role',
48
+ 'cookie', 'cf-connecting-ip', 'x-forwarded-host', 'x-forwarded-for', 'forwarded',
49
+ ]);
50
+
51
+ /**
52
+ * Build the channel. `createMcpRoutes` is the same factory server.js mounts at /mcp (mcp-server/dist/http.js).
53
+ * @returns an object with start/stop/register/unregister/url/port.
54
+ */
55
+ export function createWorkerMcpChannel({ createMcpRoutes, mainPort = null, fetchImpl = globalThis.fetch, log = () => {} } = {}) {
56
+ if (typeof createMcpRoutes !== 'function') throw new Error('createWorkerMcpChannel needs createMcpRoutes');
57
+ const tokenToRun = new Map(); // token → runId
58
+ const runToToken = new Map(); // runId → token
59
+ const selections = new Map(); // runId → immutable snapshot, never a request header
60
+ let server = null;
61
+ let port = null;
62
+
63
+ const app = express();
64
+ app.disable('x-powered-by');
65
+
66
+ // The browser proxy: a sandboxed OpenCode worker's stdio MCP child points NEURAL_INTERFACE_URL at this channel
67
+ // (.../w/<token>) and drives the browser over it; the channel forwards ONLY the browser subset to the real
68
+ // Neural Interface on 127.0.0.1:<mainPort>, pinning the run and stripping authority. The forward is made by the
69
+ // Neural Interface process itself (this listener lives in it), so it is outside the worker's sandbox. Mounted
70
+ // before the MCP router so /w/<token>/api never falls through to it.
71
+ app.use('/w/:token/api', express.raw({ type: '*/*', limit: '32mb' }), async (req, res) => {
72
+ const runId = tokenToRun.get(String(req.params.token || ''));
73
+ if (!runId) return res.status(404).json({ error: 'Unknown worker channel' });
74
+ const sub = req.url.split('?')[0]; // after /w/:token/api
75
+ if (!PROXY_PREFIXES.some((p) => sub === p || sub.startsWith(p))) {
76
+ return res.status(403).json({ error: 'The worker channel proxies the browser API only.' });
77
+ }
78
+ if (!mainPort || typeof fetchImpl !== 'function') return res.status(502).json({ error: 'Worker channel proxy unavailable.' });
79
+ const target = `http://127.0.0.1:${mainPort}/api${req.url}`;
80
+ const headers = { 'content-type': req.get('content-type') || 'application/json' };
81
+ // The run's own routing travels; authority never does.
82
+ for (const h of ['x-synabun-browser-session', 'x-synabun-browser-tab', 'x-synabun-project', 'x-synabun-memory-session', 'x-synabun-deadline', 'x-synabun-request-id', 'x-synabun-desktop-grant']) {
83
+ const v = req.get(h); if (v) headers[h] = v;
84
+ }
85
+ headers['x-synabun-terminal'] = runId; // pinned: the worker's own run, never what it claims
86
+ try {
87
+ const body = (req.method === 'GET' || req.method === 'HEAD') ? undefined : (Buffer.isBuffer(req.body) && req.body.length ? req.body : undefined);
88
+ const upstream = await fetchImpl(target, { method: req.method, headers, body });
89
+ res.status(upstream.status);
90
+ const passback = upstream.headers?.get?.('x-synabun-browser-default');
91
+ if (passback) res.set('X-Synabun-Browser-Default', passback);
92
+ const text = await upstream.text();
93
+ res.set('Content-Type', upstream.headers?.get?.('content-type') || 'application/json');
94
+ res.send(text);
95
+ } catch (err) {
96
+ res.status(502).json({ error: `Worker channel proxy failed: ${err?.message || err}` });
97
+ }
98
+ });
99
+
100
+ const mcpRouter = createMcpRoutes({
101
+ selectionFor: (req) => selections.get(req.headers['x-synabun-terminal']) || { groups: [], mode: 'strict' },
102
+ });
103
+ // Everything the channel serves is under /w/:token/mcp; the run is pinned from the token and authority is stripped.
104
+ app.use('/w/:token/mcp', (req, res, next) => {
105
+ const runId = tokenToRun.get(String(req.params.token || ''));
106
+ if (!runId) return res.status(404).json({ jsonrpc: '2.0', error: { code: -32001, message: 'Unknown worker channel' }, id: null });
107
+ req.headers['x-synabun-terminal'] = runId; // the worker's identity is its own run, never what it claims
108
+ for (const h of STRIPPED_HEADERS) delete req.headers[h];
109
+ next();
110
+ }, mcpRouter);
111
+ // Nothing else exists on this listener.
112
+ app.use((req, res) => res.status(404).json({ error: 'The worker channel serves /w/<token>/mcp only.' }));
113
+
114
+ return {
115
+ /** Start the listener on a free loopback port. Resolves with the port, or throws (the caller falls back). */
116
+ async start() {
117
+ if (server) return port;
118
+ const chosen = await freePort();
119
+ await new Promise((ok, fail) => {
120
+ server = app.listen(chosen, '127.0.0.1', ok);
121
+ server.on('error', fail);
122
+ });
123
+ server.on('error', (err) => { try { log(`[worker-mcp] channel error: ${err?.message || err}`); } catch {} });
124
+ port = chosen;
125
+ try { log(`[worker-mcp] worker-only MCP channel on 127.0.0.1:${port}`); } catch {}
126
+ return port;
127
+ },
128
+ isUp() { return !!server && !!port; },
129
+ port() { return port; },
130
+ /** Mint (or reuse) the per-run token. */
131
+ register(runId, selection) {
132
+ const id = String(runId || '');
133
+ if (!id) throw new Error('worker MCP channel needs a run id');
134
+ if (selection !== undefined) this.setSelection(id, selection);
135
+ if (runToToken.has(id)) return runToToken.get(id);
136
+ const token = randomUUID().replace(/-/g, '') + randomUUID().replace(/-/g, '');
137
+ tokenToRun.set(token, id);
138
+ runToToken.set(id, token);
139
+ return token;
140
+ },
141
+ setSelection(runId, selection) {
142
+ const valid = Array.isArray(selection?.groups);
143
+ const groups = valid ? [...selection.groups] : [];
144
+ selections.set(String(runId), { groups, mode: valid && selection?.mode === 'focus' ? 'focus' : 'strict' });
145
+ },
146
+ has(runId) { return runToToken.has(String(runId)); },
147
+ /** The MCP URL for a run (registers it if new), or null when the channel is not up. Claude's HTTP MCP uses this. */
148
+ url(runId) {
149
+ if (!port) return null;
150
+ const token = this.register(runId);
151
+ return `http://127.0.0.1:${port}/w/${token}/mcp`;
152
+ },
153
+ /** The per-run base (…/w/<token>), for a sandboxed stdio MCP child's NEURAL_INTERFACE_URL (OpenCode). */
154
+ base(runId) {
155
+ if (!port) return null;
156
+ const token = this.register(runId);
157
+ return `http://127.0.0.1:${port}/w/${token}`;
158
+ },
159
+ /** Forget a run's token (its channel path stops working at once). */
160
+ unregister(runId) {
161
+ const id = String(runId || '');
162
+ selections.delete(id);
163
+ const token = runToToken.get(id);
164
+ if (token) { tokenToRun.delete(token); runToToken.delete(id); }
165
+ },
166
+ async stop() {
167
+ selections.clear();
168
+ tokenToRun.clear();
169
+ runToToken.clear();
170
+ if (server) { await new Promise((r) => server.close(r)); server = null; port = null; }
171
+ },
172
+ // For tests.
173
+ _runs: () => [...runToToken.keys()],
174
+ };
175
+ }
@@ -0,0 +1,227 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — relay-capable local listeners a guarded worker must not reach (macOS netstat / Linux lsof, + ps)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A guarded worker's Seatbelt profile (lib/worker-sandbox.js) denies SynaBun's own control port and the OpenCode
6
+ // serve range. But other LOCAL listeners can relay the worker back to owner authority just as well:
7
+ // • a browser control port — MoreLogin's Chromium CDP endpoint (http://127.0.0.1:<debugPort>/json/version), the
8
+ // attach-mode Chrome's --remote-debugging-port, any Chromium-family browser started with remote debugging, a
9
+ // browser whose DevTools port was switched on from its own settings, a Node inspector (--inspect): with it a
10
+ // worker runs code outside its sandbox, clears the automation marker, loads SynaBun and acts as owner;
11
+ // • MoreLogin's local API (:40000, :15523): a worker starts another profile with its own CDP;
12
+ // • any OTHER SynaBun instance's server (a dev/test :3344 and its live memory), a stale OpenCode serve, the
13
+ // Codex app-server, a listening AI agent — each a password-in-env or page-cookie relay;
14
+ // • SynaBun's own MCP server over HTTP (mcp-server/dist/http.js, `npm run start:http`): SynaBun takes its pinned
15
+ // calls like an MCP tool's (lib/peer-identity.js), so a worker that reached it would act through it;
16
+ // • a proxy or tunnel — ssh -D / -L, socat, privoxy, mitmproxy, Clash / mihomo, Surge, sing-box, V2Ray / Xray,
17
+ // shadowsocks, cloudflared, ngrok, frpc … — which carries the worker's connection to SynaBun's port from a
18
+ // process outside the sandbox.
19
+ //
20
+ // These ports are DYNAMIC (MoreLogin's CDP is a high random port; a stale serve or a proxy is wherever it bound), so
21
+ // a fixed guess cannot find them. This module reads the live LISTEN set (macOS: `netstat -anvl`, which names the
22
+ // owning process of every socket in ~4 ms; Linux: lsof) and classifies each owner by its process identity (kernel
23
+ // name, argv[0], command line; lib/peer-identity.js processKinds) with `ps`, then returns only the ports owned by a
24
+ // relay-capable process. The owner's own dev servers (a plain `node server.mjs`, vite, python) and the worker's own
25
+ // servers are NOT in the set — they are not relay-capable identities, so they stay reachable. A proxy the system or
26
+ // the environment names for internet access (HTTP(S)_PROXY / ALL_PROXY, macOS's network proxy settings) is kept
27
+ // reachable too, so a worker whose internet goes through it keeps its internet: the server refuses that proxy by
28
+ // identity when it relays to SynaBun (lib/peer-identity.js), which is the boundary for it.
29
+ //
30
+ // The answer says whether detection WORKED (`ok`): a missing or slow netstat / lsof / ps, an empty list, or a list
31
+ // that does not even hold SynaBun's own port or process is a failure, never "no relays". The caller then writes a
32
+ // stricter profile for the run (no local port but the worker channel; lib/worker-sandbox.js strictLocal) and says so
33
+ // in the run's transcript. Pure classifier + parsers (unit-tested); the collector takes an injected `run` and NEVER
34
+ // throws. macOS / Linux only; on Windows the caller does not run OS isolation at all.
35
+
36
+ import { existsSync } from 'node:fs';
37
+ import {
38
+ MORELOGIN_PATH_RE, NETSTAT_ARGS, lsofCwdArgs, mcpHttpNeedsCwd, mcpHttpScriptPath, normalizeAddress, parseLsofCwd, parseNetstatTcp, parsePsColumn,
39
+ parsePsParents, processKinds,
40
+ } from './peer-identity.js';
41
+ import { INTERNET_PORTS } from './worker-sandbox.js';
42
+
43
+ const PORT_RE = /:(\d{1,5})$/;
44
+ const NETSTAT = '/usr/sbin/netstat';
45
+ const firstExisting = (paths, fallback) => paths.find((p) => { try { return existsSync(p); } catch { return false; } }) || fallback;
46
+ /** The commands this module runs, by absolute path where the system has them (PATH is not trusted for these). */
47
+ export const RELAY_TOOLS = Object.freeze({
48
+ netstat: NETSTAT,
49
+ get ps() { return firstExisting(['/bin/ps', '/usr/bin/ps'], 'ps'); },
50
+ get lsof() { return firstExisting(['/usr/sbin/lsof', '/usr/bin/lsof', '/bin/lsof'], 'lsof'); },
51
+ });
52
+ /** Ports the internet needs (SSH, DNS, HTTP, HTTPS): a relay found on one of them is denied on this computer's own
53
+ * addresses only (`localhost:<port>`), never on every host, or the worker would lose the internet (worker-sandbox.js). */
54
+ export { INTERNET_PORTS };
55
+
56
+ /** Parse `lsof -nP -iTCP -sTCP:LISTEN -Fpn` field output → Map<pid, Set<port>>. */
57
+ export function parseLsofListeners(text) {
58
+ const byPid = new Map();
59
+ let pid = null;
60
+ for (const raw of String(text || '').split('\n')) {
61
+ if (!raw) continue;
62
+ const tag = raw[0];
63
+ const rest = raw.slice(1);
64
+ if (tag === 'p') { pid = Number(rest); if (!Number.isInteger(pid) || pid <= 0) pid = null; continue; }
65
+ if (tag === 'n' && pid) {
66
+ // rest is an address like "*:7000", "127.0.0.1:3344", "[::1]:3344", "localhost:4096".
67
+ const m = PORT_RE.exec(rest.trim());
68
+ if (!m) continue;
69
+ const port = Number(m[1]);
70
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) continue;
71
+ if (!byPid.has(pid)) byPid.set(pid, new Set());
72
+ byPid.get(pid).add(port);
73
+ }
74
+ }
75
+ return byPid;
76
+ }
77
+
78
+ /** `netstat -anvl -p tcp` (macOS) → Map<pid, Set<port>> of the LISTEN sockets. */
79
+ export function netstatListeners(text) {
80
+ const byPid = new Map();
81
+ for (const row of parseNetstatTcp(text).rows) {
82
+ if (row.state !== 'LISTEN' || !(row.pid > 0) || !row.local?.port) continue;
83
+ if (!byPid.has(row.pid)) byPid.set(row.pid, new Set());
84
+ byPid.get(row.pid).add(row.local.port);
85
+ }
86
+ return byPid;
87
+ }
88
+
89
+ /** Parse `ps -Ao pid=,<field>=` output → Map<pid, string> (comm or args; everything after the pid is the value). */
90
+ export function parsePsTable(text) {
91
+ return parsePsColumn(text);
92
+ }
93
+
94
+ /**
95
+ * Is a LISTENING process (by its executable path `comm`, full command line `args` and kernel name `ucomm`) a relay
96
+ * back to owner / brain authority? Returns a short reason tag ('synabun' | 'synabun-mcp' | 'opencode' | 'morelogin' |
97
+ * 'cdp' | 'codex' | 'inspector' | 'agent' | 'proxy') or null. Matches on process IDENTITY, kept specific so an
98
+ * unrelated dev server is never caught:
99
+ * • a plain `node server.mjs 3000`, a vite/python dev server → null (reachable);
100
+ * • `node .synabun/design/mascot-hands/serve.mjs` → null (it is not a SynaBun server — only neural-interface/server.js is).
101
+ * `cwd`: the process's working directory, for a relative MCP-over-HTTP script (collectRelayListenerPorts reads it).
102
+ */
103
+ export function classifyRelayProcess({ comm = '', args = '', ucomm = '', cwd = null } = {}) {
104
+ const c = String(comm || '').toLowerCase();
105
+ const a = String(args || '').toLowerCase();
106
+ const execBase = c.split('/').pop() || '';
107
+ // A SynaBun server instance — its control port + launch beacon (this instance's are denied anyway; another
108
+ // instance's live :3344 and its memory are the point). ONLY neural-interface/server.js, never a loose "synabun".
109
+ if (/neural-interface[/\\]server\.js/.test(a)) return 'synabun';
110
+ // SynaBun's MCP server over HTTP (any copy's mcp-server/dist/http.js or preload-http.js, run by node): SynaBun takes
111
+ // its pinned calls like an MCP tool's (lib/peer-identity.js), so whoever reaches its port acts through it.
112
+ if (mcpHttpScriptPath({ ucomm, comm, args }, { cwd })) return 'synabun-mcp';
113
+ // An OpenCode serve (the shared one, an isolated one, a stale build on any port). Specific: the `opencode`
114
+ // executable, or `opencode serve` on a command line — never a path that merely contains "opencode".
115
+ if (execBase === 'opencode' || /(^|\/)opencode(\s|$)/.test(a) || /\bopencode\b\s+serve\b/.test(a)) return 'opencode';
116
+ // MoreLogin: the desktop app, its local-API helper, and its bundled Chromium (all carry the MoreLogin bundle path),
117
+ // by their executable's place or kernel name — never by a word in the arguments.
118
+ if (MORELOGIN_PATH_RE.test(String(comm || '')) || /^morelogin/i.test(String(ucomm || ''))) return 'morelogin';
119
+ // Any Chromium-family browser started with remote debugging (the CDP relay): the flag's presence is the signal
120
+ // (a listening process that carries it has an open CDP port, whatever the value — including a 0 that Chrome
121
+ // turned into a real random port).
122
+ if (/--remote-debugging-port=/.test(a)) return 'cdp';
123
+ // The Codex app-server / MCP server (the sidepanel's and the brain's engine).
124
+ if (execBase === 'codex' || /codex-app-server/.test(a) || /\bcodex\b\s+(app-server|exec|mcp|serve)/.test(a)) return 'codex';
125
+ const kinds = processKinds({ ucomm, comm, args });
126
+ // Playwright's / Firefox's automation transports, and a browser the owner uses holding a TCP listener at all: a
127
+ // browser listens only for its DevTools / remote-debugging endpoint (Chrome's own "remote debugging" switch).
128
+ if (kinds.automation || kinds.browser) return 'cdp';
129
+ // A Node (or Electron) inspector: whoever reaches it runs code in that process, outside the sandbox.
130
+ if (/(?:^|\s)--inspect(?:-brk|-port|-wait)?(?:[=\s]|$)/.test(a)) return 'inspector';
131
+ // A listening AI agent (an IDE bridge, an OAuth callback of a brain's CLI): never the worker's to call.
132
+ if (kinds.agent) return 'agent';
133
+ // A proxy / tunnel / port forwarder, by name or by its app bundle; a listening sshd (Remote Login, port forwards).
134
+ if (kinds.relay || kinds.remoteLogin) return 'proxy';
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Local proxies the system or the environment names for internet access: their ports stay reachable to a worker
140
+ * (its internet may depend on them). From the server's environment (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, any case)
141
+ * and macOS's network proxy settings (`scutil --proxy`: HTTP, HTTPS and SOCKS when enabled). Only a proxy on this
142
+ * computer counts (a remote proxy is not a local listener anyway).
143
+ */
144
+ export function localProxyPorts({ env = process.env, scutilText = '' } = {}) {
145
+ const ports = new Set();
146
+ const isLocal = (host) => {
147
+ const h = normalizeAddress(host);
148
+ return h === 'localhost' || /^127\.\d+\.\d+\.\d+$/.test(h) || h === '0:0:0:0:0:0:0:1' || h === '0.0.0.0';
149
+ };
150
+ for (const key of ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy']) {
151
+ const v = typeof env?.[key] === 'string' ? env[key].trim() : '';
152
+ if (!v) continue;
153
+ try {
154
+ const u = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(v) ? v : `http://${v}`);
155
+ const port = Number(u.port);
156
+ if (isLocal(u.hostname) && port > 0) ports.add(port);
157
+ } catch { /* not a URL */ }
158
+ }
159
+ const kv = {};
160
+ for (const line of String(scutilText || '').split('\n')) {
161
+ const m = /^\s*([A-Za-z]+)\s*:\s*(.*?)\s*$/.exec(line);
162
+ if (m) kv[m[1]] = m[2];
163
+ }
164
+ for (const kind of ['HTTP', 'HTTPS', 'SOCKS']) {
165
+ const port = Number(kv[`${kind}Port`]);
166
+ if (kv[`${kind}Enable`] === '1' && isLocal(kv[`${kind}Proxy`]) && port > 0) ports.add(port);
167
+ }
168
+ return [...ports].sort((x, y) => x - y);
169
+ }
170
+
171
+ /**
172
+ * The live set of relay-capable LISTEN ports, by process identity. Never throws.
173
+ * @param {object} o
174
+ * @param {(file:string, args:string[]) => string} o.run runs a program and returns its stdout (execFileSync-shaped;
175
+ * a throw is a failed detection).
176
+ * @param {string} [o.platform] default process.platform; anything but darwin/linux ⇒ { ok: false }.
177
+ * @param {number} [o.expectPort] SynaBun's own port: a listener list without it is not believed (ok: false).
178
+ * @param {number} [o.expectPid] SynaBun's own pid: a process list without it is not believed (ok: false).
179
+ * @param {number[]} [o.keepPorts] ports never returned (the local proxies the worker's internet goes through).
180
+ * @returns {{ ok:boolean, reason:string|null, ports:number[], detail:Array<{pid:number,port:number,why:string}> }}
181
+ */
182
+ export function collectRelayListenerPorts({ run, platform = process.platform, expectPort = null, expectPid = null, keepPorts = [] } = {}) {
183
+ const fail = (reason) => ({ ok: false, reason, ports: [], detail: [] });
184
+ if (typeof run !== 'function') return fail('no way to run the system tools');
185
+ if (platform !== 'darwin' && platform !== 'linux') return fail(`relay detection is not available on ${platform}`);
186
+ const why = (error) => String(error?.code || error?.message || error || 'failed').slice(0, 160);
187
+ let listeners;
188
+ try {
189
+ listeners = platform === 'darwin'
190
+ ? netstatListeners(run(RELAY_TOOLS.netstat, [...NETSTAT_ARGS]))
191
+ : parseLsofListeners(run(RELAY_TOOLS.lsof, ['-nP', '-iTCP', '-sTCP:LISTEN', '-Fpn']));
192
+ } catch (error) { return fail(`the list of listening programs could not be read (${why(error)})`); }
193
+ if (!listeners.size) return fail('the list of listening programs came back empty');
194
+ if (Number(expectPort) > 0 && ![...listeners.values()].some((set) => set.has(Number(expectPort)))) {
195
+ return fail(`the list of listening programs did not include SynaBun's own port ${Number(expectPort)}`);
196
+ }
197
+ let parents, comms, argv;
198
+ try {
199
+ const ps = RELAY_TOOLS.ps;
200
+ parents = parsePsParents(run(ps, ['-Ao', 'pid=,ppid=,ucomm=']));
201
+ comms = parsePsColumn(run(ps, ['-Aww', '-o', 'pid=,comm=']));
202
+ argv = parsePsColumn(run(ps, ['-Aww', '-o', 'pid=,args=']));
203
+ } catch (error) { return fail(`the process list could not be read (${why(error)})`); }
204
+ if (!argv.size) return fail('the process list came back empty');
205
+ if (Number(expectPid) > 0 && !argv.has(Number(expectPid))) return fail('the process list did not include SynaBun\'s own server');
206
+ const keep = new Set((keepPorts || []).map(Number));
207
+ const ports = new Set();
208
+ const detail = [];
209
+ for (const [pid, portSet] of listeners) {
210
+ const proc = { comm: comms.get(pid) || '', args: argv.get(pid) || '', ucomm: parents.get(pid)?.ucomm || '' };
211
+ let tag = classifyRelayProcess(proc);
212
+ // A relative `node dist/http.js` / `dist/preload-http.js` (`npm run start:http` in mcp-server/): its working
213
+ // directory says whether it is SynaBun's MCP server over HTTP. One that cannot be read is taken for it (denied).
214
+ if (!tag && mcpHttpNeedsCwd(proc)) {
215
+ let cwd = null;
216
+ try { cwd = parseLsofCwd(run(RELAY_TOOLS.lsof, lsofCwdArgs(pid))); } catch { cwd = null; }
217
+ tag = cwd ? classifyRelayProcess({ ...proc, cwd }) : 'synabun-mcp';
218
+ }
219
+ if (!tag) continue;
220
+ for (const port of portSet) {
221
+ if (keep.has(port) || ports.has(port)) continue;
222
+ ports.add(port);
223
+ detail.push({ pid, port, why: tag });
224
+ }
225
+ }
226
+ return { ok: true, reason: null, ports: [...ports].sort((x, y) => x - y), detail };
227
+ }