synabun 2.0.0-beta → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +51 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/launcher.mjs +40 -0
  14. package/lib/claude-hooks.js +61 -8
  15. package/lib/client-config-repair.js +4 -3
  16. package/lib/data-home-migration.js +4 -0
  17. package/lib/desktop-pwa.js +206 -0
  18. package/lib/external-tools.js +221 -0
  19. package/lib/first-launch-protection.js +5 -0
  20. package/lib/packaged-runtime.js +110 -0
  21. package/lib/start-launcher.js +1396 -0
  22. package/mcp-server/package-lock.json +2 -2
  23. package/mcp-server/package.json +1 -3
  24. package/neural-interface/i18n/en.json +1540 -154
  25. package/neural-interface/i18n/pt-BR.json +1538 -153
  26. package/neural-interface/lib/assistant-api.js +62 -6
  27. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  28. package/neural-interface/lib/assistant-catalog.js +179 -27
  29. package/neural-interface/lib/assistant-config.js +31 -2
  30. package/neural-interface/lib/assistant-dispatch.js +62 -13
  31. package/neural-interface/lib/assistant-envelope.js +9 -6
  32. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  33. package/neural-interface/lib/assistant-memory.js +24 -10
  34. package/neural-interface/lib/assistant-persona.js +6 -2
  35. package/neural-interface/lib/assistant-pricing.js +21 -6
  36. package/neural-interface/lib/assistant-runtime.js +181 -12
  37. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  38. package/neural-interface/lib/assistant-test-density.js +142 -0
  39. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  40. package/neural-interface/lib/assistant-user-context.js +908 -0
  41. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  42. package/neural-interface/lib/assistant-user-learn.js +404 -0
  43. package/neural-interface/lib/assistant-user-threads.js +226 -0
  44. package/neural-interface/lib/assistant-user-writer.js +181 -0
  45. package/neural-interface/lib/augmented-path.js +23 -3
  46. package/neural-interface/lib/browser-background-tab.js +134 -0
  47. package/neural-interface/lib/browser-language.js +83 -0
  48. package/neural-interface/lib/browser-screenshot.js +678 -61
  49. package/neural-interface/lib/claude-agent-bridge.js +87 -153
  50. package/neural-interface/lib/claude-executable.js +241 -0
  51. package/neural-interface/lib/claude-model-catalog.js +82 -93
  52. package/neural-interface/lib/claude-temporary.js +46 -7
  53. package/neural-interface/lib/cloudflared/install.js +533 -0
  54. package/neural-interface/lib/cloudflared/manager.js +545 -0
  55. package/neural-interface/lib/cloudflared/pins.js +115 -0
  56. package/neural-interface/lib/cloudflared/pins.json +63 -0
  57. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  58. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  59. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  60. package/neural-interface/lib/editor-completion.js +714 -0
  61. package/neural-interface/lib/editor-config.js +149 -0
  62. package/neural-interface/lib/editor-files-api.js +268 -0
  63. package/neural-interface/lib/editor-git-api.js +134 -0
  64. package/neural-interface/lib/file-explorer-api.js +1039 -0
  65. package/neural-interface/lib/file-explorer-system.js +355 -0
  66. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  67. package/neural-interface/lib/file-explorer-watch.js +239 -0
  68. package/neural-interface/lib/git-diff-api.js +103 -0
  69. package/neural-interface/lib/git-read.js +202 -0
  70. package/neural-interface/lib/greeting/api.js +89 -0
  71. package/neural-interface/lib/greeting/budgets.js +90 -0
  72. package/neural-interface/lib/greeting/engine.js +461 -0
  73. package/neural-interface/lib/greeting/facts.js +320 -0
  74. package/neural-interface/lib/greeting/sources.js +252 -0
  75. package/neural-interface/lib/greeting/writer.js +139 -0
  76. package/neural-interface/lib/http-guards.js +189 -13
  77. package/neural-interface/lib/machine-names.js +88 -0
  78. package/neural-interface/lib/memory-api.js +31 -7
  79. package/neural-interface/lib/native-binary-runtime.js +69 -337
  80. package/neural-interface/lib/native-loop-providers.js +40 -49
  81. package/neural-interface/lib/offline-page.js +54 -0
  82. package/neural-interface/lib/plugin-installer.js +4 -2
  83. package/neural-interface/lib/project-storage.js +5 -7
  84. package/neural-interface/lib/request-gate.js +589 -0
  85. package/neural-interface/lib/session-title-generator.js +5 -0
  86. package/neural-interface/lib/synabun-update-plan.js +19 -0
  87. package/neural-interface/lib/ui-blob-api.js +108 -0
  88. package/neural-interface/lib/ui-blob-store.js +68 -0
  89. package/neural-interface/package-lock.json +701 -253
  90. package/neural-interface/package.json +13 -2
  91. package/neural-interface/public/install-app.html +34 -0
  92. package/neural-interface/public/manifest.json +2 -0
  93. package/neural-interface/public/offline.html +402 -66
  94. package/neural-interface/public/onboarding.html +264 -147
  95. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  96. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  97. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  98. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  99. package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
  100. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  101. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  102. package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
  103. package/neural-interface/public/shared/assistant/asst-render.js +219 -67
  104. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  105. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  106. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  107. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  108. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  109. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  110. package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
  111. package/neural-interface/public/shared/blob-store.js +111 -22
  112. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  113. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  114. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  115. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  116. package/neural-interface/public/shared/command-runner-model.js +30 -0
  117. package/neural-interface/public/shared/constants.js +6 -1
  118. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  119. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  120. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  121. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  122. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  123. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  124. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  125. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  126. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  127. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  128. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  129. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  130. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  131. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  132. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  133. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  134. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  135. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  136. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  137. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  138. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  139. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  140. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  141. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  142. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  143. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  144. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  145. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  146. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  147. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  148. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  149. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  150. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  151. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  152. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  153. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  154. package/neural-interface/public/shared/file-links.js +339 -0
  155. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  156. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  157. package/neural-interface/public/shared/html-shell.js +169 -170
  158. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  159. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  160. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  161. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  162. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  163. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  164. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  165. package/neural-interface/public/shared/pwa-install-page.js +62 -0
  166. package/neural-interface/public/shared/pwa-install.css +12 -0
  167. package/neural-interface/public/shared/pwa-install.js +127 -0
  168. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  169. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  170. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  171. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  172. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  173. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  174. package/neural-interface/public/shared/start-bridge.js +802 -0
  175. package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
  176. package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
  177. package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
  178. package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
  179. package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
  180. package/neural-interface/public/shared/styles.css +2219 -1117
  181. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  182. package/neural-interface/public/shared/term-ready.js +307 -0
  183. package/neural-interface/public/shared/tunnel-view.js +167 -0
  184. package/neural-interface/public/shared/ui-claude-panel.js +160 -19
  185. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  186. package/neural-interface/public/shared/ui-detail.js +15 -0
  187. package/neural-interface/public/shared/ui-explorer.js +22 -0
  188. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  189. package/neural-interface/public/shared/ui-file-links.js +103 -0
  190. package/neural-interface/public/shared/ui-help.js +26 -0
  191. package/neural-interface/public/shared/ui-invite.js +126 -116
  192. package/neural-interface/public/shared/ui-judgments.js +1 -1
  193. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  194. package/neural-interface/public/shared/ui-loading.js +367 -12
  195. package/neural-interface/public/shared/ui-menubar.js +21 -55
  196. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  197. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  198. package/neural-interface/public/shared/ui-navbar.js +45 -21
  199. package/neural-interface/public/shared/ui-settings.js +491 -547
  200. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  201. package/neural-interface/public/shared/ui-sync.js +16 -0
  202. package/neural-interface/public/shared/ui-terminal.js +153 -149
  203. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  204. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  205. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  206. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  207. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  208. package/neural-interface/public/shared/worker-end.js +251 -0
  209. package/neural-interface/public/sw.js +3 -1
  210. package/neural-interface/public/variant/2d/main.js +2 -1
  211. package/neural-interface/public/variant/3d/main.js +2 -1
  212. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  309. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  310. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  311. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  312. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  313. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  314. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  315. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  316. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  317. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  318. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  319. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  320. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  321. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  322. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  323. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  324. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  325. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  326. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  327. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  328. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  329. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  330. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  331. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  332. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  333. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  334. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  335. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  336. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  337. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  338. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  339. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  340. package/neural-interface/scripts/build-vendor.mjs +78 -2
  341. package/neural-interface/scripts/rebuild-pty.js +15 -12
  342. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  343. package/neural-interface/server.js +1092 -1212
  344. package/neural-interface/templates/rulesets/README.md +1 -1
  345. package/package.json +13 -7
  346. package/postinstall.js +11 -81
  347. package/setup.js +111 -6
  348. package/benchmarks/browser-live-results.json +0 -79
  349. package/benchmarks/browser-v2-results.json +0 -258
  350. package/docs/browser-v2.md +0 -109
  351. package/docs/hook-integrity.md +0 -89
  352. package/docs/judgments.md +0 -182
  353. package/docs/style-guide.md +0 -246
  354. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  355. package/neural-interface/public/category-logos/claude.svg +0 -1
  356. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  357. package/neural-interface/public/category-logos/openai.svg +0 -1
  358. package/neural-interface/public/category-logos/synabun.png +0 -0
  359. package/neural-interface/public/claude.svg +0 -1
  360. package/neural-interface/public/gallery.svg +0 -6
  361. package/neural-interface/public/leonardoai.svg +0 -1
  362. package/neural-interface/public/mcp.svg +0 -4
  363. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  364. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  365. package/neural-interface/public/sessionicon.svg +0 -8
  366. package/neural-interface/public/toolbar-album.svg +0 -8
  367. package/neural-interface/public/toolbar-question.svg +0 -6
  368. package/neural-interface/public/toolbar-sessions.svg +0 -6
  369. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  370. package/scripts/audit-claude-hook-usage.mjs +0 -12
  371. package/scripts/audit-memory-duplicates.mjs +0 -44
  372. package/scripts/measure-claude-hook-overhead.mjs +0 -77
  373. package/scripts/recover-user-config.mjs +0 -254
  374. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,436 @@
1
+ // ── AI autocomplete through OpenCode models: a dedicated, lean `opencode serve` ──
2
+ // A fill-in request must be short and quick. On the shared serve (port 4096) it
3
+ // carries the user's instruction files and MCP tool definitions, 4,700 to 9,300
4
+ // input tokens before a line of code, and every request would show up as a
5
+ // session in the OpenCode panel. So completions get a serve of their own:
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;
14
+ // - OPENCODE_DISABLE_CLAUDE_CODE and OPENCODE_DISABLE_PROJECT_CONFIG, so no
15
+ // CLAUDE.md, AGENTS.md or opencode.json of a folder above is read either;
16
+ // - the user's own data home, so stored provider credentials keep working.
17
+ //
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
+ //
22
+ // Started on the first request, stopped after ten idle minutes and at server
23
+ // shutdown. One request is one session in an empty working folder: create,
24
+ // prompt with the model and the agent, read the text parts, delete. A start is
25
+ // over, and the serve takes requests, only once the sessions a crash left in
26
+ // that folder are deleted: every request that arrives meanwhile waits for the
27
+ // same start, so no request's own session is ever taken for a leftover. A start
28
+ // that fails after its process was spawned (the serve never answers, its client
29
+ // cannot be built, a stop is asked for meanwhile) stops that process and fails
30
+ // for everybody who waited for it; stop() ends a start that is under way too.
31
+ // There is no fallback to the shared serve. Everything that touches a process,
32
+ // a port or the network is injected: tests run against a fake.
33
+
34
+ import { mkdirSync, writeFileSync } from 'node:fs';
35
+ import { isAbsolute, join, resolve, sep } from 'node:path';
36
+
37
+ export const COMPLETION_AGENT = 'synabun-complete';
38
+ /** Every session of this serve carries this title: no title is generated, and leftovers of a crash are recognisable. */
39
+ export const COMPLETION_SESSION_TITLE = 'synabun-editor-complete';
40
+ export const IDLE_STOP_MS = 10 * 60_000;
41
+ export const START_TIMEOUT_MS = 15_000;
42
+ /** How long a start waits for the sessions a crash left behind to be listed and deleted. */
43
+ export const SWEEP_TIMEOUT_MS = 5000;
44
+
45
+ // What of the user's OpenCode configuration decides which models can be reached.
46
+ const CARRIED_KEYS = ['provider', 'disabled_providers', 'enabled_providers'];
47
+ // Reasoning settings a model may offer, the lightest first: a completion wants the least.
48
+ const LIGHT_VARIANTS = ['none', 'minimal', 'low'];
49
+
50
+ const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
51
+
52
+ // OpenCode replaces {file:path} in a configuration with the file's text. A path that is neither absolute nor
53
+ // from the home folder (~/) is read from the folder the configuration is in.
54
+ const FILE_REFERENCE = /\{file:([^}]+)\}/g;
55
+ const isRelativeReference = (path) => !(path.startsWith('~/') || isAbsolute(path) || /^[A-Za-z]:[\\/]/.test(path));
56
+
57
+ /**
58
+ * A value of the user's configuration with its relative {file:…} references written from `dir`.
59
+ * @param {any} value
60
+ * @param {string | null} dir the folder the user's configuration is in; without it the references stay as they are
61
+ * @param {{ relative: number }} seen counts the relative references met
62
+ */
63
+ function rebaseFileReferences(value, dir, seen) {
64
+ if (typeof value === 'string') {
65
+ return value.replace(FILE_REFERENCE, (whole, path) => {
66
+ if (!isRelativeReference(path)) return whole;
67
+ seen.relative++;
68
+ if (!dir) return whole;
69
+ const absolute = resolve(dir, path);
70
+ return `{file:${sep === '/' ? absolute : absolute.split(sep).join('/')}}`;
71
+ });
72
+ }
73
+ if (Array.isArray(value)) return value.map((item) => rebaseFileReferences(item, dir, seen));
74
+ if (isObject(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, rebaseFileReferences(item, dir, seen)]));
75
+ return value;
76
+ }
77
+
78
+ /**
79
+ * The configuration of the dedicated serve.
80
+ * @param {object | null} userConfig the user's own OpenCode configuration (~/.config/opencode/config.json)
81
+ * @param {{ system: string, configDir?: string | null, log?: (tag: string, message: string) => void }} o `system`:
82
+ * the completion instruction, the agent's whole prompt; `configDir`: the folder the user's configuration is in,
83
+ * which a relative {file:…} reference of a provider is resolved against. Without it, a provider that holds such
84
+ * a reference is left out (and logged): from the serve's own folder it would name another file, or none.
85
+ */
86
+ export function buildCompletionServeConfig(userConfig, { system, configDir = null, log = () => {} }) {
87
+ const config = { $schema: 'https://opencode.ai/config.json', autoupdate: false, share: 'disabled', snapshot: false };
88
+ for (const key of CARRIED_KEYS) {
89
+ const value = isObject(userConfig) ? userConfig[key] : undefined;
90
+ if (key !== 'provider') { if (Array.isArray(value)) config[key] = structuredClone(value); continue; }
91
+ if (!isObject(value)) continue;
92
+ const providers = {};
93
+ for (const [id, definition] of Object.entries(value)) {
94
+ const seen = { relative: 0 };
95
+ const carried = rebaseFileReferences(structuredClone(definition), configDir, seen);
96
+ if (seen.relative && !configDir) {
97
+ log('editor-complete:serve', `the provider "${id}" reads a file by a path relative to the OpenCode configuration, whose folder is not known here: it is left out of the completion server`);
98
+ continue;
99
+ }
100
+ providers[id] = carried;
101
+ }
102
+ if (Object.keys(providers).length || !Object.keys(value).length) config.provider = providers;
103
+ }
104
+ config.agent = {
105
+ [COMPLETION_AGENT]: {
106
+ description: 'SynaBun code editor autocomplete',
107
+ mode: 'primary',
108
+ prompt: system,
109
+ temperature: 0.1,
110
+ permission: { '*': 'deny' },
111
+ },
112
+ };
113
+ return config;
114
+ }
115
+
116
+ /**
117
+ * The environment of the dedicated serve: the server's own, pointed at the lean configuration and told to read
118
+ * nothing else.
119
+ * @param {object} base the environment to start from (process.env)
120
+ * @param {{ configHome: string }} o the XDG_CONFIG_HOME that holds opencode/config.json
121
+ */
122
+ export function completionServeEnvironment(base, { configHome }) {
123
+ const env = { ...base };
124
+ // Anything that would add configuration on top of the folder below.
125
+ for (const key of ['OPENCODE_CONFIG', 'OPENCODE_CONFIG_CONTENT', 'OPENCODE_CONFIG_DIR', 'CLAUDECODE']) delete env[key];
126
+ return {
127
+ ...env,
128
+ XDG_CONFIG_HOME: configHome,
129
+ OPENCODE_DISABLE_CLAUDE_CODE: '1',
130
+ OPENCODE_DISABLE_PROJECT_CONFIG: '1',
131
+ OPENCODE_DISABLE_EXTERNAL_SKILLS: '1',
132
+ OPENCODE_DISABLE_AUTOUPDATE: '1',
133
+ OPENCODE_DISABLE_LSP_DOWNLOAD: '1',
134
+ FORCE_COLOR: '0',
135
+ };
136
+ }
137
+
138
+ /** The lightest reasoning setting among a model's variants, or undefined when it offers none of them. */
139
+ export function lightestVariant(names) {
140
+ const list = Array.isArray(names) ? names : [];
141
+ return LIGHT_VARIANTS.find((name) => list.includes(name));
142
+ }
143
+
144
+ /** `{ providerID, modelID }` of a "provider/model" id; the model's own id may hold slashes. */
145
+ export function splitModelId(id) {
146
+ const text = String(id ?? '');
147
+ const slash = text.indexOf('/');
148
+ if (slash <= 0 || slash >= text.length - 1) throw new Error(`Not a model id: ${text || '(empty)'}`);
149
+ return { providerID: text.slice(0, slash), modelID: text.slice(slash + 1) };
150
+ }
151
+
152
+ /** Each connected model's variant names, from the serve's /provider answer: Map("provider/model" → string[]). */
153
+ export function variantsByModel(providers) {
154
+ const out = new Map();
155
+ const body = isObject(providers?.data) && (providers.data.all || providers.data.connected) ? providers.data : providers;
156
+ const connected = Array.isArray(body?.connected) ? new Set(body.connected.map(String)) : null;
157
+ for (const provider of Array.isArray(body?.all) ? body.all : []) {
158
+ const pid = provider?.id;
159
+ if (!pid || (connected && !connected.has(pid))) continue;
160
+ for (const [mid, model] of Object.entries(isObject(provider.models) ? provider.models : {})) {
161
+ if (isObject(model?.variants)) out.set(`${pid}/${model.id || mid}`, Object.keys(model.variants));
162
+ }
163
+ }
164
+ return out;
165
+ }
166
+
167
+ /**
168
+ * The text and the token counts of a prompt's answer (`POST /session/{id}/message`).
169
+ * @returns {{ text: string, tokens: { input: number, output: number }, error: string | null }} `input` counts the
170
+ * tokens read from and written to the provider's cache too; `output` counts reasoning
171
+ */
172
+ export function readPromptResult(data) {
173
+ const parts = Array.isArray(data?.parts) ? data.parts : [];
174
+ const text = parts.filter((part) => part?.type === 'text' && typeof part.text === 'string').map((part) => part.text).join('');
175
+ const t = data?.info?.tokens || {};
176
+ const n = (v) => Math.max(0, Math.round(Number(v) || 0));
177
+ const failure = data?.info?.error;
178
+ const error = failure ? String(failure.data?.message || failure.message || failure.name || 'The model call failed') : null;
179
+ return {
180
+ text,
181
+ tokens: { input: n(t.input) + n(t.cache?.read) + n(t.cache?.write), output: n(t.output) + n(t.reasoning) },
182
+ error,
183
+ };
184
+ }
185
+
186
+ function abortError() {
187
+ const error = new Error('The request was aborted');
188
+ error.name = 'AbortError';
189
+ return error;
190
+ }
191
+
192
+ /**
193
+ * @param {object} deps
194
+ * @param {string} deps.dataDir DATA_HOME/data/editor-complete: holds opencode/config.json and the empty work folder
195
+ * @param {string} deps.system the completion instruction (the agent's prompt)
196
+ * @param {() => object | null} deps.readUserConfig the user's OpenCode configuration, read at each start
197
+ * @param {() => string | null} [deps.userConfigDir] the folder that configuration is in (relative {file:…}
198
+ * references of its providers are resolved against it)
199
+ * @param {() => number | null} deps.allocPort a free port (server.js allocOpencodeIsoPort)
200
+ * @param {(o: { port: number, env: object, cwd: string }) => object} deps.spawnServe starts `opencode serve` on the
201
+ * port and returns its process (`on('exit')`, `kill()`)
202
+ * @param {(proc: object) => void} [deps.killServe] stops that process (default: SIGTERM)
203
+ * @param {(o: { port: number }) => object} deps.createClient lib/opencode-v2-client.js createClientInstance
204
+ * @param {typeof fetch} [deps.fetchImpl]
205
+ * @param {object} [deps.env] the environment the serve starts from
206
+ * @param {number} [deps.idleMs] the serve is stopped after this long without a request
207
+ * @param {number} [deps.startTimeoutMs]
208
+ * @param {number} [deps.sweepTimeoutMs] how long a start waits for the leftover sessions to be listed and deleted
209
+ * @param {number} [deps.healthPollMs]
210
+ */
211
+ export function createOpencodeCompletionEngine({
212
+ dataDir, system, readUserConfig = () => null, userConfigDir = () => null, allocPort, spawnServe,
213
+ killServe = (proc) => { try { proc.kill('SIGTERM'); } catch { /* gone */ } },
214
+ createClient, fetchImpl = (...args) => fetch(...args), env = process.env, now = Date.now,
215
+ idleMs = IDLE_STOP_MS, startTimeoutMs = START_TIMEOUT_MS, sweepTimeoutMs = SWEEP_TIMEOUT_MS, healthPollMs = 100, log = () => {},
216
+ } = {}) {
217
+ if (!dataDir || !system) throw new Error('createOpencodeCompletionEngine requires dataDir and system');
218
+ if (typeof allocPort !== 'function' || typeof spawnServe !== 'function' || typeof createClient !== 'function') {
219
+ throw new Error('createOpencodeCompletionEngine requires allocPort, spawnServe and createClient');
220
+ }
221
+ const workDir = join(dataDir, 'work');
222
+ let entry = null; // the serve that takes requests: { port, proc, client, variants, providers, exited }
223
+ let starting = null; // the start under way, shared by everything that waits for it (it ends when the serve is ready)
224
+ let spawned = null; // the serve of that start, from its spawn until it is ready or given up: stop() ends it too
225
+ let epoch = 0; // moves on with every stop: a start that finishes after one is not kept
226
+ let idleTimer = null;
227
+ let lastUsed = 0;
228
+ let starts = 0;
229
+ const deleting = new Set(); // session deletions on their way: a stop waits for them (drain)
230
+
231
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
232
+ const serveUrl = (port, path) => `http://127.0.0.1:${port}${path}${path.includes('?') ? '&' : '?'}directory=${encodeURIComponent(workDir)}`;
233
+
234
+ function touch() {
235
+ lastUsed = now();
236
+ if (idleTimer) clearTimeout(idleTimer);
237
+ idleTimer = setTimeout(() => { idleTimer = null; stop('idle'); }, idleMs);
238
+ idleTimer.unref?.();
239
+ }
240
+
241
+ /** End one serve: its client's event stream, and its process when that still runs. */
242
+ function end(serve) {
243
+ try { serve.client?.stop?.(); } catch { /* never started its event stream */ }
244
+ if (serve.proc && !serve.exited && !serve.killed) { serve.killed = true; killServe(serve.proc); }
245
+ }
246
+
247
+ function stop(reason = 'stop') {
248
+ epoch++;
249
+ if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
250
+ const gone = entry;
251
+ entry = null;
252
+ // A start that has spawned its process and is not ready yet: the process goes now, and the start fails for
253
+ // everybody who waits for it as soon as it looks again (start()).
254
+ const half = spawned;
255
+ spawned = null;
256
+ if (half) {
257
+ log('editor-complete:serve', `stopping the completion server that was starting on port ${half.port} (${reason})`);
258
+ end(half);
259
+ half.halt?.();
260
+ }
261
+ if (!gone) return !!half;
262
+ log('editor-complete:serve', `stopping the completion server on port ${gone.port} (${reason})`);
263
+ end(gone);
264
+ return true;
265
+ }
266
+
267
+ async function start() {
268
+ const mine = epoch;
269
+ const port = allocPort();
270
+ if (port == null) throw new Error('No free port for the completion server');
271
+ mkdirSync(join(dataDir, 'opencode'), { recursive: true });
272
+ mkdirSync(workDir, { recursive: true });
273
+ let userConfig = null;
274
+ try { userConfig = readUserConfig(); } catch (error) { log('editor-complete:serve', `could not read the OpenCode configuration: ${error?.message || error}`); }
275
+ let configDir = null;
276
+ try { configDir = userConfigDir() || null; } catch { configDir = null; }
277
+ writeFileSync(join(dataDir, 'opencode', 'config.json'), `${JSON.stringify(buildCompletionServeConfig(userConfig, { system, configDir, log }), null, 2)}\n`, 'utf8');
278
+
279
+ const started = { port, proc: null, client: null, variants: new Map(), providers: null, providersAt: 0, exited: false, killed: false, halt: null };
280
+ // What a stop wakes: a start that waits (for the serve to answer, for the next look) goes on at once.
281
+ const halted = new Promise((done) => { started.halt = done; });
282
+ started.proc = spawnServe({ port, env: completionServeEnvironment(env, { configHome: dataDir }), cwd: workDir });
283
+ starts++;
284
+ spawned = started;
285
+ // From here on a process runs. Whatever goes wrong before the serve is ready, it does not stay behind.
286
+ try {
287
+ started.proc?.on?.('exit', () => {
288
+ started.exited = true;
289
+ if (entry === started) { entry = null; if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; } }
290
+ });
291
+ started.proc?.on?.('error', () => { started.exited = true; });
292
+
293
+ // Both are checked before and after each wait: a stop may have been asked for, or the process may be gone.
294
+ const stillWanted = () => {
295
+ if (mine !== epoch) throw new Error('The completion server was stopped while it was starting');
296
+ if (started.exited) throw new Error('The completion server stopped while it was starting. Is OpenCode installed?');
297
+ };
298
+ const deadline = now() + startTimeoutMs;
299
+ for (;;) {
300
+ stillWanted();
301
+ try {
302
+ const res = await Promise.race([fetchImpl(`http://127.0.0.1:${port}/global/health`, { signal: AbortSignal.timeout(2000) }), halted]);
303
+ if (res?.ok) break;
304
+ } catch { /* not up yet */ }
305
+ stillWanted();
306
+ if (now() >= deadline) throw new Error(`The completion server did not answer within ${Math.round(startTimeoutMs / 1000)} seconds`);
307
+ await Promise.race([sleep(healthPollMs), halted]);
308
+ }
309
+ stillWanted();
310
+
311
+ started.client = createClient({ port });
312
+ // It answers, and takes no request yet. First the sessions a crash left behind go: a sweep that ran beside the
313
+ // first requests would find their sessions in its listing and delete them under the model call. Meanwhile,
314
+ // which lighter reasoning setting each model offers; worth a short wait, as without it the first completions
315
+ // think for longer.
316
+ await Promise.race([Promise.all([sweep(started), Promise.race([loadProviders(started).catch(() => null), sleep(1500)])]), halted]);
317
+ stillWanted();
318
+ entry = started;
319
+ touch();
320
+ log('editor-complete:serve', `completion server ready on port ${port}`);
321
+ return started;
322
+ } catch (error) {
323
+ end(started);
324
+ throw error;
325
+ } finally {
326
+ if (spawned === started) spawned = null;
327
+ }
328
+ }
329
+
330
+ function ensure() {
331
+ if (entry) { touch(); return Promise.resolve(entry); }
332
+ if (!starting) starting = start().finally(() => { starting = null; });
333
+ return starting;
334
+ }
335
+
336
+ async function loadProviders(serve, { signal } = {}) {
337
+ if (serve.providers && now() - serve.providersAt < 60_000) return serve.providers;
338
+ const res = await fetchImpl(serveUrl(serve.port, '/provider'), { signal: signal || AbortSignal.timeout(8000) });
339
+ if (!res.ok) throw new Error(`The completion server answered ${res.status} for its model list`);
340
+ serve.providers = await res.json();
341
+ serve.providersAt = now();
342
+ serve.variants = variantsByModel(serve.providers);
343
+ return serve.providers;
344
+ }
345
+
346
+ /**
347
+ * Delete the sessions a crash left behind in the work folder (a request deletes its own). Part of a start: the
348
+ * listing is asked for before the serve takes requests, so nothing in it is a request's own session. It never
349
+ * throws, and it does not hold a start for longer than `sweepTimeoutMs`: a listing that has not answered by
350
+ * then is given up on, and deletes nothing when it does answer (by then it may hold sessions of requests).
351
+ */
352
+ async function sweep(serve) {
353
+ let phase = 'listing';
354
+ const work = (async () => {
355
+ const listed = await serve.client.session.list({ directory: workDir });
356
+ if (phase !== 'listing') return;
357
+ phase = 'deleting';
358
+ const rows = Array.isArray(listed?.data) ? listed.data : [];
359
+ for (const row of rows.filter((r) => r?.title === COMPLETION_SESSION_TITLE).slice(0, 200)) {
360
+ await serve.client.session.delete({ sessionID: row.id, directory: workDir }).catch(() => {});
361
+ }
362
+ })().catch((error) => { log('editor-complete:serve', `could not delete the sessions a crash left behind: ${error?.message || error}`); });
363
+ let timer = null;
364
+ const late = await Promise.race([work.then(() => false), new Promise((done) => { timer = setTimeout(() => done(true), sweepTimeoutMs); timer.unref?.(); })]);
365
+ clearTimeout(timer);
366
+ if (!late) return;
367
+ if (phase === 'listing') {
368
+ phase = 'abandoned';
369
+ log('editor-complete:serve', 'the sessions a crash left behind were not listed in time: none is deleted at this start');
370
+ return;
371
+ }
372
+ // Listed in time, still being deleted: those are leftovers and nothing else. A stop waits for them (drain).
373
+ deleting.add(work);
374
+ work.finally(() => deleting.delete(work));
375
+ }
376
+
377
+ /**
378
+ * One completion.
379
+ * @param {{ model: string, user: string, signal?: AbortSignal }} o `model`: "provider/model"; `user`: the user
380
+ * message (the system message is the agent's prompt)
381
+ * @returns {Promise<{ text: string, tokens: { input: number, output: number } }>}
382
+ */
383
+ async function complete({ model, user, signal } = {}) {
384
+ const ref = splitModelId(model);
385
+ if (signal?.aborted) throw abortError();
386
+ const serve = await ensure();
387
+ if (signal?.aborted) throw abortError();
388
+ const created = await serve.client.session.create({ directory: workDir, title: COMPLETION_SESSION_TITLE });
389
+ const sessionID = created?.data?.id;
390
+ if (!sessionID) throw new Error('OpenCode did not create a session for the completion');
391
+ // The page gave up: the model call is cancelled in the serve, not left to finish unseen.
392
+ const onAbort = () => { Promise.resolve(serve.client.session.abort({ sessionID, directory: workDir })).catch(() => {}); };
393
+ signal?.addEventListener('abort', onAbort, { once: true });
394
+ try {
395
+ if (signal?.aborted) throw abortError();
396
+ const response = await serve.client.session.prompt({
397
+ sessionID, directory: workDir, model: ref, agent: COMPLETION_AGENT,
398
+ variant: lightestVariant(serve.variants.get(model)),
399
+ parts: [{ type: 'text', text: user }], signal,
400
+ });
401
+ const result = readPromptResult(response?.data);
402
+ if (result.error) throw new Error(result.error);
403
+ return { text: result.text, tokens: result.tokens };
404
+ } finally {
405
+ signal?.removeEventListener('abort', onAbort);
406
+ if (entry === serve) touch();
407
+ // Not waited for: the answer is not held back by the tidying up.
408
+ const gone = Promise.resolve().then(() => serve.client.session.delete({ sessionID, directory: workDir })).catch(() => {});
409
+ deleting.add(gone);
410
+ gone.finally(() => deleting.delete(gone));
411
+ }
412
+ }
413
+
414
+ /** Wait, for at most `timeoutMs`, until the sessions of finished requests are deleted (before a stop). */
415
+ async function drain(timeoutMs = 1000) {
416
+ if (!deleting.size) return;
417
+ await Promise.race([Promise.allSettled([...deleting]), sleep(timeoutMs)]);
418
+ }
419
+
420
+ return {
421
+ complete,
422
+ /**
423
+ * The serve's /provider answer (every known provider, and which are connected), or null while the serve is
424
+ * not running and `start` is not set.
425
+ */
426
+ async providers({ start: startIt = false, signal } = {}) {
427
+ const serve = startIt ? await ensure() : entry;
428
+ return serve ? loadProviders(serve, { signal }) : null;
429
+ },
430
+ running: () => !!entry,
431
+ drain,
432
+ stop,
433
+ status: () => ({ running: !!entry, starting: !!starting, port: entry?.port ?? null, lastUsed: lastUsed || null, starts }),
434
+ workDir,
435
+ };
436
+ }