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
@@ -0,0 +1,154 @@
1
+ // ═══════════════════════════════════════════
2
+ // SETTINGS — the Assistant's user context, in words
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // What `GET /api/assistant/user-context` (lib/assistant-api.js, the engine in lib/assistant-user-context.js)
6
+ // says about Settings → Automations → Automation behavior: whether the three switches can be used at all,
7
+ // what each one is set to, what stands in the way of a layer, and whether the digest is in use. Pure: no DOM
8
+ // and no import, so node runs it (tests/settings-user-context-view.test.mjs).
9
+ //
10
+ // readUserContextResponse({ status, body, error }) what the server answered → a reading
11
+ // describeUserContext(reading, { tx }) the section as ui-settings.js draws it
12
+ // describeUserContextSaveFailure(input, { tx }) the sentence for a switch that could not be saved
13
+ //
14
+ // `tx(key, params)` is the caller's translate function over the settings.redesign.* strings (the kit's `tx`).
15
+ //
16
+ // The section never shows a healthy-looking control for something that is not happening: a server without
17
+ // the engine gets disabled rows and says why, a switched-off layer says what stops, and the digest is shown
18
+ // as told to a new conversation only while "Knowledge of you" is on (the engine's preview is built either way).
19
+
20
+ const K = 'settings.redesign.hooks.uc';
21
+
22
+ /** The three layers, in the order the section lists them. */
23
+ export const USER_CONTEXT_LAYERS = Object.freeze(['knowledge', 'nudges', 'trace']);
24
+
25
+ const clean = (value, max = 200) => {
26
+ const text = String(value ?? '').replace(/\s+/g, ' ').trim();
27
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
28
+ };
29
+
30
+ /**
31
+ * What the server answered.
32
+ *
33
+ * ready the engine is there: `payload` is { settings, capabilities, preview }
34
+ * old-server 404: the running server was started before the engine existed (public/ reloads at any
35
+ * time, lib/ only on a restart)
36
+ * failed 503 USER_CONTEXT_UNAVAILABLE: the engine did not start at boot; `reason` is the error
37
+ * unreadable anything else (no answer, a refusal, a body that is not the payload); `reason` says what
38
+ *
39
+ * @param {{ status?: number, body?: object|null, error?: string|null }} response
40
+ * @returns {{ state: 'ready'|'old-server'|'failed'|'unreadable', payload: object|null, reason: string|null }}
41
+ */
42
+ export function readUserContextResponse({ status = 0, body = null, error = null } = {}) {
43
+ if (status === 200 && body?.ok === true && body.settings && typeof body.settings === 'object') return { state: 'ready', payload: body, reason: null };
44
+ if (status === 404) return { state: 'old-server', payload: null, reason: null };
45
+ if (status === 503 && body?.code === 'USER_CONTEXT_UNAVAILABLE') return { state: 'failed', payload: null, reason: clean(body.reason) || null };
46
+ return { state: 'unreadable', payload: null, reason: clean(body?.error || error) || (status ? `HTTP ${status}` : null) };
47
+ }
48
+
49
+ /** The settings as booleans, the way the engine reads them: anything that is not exactly `false` is on. */
50
+ const layersOf = (settings) => Object.fromEntries(USER_CONTEXT_LAYERS.map((key) => [key, settings?.[key] !== false]));
51
+
52
+ // Why learning cannot run → the sentence, and whether Settings → AI decisions is where to fix it.
53
+ const LEARNING_REASONS = {
54
+ 'kill-switch': { key: 'learn.killSwitch', link: false },
55
+ 'master-off': { key: 'learn.masterOff', link: 'jv-sec-master' },
56
+ 'no-key': { key: 'learn.noKey', link: 'jv-sec-connection' },
57
+ 'surface-off': { key: 'learn.surfaceOff', link: 'jv-sec-surfaces' },
58
+ waiting: { key: 'learn.waiting', link: false },
59
+ };
60
+
61
+ /**
62
+ * The section as it is drawn.
63
+ *
64
+ * `note`: one line above the switches when they cannot be used (and why), else null.
65
+ * `layers[key]`: `on` (the stored value; false while it cannot be read), `disabled`, and `notes`: what stands
66
+ * in the way of that layer right now, each `{ text, tone: 'warn'|'info', link }` where `link` is
67
+ * `{ label, target }` (a Settings deep link) or null.
68
+ * `applies`: the line that says when a change takes effect, or ''.
69
+ * `digest`: `shown` (false without the engine), `state` 'in-use' | 'empty' | 'off', the `count` text, the `rows`
70
+ * to list (none while off), the `message` shown in place of rows (or ''), and the `omitted` line (or '').
71
+ *
72
+ * @param {{ state: string, payload: object|null, reason: string|null }} reading
73
+ * @param {{ tx: (key: string, params?: object) => string }} deps
74
+ */
75
+ export function describeUserContext(reading, { tx }) {
76
+ const state = reading?.state || 'unreadable';
77
+ const ready = state === 'ready';
78
+ const page = tx('settings.redesign.page.ai_decisions.title');
79
+ const view = {
80
+ state, ready, note: null, applies: ready ? tx(`${K}.applies`) : '',
81
+ layers: Object.fromEntries(USER_CONTEXT_LAYERS.map((key) => [key, { on: false, disabled: !ready, notes: [] }])),
82
+ digest: { shown: false, state: 'off', count: '', rows: [], message: '', omitted: '' },
83
+ };
84
+
85
+ if (!ready) {
86
+ const reason = clean(reading?.reason);
87
+ if (state === 'old-server') view.note = { text: tx(`${K}.restart`), tone: 'warn' };
88
+ else if (state === 'failed') view.note = { text: reason ? tx(`${K}.failed`, { reason }) : tx(`${K}.failedNoReason`), tone: 'warn' };
89
+ else view.note = { text: reason ? tx(`${K}.unreadable`, { reason }) : tx(`${K}.unreadableNoReason`), tone: 'warn' };
90
+ return view;
91
+ }
92
+
93
+ const { settings, capabilities, preview } = reading.payload;
94
+ const on = layersOf(settings);
95
+ const caps = capabilities && typeof capabilities === 'object' ? capabilities : {};
96
+ for (const key of USER_CONTEXT_LAYERS) view.layers[key].on = on[key];
97
+
98
+ // Without the memory store nothing is read, learned or recorded: both layers that live in it say so.
99
+ const noStore = caps.store === false;
100
+ if (noStore) {
101
+ if (on.knowledge) view.layers.knowledge.notes.push({ text: tx(`${K}.noStore`), tone: 'warn', link: null });
102
+ if (on.nudges) view.layers.nudges.notes.push({ text: tx(`${K}.noStore`), tone: 'warn', link: null });
103
+ }
104
+ // Learning needs Jev on the `user-learning` surface; the digest does not.
105
+ if (on.knowledge && !noStore && caps.jev === false) {
106
+ const known = LEARNING_REASONS[caps.jevReason] || { key: 'learn.unavailable', link: false };
107
+ view.layers.knowledge.notes.push({
108
+ text: tx(`${K}.${known.key}`, { page }), tone: 'warn',
109
+ link: known.link ? { label: tx(`${K}.openPage`, { page }), target: { tab: 'ai-decisions', scrollTo: known.link } } : null,
110
+ });
111
+ }
112
+ // The statement writer: without it (or when its last one-shot failed) a statement stays in the user's own words.
113
+ if (on.knowledge && !noStore && caps.jev !== false) {
114
+ if (caps.writer === false) view.layers.knowledge.notes.push({ text: tx(`${K}.writer.missing`), tone: 'info', link: null });
115
+ else if (caps.writerLast && caps.writerLast.ok === false) {
116
+ const reason = clean(caps.writerLast.reason, 120);
117
+ view.layers.knowledge.notes.push({ text: reason ? tx(`${K}.writer.failed`, { reason }) : tx(`${K}.writer.failedNoReason`), tone: 'info', link: null });
118
+ }
119
+ }
120
+ // The trace is a line about the two layers: with both off the engine emits nothing.
121
+ if (on.trace && !on.knowledge && !on.nudges) view.layers.trace.notes.push({ text: tx(`${K}.trace.nothing`), tone: 'info', link: null });
122
+
123
+ // The digest: what a new conversation is told, which is nothing while the layer is off.
124
+ const rows = Array.isArray(preview?.rows) ? preview.rows.filter((row) => row && row.id && row.statement) : [];
125
+ const omitted = Number(preview?.omitted) > 0 ? Math.round(Number(preview.omitted)) : 0;
126
+ view.digest.shown = true;
127
+ if (!on.knowledge) {
128
+ view.digest.state = 'off';
129
+ view.digest.count = tx('settings.redesign.common.off');
130
+ view.digest.message = tx(`${K}.off.note`);
131
+ } else {
132
+ view.digest.state = rows.length ? 'in-use' : 'empty';
133
+ view.digest.count = tx(`${K}.tokens`, { tokens: Math.max(0, Math.round(Number(preview?.tokens) || 0)), max: Math.round(Number(preview?.maxTokens) || 300) });
134
+ view.digest.rows = rows.map((row) => ({ id: String(row.id), statement: String(row.statement), facet: String(row.facet || ''), seed: row.seed === true }));
135
+ view.digest.message = rows.length ? '' : tx(`${K}.empty`);
136
+ view.digest.omitted = omitted > 0 ? tx(`${K}.omitted`, { n: omitted }) : '';
137
+ }
138
+ return view;
139
+ }
140
+
141
+ /**
142
+ * A switch that could not be saved: it goes back to where it was, and this is the sentence that says so.
143
+ *
144
+ * @param {{ name: string, wanted: boolean, status?: number, body?: object|null, error?: string|null }} input
145
+ * `name` is the switch's label, `wanted` what the click asked for
146
+ * @returns {string}
147
+ */
148
+ export function describeUserContextSaveFailure({ name, wanted, status = 0, body = null, error = null } = {}, { tx }) {
149
+ let reason = clean(body?.error || body?.message, 160);
150
+ if (!reason && status === 404) reason = tx(`${K}.save.oldServer`);
151
+ if (!reason && status) reason = `HTTP ${status}`;
152
+ if (!reason) reason = clean(error, 160) || tx(`${K}.save.noAnswer`);
153
+ return tx(`${K}.save.${wanted ? 'on' : 'off'}`, { name, reason });
154
+ }
@@ -12,6 +12,11 @@
12
12
  // the time it happened), and its own probe of the server. Nothing is guessed:
13
13
  // no percentages, and no step is named before the launch reported it.
14
14
  //
15
+ // A restart the user asked for is shown by the same bridge (`restart()`): the
16
+ // screen is drawn in the click, and what it says comes from the same three
17
+ // things, plus the server's own word that it is shutting down. The supervisor
18
+ // that brings the server back reports on the same beacon (`reason: 'restart'`).
19
+ //
15
20
  // offline.html is shown when nothing can be fetched, so the server pastes this
16
21
  // file into it (lib/offline-page.js drops the `export` keywords). Keep every
17
22
  // export a plain `export function` / `export const` at the start of a line,
@@ -37,6 +42,7 @@ export const START_LIMITS = {
37
42
  lostAfterMs: 3000, // a launch that was reporting went silent: stop repeating what it said
38
43
  idleBeaconChecks: 2, // on load, look for a start already under way (a reload in the middle of one)
39
44
  reloadDelayMs: 150, // the "it is up" frame before the page reloads
45
+ restartGraceMs: 15000, // a restart was asked and the server neither went away nor said it is going: stop waiting
40
46
  beaconTimeoutMs: 1200,
41
47
  };
42
48
 
@@ -152,7 +158,7 @@ export function startBeaconTarget(loc) {
152
158
  }
153
159
 
154
160
  const START_LAUNCH_STATES = ['starting', 'started', 'failed', 'gave-up'];
155
- const START_LAUNCH_STEPS = ['received', 'locked', 'terminal', 'spawned'];
161
+ const START_LAUNCH_STEPS = ['received', 'locked', 'terminal', 'spawned', 'exited'];
156
162
  const START_SUPERVISOR_PHASES = ['checking', 'dependencies', 'browser', 'build', 'snapshot', 'server', 'listening', 'failed'];
157
163
 
158
164
  function startCleanError(error) {
@@ -217,6 +223,8 @@ export function readStartBeacon(data, { port, askedAt = null, following = null,
217
223
  const newest = Math.max(last.at, supervisor ? supervisor.at : 0, startedAt);
218
224
  return {
219
225
  id,
226
+ // 'restart': a supervisor bringing its server back, not a launcher starting one.
227
+ reason: data.reason === 'restart' ? 'restart' : 'start',
220
228
  state: data.state,
221
229
  startedAt,
222
230
  step: last.id,
@@ -251,7 +259,10 @@ export function fetchStartBeacon(target, { fetchImpl, timeoutMs = START_LIMITS.b
251
259
  * idle → launching → starting | waiting → stalled | failed, or online at any
252
260
  * point. The page keeps probing in every state, so a server started by hand is
253
261
  * noticed just the same.
254
- * probe() → Promise<boolean> is the server answering?
262
+ * probe() → Promise<boolean | { up, boot, stopping }> is the server
263
+ * answering? `boot` names the server that answered and
264
+ * `stopping` is its own word that it is shutting down for a
265
+ * restart; a server that says neither is just `true`.
255
266
  * launch() → hand START_LINK to the OS
256
267
  * beacon() → Promise<object|null> the launch's raw report (optional)
257
268
  * port → the server's port (what a beacon must be about)
@@ -267,6 +278,13 @@ export function fetchStartBeacon(target, { fetchImpl, timeoutMs = START_LIMITS.b
267
278
  * 'stalled' stallAfterMs with no word, or the launcher gave up waiting
268
279
  * 'failed' the launcher reports that the start failed, and why
269
280
  * 'online' the server answers this page
281
+ *
282
+ * A restart (`restart()`) adds two:
283
+ * 'restarting' the server was asked to restart, or said it is restarting;
284
+ * it has not come back and no supervisor reports on it yet
285
+ * 'refused' the restart did not happen and the server still answers
286
+ * While one is under way `snapshot().restart` says what is known of it, and
287
+ * 'online' means a server other than the one that was asked answers.
270
288
  */
271
289
  export function createStartBridge({
272
290
  probe,
@@ -292,9 +310,13 @@ export function createStartBridge({
292
310
  let launchSeenAt = 0;
293
311
  let idleChecks = 0;
294
312
  let shown = '';
313
+ // The restart under way, on this page's clock: when it was asked for (or
314
+ // this page was told), when the server said it is restarting, when it first
315
+ // did not answer, and the server that is being replaced.
316
+ let restart = null;
295
317
  const past = [];
296
318
 
297
- const busy = () => state === 'launching' || state === 'starting' || state === 'waiting';
319
+ const busy = () => state === 'launching' || state === 'starting' || state === 'waiting' || state === 'restarting';
298
320
 
299
321
  function snapshot() {
300
322
  return {
@@ -304,15 +326,24 @@ export function createStartBridge({
304
326
  since,
305
327
  elapsedMs: since === null ? 0 : Math.max(0, now() - since),
306
328
  launch: launchInfo,
329
+ restart: restart ? {
330
+ asked: restart.asked,
331
+ askedAt: restart.askedAt,
332
+ acceptedAt: restart.acceptedAt,
333
+ downAt: restart.downAt,
334
+ refused: restart.refused,
335
+ } : null,
307
336
  };
308
337
  }
309
338
 
310
339
  function update() {
311
340
  const snap = snapshot();
312
341
  const l = snap.launch;
342
+ const r = snap.restart;
313
343
  const key = [
314
344
  snap.state, snap.clicks, busy() ? Math.floor(snap.elapsedMs / 1000) : '',
315
345
  l ? [l.id, l.state, l.step, l.supervisor?.phase, l.supervisor?.detail?.step, l.supervisor?.detail?.files, l.quietMs >= lim.quietAfterMs, l.error?.message].join('|') : '',
346
+ r ? [r.acceptedAt !== null, r.downAt !== null, r.refused?.code].join('|') : '',
316
347
  ].join('/');
317
348
  if (key === shown) return;
318
349
  shown = key;
@@ -326,14 +357,65 @@ export function createStartBridge({
326
357
  }
327
358
 
328
359
  function schedule(delay) {
329
- if (stopped || state === 'online') return;
360
+ if (stopped || state === 'online' || state === 'refused') return;
330
361
  if (timer) clearTimer(timer);
331
362
  timer = setTimer(tick, delay ?? (busy() ? lim.pollMs : lim.idlePollMs));
332
363
  }
333
364
 
365
+ /** One probe answer as { up, boot, stopping }; a bare `true` is a server that says nothing about itself. */
366
+ function readProbe(value) {
367
+ if (value === true) return { up: true, boot: '', stopping: false };
368
+ if (value && typeof value === 'object' && value.up === true) {
369
+ return { up: true, boot: typeof value.boot === 'string' ? value.boot : '', stopping: value.stopping === true };
370
+ }
371
+ return { up: false, boot: '', stopping: false };
372
+ }
373
+
374
+ /** The restart did not happen and the server is still there: say why and stop. */
375
+ function refuse(why) {
376
+ restart.refused = why;
377
+ set('refused');
378
+ }
379
+
380
+ /**
381
+ * During a restart an answer is not yet the server being back: the one that
382
+ * was asked keeps answering while it shuts down. True only when the server
383
+ * that answers is another one. A server that names itself (`boot`) settles
384
+ * it; one that does not (an older server) is believed once it was seen gone.
385
+ */
386
+ function judgeRestart(seen) {
387
+ const r = restart;
388
+ if (!seen.up) {
389
+ if (r.downAt === null) r.downAt = now();
390
+ return false;
391
+ }
392
+ if (seen.stopping) {
393
+ // Its own word: it is the one being replaced, and it is on its way out.
394
+ if (r.acceptedAt === null) r.acceptedAt = now();
395
+ if (!r.boot && seen.boot) r.boot = seen.boot;
396
+ r.downAt = null; // an answer it missed was not it stopping
397
+ return false;
398
+ }
399
+ const named = !!r.boot && !!seen.boot;
400
+ if (named && seen.boot !== r.boot) return true;
401
+ if (!named && r.downAt !== null) return true;
402
+ // The same server (or one that cannot be told apart), not shutting down.
403
+ if (named) r.downAt = null;
404
+ if (r.pending) return false; // the request is still on its way
405
+ if (r.rejected) { refuse(r.rejected); return false; }
406
+ if (r.unanswered) { refuse({ code: 'unreachable', status: 0, message: '' }); return false; }
407
+ if (r.acceptedAt !== null && now() - r.acceptedAt >= lim.restartGraceMs) {
408
+ if (named) { refuse({ code: 'still-running', status: 0, message: '' }); return false; }
409
+ return true; // it tells nothing about itself: load it again and see
410
+ }
411
+ return false;
412
+ }
413
+
334
414
  /** What the launch says, when there is one to listen to right now. */
335
415
  async function listen() {
336
416
  if (!beacon) return;
417
+ // A restart: nobody is starting anything while the old server still answers.
418
+ if (restart && restart.downAt === null) return;
337
419
  // Idle: only just after load, for a start that was already under way.
338
420
  if (state === 'idle' && idleChecks >= lim.idleBeaconChecks) return;
339
421
  if (state === 'idle') idleChecks++;
@@ -357,7 +439,8 @@ export function createStartBridge({
357
439
  // reading how. What it said last is no longer known to be true.
358
440
  if (state === 'starting' && now() - launchSeenAt >= lim.lostAfterMs) {
359
441
  launchInfo = null;
360
- if (askedAt === null) { since = null; set('idle'); } // a start this page never asked for: back to watching
442
+ if (restart) set('restarting'); // back to what this page sees for itself
443
+ else if (askedAt === null) { since = null; set('idle'); } // a start this page never asked for: back to watching
361
444
  else set(now() - askedAt >= lim.hintAfterMs ? 'waiting' : 'launching');
362
445
  }
363
446
  }
@@ -366,17 +449,22 @@ export function createStartBridge({
366
449
  timer = null;
367
450
  if (stopped || probing) return;
368
451
  probing = true;
369
- let up = false;
370
- try { up = (await probe()) === true; } catch {}
371
- if (!up && !stopped) await listen();
452
+ let seen = readProbe(false);
453
+ try { seen = readProbe(await probe()); } catch {}
454
+ let up = seen.up && !seen.stopping; // a server on its way out is not one to connect to
455
+ if (restart && !stopped && state !== 'refused') up = judgeRestart(seen);
456
+ if (!up && !stopped && state !== 'refused') await listen();
372
457
  probing = false;
373
458
  if (stopped) return;
374
459
  if (up) { set('online'); update(); return; }
460
+ if (state === 'refused') { update(); return; }
375
461
  if (state === 'launching' || state === 'waiting') {
376
462
  const waited = now() - askedAt;
377
463
  if (waited >= lim.stallAfterMs) set('stalled');
378
464
  else if (waited >= lim.hintAfterMs) set('waiting');
379
465
  }
466
+ // Gone, and nobody reports bringing it back: the button and the command return.
467
+ if (state === 'restarting' && restart.downAt !== null && now() - restart.downAt >= lim.stallAfterMs) set('stalled');
380
468
  update();
381
469
  schedule();
382
470
  }
@@ -388,13 +476,14 @@ export function createStartBridge({
388
476
  watch() { schedule(0); },
389
477
  /** The click. Ignored while a start is already under way. */
390
478
  start() {
391
- if (stopped || state === 'online' || busy()) return false;
479
+ if (stopped || state === 'online' || state === 'refused' || busy()) return false;
392
480
  clicks++;
393
481
  askedAt = now();
394
482
  since = askedAt;
395
483
  // What the last try reported is over: a launcher still saying so is not this one.
396
484
  if (launchInfo && !past.includes(launchInfo.id)) past.push(launchInfo.id);
397
485
  launchInfo = null;
486
+ restart = null; // a restart that did not bring it back is over: this is a start
398
487
  state = 'idle'; // a second try after 'stalled' announces 'launching' again
399
488
  set('launching');
400
489
  update();
@@ -402,6 +491,67 @@ export function createStartBridge({
402
491
  schedule();
403
492
  return true;
404
493
  },
494
+ /**
495
+ * A restart of the server. Call it from the click itself: the first
496
+ * snapshot is handed to onUpdate before anything is asked of the network.
497
+ * request() → Promise<{ ok, boot, status, message }> the asking (this
498
+ * page's own click); leave it out when the page is only told
499
+ * announced → the server itself said it is restarting (`boot`: which one)
500
+ * A request that fails counts as refused only once the server is seen to
501
+ * be still there. Returns false when a restart is already being shown; an
502
+ * announcement then still counts as the server's word.
503
+ */
504
+ restart({ request = null, announced = false, boot = '' } = {}) {
505
+ if (stopped || state === 'online') return false;
506
+ if (restart) {
507
+ if (announced && !restart.refused) {
508
+ if (restart.acceptedAt === null) restart.acceptedAt = now();
509
+ if (!restart.boot && boot) restart.boot = String(boot);
510
+ update();
511
+ }
512
+ return false;
513
+ }
514
+ if (typeof request !== 'function' && !announced) return false;
515
+ askedAt = now();
516
+ since = askedAt;
517
+ if (launchInfo && !past.includes(launchInfo.id)) past.push(launchInfo.id);
518
+ launchInfo = null;
519
+ const mine = restart = {
520
+ asked: typeof request === 'function',
521
+ askedAt,
522
+ acceptedAt: announced ? askedAt : null,
523
+ downAt: null,
524
+ boot: announced && boot ? String(boot) : '',
525
+ pending: typeof request === 'function',
526
+ rejected: null,
527
+ unanswered: false,
528
+ refused: null,
529
+ };
530
+ state = 'idle';
531
+ set('restarting');
532
+ update();
533
+ if (mine.asked) {
534
+ Promise.resolve().then(() => request()).then((answer) => {
535
+ if (stopped || restart !== mine) return;
536
+ mine.pending = false;
537
+ if (answer && answer.ok === true) {
538
+ if (mine.acceptedAt === null) mine.acceptedAt = now();
539
+ if (answer.boot) mine.boot = String(answer.boot);
540
+ } else {
541
+ mine.rejected = { code: 'refused', status: Number(answer?.status) || 0, message: String(answer?.message || '').slice(0, 200) };
542
+ }
543
+ update();
544
+ schedule(0); // look at once: a refusal is only one when the server still answers
545
+ }, () => {
546
+ if (stopped || restart !== mine) return;
547
+ mine.pending = false;
548
+ mine.unanswered = true;
549
+ schedule(0);
550
+ });
551
+ }
552
+ schedule();
553
+ return true;
554
+ },
405
555
  stop() {
406
556
  stopped = true;
407
557
  if (timer) clearTimer(timer);
@@ -452,6 +602,22 @@ export const START_COPY = {
452
602
  headingStarting: 'Starting the server',
453
603
  headingReady: 'Server is up',
454
604
  headingFailed: 'The server did not start',
605
+ // A restart: each sentence is said only once the page holds what it says.
606
+ restartAsked: 'Asked the server to restart.',
607
+ restartStopping: 'The server accepted the restart and is shutting down.',
608
+ restartSlowStop: 'Still shutting down after {seconds} s.',
609
+ restartDown: 'The server stopped answering. Waiting for it to come back.',
610
+ restartRespawning: 'The supervisor is starting the server again.',
611
+ restartStalled: 'The server has not come back. Start it with the button, or run the command below.',
612
+ restartRefused: 'The server did not accept the restart: {reason}',
613
+ restartUnreachable: 'The server did not answer the restart request.',
614
+ restartStillRunning: 'The server accepted the restart but did not shut down.',
615
+ trailAccepted: 'Restart accepted',
616
+ trailNoAnswer: 'Server stopped answering',
617
+ trailStopped: 'Server process exited',
618
+ restartElapsedLabel: 'Time since the restart began',
619
+ headingRestarting: 'Restarting the server',
620
+ headingRestartFailed: 'The server did not restart',
455
621
  };
456
622
 
457
623
  /** `{name}` placeholders, like the app's t(). */
@@ -474,12 +640,15 @@ function startStepSeconds(ms) {
474
640
 
475
641
  /**
476
642
  * Everything a surface renders for one snapshot, as data:
643
+ * reason 'start' | 'restart' what is under way: a start, or a restart of a server that was up
477
644
  * mascot 'asleep' | 'waking' | 'working' | 'ready' | 'failed' | 'stalled'
478
645
  * busy the button is disabled (a start is under way)
479
- * button 'start' | 'starting' | 'retry'
646
+ * button 'start' | 'starting' | 'retry' | 'none' (a restart: nothing to press) | 'back' (it was refused)
647
+ * heading key | null the title when it is not the surface's own for this mascot
480
648
  * headline { key, params } | null the status sentence (the live region)
481
649
  * hint { key, params } | null a second, quieter sentence
482
650
  * elapsed '12 s' | '' the page's clock since the click
651
+ * elapsedLabel key what that clock counts from
483
652
  * trail [{ key, time }] what was reported, with when
484
653
  * error { message, log } | null
485
654
  * A headline names a step only when the snapshot holds the report of it.
@@ -488,38 +657,57 @@ export function startStatusView(snap, { platform = 'linux', limits = START_LIMIT
488
657
  const state = snap?.state || 'idle';
489
658
  const launchOf = snap?.launch || null;
490
659
  const sup = launchOf?.supervisor || null;
660
+ // A restart this page asked for or was told of; or one it only reads the report of.
661
+ const restart = snap?.restart || null;
662
+ const restartReport = launchOf?.reason === 'restart';
491
663
  const view = {
492
664
  state,
665
+ reason: restart || restartReport ? 'restart' : 'start',
493
666
  mascot: 'asleep',
494
- busy: state === 'launching' || state === 'starting' || state === 'waiting',
667
+ busy: state === 'launching' || state === 'starting' || state === 'waiting' || state === 'restarting',
495
668
  button: 'start',
669
+ heading: null,
496
670
  headline: null,
497
671
  hint: null,
498
672
  elapsed: '',
673
+ elapsedLabel: restart || restartReport ? 'restartElapsedLabel' : 'elapsedLabel',
499
674
  trail: [],
500
675
  error: null,
501
676
  };
502
677
  if (view.busy) {
503
- view.button = 'starting';
678
+ view.button = restart ? 'none' : 'starting';
504
679
  view.elapsed = formatStartElapsed(snap.elapsedMs);
680
+ if (view.reason === 'restart') view.heading = 'headingRestarting';
505
681
  }
506
682
 
507
683
  // The reported steps, each at the time it was reported for, counted from the
508
684
  // click (or from the launch's own start when another page asked for it).
509
- if (launchOf) {
685
+ const firstPhase = (phase) => (sup ? sup.phases.find(p => p.phase === phase)?.at || (sup.phase === phase ? sup.at : 0) : 0);
686
+ if (restart) {
687
+ // A restart this page follows from its beginning: what the server said to
688
+ // it, what its own probe saw, then what the supervisor reports.
689
+ const add = (key, at) => { if (at !== null && at !== undefined) view.trail.push({ key, time: startStepSeconds(at - restart.askedAt) }); };
690
+ add('trailAccepted', restart.acceptedAt);
691
+ const exited = launchOf ? launchOf.steps.find(step => step.id === 'exited')?.at : undefined;
692
+ if (exited !== undefined) add('trailStopped', exited);
693
+ else add('trailNoAnswer', restart.downAt);
694
+ add('trailServer', firstPhase('server') || null);
695
+ add('trailListening', firstPhase('listening') || null);
696
+ } else if (launchOf) {
510
697
  const asked = snap.askedAt !== null && snap.askedAt !== undefined && launchOf.startedAt >= snap.askedAt - 2000;
511
698
  const zero = asked ? snap.askedAt : launchOf.startedAt;
512
699
  const add = (key, at) => { if (at) view.trail.push({ key, time: startStepSeconds(at - zero) }); };
513
- add('trailLauncher', launchOf.startedAt);
700
+ if (!restartReport) add('trailLauncher', launchOf.startedAt); // a restart has no launcher
514
701
  for (const step of launchOf.steps) {
515
702
  if (step.id === 'terminal') add('trailTerminal', step.at);
516
703
  if (step.id === 'spawned') add('trailProcess', step.at);
704
+ if (step.id === 'exited') add('trailStopped', step.at);
517
705
  }
518
706
  if (sup) {
519
- add('trailSupervisor', sup.startedAt);
520
- const first = (phase) => sup.phases.find(p => p.phase === phase)?.at || (sup.phase === phase ? sup.at : 0);
521
- add('trailServer', first('server'));
522
- add('trailListening', first('listening'));
707
+ // The supervisor of a restart was usually there long before it: listed only when it began with it.
708
+ if (!restartReport || sup.startedAt >= launchOf.startedAt - 2000) add('trailSupervisor', sup.startedAt);
709
+ add('trailServer', firstPhase('server'));
710
+ add('trailListening', firstPhase('listening'));
523
711
  }
524
712
  }
525
713
 
@@ -540,7 +728,34 @@ export function startStatusView(snap, { platform = 'linux', limits = START_LIMIT
540
728
  }
541
729
  if (state === 'stalled') {
542
730
  view.mascot = 'stalled';
543
- view.headline = launchOf?.state === 'gave-up' ? { key: 'gaveUp', params: {} } : { key: 'stalled', params: {} };
731
+ view.headline = launchOf?.state === 'gave-up' ? { key: 'gaveUp', params: {} }
732
+ : restart ? { key: 'restartStalled', params: {} } : { key: 'stalled', params: {} };
733
+ return view;
734
+ }
735
+ if (state === 'refused') {
736
+ // The server is still there: nothing to start, only the way back to the app.
737
+ const why = restart?.refused || {};
738
+ view.mascot = 'failed';
739
+ view.button = 'back';
740
+ view.heading = 'headingRestartFailed';
741
+ view.headline = why.code === 'unreachable' ? { key: 'restartUnreachable', params: {} }
742
+ : why.code === 'still-running' ? { key: 'restartStillRunning', params: {} }
743
+ : { key: 'restartRefused', params: { reason: why.message || `HTTP ${why.status || 0}` } };
744
+ return view;
745
+ }
746
+ if (state === 'restarting' && restart) {
747
+ if (restart.downAt !== null) {
748
+ view.mascot = 'working';
749
+ view.headline = { key: 'restartDown', params: {} };
750
+ } else if (restart.acceptedAt !== null) {
751
+ view.mascot = 'working';
752
+ view.headline = { key: 'restartStopping', params: {} };
753
+ const stoppingMs = snap.elapsedMs - (restart.acceptedAt - restart.askedAt);
754
+ if (stoppingMs >= limits.quietAfterMs) view.hint = { key: 'restartSlowStop', params: { seconds: Math.floor(stoppingMs / 1000) } };
755
+ } else {
756
+ view.mascot = 'waking';
757
+ view.headline = { key: 'restartAsked', params: {} };
758
+ }
544
759
  return view;
545
760
  }
546
761
  if (state === 'failed') {
@@ -568,6 +783,10 @@ export function startStatusView(snap, { platform = 'linux', limits = START_LIMIT
568
783
  // Slow on purpose (an install, a download, a backup): silence there is expected.
569
784
  const slow = ['dependencies', 'browser', 'build', 'snapshot'].includes(sup.phase);
570
785
  if (isQuiet && !slow) view.hint = { key: 'quiet', params: { seconds: quiet } };
786
+ } else if (restartReport) {
787
+ // The supervisor opened this report to bring the server back; it has named no step yet.
788
+ view.headline = { key: 'restartRespawning', params: {} };
789
+ if (isQuiet) view.hint = { key: 'quiet', params: { seconds: quiet } };
571
790
  } else if (launchOf.step === 'terminal') {
572
791
  view.headline = isQuiet ? { key: 'terminalQuiet', params: { seconds: quiet } } : { key: 'terminalOpened', params: {} };
573
792
  } else if (launchOf.step === 'spawned') {