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,172 @@
1
+ // One app-server per dispatched worker. The SDK-shaped surface keeps the native
2
+ // worker's event/usage contract while thread/resume reopens its durable thread.
3
+ import { spawn, execFileSync } from 'node:child_process';
4
+ import { StringDecoder } from 'node:string_decoder';
5
+
6
+ const toml = (v) => Array.isArray(v) ? `[${v.map(toml).join(',')}]`
7
+ : v && typeof v === 'object' ? `{${Object.entries(v).filter(([, x]) => x != null).map(([k, x]) => `${JSON.stringify(k)}=${toml(x)}`).join(',')}}`
8
+ : JSON.stringify(v);
9
+
10
+ // Match the SDK's leaf overrides: pins/switches address only their intended
11
+ // fields and leave native command/url/env definitions in the native config.
12
+ // Codex splits override paths literally on dots (it does not parse TOML keys).
13
+ // Top-level keys may already be override paths; nested keys are literal segments.
14
+ export function codexWorkerConfigOverrides(config) {
15
+ const overrides = [];
16
+ const visit = (value, path) => {
17
+ if (value == null) return;
18
+ if (typeof value === 'object' && !Array.isArray(value)) {
19
+ for (const [key, child] of Object.entries(value)) {
20
+ if (path && /[.=\x00-\x1f\x7f]/.test(key)) throw new Error('Codex override paths cannot address this config key');
21
+ visit(child, path ? `${path}.${key}` : key);
22
+ }
23
+ } else overrides.push(`${path}=${toml(value)}`);
24
+ };
25
+ visit(config, '');
26
+ return overrides;
27
+ }
28
+
29
+ export class CodexWorkerAppServer {
30
+ constructor({ codexPathOverride, env, config = {}, configArgs = [], apiKey, signal, onProcess = () => {} }) {
31
+ this.pending = new Map();
32
+ this.seq = 0;
33
+ this.listener = null;
34
+ this.failure = null;
35
+ const args = [...configArgs];
36
+ for (const override of codexWorkerConfigOverrides(config)) args.push('-c', override);
37
+ // Run identity in the command line permits conservative orphan identification.
38
+ args.push('-c', `synabun_worker_run=${JSON.stringify(env.SYNABUN_TERMINAL_SESSION || '')}`, 'app-server');
39
+ this.proc = spawn(codexPathOverride, args, { env, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
40
+ this.exited = new Promise((r) => this.proc.once('close', r));
41
+ onProcess({ pid: this.proc.pid, startedAt: new Date().toISOString() });
42
+ let buffer = '';
43
+ const decoder = new StringDecoder('utf8');
44
+ this.proc.stdout.on('data', (chunk) => {
45
+ buffer += decoder.write(chunk);
46
+ let end;
47
+ while ((end = buffer.indexOf('\n')) >= 0) {
48
+ const line = buffer.slice(0, end); buffer = buffer.slice(end + 1);
49
+ let msg; try { msg = JSON.parse(line); } catch { continue; }
50
+ if (msg.id != null && !msg.method) {
51
+ const p = this.pending.get(msg.id); if (!p) continue;
52
+ this.pending.delete(msg.id); clearTimeout(p.timer);
53
+ msg.error ? p.reject(new Error(msg.error.message || 'Codex RPC failed')) : p.resolve(msg.result);
54
+ } else if (msg.id != null) {
55
+ // Workers have approvalPolicy never. Never grant an unexpected request.
56
+ this.write({ id: msg.id, error: { code: -32601, message: 'Unattended worker does not accept interactive requests' } });
57
+ } else this.listener?.(msg);
58
+ }
59
+ });
60
+ // Drain stderr without exposing credentials/config to the worker transcript.
61
+ this.proc.stderr.resume();
62
+ this.proc.on('error', (error) => this.fail(error));
63
+ this.proc.stdin.on('error', (error) => this.fail(error));
64
+ this.proc.on('close', (code, signal) => this.fail(new Error(`Codex app-server exited (${signal || code})`)));
65
+ this.ready = (async () => {
66
+ await this.rpc('initialize', { clientInfo: { name: 'synabun-worker', version: '1.0.0' }, capabilities: { experimentalApi: true } });
67
+ this.write({ method: 'initialized', params: {} });
68
+ if (apiKey) await this.rpc('account/login/start', { type: 'apiKey', apiKey });
69
+ })();
70
+ this.ready.catch(() => {});
71
+ this.startupSignal = signal;
72
+ this.cancelStartup = () => { this.dispose().catch(() => {}); };
73
+ signal?.addEventListener('abort', this.cancelStartup, { once: true });
74
+ if (signal?.aborted) this.cancelStartup();
75
+ }
76
+ write(value) { if (!this.proc.stdin.destroyed) this.proc.stdin.write(JSON.stringify(value) + '\n'); }
77
+ fail(error) {
78
+ this.failure = error;
79
+ for (const p of this.pending.values()) { clearTimeout(p.timer); p.reject(error); }
80
+ this.pending.clear();
81
+ this.listener?.({ method: 'synabun/exit', error });
82
+ }
83
+ rpc(method, params) {
84
+ if (this.failure) return Promise.reject(this.failure);
85
+ return new Promise((resolve, reject) => {
86
+ const id = ++this.seq;
87
+ const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`Codex ${method} timed out`)); }, 30_000);
88
+ this.pending.set(id, { resolve, reject, timer });
89
+ this.write({ id, method, params });
90
+ });
91
+ }
92
+ startThread(options) { return this.thread(null, options); }
93
+ resumeThread(id, options) { return this.thread(id, options); }
94
+ thread(id, options) {
95
+ const owner = this;
96
+ const sandboxPolicy = options.sandboxMode === 'read-only' ? { type: 'readOnly', networkAccess: !!options.networkAccessEnabled }
97
+ : options.sandboxMode === 'workspace-write' ? { type: 'workspaceWrite', writableRoots: [options.workingDirectory, ...(options.additionalDirectories || [])], networkAccess: !!options.networkAccessEnabled }
98
+ : { type: 'dangerFullAccess' };
99
+ const ready = this.ready.then(async () => {
100
+ const reply = await this.rpc(id ? 'thread/resume' : 'thread/start', {
101
+ ...(id ? { threadId: id } : {}), cwd: options.workingDirectory,
102
+ model: options.model || null, approvalPolicy: 'never', sandbox: options.sandboxMode,
103
+ config: { web_search: options.webSearchMode || 'disabled', sandbox_workspace_write: { network_access: !!options.networkAccessEnabled, writable_roots: options.additionalDirectories || [] } },
104
+ });
105
+ const actual = reply?.thread?.id;
106
+ if (!actual || (id && actual !== id)) throw new Error('Codex did not resume the requested thread');
107
+ id = actual;
108
+ });
109
+ ready.catch(() => {});
110
+ return {
111
+ get id() { return id; }, ready: () => ready,
112
+ async runStreamed(prompt, { signal, outputSchema } = {}) {
113
+ await ready;
114
+ const queue = []; const text = new Map(); let wake; let done = false; let failure; let turnId;
115
+ const push = (event) => { queue.push(event); wake?.(); };
116
+ const abort = () => {
117
+ failure = Object.assign(new Error('Codex turn aborted'), { name: 'AbortError' }); done = true; wake?.();
118
+ if (turnId) owner.rpc('turn/interrupt', { threadId: id, turnId }).catch(() => {});
119
+ };
120
+ owner.listener = (msg) => {
121
+ const p = msg.params || {};
122
+ if (p.threadId && p.threadId !== id) return;
123
+ if (msg.method === 'synabun/exit') { failure = msg.error; done = true; wake?.(); }
124
+ if (msg.method === 'item/agentMessage/delta') {
125
+ const value = (text.get(p.itemId) || '') + (p.delta || '');
126
+ text.set(p.itemId, value);
127
+ push({ type: 'item.updated', item: { id: p.itemId, type: 'agent_message', text: value } });
128
+ }
129
+ if (msg.method === 'item/started' || msg.method === 'item/completed') {
130
+ const item = p.item || {};
131
+ const types = { agentMessage: 'agent_message', commandExecution: 'command_execution', fileChange: 'file_change', mcpToolCall: 'mcp_tool_call', webSearch: 'web_search', reasoning: 'reasoning' };
132
+ push({ type: msg.method === 'item/started' ? 'item.started' : 'item.completed', item: { ...item, type: types[item.type] || item.type, aggregated_output: item.aggregatedOutput, exit_code: item.exitCode } });
133
+ }
134
+ if (msg.method === 'turn/completed') {
135
+ if (p.turn?.status === 'failed') push({ type: 'turn.failed', error: p.turn.error || { message: 'Codex turn failed' } });
136
+ else if (p.turn?.status === 'interrupted') failure = Object.assign(new Error('Codex turn interrupted'), { name: 'AbortError' });
137
+ else push({ type: 'turn.completed' }); // rollout meter is authoritative across resumes
138
+ done = true; wake?.();
139
+ }
140
+ };
141
+ signal?.addEventListener('abort', abort, { once: true });
142
+ try {
143
+ if (signal?.aborted) throw Object.assign(new Error('Codex turn aborted'), { name: 'AbortError' });
144
+ const reply = await owner.rpc('turn/start', { threadId: id, input: [{ type: 'text', text: String(prompt), text_elements: [] }], approvalPolicy: 'never', sandboxPolicy, effort: options.modelReasoningEffort || null, ...(outputSchema ? { outputSchema } : {}) });
145
+ turnId = reply?.turn?.id;
146
+ if (signal?.aborted) abort();
147
+ } catch (error) { owner.listener = null; signal?.removeEventListener('abort', abort); throw error; }
148
+ return { events: (async function* () {
149
+ try {
150
+ yield { type: 'thread.started', thread_id: id };
151
+ while (!done || queue.length) {
152
+ if (queue.length) yield queue.shift();
153
+ else await new Promise((r) => { wake = r; });
154
+ }
155
+ if (failure) throw failure;
156
+ } finally { owner.listener = null; signal?.removeEventListener('abort', abort); }
157
+ })() };
158
+ },
159
+ };
160
+ }
161
+ async dispose() {
162
+ this.startupSignal?.removeEventListener('abort', this.cancelStartup);
163
+ if (this.proc.exitCode !== null || this.proc.signalCode) return;
164
+ if (process.platform === 'win32') {
165
+ try { execFileSync('taskkill', ['/pid', String(this.proc.pid), '/T', '/F'], { timeout: 5000, stdio: 'ignore', windowsHide: true }); } catch {}
166
+ } else this.proc.kill('SIGTERM');
167
+ let timer;
168
+ await Promise.race([this.exited, new Promise((r) => { timer = setTimeout(r, 1500); })]);
169
+ clearTimeout(timer);
170
+ if (this.proc.exitCode === null && !this.proc.signalCode) this.proc.kill('SIGKILL');
171
+ }
172
+ }
@@ -19,7 +19,7 @@
19
19
  // sequence; it does not remove it.
20
20
 
21
21
  import {
22
- openSync, closeSync, fstatSync, lstatSync, readSync, writeSync, ftruncateSync, fsyncSync,
22
+ openSync, closeSync, fstatSync, fchmodSync, lstatSync, readSync, writeSync, ftruncateSync, fsyncSync,
23
23
  mkdirSync, readdirSync, rmSync, unlinkSync, renameSync, realpathSync, createReadStream, constants,
24
24
  } from 'node:fs';
25
25
  import { randomBytes } from 'node:crypto';
@@ -142,6 +142,17 @@ export function writeConfinedFile(base, path, data, { exclusive = false, mode =
142
142
  if (!st.isFile()) throw new PathConfineError(400, 'Not a regular file.');
143
143
  if (!created) ftruncateSync(fd, 0);
144
144
  writeAll(fd, data);
145
+ } catch (err) {
146
+ // A file this call created and could not fill (the disk filled, the data could not be had) is not left
147
+ // half written: it is removed while it is still the file that was opened.
148
+ if (created) {
149
+ try {
150
+ const mine = fstatSync(fd);
151
+ const now = lstatSync(real);
152
+ if (now.dev === mine.dev && now.ino === mine.ino) unlinkSync(real);
153
+ } catch { /* gone already, or no longer ours */ }
154
+ }
155
+ throw err;
145
156
  } finally { try { closeSync(fd); } catch {} }
146
157
  return real;
147
158
  }
@@ -173,14 +184,29 @@ export function removeConfinedEntry(base, path) {
173
184
  return true;
174
185
  }
175
186
 
187
+ /**
188
+ * What is at `path` itself, a link not followed: 'absent', 'file', 'dir', 'link' or 'other' (a FIFO, a
189
+ * socket, a device). Nothing is opened.
190
+ */
191
+ export function entryKind(path) {
192
+ let st;
193
+ try { st = lstatSync(String(path)); } catch { return 'absent'; }
194
+ if (st.isSymbolicLink()) return 'link';
195
+ if (st.isFile()) return 'file';
196
+ if (st.isDirectory()) return 'dir';
197
+ return 'other';
198
+ }
199
+
176
200
  /**
177
201
  * Replace `path` with `data` through a temporary file in the same folder:
178
202
  * created exclusively under an unpredictable name, never through a link, then
179
203
  * renamed over `path` (a rename replaces a link, it does not follow it).
180
204
  * `expect` is the file's state when it was read ({ dev, ino, mtimeMs, size }):
181
- * a file that changed since is not overwritten (409).
205
+ * a file that changed since is not overwritten (409). `exactMode`: the new
206
+ * file gets `mode` as it is, not narrowed by the process umask (a file that
207
+ * replaces another keeps the permissions that one had).
182
208
  */
183
- export function replaceFileAtomic(path, data, { mode = 0o600, expect = null } = {}) {
209
+ export function replaceFileAtomic(path, data, { mode = 0o600, expect = null, exactMode = false } = {}) {
184
210
  const dir = realPathOf(dirname(String(path)));
185
211
  const target = join(dir, basename(String(path)));
186
212
  const tmp = join(dir, `.${basename(target)}.${randomBytes(12).toString('hex')}.tmp`);
@@ -190,6 +216,7 @@ export function replaceFileAtomic(path, data, { mode = 0o600, expect = null } =
190
216
  try {
191
217
  const st = verifyOpened(fd, tmp);
192
218
  if (!st.isFile()) throw moved();
219
+ if (exactMode) fchmodSync(fd, mode & 0o7777);
193
220
  writeAll(fd, data);
194
221
  try { fsyncSync(fd); } catch {}
195
222
  } finally { try { closeSync(fd); } catch {} }
@@ -0,0 +1,98 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — /api/system/crash-notice: the notice after an automatic restart
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // GET /api/system/crash-notice { ok, notice } null, or the crash the supervisor recovered from
6
+ // POST /api/system/crash-notice/dismiss { id } { ok, notice }: what is left (a newer crash's, or null)
7
+ //
8
+ // When the server crashes after it was up, its supervisor (lib/server-supervisor.js at the package root) starts
9
+ // it again and leaves DATA_HOME/data/server-crash.json (lib/server-crash-record.js) for it. The owner's pages ask
10
+ // for it when they load and every time their sync socket connects (a page that was open through the crash
11
+ // reconnects, it is not reloaded: public/shared/ui-crash-notice.js), and show it until the owner dismisses it. A
12
+ // dismissal removes the record, and every open tab and window drops the notice at once (`crash-notice:changed`
13
+ // on the sync socket); none shows it again after a reload. The next crash writes a new record, with a new id,
14
+ // which shows again. A record that cannot be removed (a read-only data folder) is kept dismissed in memory for
15
+ // the life of this server.
16
+ //
17
+ // The owner's only: /api/system is admin-only (lib/request-gate.js), and every route takes the owner's guard by
18
+ // itself (lib/http-guards.js ownerRouteGuard: this server's Host, its Origin on the write, no guest, no cross-site
19
+ // write) and refuses an agent's request (uiOnly: X-SynaBun-Terminal, -Role, -Assistant, -Desktop-Grant, as
20
+ // /api/system/app does). The dismissal is the owner's write from SynaBun's own page: X-SynaBun-UI: 1, a JSON body,
21
+ // and this boot's page secret (lib/page-session.js, as Skills Studio's owner writes), which a local process that
22
+ // merely leaves the agent headers out lacks; a tab that stayed open through the restart carries the previous boot's
23
+ // cookie, and the page's own recovery (public/shared/page-session.js) gets this boot's and sends the dismissal once
24
+ // more. A request names an id, never a path.
25
+
26
+ import { Router } from 'express';
27
+ import { clearCrashRecord, readCrashRecord } from '../../lib/server-crash-record.js';
28
+ import { sendGuardError, uiOnly } from './http-guards.js';
29
+
30
+ const ID = /^[0-9a-f]{16}$/;
31
+
32
+ /** What a page is shown of a record (already checked field by field when it was read), or null. */
33
+ export function crashNoticeOf(record) {
34
+ if (!record || typeof record !== 'object') return null;
35
+ const { v, ...notice } = record;
36
+ return notice;
37
+ }
38
+
39
+ /**
40
+ * @param {object} deps
41
+ * @param {string} deps.dataHome
42
+ * @param {import('express').RequestHandler} deps.guard the owner's guard (lib/http-guards.js ownerRouteGuard)
43
+ * @param {{ verify(req): boolean, require: import('express').RequestHandler }} [deps.pageSession] lib/page-session.js:
44
+ * the page secret a dismissal carries (server.js hands it in; without one, as in a unit test, none is asked)
45
+ * @param {(message: object) => void} [deps.broadcast] the owner's open pages (server.js broadcastSync keeps
46
+ * `crash-notice:` from invite guests)
47
+ * @param {() => Promise<{ tunnel?: 'unknown' }>} [deps.leftovers] what the crashed server may have left running that
48
+ * this one could not end (server.js: its cloudflared, sweepLeftoverTunnels): added to the notice
49
+ */
50
+ export function createCrashNoticeApi({
51
+ dataHome, guard, pageSession = null, broadcast = () => {}, log = () => {}, read = readCrashRecord, clear = clearCrashRecord,
52
+ leftovers = async () => ({}),
53
+ } = {}) {
54
+ if (!dataHome || typeof guard !== 'function') throw new Error('createCrashNoticeApi needs the data home and the owner\'s guard');
55
+ const router = Router();
56
+ // Dismissed while the record could not be removed: never shown again by this server.
57
+ const dismissed = new Set();
58
+ const current = () => {
59
+ const notice = crashNoticeOf(read(dataHome));
60
+ return notice && !dismissed.has(notice.id) ? notice : null;
61
+ };
62
+ // The notice as a page is shown it: the record, and what the crashed server may have left open.
63
+ const shown = async (notice) => {
64
+ if (!notice) return null;
65
+ let left = {};
66
+ try { left = await leftovers(); } catch { left = {}; }
67
+ return { ...notice, ...(left?.tunnel === 'unknown' ? { tunnel: 'unknown' } : {}) };
68
+ };
69
+
70
+ router.use(guard, uiOnly, (req, res, next) => { res.set('Cache-Control', 'no-store'); next(); });
71
+
72
+ router.get('/', async (req, res) => { res.json({ ok: true, notice: await shown(current()) }); });
73
+
74
+ router.post('/dismiss', (req, res, next) => {
75
+ if (req.get('x-synabun-ui') !== '1') return sendGuardError(res, 403, 'UI_HEADER_REQUIRED', 'A notice is dismissed from SynaBun\'s own page.');
76
+ // The page secret: what a local process that merely sends the page's headers lacks.
77
+ if (pageSession && !pageSession.verify(req)) return pageSession.require(req, res, next);
78
+ next();
79
+ }, async (req, res) => {
80
+ if (!req.is('application/json')) return res.status(415).json({ ok: false, code: 'JSON_REQUIRED', error: 'Send JSON.' });
81
+ const id = req.body?.id;
82
+ if (typeof id !== 'string' || !ID.test(id)) return res.status(400).json({ ok: false, code: 'BAD_REQUEST', error: 'Send { id } of the notice.' });
83
+ if (!clear(dataHome, { id })) {
84
+ const still = crashNoticeOf(read(dataHome));
85
+ if (still?.id === id) {
86
+ dismissed.add(id);
87
+ log(`[crash-notice] the record could not be removed; ${id} stays dismissed until the server restarts`);
88
+ }
89
+ } else {
90
+ log(`[crash-notice] dismissed ${id}`);
91
+ }
92
+ const notice = await shown(current());
93
+ try { broadcast({ type: 'crash-notice:changed', notice }); } catch {}
94
+ res.json({ ok: true, notice });
95
+ });
96
+
97
+ return router;
98
+ }
@@ -4,20 +4,18 @@
4
4
  // input tokens before a line of code, and every request would show up as a
5
5
  // session in the OpenCode panel. So completions get a serve of their own:
6
6
  //
7
- // - its own XDG_CONFIG_HOME (DATA_HOME/data/editor-complete/) whose config
8
- // holds what makes the user's models reachable (their `provider` blocks and
9
- // the provider allow / deny lists) and one agent, `synabun-complete`: the
10
- // completion instruction as its prompt, every tool denied. No MCP servers,
11
- // no instruction files, no plugins. A provider block that names a file by
12
- // a path relative to the user's configuration ({file:./key}) is written
13
- // with that path made whole: here it would be looked for in this folder;
7
+ // - its own XDG_CONFIG_HOME (DATA_HOME/data/editor-complete/) whose
8
+ // opencode.json holds what makes the user's models reachable (their
9
+ // provider blocks and the provider allow / deny rules, as the user wrote
10
+ // them) and one agent, `synabun-complete`: the completion instruction as
11
+ // its system prompt, every tool denied. No MCP servers, no instruction
12
+ // files, no plugins. A provider block that names a file by a path relative
13
+ // to the user's configuration ({file:./key}) is written with that path
14
+ // made whole: here it would be looked for in this folder;
14
15
  // - OPENCODE_DISABLE_CLAUDE_CODE and OPENCODE_DISABLE_PROJECT_CONFIG, so no
15
16
  // CLAUDE.md, AGENTS.md or opencode.json of a folder above is read either;
16
17
  // - the user's own data home, so stored provider credentials keep working.
17
18
  //
18
- // Measured on OpenCode 1.18.34 (2026-10-12): about 200 input tokens of overhead
19
- // per request against 5,500 with the tools left on, and 810 with an AGENTS.md
20
- // one folder up and project configuration left on.
21
19
  //
22
20
  // Started on the first request, stopped after ten idle minutes and at server
23
21
  // shutdown. One request is one session in an empty working folder: create,
@@ -35,13 +33,12 @@
35
33
 
36
34
  import { mkdirSync, writeFileSync } from 'node:fs';
37
35
  import { isAbsolute, join, resolve, sep } from 'node:path';
38
- import { checkOpencodeServer, newServePassword } from './opencode-protocol.js';
36
+ import { checkOpencodeServer, newServePassword, servePasswordReader } from './opencode-protocol.js';
37
+ import { OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA } from './opencode-config.js';
39
38
 
40
- // OpenCode 2 (lib/opencode-protocol.js) runs the same way: the serve gets a
41
- // password of its own, is healthy only when its /api answers JSON, and is
42
- // spoken to through lib/opencode2-client.js; its configuration is the OpenCode
43
- // 1 file below, which OpenCode 2 reads (agent → agents, prompt → system,
44
- // temperature → request body).
39
+ // The serve gets a password of its own, is healthy only when OpenCode 2's /api
40
+ // answers JSON (lib/opencode-protocol.js), and is spoken to through
41
+ // lib/opencode2-client.js; its configuration is OpenCode 2's opencode.json.
45
42
 
46
43
  export const COMPLETION_AGENT = 'synabun-complete';
47
44
  /** Every session of this serve carries this title: no title is generated, and leftovers of a crash are recognisable. */
@@ -53,8 +50,10 @@ export const SWEEP_TIMEOUT_MS = 5000;
53
50
  /** How many ports a start tries when another server takes the one it was given. */
54
51
  export const START_ATTEMPTS = 3;
55
52
 
56
- // What of the user's OpenCode configuration decides which models can be reached.
57
- const CARRIED_KEYS = ['provider', 'disabled_providers', 'enabled_providers'];
53
+ // What of the user's OpenCode configuration decides which models can be reached: the provider blocks and the
54
+ // provider allow / deny rules, in either shape the user wrote them (OpenCode 2 reads both).
55
+ const PROVIDER_KEYS = ['providers', 'provider'];
56
+ const CARRIED_LISTS = ['policies', 'disabled_providers', 'enabled_providers'];
58
57
  // Reasoning settings a model may offer, the lightest first: a completion wants the least.
59
58
  const LIGHT_VARIANTS = ['none', 'minimal', 'low'];
60
59
 
@@ -87,18 +86,21 @@ function rebaseFileReferences(value, dir, seen) {
87
86
  }
88
87
 
89
88
  /**
90
- * The configuration of the dedicated serve.
91
- * @param {object | null} userConfig the user's own OpenCode configuration (~/.config/opencode/config.json)
89
+ * The configuration of the dedicated serve (its opencode.json).
90
+ * @param {object | null} userConfig the user's own OpenCode configuration (opencode.json, then opencode.jsonc over it)
92
91
  * @param {{ system: string, configDir?: string | null, log?: (tag: string, message: string) => void }} o `system`:
93
92
  * the completion instruction, the agent's whole prompt; `configDir`: the folder the user's configuration is in,
94
93
  * which a relative {file:…} reference of a provider is resolved against. Without it, a provider that holds such
95
94
  * a reference is left out (and logged): from the serve's own folder it would name another file, or none.
96
95
  */
97
96
  export function buildCompletionServeConfig(userConfig, { system, configDir = null, log = () => {} }) {
98
- const config = { $schema: 'https://opencode.ai/config.json', autoupdate: false, share: 'disabled', snapshot: false };
99
- for (const key of CARRIED_KEYS) {
97
+ const config = { $schema: OPENCODE_CONFIG_SCHEMA, update: 'disable', share: 'disabled', snapshots: false };
98
+ for (const key of CARRIED_LISTS) {
99
+ const value = isObject(userConfig) ? userConfig[key] : undefined;
100
+ if (Array.isArray(value)) config[key] = structuredClone(value);
101
+ }
102
+ for (const key of PROVIDER_KEYS) {
100
103
  const value = isObject(userConfig) ? userConfig[key] : undefined;
101
- if (key !== 'provider') { if (Array.isArray(value)) config[key] = structuredClone(value); continue; }
102
104
  if (!isObject(value)) continue;
103
105
  const providers = {};
104
106
  for (const [id, definition] of Object.entries(value)) {
@@ -110,15 +112,15 @@ export function buildCompletionServeConfig(userConfig, { system, configDir = nul
110
112
  }
111
113
  providers[id] = carried;
112
114
  }
113
- if (Object.keys(providers).length || !Object.keys(value).length) config.provider = providers;
115
+ if (Object.keys(providers).length || !Object.keys(value).length) config[key] = providers;
114
116
  }
115
- config.agent = {
117
+ config.agents = {
116
118
  [COMPLETION_AGENT]: {
117
119
  description: 'SynaBun code editor autocomplete',
118
120
  mode: 'primary',
119
- prompt: system,
120
- temperature: 0.1,
121
- permission: { '*': 'deny' },
121
+ system,
122
+ request: { body: { temperature: 0.1 } },
123
+ permissions: [{ action: '*', resource: '*', effect: 'deny' }],
122
124
  },
123
125
  };
124
126
  return config;
@@ -128,7 +130,7 @@ export function buildCompletionServeConfig(userConfig, { system, configDir = nul
128
130
  * The environment of the dedicated serve: the server's own, pointed at the lean configuration and told to read
129
131
  * nothing else.
130
132
  * @param {object} base the environment to start from (process.env)
131
- * @param {{ configHome: string }} o the XDG_CONFIG_HOME that holds opencode/config.json
133
+ * @param {{ configHome: string }} o the XDG_CONFIG_HOME that holds opencode/opencode.json
132
134
  */
133
135
  export function completionServeEnvironment(base, { configHome }) {
134
136
  const env = { ...base };
@@ -160,7 +162,7 @@ export function splitModelId(id) {
160
162
  return { providerID: text.slice(0, slash), modelID: text.slice(slash + 1) };
161
163
  }
162
164
 
163
- /** Each connected model's variant names, from the serve's /provider answer: Map("provider/model" → string[]). */
165
+ /** Each connected model's variant names, from the serve's provider list: Map("provider/model" → string[]). */
164
166
  export function variantsByModel(providers) {
165
167
  const out = new Map();
166
168
  const body = isObject(providers?.data) && (providers.data.all || providers.data.connected) ? providers.data : providers;
@@ -176,7 +178,7 @@ export function variantsByModel(providers) {
176
178
  }
177
179
 
178
180
  /**
179
- * The text and the token counts of a prompt's answer (`POST /session/{id}/message`).
181
+ * The text and the token counts of a prompt's answer (the client's `session.prompt`).
180
182
  * @returns {{ text: string, tokens: { input: number, output: number }, error: string | null }} `input` counts the
181
183
  * tokens read from and written to the provider's cache too; `output` counts reasoning
182
184
  */
@@ -202,23 +204,24 @@ function abortError() {
202
204
 
203
205
  /**
204
206
  * @param {object} deps
205
- * @param {string} deps.dataDir DATA_HOME/data/editor-complete: holds opencode/config.json and the empty work folder
207
+ * @param {string} deps.dataDir DATA_HOME/data/editor-complete: holds opencode/opencode.json and the empty work folder
206
208
  * @param {string} deps.system the completion instruction (the agent's prompt)
207
209
  * @param {() => object | null} deps.readUserConfig the user's OpenCode configuration, read at each start
208
210
  * @param {() => string | null} [deps.userConfigDir] the folder that configuration is in (relative {file:…}
209
211
  * references of its providers are resolved against it)
210
212
  * @param {() => number | null | Promise<number | null>} deps.allocPort a free port (server.js: what an earlier
211
213
  * SynaBun left running goes first, then allocOpencodeIsoPort)
212
- * @param {(o: { port: number, env: object, cwd: string, protocol: 1 | 2 | null, password: string | null }) => object}
213
- * deps.spawnServe starts `opencode serve` on the port (OpenCode 2: with that password) and returns its process
214
+ * @param {(o: { port: number, env: object, cwd: string, password: string }) => object}
215
+ * deps.spawnServe starts `opencode serve` on the port with that password and returns its process
214
216
  * (`on('exit')`, `kill()`)
215
217
  * @param {(proc: object, signal?: string) => void} [deps.killServe] stops that process: SIGTERM, or SIGKILL for a
216
218
  * failed start's process that did not end (default: proc.kill(signal))
217
- * @param {(o: { port: number, protocol?: number, password?: string | null, version?: string }) => object}
219
+ * @param {(o: { port: number, password?: string | null, version?: string }) => object}
218
220
  * deps.createClient lib/opencode-v2-client.js createClientInstance
219
- * @param {() => Promise<number | null | { protocol: number | null, major?: number | null, supported?: boolean, reason?: string }>}
220
- * [deps.detectProtocol] the OpenCode major the serve will be (server.js detectOpencodeProtocol); 1 when not given
221
- * @param {(protocol: number | null, run: () => Promise<any>) => Promise<any>} [deps.startOneAtATime] runs a start
221
+ * @param {() => Promise<{ major?: number | null, supported?: boolean, reason?: string } | null>}
222
+ * [deps.detectOpencode] which OpenCode the serve will be (server.js detectOpencode): one SynaBun does not drive
223
+ * never starts
224
+ * @param {(run: () => Promise<any>) => Promise<any>} [deps.startOneAtATime] runs a start
222
225
  * in its turn among every serve SynaBun starts (server.js oneOpencodeStartAtATime); by itself when not given
223
226
  * @param {number} [deps.exitWaitMs] how long a failed start waits for its process to exit after SIGTERM, and again
224
227
  * after SIGKILL, before its turn ends
@@ -238,8 +241,9 @@ export function createOpencodeCompletionEngine({
238
241
  killServe = (proc, signal = 'SIGTERM') => { try { proc.kill(signal); } catch { /* gone */ } },
239
242
  createClient, fetchImpl = (...args) => fetch(...args), env = process.env, now = Date.now,
240
243
  idleMs = IDLE_STOP_MS, startTimeoutMs = START_TIMEOUT_MS, sweepTimeoutMs = SWEEP_TIMEOUT_MS, healthPollMs = 100, log = () => {},
241
- detectProtocol = async () => 1, newPassword = newServePassword,
242
- startOneAtATime = (_protocol, run) => run(), exitWaitMs = 3000, trackServe = () => {},
244
+ detectOpencode = async () => null, newPassword = newServePassword,
245
+ passwordFrom = 'env', checkServer = checkOpencodeServer,
246
+ startOneAtATime = (run) => run(), exitWaitMs = 3000, trackServe = () => {},
243
247
  } = {}) {
244
248
  if (!dataDir || !system) throw new Error('createOpencodeCompletionEngine requires dataDir and system');
245
249
  if (typeof allocPort !== 'function' || typeof spawnServe !== 'function' || typeof createClient !== 'function') {
@@ -256,7 +260,6 @@ export function createOpencodeCompletionEngine({
256
260
  const deleting = new Set(); // session deletions on their way: a stop waits for them (drain)
257
261
 
258
262
  const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
259
- const serveUrl = (port, path) => `http://127.0.0.1:${port}${path}${path.includes('?') ? '&' : '?'}directory=${encodeURIComponent(workDir)}`;
260
263
 
261
264
  function touch() {
262
265
  lastUsed = now();
@@ -301,22 +304,22 @@ export function createOpencodeCompletionEngine({
301
304
  try { userConfig = readUserConfig(); } catch (error) { log('editor-complete:serve', `could not read the OpenCode configuration: ${error?.message || error}`); }
302
305
  let configDir = null;
303
306
  try { configDir = userConfigDir() || null; } catch { configDir = null; }
304
- writeFileSync(join(dataDir, 'opencode', 'config.json'), `${JSON.stringify(buildCompletionServeConfig(userConfig, { system, configDir, log }), null, 2)}\n`, 'utf8');
307
+ writeFileSync(join(dataDir, 'opencode', OPENCODE_CONFIG_FILE), `${JSON.stringify(buildCompletionServeConfig(userConfig, { system, configDir, log }), null, 2)}\n`, 'utf8');
305
308
 
306
- // Which OpenCode this is (null: not known, told apart by what answers); a major SynaBun does not speak never starts.
307
- const detected = await detectProtocol();
308
- const protocol = detected && typeof detected === 'object' ? (detected.protocol ?? null) : (detected ?? null);
309
+ // An OpenCode SynaBun does not drive (OpenCode 1, an OpenCode 2 older than the minimum) never starts; a version
310
+ // not known here (a command line in Settings) is told by what answers.
311
+ const detected = await detectOpencode();
309
312
  if (detected && typeof detected === 'object' && detected.supported === false && detected.major != null) {
310
313
  throw new Error(detected.reason || `OpenCode ${detected.major} is not supported`);
311
314
  }
312
315
  if (mine !== epoch) throw new Error('The completion server was stopped while it was starting');
313
- const password = protocol === 1 ? null : newPassword();
316
+ const password = passwordFrom === 'stdout' ? null : newPassword();
314
317
 
315
318
  // Another server that takes the port while the serve boots is a start collision: it is left running, this start's
316
319
  // own process is ended, and the start tries another port (START_ATTEMPTS ports at most).
317
320
  for (let attempt = 1; ; attempt++) {
318
321
  try {
319
- return await startOn(port, { mine, protocol, password });
322
+ return await startOn(port, { mine, password });
320
323
  } catch (error) {
321
324
  if (!error?.portTaken || attempt >= START_ATTEMPTS || mine !== epoch) throw error;
322
325
  log('editor-complete:serve', `another server answers on port ${port}: it is left running, and the completion server tries another port`);
@@ -343,8 +346,8 @@ export function createOpencodeCompletionEngine({
343
346
  return false;
344
347
  }
345
348
 
346
- async function startOn(port, { mine, protocol, password }) {
347
- const started = { port, proc: null, client: null, variants: new Map(), providers: null, providersAt: 0, exited: false, killed: false, halt: null, protocol, password };
349
+ async function startOn(port, { mine, password }) {
350
+ const started = { port, proc: null, client: null, variants: new Map(), providers: null, providersAt: 0, exited: false, killed: false, halt: null, password };
348
351
  // What a stop wakes: a start that waits (for the serve to answer, for the next look) goes on at once.
349
352
  const halted = new Promise((done) => { started.halt = done; });
350
353
  // Both are checked before and after each wait: a stop may have been asked for, or the process may be gone.
@@ -357,9 +360,13 @@ export function createOpencodeCompletionEngine({
357
360
  // One start at a time with every other serve SynaBun starts (OpenCode 2 cannot open its database while
358
361
  // another serve is opening it): from the spawn until this serve answers, or until a failed start's process
359
362
  // is gone.
360
- const check = await startOneAtATime(protocol, async () => {
363
+ const check = await startOneAtATime(async () => {
361
364
  stillWanted();
362
- started.proc = spawnServe({ port, env: completionServeEnvironment(env, { configHome: dataDir }), cwd: workDir, protocol, password });
365
+ started.proc = spawnServe({ port, env: completionServeEnvironment(env, { configHome: dataDir }), cwd: workDir, password, passwordFrom });
366
+ if (passwordFrom === 'stdout') {
367
+ const readPassword = servePasswordReader();
368
+ started.proc?.stdout?.on('data', chunk => { started.password = readPassword(chunk); });
369
+ }
363
370
  starts++;
364
371
  spawned = started;
365
372
  started.proc?.on?.('exit', () => {
@@ -371,8 +378,9 @@ export function createOpencodeCompletionEngine({
371
378
  const deadline = now() + startTimeoutMs;
372
379
  for (;;) {
373
380
  stillWanted();
374
- // The right API answering JSON, never a web page (lib/opencode-protocol.js).
375
- const answer = await Promise.race([checkOpencodeServer({ port, protocol, password, fetchImpl, timeoutMs: 2000 }), halted]);
381
+ // OpenCode 2's API answering JSON, never a web page (lib/opencode-protocol.js).
382
+ const answer = passwordFrom === 'stdout' && !started.password ? null
383
+ : await Promise.race([checkServer({ port, password: started.password, fetchImpl, timeoutMs: 2000 }), halted]);
376
384
  if (answer?.ok) {
377
385
  // Only this start's own process counts: another server that took the port answers for itself.
378
386
  let verdict = null;
@@ -395,8 +403,7 @@ export function createOpencodeCompletionEngine({
395
403
  });
396
404
  stillWanted();
397
405
 
398
- started.protocol = check.protocol || protocol || 1;
399
- started.client = createClient({ port, protocol: started.protocol, password, version: check.version || '' });
406
+ started.client = createClient({ port, password: started.password, version: check.version || '' });
400
407
  // It answers, and takes no request yet. First the sessions a crash left behind go: a sweep that ran beside the
401
408
  // first requests would find their sessions in its listing and delete them under the model call. Meanwhile,
402
409
  // which lighter reasoning setting each model offers; worth a short wait, as without it the first completions
@@ -423,18 +430,11 @@ export function createOpencodeCompletionEngine({
423
430
 
424
431
  async function loadProviders(serve, { signal } = {}) {
425
432
  if (serve.providers && now() - serve.providersAt < 60_000) return serve.providers;
426
- if (serve.protocol === 2) {
427
- // OpenCode 2 has no /provider: its client answers in OpenCode 1's shape ({ all, default, connected }).
428
- const listed = await serve.client.getClient().provider.list({ directory: workDir });
429
- if (listed?.error) throw new Error(`The completion server answered ${listed.response?.status || 'an error'} for its model list`);
430
- serve.providers = listed.data;
431
- serve.providersAt = now();
432
- serve.variants = variantsByModel(serve.providers);
433
- return serve.providers;
434
- }
435
- const res = await fetchImpl(serveUrl(serve.port, '/provider'), { signal: signal || AbortSignal.timeout(8000) });
436
- if (!res.ok) throw new Error(`The completion server answered ${res.status} for its model list`);
437
- serve.providers = await res.json();
433
+ if (signal?.aborted) throw abortError();
434
+ // The client answers with the provider list SynaBun reads ({ all, default, connected }).
435
+ const listed = await serve.client.getClient().provider.list({ directory: workDir });
436
+ if (listed?.error) throw new Error(`The completion server answered ${listed.response?.status || 'an error'} for its model list`);
437
+ serve.providers = listed.data;
438
438
  serve.providersAt = now();
439
439
  serve.variants = variantsByModel(serve.providers);
440
440
  return serve.providers;
@@ -517,7 +517,7 @@ export function createOpencodeCompletionEngine({
517
517
  return {
518
518
  complete,
519
519
  /**
520
- * The serve's /provider answer (every known provider, and which are connected), or null while the serve is
520
+ * The serve's provider list (every known provider, and which are connected), or null while the serve is
521
521
  * not running and `start` is not set.
522
522
  */
523
523
  async providers({ start: startIt = false, signal } = {}) {