synabun 2.0.0 → 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 (334) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +46 -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/lib/start-launcher.js +88 -1
  14. package/mcp-server/package-lock.json +2 -2
  15. package/mcp-server/package.json +1 -3
  16. package/neural-interface/i18n/en.json +1500 -153
  17. package/neural-interface/i18n/pt-BR.json +1498 -152
  18. package/neural-interface/lib/assistant-api.js +62 -6
  19. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  20. package/neural-interface/lib/assistant-catalog.js +179 -27
  21. package/neural-interface/lib/assistant-config.js +31 -2
  22. package/neural-interface/lib/assistant-dispatch.js +62 -13
  23. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  24. package/neural-interface/lib/assistant-memory.js +24 -10
  25. package/neural-interface/lib/assistant-persona.js +6 -2
  26. package/neural-interface/lib/assistant-pricing.js +21 -6
  27. package/neural-interface/lib/assistant-runtime.js +181 -12
  28. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  29. package/neural-interface/lib/assistant-test-density.js +142 -0
  30. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  31. package/neural-interface/lib/assistant-user-context.js +908 -0
  32. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  33. package/neural-interface/lib/assistant-user-learn.js +404 -0
  34. package/neural-interface/lib/assistant-user-threads.js +226 -0
  35. package/neural-interface/lib/assistant-user-writer.js +181 -0
  36. package/neural-interface/lib/browser-background-tab.js +134 -0
  37. package/neural-interface/lib/browser-screenshot.js +678 -61
  38. package/neural-interface/lib/claude-agent-bridge.js +33 -6
  39. package/neural-interface/lib/claude-model-catalog.js +30 -2
  40. package/neural-interface/lib/claude-temporary.js +46 -7
  41. package/neural-interface/lib/cloudflared/install.js +533 -0
  42. package/neural-interface/lib/cloudflared/manager.js +545 -0
  43. package/neural-interface/lib/cloudflared/pins.js +115 -0
  44. package/neural-interface/lib/cloudflared/pins.json +63 -0
  45. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  46. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  47. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  48. package/neural-interface/lib/editor-completion.js +714 -0
  49. package/neural-interface/lib/editor-config.js +149 -0
  50. package/neural-interface/lib/editor-files-api.js +268 -0
  51. package/neural-interface/lib/editor-git-api.js +134 -0
  52. package/neural-interface/lib/file-explorer-api.js +1039 -0
  53. package/neural-interface/lib/file-explorer-system.js +355 -0
  54. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  55. package/neural-interface/lib/file-explorer-watch.js +239 -0
  56. package/neural-interface/lib/git-diff-api.js +103 -0
  57. package/neural-interface/lib/git-read.js +202 -0
  58. package/neural-interface/lib/greeting/api.js +89 -0
  59. package/neural-interface/lib/greeting/budgets.js +90 -0
  60. package/neural-interface/lib/greeting/engine.js +461 -0
  61. package/neural-interface/lib/greeting/facts.js +320 -0
  62. package/neural-interface/lib/greeting/sources.js +252 -0
  63. package/neural-interface/lib/greeting/writer.js +139 -0
  64. package/neural-interface/lib/http-guards.js +189 -13
  65. package/neural-interface/lib/machine-names.js +88 -0
  66. package/neural-interface/lib/memory-api.js +31 -7
  67. package/neural-interface/lib/native-loop-providers.js +10 -5
  68. package/neural-interface/lib/plugin-installer.js +4 -2
  69. package/neural-interface/lib/project-storage.js +5 -7
  70. package/neural-interface/lib/request-gate.js +589 -0
  71. package/neural-interface/lib/ui-blob-api.js +108 -0
  72. package/neural-interface/lib/ui-blob-store.js +68 -0
  73. package/neural-interface/package-lock.json +701 -2
  74. package/neural-interface/package.json +13 -2
  75. package/neural-interface/public/install-app.html +9 -6
  76. package/neural-interface/public/offline.html +3 -2
  77. package/neural-interface/public/onboarding.html +12 -1
  78. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  79. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  80. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  81. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  82. package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
  83. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  84. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  85. package/neural-interface/public/shared/assistant/asst-render.js +162 -19
  86. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  87. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  88. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  89. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  90. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  91. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  92. package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
  93. package/neural-interface/public/shared/blob-store.js +111 -22
  94. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  95. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  96. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  97. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  98. package/neural-interface/public/shared/constants.js +6 -1
  99. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  100. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  101. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  102. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  103. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  104. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  105. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  106. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  107. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  108. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  109. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  110. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  111. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  112. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  113. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  114. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  115. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  116. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  117. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  118. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  119. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  120. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  121. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  122. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  123. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  124. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  125. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  126. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  127. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  128. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  129. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  130. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  131. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  132. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  133. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  134. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  135. package/neural-interface/public/shared/file-links.js +339 -0
  136. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  137. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  138. package/neural-interface/public/shared/html-shell.js +160 -165
  139. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  140. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  141. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  142. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  143. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  144. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  145. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  146. package/neural-interface/public/shared/pwa-install-page.js +29 -1
  147. package/neural-interface/public/shared/pwa-install.css +3 -3
  148. package/neural-interface/public/shared/pwa-install.js +100 -1
  149. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  150. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  151. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  152. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  153. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  154. package/neural-interface/public/shared/start-bridge.js +238 -19
  155. package/neural-interface/public/shared/styles.css +1414 -721
  156. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  157. package/neural-interface/public/shared/tunnel-view.js +167 -0
  158. package/neural-interface/public/shared/ui-claude-panel.js +157 -16
  159. package/neural-interface/public/shared/ui-detail.js +15 -0
  160. package/neural-interface/public/shared/ui-explorer.js +22 -0
  161. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  162. package/neural-interface/public/shared/ui-file-links.js +103 -0
  163. package/neural-interface/public/shared/ui-help.js +26 -0
  164. package/neural-interface/public/shared/ui-invite.js +126 -116
  165. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  166. package/neural-interface/public/shared/ui-loading.js +159 -15
  167. package/neural-interface/public/shared/ui-menubar.js +12 -30
  168. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  169. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  170. package/neural-interface/public/shared/ui-navbar.js +45 -21
  171. package/neural-interface/public/shared/ui-settings.js +455 -488
  172. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  173. package/neural-interface/public/shared/ui-sync.js +16 -0
  174. package/neural-interface/public/shared/ui-terminal.js +23 -5
  175. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  176. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  177. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  178. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  179. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  180. package/neural-interface/public/shared/worker-end.js +251 -0
  181. package/neural-interface/public/sw.js +2 -2
  182. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  183. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  184. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  185. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  186. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  187. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  188. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  189. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  190. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  191. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  192. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  193. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  194. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  195. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  196. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  197. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  198. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  199. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  200. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  201. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  202. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  203. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  204. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  205. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  206. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  207. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  208. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  209. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  210. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  211. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  212. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  309. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  310. package/neural-interface/scripts/build-vendor.mjs +78 -2
  311. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  312. package/neural-interface/server.js +873 -1028
  313. package/neural-interface/templates/rulesets/README.md +1 -1
  314. package/package.json +2 -8
  315. package/setup.js +44 -7
  316. package/benchmarks/browser-live-results.json +0 -79
  317. package/benchmarks/browser-v2-results.json +0 -258
  318. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  319. package/neural-interface/public/category-logos/claude.svg +0 -1
  320. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  321. package/neural-interface/public/category-logos/openai.svg +0 -1
  322. package/neural-interface/public/category-logos/synabun.png +0 -0
  323. package/neural-interface/public/claude.svg +0 -1
  324. package/neural-interface/public/gallery.svg +0 -6
  325. package/neural-interface/public/leonardoai.svg +0 -1
  326. package/neural-interface/public/mcp.svg +0 -4
  327. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  328. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  329. package/neural-interface/public/sessionicon.svg +0 -8
  330. package/neural-interface/public/toolbar-album.svg +0 -8
  331. package/neural-interface/public/toolbar-question.svg +0 -6
  332. package/neural-interface/public/toolbar-sessions.svg +0 -6
  333. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  334. package/skills/synabun/synabunicon.png +0 -0
@@ -43,13 +43,17 @@ import { mountChangesBar } from './ocp-v2-changes.js';
43
43
  import { mountEnvironmentPopover } from './ocp-v2-status.js';
44
44
  import { watchWorktree, newSessionPlan, checkoutOutcome } from './ocp-v2-changes-logic.js';
45
45
  import { mountRenderer } from './ocp-v2-render.js';
46
+ import { offerGreeting } from './ocp-v2-greeting.js';
46
47
  import { mountCompose, focusCompose, appendPathToCompose } from './ocp-v2-send.js';
47
48
  import { mountProjectBar, unmountProjectBar } from './ocp-v2-projectbar.js';
48
49
  import { registerSidepanel, syncSidepanelLayout, focusSidepanel, openAnotherSidepanelSession, isHostedSessionFocused } from '../ui-sidepanel-windows.js';
49
50
  import { nativeLoopWindowId } from '../ui-native-window-id.js';
50
51
  import { requestGeneratedSessionTitle } from '../session-title.js';
52
+ import { wireFileLinks } from '../ui-file-links.js';
53
+ import { backToAssistant, buildWorkerEndCard, endWorkerRunOnClose, forgetWorkerEnd, trackWorkerEnd } from '../ui-worker-end.js';
54
+ import { isDispatchedRun } from '../worker-end.js';
51
55
  import {
52
- registerAutomationSession, unregisterAutomationSession, stopAutomationRun,
56
+ registerAutomationSession, unregisterAutomationSession, stopAutomationRun, isAutomationSession,
53
57
  } from './ocp-v2-automation-ownership.js';
54
58
  import {
55
59
  registerPrimaryPanel, bindPrimarySession, isChildPanelOpen, getVisibleChildPanel, openChildSession,
@@ -92,6 +96,8 @@ const _automationRunning = new Set();
92
96
  const _automationContinuedManually = new Set();
93
97
  const _automationManualSendInFlight = new Set();
94
98
  const _foreignActiveAutomationSessions = new Set();
99
+ const _workerEnds = new Map(); // sessionId -> end card of the dispatched worker it belongs to (ui-worker-end.js)
100
+ let _renderer = null;
95
101
  let _visible = false;
96
102
  let _booted = false;
97
103
  let _bootPromise = null;
@@ -420,6 +426,9 @@ function buildPanel() {
420
426
  messages.className = 'ocpv2-messages';
421
427
  messagesContainer.appendChild(messages);
422
428
  panel.appendChild(messagesContainer);
429
+ // A link to a file the code editor can open opens it there (shared with the other panels). A relative
430
+ // path is a path in the session's directory.
431
+ wireFileLinks(messagesContainer, { cwd: () => getState().cwd || '' });
423
432
 
424
433
  // ── What the session changed on disk (session.diff) ─────────────────────
425
434
  mountChangesBar(panel, getDefaultStore());
@@ -445,7 +454,17 @@ function buildPanel() {
445
454
  });
446
455
 
447
456
  let composer = null;
448
- const renderer = mountRenderer(messages, getDefaultStore(), {
457
+ const renderer = _renderer = mountRenderer(messages, getDefaultStore(), {
458
+ // A dispatched worker's end: the last card of its session's transcript.
459
+ endCard: (s) => workerEndCard(s?.sessionId),
460
+ // SynaBun's greeting for a person who comes back to a new session: never for an automation run's.
461
+ onEmpty: (host, s) => offerGreeting(host, {
462
+ sessionId: s?.sessionId || null,
463
+ cwd: s?.cwd || '',
464
+ automation: !!s?.sessionId && (_automationRunIds.has(s.sessionId) || isAutomationSession(s.sessionId)),
465
+ onOpenMemory: (id) => { if (id) emit('memory:open', { id }); },
466
+ isCurrent: () => (getState().sessionId || null) === (s?.sessionId || null) && getState().messageOrder.length === 0,
467
+ }),
449
468
  onOpenSession: (session, extra) => openCreatedSession(session, extra),
450
469
  // Fork is a navigation: its intent begins at the click, and the fork takes
451
470
  // the panel only while that click is still the user's latest choice.
@@ -504,7 +523,18 @@ function buildPanel() {
504
523
  const sessionId = session?.sessionId;
505
524
  if (phase === 'started') requestOpenCodeSessionTitle(session, meta);
506
525
  if (!sessionId || !_automationRunIds.has(sessionId) || _automationRunning.has(sessionId)) return;
507
- if (phase === 'started') _automationManualSendInFlight.add(sessionId);
526
+ if (phase === 'started') {
527
+ const first = !_automationContinuedManually.has(sessionId) && !_automationManualSendInFlight.has(sessionId);
528
+ _automationManualSendInFlight.add(sessionId);
529
+ if (first) {
530
+ // The tab is the user's from here on (Codex's releaseAutomationForManualUse): the run's
531
+ // end card goes, and the router leaves the run alone, so nothing of it comes back here.
532
+ const runId = _automationRunIds.get(sessionId);
533
+ forgetWorkerEnd(runId);
534
+ showWorkerEnd(sessionId, null);
535
+ window.dispatchEvent(new CustomEvent('native-loop:dismissed', { detail: { runId } }));
536
+ }
537
+ }
508
538
  if (phase === 'failed') _automationManualSendInFlight.delete(sessionId);
509
539
  if (phase === 'succeeded') {
510
540
  _automationManualSendInFlight.delete(sessionId);
@@ -2238,11 +2268,12 @@ function dismissAutomationSession(sessionId) {
2238
2268
  if (!runId) return false;
2239
2269
  window.dispatchEvent(new CustomEvent('native-loop:dismissed', { detail: { runId } }));
2240
2270
  if (_automationRunning.has(sessionId)) {
2241
- fetch('/api/loop/stop', {
2271
+ // A worker that finished its task is completed; any other run is stopped.
2272
+ endWorkerRunOnClose(runId, () => fetch('/api/loop/stop', {
2242
2273
  method: 'POST',
2243
2274
  headers: { 'Content-Type': 'application/json' },
2244
2275
  body: JSON.stringify({ runId }),
2245
- }).catch(() => {});
2276
+ }).catch(() => {}));
2246
2277
  }
2247
2278
  return true;
2248
2279
  }
@@ -2317,6 +2348,8 @@ function removeTab(sessionId, { byUser = false } = {}) {
2317
2348
  const titleTimer = _titleReassertTimers.get(sessionId);
2318
2349
  if (titleTimer) clearTimeout(titleTimer);
2319
2350
  _titleReassertTimers.delete(sessionId);
2351
+ forgetWorkerEnd(_automationRunIds.get(sessionId));
2352
+ _workerEnds.delete(sessionId);
2320
2353
  _automationRunIds.delete(sessionId);
2321
2354
  unregisterAutomationSession(sessionId);
2322
2355
  _automationRunning.delete(sessionId);
@@ -2354,22 +2387,56 @@ function createTabPill(sessionId) {
2354
2387
  if (event.target.closest('.term-minimized-pill-close')) return;
2355
2388
  handlePillClick(sessionId);
2356
2389
  });
2357
- pill.querySelector('.term-minimized-pill-close')?.addEventListener('click', async (event) => {
2390
+ pill.querySelector('.term-minimized-pill-close')?.addEventListener('click', (event) => {
2358
2391
  event.stopPropagation();
2359
- // Mirror header X. Native transcripts are preserved unless the user has
2360
- // continued the terminal automation manually; backend cleanup is best-effort.
2361
- // Asked first when prompts are still waiting in that session; declined:
2362
- // the tab stays and nothing is deleted.
2363
- if (!(await mayCloseTab(sessionId))) return;
2364
- const wasRunning = sessionId === getState().sessionId && !!getState().running;
2365
- closeSessionBackend(sessionId, { wasRunning, context: 'pill close' }).catch(() => {});
2366
- try { await handlePillClose(sessionId, { byUser: true }); }
2367
- catch (err) { console.warn('[ocp-v2-panel] local pill close failed', err); }
2392
+ closeTabByUser(sessionId, 'pill close');
2368
2393
  });
2369
2394
  tray.appendChild(pill);
2370
2395
  return pill;
2371
2396
  }
2372
2397
 
2398
+ // Mirror header X. Native transcripts are preserved unless the user has
2399
+ // continued the terminal automation manually; backend cleanup is best-effort.
2400
+ // Asked first when prompts are still waiting in that session; declined: the
2401
+ // tab stays, nothing is deleted, and this resolves false.
2402
+ async function closeTabByUser(sessionId, context) {
2403
+ if (!(await mayCloseTab(sessionId))) return false;
2404
+ const wasRunning = sessionId === getState().sessionId && !!getState().running;
2405
+ closeSessionBackend(sessionId, { wasRunning, context }).catch(() => {});
2406
+ try { await handlePillClose(sessionId, { byUser: true }); }
2407
+ catch (err) { console.warn(`[ocp-v2-panel] local ${context} failed`, err); }
2408
+ return true;
2409
+ }
2410
+
2411
+ // The end of a worker the Assistant dispatched: its button closes the tab the
2412
+ // way the tab's X does (a warm worker that finished its task is completed, one
2413
+ // still at work is stopped; its record stays for the Assistant) and shows the
2414
+ // Assistant session that dispatched it.
2415
+ function workerEndCard(sessionId) {
2416
+ const view = sessionId ? _workerEnds.get(sessionId) : null;
2417
+ if (!view) return null;
2418
+ return {
2419
+ sig: view.sig,
2420
+ build: () => buildWorkerEndCard(view, {
2421
+ classes: {
2422
+ card: 'ocpv2-post-plan-card ocpv2-worker-end', title: 'ocpv2-post-plan-header', detail: 'ocpv2-post-plan-body',
2423
+ meta: 'ocpv2-post-plan-saved', note: 'ocpv2-post-plan-note', actions: 'ocpv2-post-plan-actions', button: 'ocpv2-post-plan-btn primary',
2424
+ },
2425
+ onBack: async () => {
2426
+ if (!(await closeTabByUser(sessionId, 'worker end close'))) return false;
2427
+ backToAssistant(view.assistantSessionId);
2428
+ return true;
2429
+ },
2430
+ }),
2431
+ };
2432
+ }
2433
+
2434
+ function showWorkerEnd(sessionId, view) {
2435
+ if (view) _workerEnds.set(sessionId, view);
2436
+ else if (!_workerEnds.delete(sessionId)) return;
2437
+ _renderer?.render();
2438
+ }
2439
+
2373
2440
  function escapeHtml(s) {
2374
2441
  return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2375
2442
  }
@@ -2539,9 +2606,12 @@ export async function attachOpenCodeAutomation(run, { focus = !!run?.focus, isCa
2539
2606
  // Establish ownership before connect()/boot(): native provider events use a
2540
2607
  // separate socket and may report a Task child while this attach is awaiting.
2541
2608
  const previousRunId = _automationRunIds.get(sessionId);
2609
+ const running = run.status === 'starting' || run.status === 'running';
2610
+ // The user continued this run's session by hand after the run ended: the tab stays theirs.
2611
+ const released = !running && previousRunId === run.runId
2612
+ && (_automationContinuedManually.has(sessionId) || _automationManualSendInFlight.has(sessionId));
2542
2613
  _automationRunIds.set(sessionId, run.runId);
2543
2614
  registerAutomationSession(sessionId, run.runId);
2544
- const running = run.status === 'starting' || run.status === 'running';
2545
2615
  if (running || previousRunId !== run.runId) {
2546
2616
  _automationContinuedManually.delete(sessionId);
2547
2617
  _automationManualSendInFlight.delete(sessionId);
@@ -2565,6 +2635,16 @@ export async function attachOpenCodeAutomation(run, { focus = !!run?.focus, isCa
2565
2635
  await handlePillClose(sessionId).catch(() => {});
2566
2636
  return { ok: false, reason: 'dismissed' };
2567
2637
  }
2638
+ if (released) {
2639
+ // The dock's Focus raises it; the run is let go again, and nothing in the tab changes.
2640
+ window.dispatchEvent(new CustomEvent('native-loop:dismissed', { detail: { runId: run.runId } }));
2641
+ if (focus && _nav.latest() === attachBegan && _tabSessionIds.includes(sessionId)) {
2642
+ const nav = _nav.observe();
2643
+ if (!_visible) await showPrimaryOpencodePanel();
2644
+ await switchToSession(sessionId, _tabInfoCache.get(sessionId) || null, { nav });
2645
+ }
2646
+ return { ok: true, sessionId, released: true };
2647
+ }
2568
2648
  ensureTab(sessionId, info);
2569
2649
  if (focus && _nav.latest() === attachBegan) {
2570
2650
  // (Its own navigation, observed after boot landed: a click made while the
@@ -2577,13 +2657,15 @@ export async function attachOpenCodeAutomation(run, { focus = !!run?.focus, isCa
2577
2657
  await rehydrateActiveSession().catch(() => {});
2578
2658
  }
2579
2659
  if (getState().sessionId === sessionId) setRunning(running);
2580
- if (run.status === 'failed' && run.error && getState().sessionId === sessionId
2660
+ // (A dispatched worker's failure, with its reason, is on its end card.)
2661
+ if (run.status === 'failed' && run.error && !isDispatchedRun(run) && getState().sessionId === sessionId
2581
2662
  && _tabInfoCache.get(sessionId)?._automationLifecycleError !== run.error) {
2582
2663
  const cachedInfo = _tabInfoCache.get(sessionId) || info;
2583
2664
  cachedInfo._automationLifecycleError = run.error;
2584
2665
  _tabInfoCache.set(sessionId, _stoppedShares.clean(cachedInfo));
2585
2666
  pushError({ message: run.error });
2586
2667
  }
2668
+ trackWorkerEnd(run, { apply: (view) => showWorkerEnd(sessionId, view) });
2587
2669
  renderPill();
2588
2670
  return { ok: true, sessionId };
2589
2671
  }
@@ -212,9 +212,13 @@ function render(_container, store, opts = {}) {
212
212
  );
213
213
  const questions = requestsVisibleIn(s.pendingQuestions, s.sessionId, isKnownDescendantSession);
214
214
  const retry = retryBannerView(s.sessionStatus);
215
- const hasInteractiveCards = permissions.length > 0 || questions.length > 0 || !!s.showPostPlanActions;
215
+ // The panel's last card for the session on screen (a dispatched worker's end): { sig, build } or null.
216
+ const endCard = typeof opts.endCard === 'function' ? opts.endCard(s) : null;
217
+ const hasInteractiveCards = permissions.length > 0 || questions.length > 0 || !!s.showPostPlanActions || !!endCard;
216
218
  if (s.messageOrder.length === 0 && !hasInteractiveCards && !s.errors.length && !retry && !revertBanner) {
217
219
  _reconcile(_container, [{ key: '__empty__', sig: 'empty', build: _buildEmpty }]);
220
+ // The panel may put SynaBun's greeting in it (ocp-v2-greeting.js).
221
+ try { opts.onEmpty?.(_container.querySelector(':scope > .ocpv2-empty'), s); } catch { /* the empty state stands on its own */ }
218
222
  return;
219
223
  }
220
224
 
@@ -346,6 +350,8 @@ function render(_container, store, opts = {}) {
346
350
  });
347
351
  }
348
352
 
353
+ if (endCard) desired.push({ key: 'end', sig: `end:${endCard.sig}`, build: endCard.build });
354
+
349
355
  _reconcile(_container, desired);
350
356
  }
351
357
 
@@ -33,6 +33,7 @@ import {
33
33
  } from './ocp-v2-send-logic.js';
34
34
  import { hydrateTranscript } from './ocp-v2-rehydrate.js';
35
35
  import { captureBinding, trackBinding, isBindingToken, searchOnBinding } from './ocp-v2-binding.js';
36
+ import { takeGreetingId } from './ocp-v2-greeting.js';
36
37
 
37
38
  const STOR_MODE = 'opencode-v2-mode'; // the last agent picked (build / plan / a custom one)
38
39
  const STOR_HISTORY = 'ocp-v2-prompt-history';
@@ -1449,6 +1450,10 @@ export function mountCompose(rootEl, store = getDefaultStore(), opts = {}) {
1449
1450
  variant: turn.variant || undefined,
1450
1451
  cwd: options.cwd || turn.cwd || undefined,
1451
1452
  mcpProfile: turn.mcpProfile || undefined,
1453
+ // A person's own message (the greeting engine keeps when they were last here), and the
1454
+ // greeting this session opened with, told to the model once.
1455
+ human: true,
1456
+ greetingId: takeGreetingId(turnSessionId) || undefined,
1452
1457
  });
1453
1458
  if (res?.error) return failSend(shouldSuppressAbortError(res.error) ? '' : res.error);
1454
1459
  // Delivered: that stands, whatever the panel shows by now.
@@ -1097,6 +1097,46 @@ const CSS = `
1097
1097
  cursor: default;
1098
1098
  }
1099
1099
 
1100
+ /* A dispatched worker's end (ui-worker-end.js): the post-plan card's frame. Complete keeps its
1101
+ green; the other ends take the panel's own amber, error red or neutral. The title says the state. */
1102
+ .ocpv2-worker-end .ocpv2-post-plan-body { overflow-wrap: anywhere; }
1103
+ .ocpv2-worker-end .ocpv2-post-plan-btn:focus-visible { outline: 2px solid rgba(255,255,255,0.7); outline-offset: 2px; }
1104
+ .ocpv2-worker-end[data-tone="warn"] {
1105
+ border-color: rgba(232,180,80,0.32);
1106
+ background: rgba(232,180,80,0.04);
1107
+ box-shadow: 0 12px 32px rgba(0,0,0,0.22), 0 0 0 1px rgba(232,180,80,0.08) inset;
1108
+ }
1109
+ .ocpv2-worker-end[data-tone="warn"] .ocpv2-post-plan-header {
1110
+ color: rgba(255,200,120,0.95);
1111
+ background: rgba(232,180,80,0.14);
1112
+ border-color: rgba(232,180,80,0.28);
1113
+ }
1114
+ .ocpv2-worker-end[data-tone="err"] {
1115
+ border-color: rgba(220,100,100,0.32);
1116
+ background: rgba(220,100,100,0.05);
1117
+ box-shadow: 0 12px 32px rgba(0,0,0,0.22), 0 0 0 1px rgba(220,100,100,0.08) inset;
1118
+ }
1119
+ .ocpv2-worker-end[data-tone="err"] .ocpv2-post-plan-header {
1120
+ color: rgba(255,166,166,0.92);
1121
+ background: rgba(220,100,100,0.12);
1122
+ border-color: rgba(220,100,100,0.28);
1123
+ }
1124
+ .ocpv2-worker-end[data-tone="off"] {
1125
+ border-color: rgba(255,255,255,0.12);
1126
+ background: rgba(255,255,255,0.03);
1127
+ box-shadow: 0 12px 32px rgba(0,0,0,0.22);
1128
+ }
1129
+ .ocpv2-worker-end[data-tone="off"] .ocpv2-post-plan-header {
1130
+ color: rgba(255,255,255,0.7);
1131
+ background: rgba(255,255,255,0.06);
1132
+ border-color: rgba(255,255,255,0.12);
1133
+ }
1134
+ .ocpv2-worker-end:not([data-tone="ok"]) .ocpv2-post-plan-saved { color: rgba(255,255,255,0.45); }
1135
+ .ocpv2-worker-end:not([data-tone="ok"]) .ocpv2-post-plan-btn.primary {
1136
+ background: rgba(255,255,255,0.085);
1137
+ border-color: rgba(255,255,255,0.2);
1138
+ }
1139
+
1100
1140
  /* Stub shown in place of raw question-tool JSON while the AskUser card handles it */
1101
1141
  .ocpv2-part-question-stub {
1102
1142
  font-size: 10.5px;
@@ -486,8 +486,9 @@ export const api = {
486
486
  sessionDelete: (sessionId) => request({ type: 'session:delete', sessionId }),
487
487
  sessionMessages:(sessionId) => request({ type: 'session:messages', sessionId }),
488
488
  sessionContext: ({ sessionId, cwd } = {}) => request({ type: 'session:context', sessionId, cwd }),
489
- send: ({ sessionId, parts, model, agent, mode, variant, cwd, mcpProfile, noAbortPrev }) =>
490
- request({ type: 'message:send', sessionId, parts, model, agent, mode, variant, cwd, mcpProfile, noAbortPrev: !!noAbortPrev }, 30 * 60 * 1000),
489
+ // `human` / `greetingId`: lib/greeting/ (a server that does not know them ignores them).
490
+ send: ({ sessionId, parts, model, agent, mode, variant, cwd, mcpProfile, noAbortPrev, human, greetingId }) =>
491
+ request({ type: 'message:send', sessionId, parts, model, agent, mode, variant, cwd, mcpProfile, noAbortPrev: !!noAbortPrev, ...(human ? { human: true } : {}), ...(greetingId ? { greetingId } : {}) }, 30 * 60 * 1000),
491
492
  abort: (sessionId) => request({ type: 'message:abort', sessionId }),
492
493
  compact: ({ sessionId, cwd, mcpProfile, model } = {}) => request({ type: 'session:compact', sessionId, cwd, mcpProfile, model }, 5 * 60 * 1000),
493
494
  permissionReply: ({ sessionId, permissionId, response, cwd, message }) =>
@@ -1,4 +1,16 @@
1
- import { initPwaInstall, isStandalone, requestPwaInstall } from './pwa-install.js';
1
+ import { canPromptPwaInstall, initPwaInstall, installGuideMode, installGuideRequested, isStandalone,
2
+ leavePwaInstallHandoff, requestPwaInstall } from './pwa-install.js';
3
+
4
+ // The wizard (first run) and the main UI (setup done) open the browser's dialog from a press the user makes
5
+ // anyway, so a browser that has one shows this page only when Apps > Install as App asked for it. true, false,
6
+ // or null for a read that did not answer: only an answer sends the user on, null keeps the guide.
7
+ async function setupComplete() {
8
+ try {
9
+ const res = await fetch('/api/setup/onboarding', { signal: AbortSignal.timeout(3000) });
10
+ const { setupComplete: done } = res.ok ? await res.json() : {};
11
+ return typeof done === 'boolean' ? done : null;
12
+ } catch { return null; }
13
+ }
2
14
 
3
15
  if (isStandalone()) location.replace('/');
4
16
  else {
@@ -8,6 +20,7 @@ else {
8
20
  'install-intro': 'O servidor está rodando em segundo plano. Confirme a instalação uma vez no navegador para abrir o SynaBun em sua própria janela.',
9
21
  'install-chromium': 'Chrome ou Edge: escolha Instalar como app abaixo ou a opção Instalar app no menu do navegador.',
10
22
  'install-safari': 'Safari no macOS: escolha Arquivo → Adicionar ao Dock. Mantenha o nome SynaBun.',
23
+ 'install-other': 'O SynaBun é instalado como app pelo Chrome, pelo Edge ou pelo Safari no macOS. Abra-o em um deles para instalar.',
11
24
  'install-later': 'Após instalar, abra novamente o iniciador nativo do SynaBun. Ele inicia ou reutiliza o servidor e abre o PWA instalado. Fechar a janela mantém o servidor ativo. Use Apps → Stop Server para pará-lo.',
12
25
  'install-button': 'Instalar como app', 'install-continue': 'Continuar para o SynaBun',
13
26
  } : {};
@@ -17,6 +30,7 @@ else {
17
30
  const instructions = pt
18
31
  ? 'Use a opção Instalar app do Chrome/Edge ou Arquivo → Adicionar ao Dock no Safari. A instalação exige sua confirmação no navegador.'
19
32
  : 'Use Chrome/Edge’s Install app menu or Safari’s File → Add to Dock. Installation requires your browser confirmation.';
33
+ // Before the read below: the event can arrive while it is on its way.
20
34
  initPwaInstall({ changed: value => {
21
35
  status.textContent = value === 'installed'
22
36
  ? (pt ? 'Instalado. Abra novamente o iniciador do SynaBun para usar o PWA.' : 'Installed. Reopen the SynaBun launcher to use the PWA.')
@@ -31,4 +45,18 @@ else {
31
45
  } catch { status.textContent = instructions; }
32
46
  });
33
47
  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js').catch(() => {});
48
+
49
+ // One plain action or one plain instruction, never both: the button only where the browser has a dialog to open.
50
+ const canPrompt = canPromptPwaInstall();
51
+ const show = done => {
52
+ let mode = installGuideMode({ canPrompt, setupComplete: done, requested: installGuideRequested(location.search), vendor: navigator.vendor });
53
+ // The main UI opens the dialog only for the mark this leaves: where it cannot be left, the button stays.
54
+ if (mode === 'handoff' && !leavePwaInstallHandoff()) mode = 'button';
55
+ if (mode === 'wizard' || mode === 'handoff') return location.replace('/');
56
+ document.getElementById(mode === 'button' ? 'install-chromium' : `install-${mode}`).hidden = false;
57
+ document.getElementById('install-button').hidden = mode !== 'button';
58
+ document.querySelector('main').hidden = false;
59
+ };
60
+ if (canPrompt) setupComplete().then(show);
61
+ else show(null);
34
62
  }
@@ -2,9 +2,9 @@
2
2
  #pwa-install { display: grid; place-items: center; overflow: auto; padding: 24px; color: var(--t-bright); }
3
3
  #pwa-install main { width: min(560px, 100%); padding: 24px; background: var(--s-panel); border: 1px solid var(--b-light); border-radius: var(--r-card); }
4
4
  #pwa-install h1 { font-family: var(--ff-heading); font-size: calc(28px * var(--font-scale)); font-weight: 600; line-height: 1.2; margin: 16px 0; }
5
- #pwa-install p, #pwa-install li { font-size: var(--fs-sm); line-height: 1.5; }
6
- #pwa-install ol { padding-left: 24px; margin: 16px 0; }
7
- #pwa-install li + li { margin-top: 8px; }
5
+ #pwa-install [hidden] { display: none !important; }
6
+ #pwa-install p { font-size: var(--fs-sm); line-height: 1.5; }
7
+ #pwa-install p + p { margin-top: 16px; }
8
8
  #pwa-install .actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; margin-top: 24px; }
9
9
  #pwa-install button { background: var(--s-active); border: 1px solid var(--b-visible); border-radius: var(--r-small); color: var(--t-bright); font: inherit; padding: 8px 12px; cursor: pointer; }
10
10
  #pwa-install button:hover { background: var(--s-hover); }
@@ -1,9 +1,14 @@
1
- /** Shared by the existing Install as App menu and the native launcher's first-run page. */
1
+ /** Shared by the Install as App menu, the first-run wizard, the native launcher's guide page and the main UI it hands over to. */
2
2
  export function isStandalone(win = window, nav = navigator) {
3
3
  return win.matchMedia('(display-mode: standalone)').matches
4
4
  || win.matchMedia('(display-mode: window-controls-overlay)').matches || !!nav.standalone;
5
5
  }
6
6
 
7
+ /** Chromium only: Safari and Firefox have no install event, so there is nothing a page can open there. */
8
+ export function canPromptPwaInstall(win = window) {
9
+ return 'onbeforeinstallprompt' in win;
10
+ }
11
+
7
12
  export function initPwaInstall({ win = window, nav = navigator, changed = () => {} } = {}) {
8
13
  win.addEventListener('beforeinstallprompt', event => {
9
14
  event.preventDefault();
@@ -26,3 +31,97 @@ export async function requestPwaInstall(win = window) {
26
31
  win._pwaInstallPrompt = null; // Browser install events are single-use, including dismissal.
27
32
  return outcome;
28
33
  }
34
+
35
+ const OFFERED_KEY = 'synabun-pwa-install-offered';
36
+
37
+ function wasOffered(win) {
38
+ if (win._pwaInstallOffered) return true;
39
+ try { return win.sessionStorage.getItem(OFFERED_KEY) === '1'; } catch { return false; }
40
+ }
41
+
42
+ /**
43
+ * The first-run wizard asks nothing itself: it opens the browser's own install dialog once, from a click the
44
+ * user makes anyway. Call this synchronously in that click, before any await: prompt() needs the click's user
45
+ * activation and uses it up, so never call it from a click that opens a popup, the clipboard or a file picker.
46
+ * Nobody waits for the answer. Without an event it does nothing and the next click tries again; once the dialog
47
+ * has opened, this tab never opens it again (sessionStorage: a reload of the wizard does not ask twice).
48
+ */
49
+ export function offerPwaInstallOnce({ win = window, nav = navigator } = {}) {
50
+ const prompt = win._pwaInstallPrompt;
51
+ if (!prompt || isStandalone(win, nav) || wasOffered(win)) return false;
52
+ if (nav.userActivation && !nav.userActivation.isActive) return false; // A scripted click: keep the event for a real one.
53
+ win._pwaInstallPrompt = null; // Single-use, whatever the user answers.
54
+ try { Promise.resolve(prompt.prompt()).catch(() => {}); } catch { return false; }
55
+ win._pwaInstallOffered = true;
56
+ try { win.sessionStorage.setItem(OFFERED_KEY, '1'); } catch {}
57
+ return true;
58
+ }
59
+
60
+ const HANDOFF_KEY = 'synabun-pwa-install-handoff';
61
+
62
+ /**
63
+ * The guide page's half of the handoff: it leaves a mark for the main UI and goes there instead of asking.
64
+ * sessionStorage, so the mark is this tab's alone. False when it could not be left: the guide stays then.
65
+ */
66
+ export function leavePwaInstallHandoff(win = window) {
67
+ try { win.sessionStorage.setItem(HANDOFF_KEY, '1'); return true; } catch { return false; }
68
+ }
69
+
70
+ // A press that follows a link, or belongs to a shortcut (reload, new tab, DevTools), would spend the one dialog
71
+ // on a page that is leaving or busy. The next press gets it.
72
+ function keepsItsPress(event) {
73
+ return !!(event.ctrlKey || event.metaKey || event.altKey) || /^F\d+$/.test(event.key || '')
74
+ || !!event.target?.closest?.('a[href]');
75
+ }
76
+
77
+ /**
78
+ * The main UI's half. It takes the mark, so only the one page load the guide handed over is armed: not a
79
+ * reload, and never a normal tab. Armed, the first click or key press anywhere opens the browser's dialog
80
+ * through offerPwaInstallOnce(), in the capture phase because prompt() must run inside the press and the UI
81
+ * stops many of its events. Without an event yet the next press tries. `leave` names presses to keep out of
82
+ * it (the menu item that opens the dialog itself). Returns the function that disarms it.
83
+ */
84
+ export function armPwaInstallHandoff({ win = window, nav = navigator, leave = () => false } = {}) {
85
+ let marked = false;
86
+ try {
87
+ marked = win.sessionStorage.getItem(HANDOFF_KEY) === '1';
88
+ win.sessionStorage.removeItem(HANDOFF_KEY);
89
+ } catch {}
90
+ if (!marked || isStandalone(win, nav) || wasOffered(win)) return () => {};
91
+ const disarm = () => ['click', 'keydown'].forEach(type => win.removeEventListener(type, press, true));
92
+ const press = event => {
93
+ if (keepsItsPress(event) || leave(event)) return;
94
+ if (offerPwaInstallOnce({ win, nav })) disarm();
95
+ };
96
+ ['click', 'keydown'].forEach(type => win.addEventListener(type, press, true));
97
+ return disarm;
98
+ }
99
+
100
+ const GUIDE_URL = '/install-app.html';
101
+
102
+ /**
103
+ * Where Apps > Install as App goes when the browser has no dialog ready. The mark says the user asked for the
104
+ * page, so it shows instead of handing over. Safari can add the page it is on to the Dock and the launcher
105
+ * matches that URL without a query, so only a browser with the install event gets the mark.
106
+ */
107
+ export function installGuideUrl(win = window) {
108
+ return canPromptPwaInstall(win) ? `${GUIDE_URL}?from=menu` : GUIDE_URL;
109
+ }
110
+
111
+ export function installGuideRequested(search = '') {
112
+ return new URLSearchParams(search).get('from') === 'menu';
113
+ }
114
+
115
+ /**
116
+ * What the launcher's guide page is, for this browser and this install:
117
+ * 'app' (already the installed app: leave), 'wizard' (first run where the wizard opens the dialog: leave),
118
+ * 'handoff' (setup done and nobody asked for the page: leave, the main UI opens the dialog),
119
+ * 'button' (one button that opens the browser's dialog: the page was asked for, or the setup read did not
120
+ * answer), 'safari' or 'other' (plain instructions).
121
+ */
122
+ export function installGuideMode({ standalone = false, canPrompt = false, setupComplete = null, requested = false, vendor = '' } = {}) {
123
+ if (standalone) return 'app';
124
+ if (!canPrompt) return /apple/i.test(vendor) ? 'safari' : 'other';
125
+ if (setupComplete === false) return 'wizard';
126
+ return setupComplete === true && !requested ? 'handoff' : 'button';
127
+ }