synabun 2.0.0-beta → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +51 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-compact.mjs +11 -0
- package/hooks/claude-code/prompt-submit.mjs +60 -184
- package/hooks/claude-code/session-start.mjs +73 -16
- package/hooks/claude-code/shared.mjs +20 -0
- package/hooks/claude-code/stop.mjs +5 -5
- package/launcher.mjs +40 -0
- package/lib/claude-hooks.js +61 -8
- package/lib/client-config-repair.js +4 -3
- package/lib/data-home-migration.js +4 -0
- package/lib/desktop-pwa.js +206 -0
- package/lib/external-tools.js +221 -0
- package/lib/first-launch-protection.js +5 -0
- package/lib/packaged-runtime.js +110 -0
- package/lib/start-launcher.js +1396 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/neural-interface/i18n/en.json +1540 -154
- package/neural-interface/i18n/pt-BR.json +1538 -153
- package/neural-interface/lib/assistant-api.js +62 -6
- package/neural-interface/lib/assistant-brains/claude.js +4 -0
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +31 -2
- package/neural-interface/lib/assistant-dispatch.js +62 -13
- package/neural-interface/lib/assistant-envelope.js +9 -6
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +6 -2
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-runtime.js +181 -12
- package/neural-interface/lib/assistant-task-prompt.js +13 -3
- package/neural-interface/lib/assistant-test-density.js +142 -0
- package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
- package/neural-interface/lib/assistant-user-context.js +908 -0
- package/neural-interface/lib/assistant-user-knowledge.js +328 -0
- package/neural-interface/lib/assistant-user-learn.js +404 -0
- package/neural-interface/lib/assistant-user-threads.js +226 -0
- package/neural-interface/lib/assistant-user-writer.js +181 -0
- package/neural-interface/lib/augmented-path.js +23 -3
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-language.js +83 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-agent-bridge.js +87 -153
- package/neural-interface/lib/claude-executable.js +241 -0
- package/neural-interface/lib/claude-model-catalog.js +82 -93
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cloudflared/install.js +533 -0
- package/neural-interface/lib/cloudflared/manager.js +545 -0
- package/neural-interface/lib/cloudflared/pins.js +115 -0
- package/neural-interface/lib/cloudflared/pins.json +63 -0
- package/neural-interface/lib/cloudflared/tunnel.js +265 -0
- package/neural-interface/lib/editor-completion-endpoint.js +264 -0
- package/neural-interface/lib/editor-completion-opencode.js +436 -0
- package/neural-interface/lib/editor-completion.js +714 -0
- package/neural-interface/lib/editor-config.js +149 -0
- package/neural-interface/lib/editor-files-api.js +268 -0
- package/neural-interface/lib/editor-git-api.js +134 -0
- package/neural-interface/lib/file-explorer-api.js +1039 -0
- package/neural-interface/lib/file-explorer-system.js +355 -0
- package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
- package/neural-interface/lib/file-explorer-watch.js +239 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +202 -0
- package/neural-interface/lib/greeting/api.js +89 -0
- package/neural-interface/lib/greeting/budgets.js +90 -0
- package/neural-interface/lib/greeting/engine.js +461 -0
- package/neural-interface/lib/greeting/facts.js +320 -0
- package/neural-interface/lib/greeting/sources.js +252 -0
- package/neural-interface/lib/greeting/writer.js +139 -0
- package/neural-interface/lib/http-guards.js +189 -13
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-binary-runtime.js +69 -337
- package/neural-interface/lib/native-loop-providers.js +40 -49
- package/neural-interface/lib/offline-page.js +54 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +5 -0
- package/neural-interface/lib/synabun-update-plan.js +19 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/package-lock.json +701 -253
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/install-app.html +34 -0
- package/neural-interface/public/manifest.json +2 -0
- package/neural-interface/public/offline.html +402 -66
- package/neural-interface/public/onboarding.html +264 -147
- package/neural-interface/public/shared/agent-runtime-options.js +22 -6
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
- package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
- package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
- package/neural-interface/public/shared/assistant/asst-render.js +219 -67
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +23 -329
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
- package/neural-interface/public/shared/assistant/asst-state.js +185 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
- package/neural-interface/public/shared/command-runner-model.js +30 -0
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +15 -0
- package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
- package/neural-interface/public/shared/editor/editor-ai.js +619 -0
- package/neural-interface/public/shared/editor/editor-commands.js +212 -0
- package/neural-interface/public/shared/editor/editor-compare.js +103 -0
- package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
- package/neural-interface/public/shared/editor/editor-doc.js +257 -0
- package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
- package/neural-interface/public/shared/editor/editor-git.js +95 -0
- package/neural-interface/public/shared/editor/editor-languages.js +156 -0
- package/neural-interface/public/shared/editor/editor-lint.js +229 -0
- package/neural-interface/public/shared/editor/editor-loader.js +51 -0
- package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
- package/neural-interface/public/shared/editor/editor-menu.js +233 -0
- package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
- package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
- package/neural-interface/public/shared/editor/editor-palette.js +125 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
- package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
- package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
- package/neural-interface/public/shared/editor/editor-safety.js +500 -0
- package/neural-interface/public/shared/editor/editor-search.js +352 -0
- package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
- package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
- package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
- package/neural-interface/public/shared/editor/editor-theme.js +244 -0
- package/neural-interface/public/shared/editor/editor-view.js +332 -0
- package/neural-interface/public/shared/explorer/fx-api.js +216 -0
- package/neural-interface/public/shared/explorer/fx-links.js +204 -0
- package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
- package/neural-interface/public/shared/explorer/fx-main.js +865 -0
- package/neural-interface/public/shared/explorer/fx-names.js +87 -0
- package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
- package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
- package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
- package/neural-interface/public/shared/file-links.js +339 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
- package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
- package/neural-interface/public/shared/html-shell.js +169 -170
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +62 -0
- package/neural-interface/public/shared/pwa-install.css +12 -0
- package/neural-interface/public/shared/pwa-install.js +127 -0
- package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
- package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
- package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
- package/neural-interface/public/shared/settings/settings-ia.js +18 -4
- package/neural-interface/public/shared/settings/settings-rules.js +160 -0
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/start-bridge.js +802 -0
- package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
- package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
- package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
- package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
- package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
- package/neural-interface/public/shared/styles.css +2219 -1117
- package/neural-interface/public/shared/synabun-mascot.js +20 -56
- package/neural-interface/public/shared/term-ready.js +307 -0
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +160 -19
- package/neural-interface/public/shared/ui-command-runner.js +251 -468
- package/neural-interface/public/shared/ui-detail.js +15 -0
- package/neural-interface/public/shared/ui-explorer.js +22 -0
- package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
- package/neural-interface/public/shared/ui-file-links.js +103 -0
- package/neural-interface/public/shared/ui-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +367 -12
- package/neural-interface/public/shared/ui-menubar.js +21 -55
- package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
- package/neural-interface/public/shared/ui-navbar.js +45 -21
- package/neural-interface/public/shared/ui-settings.js +491 -547
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sync.js +16 -0
- package/neural-interface/public/shared/ui-terminal.js +153 -149
- package/neural-interface/public/shared/ui-tunnel.js +130 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
- package/neural-interface/public/shared/ui-whiteboard.js +12 -0
- package/neural-interface/public/shared/ui-worker-end.js +245 -0
- package/neural-interface/public/shared/ui-workspaces.js +41 -27
- package/neural-interface/public/shared/worker-end.js +251 -0
- package/neural-interface/public/sw.js +3 -1
- package/neural-interface/public/variant/2d/main.js +2 -1
- package/neural-interface/public/variant/3d/main.js +2 -1
- package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
- package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
- package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
- package/neural-interface/scripts/build-vendor.mjs +78 -2
- package/neural-interface/scripts/rebuild-pty.js +15 -12
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +1092 -1212
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +13 -7
- package/postinstall.js +11 -81
- package/setup.js +111 -6
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/docs/browser-v2.md +0 -109
- package/docs/hook-integrity.md +0 -89
- package/docs/judgments.md +0 -182
- package/docs/style-guide.md +0 -246
- package/neural-interface/lib/codex-capabilities.d.ts +0 -41
- package/neural-interface/public/category-logos/claude.svg +0 -1
- package/neural-interface/public/category-logos/gemini-color.svg +0 -1
- package/neural-interface/public/category-logos/openai.svg +0 -1
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +0 -1
- package/neural-interface/public/gallery.svg +0 -6
- package/neural-interface/public/leonardoai.svg +0 -1
- package/neural-interface/public/mcp.svg +0 -4
- package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
- package/neural-interface/public/pin-svgrepo-com.svg +0 -25
- package/neural-interface/public/sessionicon.svg +0 -8
- package/neural-interface/public/toolbar-album.svg +0 -8
- package/neural-interface/public/toolbar-question.svg +0 -6
- package/neural-interface/public/toolbar-sessions.svg +0 -6
- package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
- package/scripts/audit-claude-hook-usage.mjs +0 -12
- package/scripts/audit-memory-duplicates.mjs +0 -44
- package/scripts/measure-claude-hook-overhead.mjs +0 -77
- package/scripts/recover-user-config.mjs +0 -254
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Start Server bridge (page side)
|
|
3
|
+
// A page cannot ask a server that is down to start itself. It hands the
|
|
4
|
+
// operating system a synabun://start link instead; the launcher registered
|
|
5
|
+
// for that link (lib/start-launcher.js) starts the server, and the page
|
|
6
|
+
// watches for it. DOM-free and dependency-free.
|
|
7
|
+
//
|
|
8
|
+
// The page also cannot ask a stopped server how its start is going. The
|
|
9
|
+
// launcher says so itself, on a loopback beacon (port + 10000) that lives only
|
|
10
|
+
// while a launch does. Everything the page shows about a start comes from one
|
|
11
|
+
// of three things: its own clock, what that beacon reported (each step with
|
|
12
|
+
// the time it happened), and its own probe of the server. Nothing is guessed:
|
|
13
|
+
// no percentages, and no step is named before the launch reported it.
|
|
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
|
+
//
|
|
20
|
+
// offline.html is shown when nothing can be fetched, so the server pastes this
|
|
21
|
+
// file into it (lib/offline-page.js drops the `export` keywords). Keep every
|
|
22
|
+
// export a plain `export function` / `export const` at the start of a line,
|
|
23
|
+
// and never import anything here.
|
|
24
|
+
// ═══════════════════════════════════════════
|
|
25
|
+
|
|
26
|
+
export const START_LINK = 'synabun://start';
|
|
27
|
+
|
|
28
|
+
// What the page remembers from the last time the server answered.
|
|
29
|
+
export const START_STORAGE = {
|
|
30
|
+
projectDir: 'synabun-project-dir',
|
|
31
|
+
install: 'synabun-install-kind', // 'npm' | 'git' | 'app' (a packaged application)
|
|
32
|
+
launcher: 'synabun-start-launcher', // 'registered' | 'stale' | 'missing' | 'skipped'
|
|
33
|
+
entry: 'synabun-start-entry', // a packaged application's entry executable
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const START_LIMITS = {
|
|
37
|
+
pollMs: 300, // while a start is under way: is the server up, what does the launch say
|
|
38
|
+
idlePollMs: 3000, // while nothing was asked for
|
|
39
|
+
hintAfterMs: 4000, // nothing from the launcher yet: say what to look for
|
|
40
|
+
stallAfterMs: 60000, // still nothing from anyone: offer the button and the command again
|
|
41
|
+
quietAfterMs: 15000, // the launch has reported nothing new for this long: say so
|
|
42
|
+
lostAfterMs: 3000, // a launch that was reporting went silent: stop repeating what it said
|
|
43
|
+
idleBeaconChecks: 2, // on load, look for a start already under way (a reload in the middle of one)
|
|
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
|
|
46
|
+
beaconTimeoutMs: 1200,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const START_BEACON_PATH = '/synabun-start-status';
|
|
50
|
+
export const START_BEACON_VERSION = 1;
|
|
51
|
+
|
|
52
|
+
export function isWindowsPlatform(nav) {
|
|
53
|
+
const platform = String(nav?.userAgentData?.platform || nav?.platform || '');
|
|
54
|
+
return /win/i.test(platform) || /Windows/i.test(String(nav?.userAgent || ''));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isMacPlatform(nav) {
|
|
58
|
+
const platform = String(nav?.userAgentData?.platform || nav?.platform || '');
|
|
59
|
+
return /mac/i.test(platform);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* How to hand the link to the OS. Firefox replaces the page with an error when
|
|
64
|
+
* a top-level navigation has no handler, so it gets a hidden frame; every
|
|
65
|
+
* other engine asks the user from the top frame and ignores an unknown link.
|
|
66
|
+
*/
|
|
67
|
+
export function startLinkMode(userAgent) {
|
|
68
|
+
return /\bFirefox\//.test(String(userAgent || '')) ? 'frame' : 'top';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Hand START_LINK to the OS. Call it from the click itself: browsers only ask
|
|
73
|
+
* "open SynaBun?" for a navigation the user started.
|
|
74
|
+
*/
|
|
75
|
+
export function openStartLink({ document: doc, location: loc, userAgent } = {}) {
|
|
76
|
+
if (startLinkMode(userAgent) === 'frame') {
|
|
77
|
+
const frame = doc.createElement('iframe');
|
|
78
|
+
frame.style.display = 'none';
|
|
79
|
+
frame.src = START_LINK;
|
|
80
|
+
doc.body.appendChild(frame);
|
|
81
|
+
const tidy = setTimeout(() => { try { frame.remove(); } catch {} }, 5000);
|
|
82
|
+
if (typeof tidy === 'object') tidy?.unref?.(); // only outside a browser
|
|
83
|
+
return 'frame';
|
|
84
|
+
}
|
|
85
|
+
loc.href = START_LINK;
|
|
86
|
+
return 'top';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A path as one argument, in whatever shell the platform's terminal runs. */
|
|
90
|
+
export function quoteShellPath(path, windows) {
|
|
91
|
+
const value = String(path || '');
|
|
92
|
+
// cmd.exe and PowerShell both take a double-quoted path; a Windows path cannot contain a quote.
|
|
93
|
+
if (windows) return `"${value}"`;
|
|
94
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
95
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* How a packaged application is started by hand: its entry executable with
|
|
100
|
+
* `start`. A path that needs no quotes reads the same in every shell. One that
|
|
101
|
+
* does goes through `cmd /c` on Windows, because PowerShell does not run a
|
|
102
|
+
* quoted path by itself and cmd.exe does not know PowerShell's `&`.
|
|
103
|
+
*/
|
|
104
|
+
export function packagedStartCommand(entry) {
|
|
105
|
+
const value = String(entry || '');
|
|
106
|
+
if (/^[A-Za-z]:[\\/]|^\\\\/.test(value)) {
|
|
107
|
+
const path = value.replace(/\//g, '\\');
|
|
108
|
+
return /^[A-Za-z0-9_.:\\-]+$/.test(path) ? `${path} start` : `cmd /c "${path}" start`;
|
|
109
|
+
}
|
|
110
|
+
return `${quoteShellPath(value, false)} start`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The commands a person can type instead. One line each, no `cd`, no `;` or
|
|
115
|
+
* `&` between commands: `npm --prefix <dir> start` reads the same in cmd.exe,
|
|
116
|
+
* PowerShell, bash, zsh and fish.
|
|
117
|
+
* Returns [{ id: 'npm' | 'git' | 'app', command }] — the one that matches the
|
|
118
|
+
* install when it is known, both npm and git when it is not. A packaged
|
|
119
|
+
* application (`install: 'app'`) is started through its `entry` executable.
|
|
120
|
+
*/
|
|
121
|
+
export function manualStartCommands({ projectDir, install, windows, entry } = {}) {
|
|
122
|
+
if (install === 'app' && entry) return [{ id: 'app', command: packagedStartCommand(entry) }];
|
|
123
|
+
const npm = { id: 'npm', command: 'synabun' };
|
|
124
|
+
const git = projectDir ? { id: 'git', command: `npm --prefix ${quoteShellPath(projectDir, windows)} start` } : null;
|
|
125
|
+
if (install === 'npm') return [npm];
|
|
126
|
+
if (install === 'git' && git) return [git];
|
|
127
|
+
return git ? [npm, git] : [npm];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Whether the button can work: only a registration known to be absent rules it out. */
|
|
131
|
+
export function startButtonUsable(launcherState) {
|
|
132
|
+
return launcherState !== 'missing';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ═══════════════════════════════════════════
|
|
136
|
+
// The launch's own report (the beacon)
|
|
137
|
+
// ═══════════════════════════════════════════
|
|
138
|
+
|
|
139
|
+
/** The beacon's port for a server port: + 10000, − 10000 past the last port. As lib/start-launcher.js `startBeaconPort`. */
|
|
140
|
+
export function startBeaconPort(port) {
|
|
141
|
+
const n = Number(port);
|
|
142
|
+
const p = Number.isInteger(n) && n >= 1 && n <= 65535 ? n : 3344;
|
|
143
|
+
return p + 10000 <= 65535 ? p + 10000 : p - 10000;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Where this page reads the beacon, or null when it cannot: the launcher runs
|
|
148
|
+
* on the machine the server runs on, so only a page opened from that machine's
|
|
149
|
+
* own loopback address (and over plain http, like the beacon) has one to read.
|
|
150
|
+
* Returns { url, port } with `port` the server's.
|
|
151
|
+
*/
|
|
152
|
+
export function startBeaconTarget(loc) {
|
|
153
|
+
const host = String(loc?.hostname || '').toLowerCase();
|
|
154
|
+
if (String(loc?.protocol || '') !== 'http:') return null;
|
|
155
|
+
if (host !== 'localhost' && host !== '127.0.0.1' && host !== '[::1]' && host !== '::1') return null;
|
|
156
|
+
const port = Number(loc?.port) || 80;
|
|
157
|
+
return { port, url: `http://127.0.0.1:${startBeaconPort(port)}${START_BEACON_PATH}` };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const START_LAUNCH_STATES = ['starting', 'started', 'failed', 'gave-up'];
|
|
161
|
+
const START_LAUNCH_STEPS = ['received', 'locked', 'terminal', 'spawned', 'exited'];
|
|
162
|
+
const START_SUPERVISOR_PHASES = ['checking', 'dependencies', 'browser', 'build', 'snapshot', 'server', 'listening', 'failed'];
|
|
163
|
+
|
|
164
|
+
function startCleanError(error) {
|
|
165
|
+
if (!error || typeof error !== 'object') return null;
|
|
166
|
+
const exitCode = Number(error.exitCode);
|
|
167
|
+
return {
|
|
168
|
+
code: String(error.code || '').slice(0, 40),
|
|
169
|
+
message: String(error.message || '').slice(0, 300),
|
|
170
|
+
exitCode: Number.isInteger(exitCode) ? exitCode : null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Read one beacon answer. Returns the launch as this page may show it, or
|
|
176
|
+
* null: not a beacon, another server's, or the end of a launch this page has
|
|
177
|
+
* nothing to do with (a failure still being reported from before the click).
|
|
178
|
+
* A launch that is still under way is this server's start whoever asked.
|
|
179
|
+
* askedAt — when this page handed the link over (null: it did not)
|
|
180
|
+
* following — the launch id this page is already showing
|
|
181
|
+
* past — launch ids this page has moved on from (a retry was clicked)
|
|
182
|
+
*/
|
|
183
|
+
export function readStartBeacon(data, { port, askedAt = null, following = null, past = [] } = {}) {
|
|
184
|
+
if (!data || typeof data !== 'object' || data.synabun !== 'start-beacon' || data.v !== START_BEACON_VERSION) return null;
|
|
185
|
+
if (Number(data.port) !== Number(port)) return null;
|
|
186
|
+
if (!START_LAUNCH_STATES.includes(data.state)) return null;
|
|
187
|
+
const id = String(data.launchId || '');
|
|
188
|
+
const startedAt = Number(data.startedAt) || 0;
|
|
189
|
+
if (!id || !startedAt) return null;
|
|
190
|
+
if (data.state !== 'starting') {
|
|
191
|
+
if (past.includes(id)) return null;
|
|
192
|
+
const mine = id === following || (askedAt !== null && startedAt >= askedAt - 2000);
|
|
193
|
+
if (!mine) return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const steps = [];
|
|
197
|
+
for (const step of Array.isArray(data.steps) ? data.steps : []) {
|
|
198
|
+
const at = Number(step?.at);
|
|
199
|
+
if (START_LAUNCH_STEPS.includes(step?.id) && Number.isFinite(at)) steps.push({ id: step.id, at });
|
|
200
|
+
}
|
|
201
|
+
let supervisor = null;
|
|
202
|
+
const sup = data.supervisor;
|
|
203
|
+
if (sup && typeof sup === 'object' && START_SUPERVISOR_PHASES.includes(sup.phase)) {
|
|
204
|
+
const detail = sup.detail && typeof sup.detail === 'object' ? sup.detail : {};
|
|
205
|
+
const number = (value) => (Number.isFinite(Number(value)) && value !== null ? Number(value) : null);
|
|
206
|
+
supervisor = {
|
|
207
|
+
phase: sup.phase,
|
|
208
|
+
at: Number(sup.at) || 0,
|
|
209
|
+
startedAt: Number(sup.startedAt) || 0,
|
|
210
|
+
phases: (Array.isArray(sup.phases) ? sup.phases : [])
|
|
211
|
+
.filter(p => START_SUPERVISOR_PHASES.includes(p?.phase) && Number.isFinite(Number(p?.at)))
|
|
212
|
+
.map(p => ({ phase: p.phase, at: Number(p.at) })),
|
|
213
|
+
detail: {
|
|
214
|
+
name: typeof detail.name === 'string' ? detail.name.slice(0, 80) : '',
|
|
215
|
+
step: typeof detail.step === 'string' ? detail.step.slice(0, 40) : '',
|
|
216
|
+
files: number(detail.files),
|
|
217
|
+
bytes: number(detail.bytes),
|
|
218
|
+
},
|
|
219
|
+
error: startCleanError(sup.error),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const last = steps[steps.length - 1] || { id: 'received', at: startedAt };
|
|
223
|
+
const newest = Math.max(last.at, supervisor ? supervisor.at : 0, startedAt);
|
|
224
|
+
return {
|
|
225
|
+
id,
|
|
226
|
+
// 'restart': a supervisor bringing its server back, not a launcher starting one.
|
|
227
|
+
reason: data.reason === 'restart' ? 'restart' : 'start',
|
|
228
|
+
state: data.state,
|
|
229
|
+
startedAt,
|
|
230
|
+
step: last.id,
|
|
231
|
+
steps,
|
|
232
|
+
supervisor,
|
|
233
|
+
error: startCleanError(data.error) || (supervisor && supervisor.error) || null,
|
|
234
|
+
log: typeof data.log === 'string' ? data.log.slice(0, 400) : '',
|
|
235
|
+
// On the launcher's own clock: how long since it last saw something happen.
|
|
236
|
+
quietMs: Math.max(0, (Number(data.now) || newest) - newest),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Fetch the beacon once: its JSON, or null when nothing is there to answer. */
|
|
241
|
+
export function fetchStartBeacon(target, { fetchImpl, timeoutMs = START_LIMITS.beaconTimeoutMs } = {}) {
|
|
242
|
+
const doFetch = fetchImpl || (typeof fetch === 'function' ? fetch : null);
|
|
243
|
+
if (!target || !doFetch) return Promise.resolve(null);
|
|
244
|
+
const options = { cache: 'no-store', credentials: 'omit', mode: 'cors' };
|
|
245
|
+
try {
|
|
246
|
+
if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') options.signal = AbortSignal.timeout(timeoutMs);
|
|
247
|
+
} catch {}
|
|
248
|
+
return Promise.resolve()
|
|
249
|
+
.then(() => doFetch(target.url, options))
|
|
250
|
+
.then(res => (res && res.ok ? res.json() : null))
|
|
251
|
+
.catch(() => null);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ═══════════════════════════════════════════
|
|
255
|
+
// The button's life
|
|
256
|
+
// ═══════════════════════════════════════════
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* idle → launching → starting | waiting → stalled | failed, or online at any
|
|
260
|
+
* point. The page keeps probing in every state, so a server started by hand is
|
|
261
|
+
* noticed just the same.
|
|
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`.
|
|
266
|
+
* launch() → hand START_LINK to the OS
|
|
267
|
+
* beacon() → Promise<object|null> the launch's raw report (optional)
|
|
268
|
+
* port → the server's port (what a beacon must be about)
|
|
269
|
+
* onState(state, detail) on every change of state
|
|
270
|
+
* onUpdate(snapshot) whenever what there is to show changed: the
|
|
271
|
+
* state, what the launch reported, or the second
|
|
272
|
+
*
|
|
273
|
+
* States:
|
|
274
|
+
* 'idle' nothing asked
|
|
275
|
+
* 'launching' the link was handed to the OS; nothing has confirmed it
|
|
276
|
+
* 'starting' the launcher itself reports a start under way (evidence)
|
|
277
|
+
* 'waiting' hintAfterMs with no word from the launcher
|
|
278
|
+
* 'stalled' stallAfterMs with no word, or the launcher gave up waiting
|
|
279
|
+
* 'failed' the launcher reports that the start failed, and why
|
|
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.
|
|
288
|
+
*/
|
|
289
|
+
export function createStartBridge({
|
|
290
|
+
probe,
|
|
291
|
+
launch,
|
|
292
|
+
beacon = null,
|
|
293
|
+
port = 0,
|
|
294
|
+
onState = () => {},
|
|
295
|
+
onUpdate = () => {},
|
|
296
|
+
limits = START_LIMITS,
|
|
297
|
+
now = () => Date.now(),
|
|
298
|
+
setTimer = (fn, ms) => setTimeout(fn, ms),
|
|
299
|
+
clearTimer = (id) => clearTimeout(id),
|
|
300
|
+
} = {}) {
|
|
301
|
+
const lim = { ...START_LIMITS, ...limits };
|
|
302
|
+
let state = 'idle';
|
|
303
|
+
let askedAt = null; // when this page handed the link over (null: it has not)
|
|
304
|
+
let since = null; // what the elapsed time counts from
|
|
305
|
+
let clicks = 0;
|
|
306
|
+
let timer = null;
|
|
307
|
+
let stopped = false;
|
|
308
|
+
let probing = false;
|
|
309
|
+
let launchInfo = null; // the launch as last reported
|
|
310
|
+
let launchSeenAt = 0;
|
|
311
|
+
let idleChecks = 0;
|
|
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;
|
|
317
|
+
const past = [];
|
|
318
|
+
|
|
319
|
+
const busy = () => state === 'launching' || state === 'starting' || state === 'waiting' || state === 'restarting';
|
|
320
|
+
|
|
321
|
+
function snapshot() {
|
|
322
|
+
return {
|
|
323
|
+
state,
|
|
324
|
+
clicks,
|
|
325
|
+
askedAt,
|
|
326
|
+
since,
|
|
327
|
+
elapsedMs: since === null ? 0 : Math.max(0, now() - since),
|
|
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,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function update() {
|
|
340
|
+
const snap = snapshot();
|
|
341
|
+
const l = snap.launch;
|
|
342
|
+
const r = snap.restart;
|
|
343
|
+
const key = [
|
|
344
|
+
snap.state, snap.clicks, busy() ? Math.floor(snap.elapsedMs / 1000) : '',
|
|
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('|') : '',
|
|
347
|
+
].join('/');
|
|
348
|
+
if (key === shown) return;
|
|
349
|
+
shown = key;
|
|
350
|
+
try { onUpdate(snap); } catch {}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function set(next, detail = {}) {
|
|
354
|
+
if (state === next) return;
|
|
355
|
+
state = next;
|
|
356
|
+
try { onState(state, { clicks, ...detail }); } catch {}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function schedule(delay) {
|
|
360
|
+
if (stopped || state === 'online' || state === 'refused') return;
|
|
361
|
+
if (timer) clearTimer(timer);
|
|
362
|
+
timer = setTimer(tick, delay ?? (busy() ? lim.pollMs : lim.idlePollMs));
|
|
363
|
+
}
|
|
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
|
+
|
|
414
|
+
/** What the launch says, when there is one to listen to right now. */
|
|
415
|
+
async function listen() {
|
|
416
|
+
if (!beacon) return;
|
|
417
|
+
// A restart: nobody is starting anything while the old server still answers.
|
|
418
|
+
if (restart && restart.downAt === null) return;
|
|
419
|
+
// Idle: only just after load, for a start that was already under way.
|
|
420
|
+
if (state === 'idle' && idleChecks >= lim.idleBeaconChecks) return;
|
|
421
|
+
if (state === 'idle') idleChecks++;
|
|
422
|
+
// Failed: the launcher said how it ended, there is no more to hear. Stalled
|
|
423
|
+
// keeps listening: a prompt answered a minute late still starts a launch.
|
|
424
|
+
if (state === 'failed') return;
|
|
425
|
+
let raw = null;
|
|
426
|
+
try { raw = await beacon(); } catch {}
|
|
427
|
+
if (stopped || state === 'online') return;
|
|
428
|
+
const read = readStartBeacon(raw, { port, askedAt, following: launchInfo?.id || null, past });
|
|
429
|
+
if (read) {
|
|
430
|
+
launchInfo = read;
|
|
431
|
+
launchSeenAt = now();
|
|
432
|
+
if (since === null) since = read.startedAt; // a start this page did not ask for: count from its own beginning
|
|
433
|
+
if (read.state === 'failed') set('failed');
|
|
434
|
+
else if (read.state === 'gave-up') set('stalled');
|
|
435
|
+
else set('starting');
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// It was reporting and stopped: a launcher that ended without this page
|
|
439
|
+
// reading how. What it said last is no longer known to be true.
|
|
440
|
+
if (state === 'starting' && now() - launchSeenAt >= lim.lostAfterMs) {
|
|
441
|
+
launchInfo = null;
|
|
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
|
|
444
|
+
else set(now() - askedAt >= lim.hintAfterMs ? 'waiting' : 'launching');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function tick() {
|
|
449
|
+
timer = null;
|
|
450
|
+
if (stopped || probing) return;
|
|
451
|
+
probing = true;
|
|
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();
|
|
457
|
+
probing = false;
|
|
458
|
+
if (stopped) return;
|
|
459
|
+
if (up) { set('online'); update(); return; }
|
|
460
|
+
if (state === 'refused') { update(); return; }
|
|
461
|
+
if (state === 'launching' || state === 'waiting') {
|
|
462
|
+
const waited = now() - askedAt;
|
|
463
|
+
if (waited >= lim.stallAfterMs) set('stalled');
|
|
464
|
+
else if (waited >= lim.hintAfterMs) set('waiting');
|
|
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');
|
|
468
|
+
update();
|
|
469
|
+
schedule();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
get state() { return state; },
|
|
474
|
+
snapshot,
|
|
475
|
+
/** Begin watching for the server (call once the page is up). */
|
|
476
|
+
watch() { schedule(0); },
|
|
477
|
+
/** The click. Ignored while a start is already under way. */
|
|
478
|
+
start() {
|
|
479
|
+
if (stopped || state === 'online' || state === 'refused' || busy()) return false;
|
|
480
|
+
clicks++;
|
|
481
|
+
askedAt = now();
|
|
482
|
+
since = askedAt;
|
|
483
|
+
// What the last try reported is over: a launcher still saying so is not this one.
|
|
484
|
+
if (launchInfo && !past.includes(launchInfo.id)) past.push(launchInfo.id);
|
|
485
|
+
launchInfo = null;
|
|
486
|
+
restart = null; // a restart that did not bring it back is over: this is a start
|
|
487
|
+
state = 'idle'; // a second try after 'stalled' announces 'launching' again
|
|
488
|
+
set('launching');
|
|
489
|
+
update();
|
|
490
|
+
try { launch(); } catch {}
|
|
491
|
+
schedule();
|
|
492
|
+
return true;
|
|
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
|
+
},
|
|
555
|
+
stop() {
|
|
556
|
+
stopped = true;
|
|
557
|
+
if (timer) clearTimer(timer);
|
|
558
|
+
timer = null;
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ═══════════════════════════════════════════
|
|
564
|
+
// What to show
|
|
565
|
+
// ═══════════════════════════════════════════
|
|
566
|
+
|
|
567
|
+
// The sentences, in English. The offline page uses them as they are; the app
|
|
568
|
+
// reads the same keys from its dictionary (`loading.start.<key>` in
|
|
569
|
+
// i18n/*.json, kept equal to these by tests/start-bridge.test.mjs).
|
|
570
|
+
export const START_COPY = {
|
|
571
|
+
asked: 'Asked your system to start SynaBun. If your browser shows a prompt, choose Open.',
|
|
572
|
+
unconfirmedMac: 'No word from the launcher yet. If your browser is asking for permission, choose Open: a Terminal window then opens with the server.',
|
|
573
|
+
unconfirmedWindows: 'No word from the launcher yet. If your browser is asking for permission, choose Open: a console window then opens with the server.',
|
|
574
|
+
unconfirmedLinux: 'No word from the launcher yet. If your browser is asking for permission, choose Open: the server then starts in the background.',
|
|
575
|
+
stalled: 'Nothing has answered. If no prompt or window appeared, run the command below once: it also repairs this button.',
|
|
576
|
+
launcherRunning: 'The launcher is running.',
|
|
577
|
+
terminalOpened: 'A Terminal window was opened for the server.',
|
|
578
|
+
terminalQuiet: 'A Terminal window was opened {seconds} s ago, but the server has not started in it. Look at that window.',
|
|
579
|
+
processStarted: 'The server\'s supervisor was started.',
|
|
580
|
+
checking: 'Checking the install.',
|
|
581
|
+
dependencies: 'Installing dependencies with npm. A first start, or the first after an update, takes a few minutes.',
|
|
582
|
+
browser: 'Downloading the automation browser. This happens once.',
|
|
583
|
+
build: 'Building the MCP server.',
|
|
584
|
+
snapshot: 'Backing up your data before the first start of this version.',
|
|
585
|
+
snapshotSized: 'Backing up your data before the first start of this version: {files} files, {megabytes} MB.',
|
|
586
|
+
server: 'The server process is running. Waiting for it to answer.',
|
|
587
|
+
listening: 'The server is listening. Connecting.',
|
|
588
|
+
answered: 'The server answered the launcher. Connecting.',
|
|
589
|
+
quiet: 'Nothing new from the start for {seconds} s.',
|
|
590
|
+
failed: 'The start failed: {reason}',
|
|
591
|
+
failedHint: 'Run the command below to see the full output, or try again.',
|
|
592
|
+
failedLog: 'Launcher log: {path}',
|
|
593
|
+
gaveUp: 'The server was started but has not answered. Check its window, or run the command below.',
|
|
594
|
+
online: 'The server is up. Loading SynaBun.',
|
|
595
|
+
trailLauncher: 'Launcher started',
|
|
596
|
+
trailTerminal: 'Terminal opened',
|
|
597
|
+
trailProcess: 'Supervisor started',
|
|
598
|
+
trailSupervisor: 'Supervisor running',
|
|
599
|
+
trailServer: 'Server process started',
|
|
600
|
+
trailListening: 'Server listening',
|
|
601
|
+
elapsedLabel: 'Time since Start was pressed',
|
|
602
|
+
headingStarting: 'Starting the server',
|
|
603
|
+
headingReady: 'Server is up',
|
|
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',
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
/** `{name}` placeholders, like the app's t(). */
|
|
624
|
+
export function formatStartText(template, params) {
|
|
625
|
+
return String(template ?? '').replace(/\{(\w+)\}/g, (all, key) => (params && params[key] != null ? String(params[key]) : all));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** 7 s · 1 min 05 s. Whole seconds: the page's own clock, nothing finer is claimed. */
|
|
629
|
+
export function formatStartElapsed(ms) {
|
|
630
|
+
const total = Math.max(0, Math.floor((Number(ms) || 0) / 1000));
|
|
631
|
+
if (total < 60) return `${total} s`;
|
|
632
|
+
return `${Math.floor(total / 60)} min ${String(total % 60).padStart(2, '0')} s`;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** 0.4 s for a step's own time: these come with milliseconds. */
|
|
636
|
+
function startStepSeconds(ms) {
|
|
637
|
+
const value = Math.max(0, Number(ms) || 0) / 1000;
|
|
638
|
+
return value < 10 ? `${value.toFixed(1)} s` : `${Math.round(value)} s`;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
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
|
|
644
|
+
* mascot 'asleep' | 'waking' | 'working' | 'ready' | 'failed' | 'stalled'
|
|
645
|
+
* busy the button is disabled (a start is under way)
|
|
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
|
|
648
|
+
* headline { key, params } | null the status sentence (the live region)
|
|
649
|
+
* hint { key, params } | null a second, quieter sentence
|
|
650
|
+
* elapsed '12 s' | '' the page's clock since the click
|
|
651
|
+
* elapsedLabel key what that clock counts from
|
|
652
|
+
* trail [{ key, time }] what was reported, with when
|
|
653
|
+
* error { message, log } | null
|
|
654
|
+
* A headline names a step only when the snapshot holds the report of it.
|
|
655
|
+
*/
|
|
656
|
+
export function startStatusView(snap, { platform = 'linux', limits = START_LIMITS } = {}) {
|
|
657
|
+
const state = snap?.state || 'idle';
|
|
658
|
+
const launchOf = snap?.launch || null;
|
|
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';
|
|
663
|
+
const view = {
|
|
664
|
+
state,
|
|
665
|
+
reason: restart || restartReport ? 'restart' : 'start',
|
|
666
|
+
mascot: 'asleep',
|
|
667
|
+
busy: state === 'launching' || state === 'starting' || state === 'waiting' || state === 'restarting',
|
|
668
|
+
button: 'start',
|
|
669
|
+
heading: null,
|
|
670
|
+
headline: null,
|
|
671
|
+
hint: null,
|
|
672
|
+
elapsed: '',
|
|
673
|
+
elapsedLabel: restart || restartReport ? 'restartElapsedLabel' : 'elapsedLabel',
|
|
674
|
+
trail: [],
|
|
675
|
+
error: null,
|
|
676
|
+
};
|
|
677
|
+
if (view.busy) {
|
|
678
|
+
view.button = restart ? 'none' : 'starting';
|
|
679
|
+
view.elapsed = formatStartElapsed(snap.elapsedMs);
|
|
680
|
+
if (view.reason === 'restart') view.heading = 'headingRestarting';
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// The reported steps, each at the time it was reported for, counted from the
|
|
684
|
+
// click (or from the launch's own start when another page asked for it).
|
|
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) {
|
|
697
|
+
const asked = snap.askedAt !== null && snap.askedAt !== undefined && launchOf.startedAt >= snap.askedAt - 2000;
|
|
698
|
+
const zero = asked ? snap.askedAt : launchOf.startedAt;
|
|
699
|
+
const add = (key, at) => { if (at) view.trail.push({ key, time: startStepSeconds(at - zero) }); };
|
|
700
|
+
if (!restartReport) add('trailLauncher', launchOf.startedAt); // a restart has no launcher
|
|
701
|
+
for (const step of launchOf.steps) {
|
|
702
|
+
if (step.id === 'terminal') add('trailTerminal', step.at);
|
|
703
|
+
if (step.id === 'spawned') add('trailProcess', step.at);
|
|
704
|
+
if (step.id === 'exited') add('trailStopped', step.at);
|
|
705
|
+
}
|
|
706
|
+
if (sup) {
|
|
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'));
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (state === 'online') {
|
|
715
|
+
view.mascot = 'ready';
|
|
716
|
+
view.headline = { key: 'online', params: {} };
|
|
717
|
+
return view;
|
|
718
|
+
}
|
|
719
|
+
if (state === 'launching') {
|
|
720
|
+
view.mascot = 'waking';
|
|
721
|
+
view.headline = { key: 'asked', params: {} };
|
|
722
|
+
return view;
|
|
723
|
+
}
|
|
724
|
+
if (state === 'waiting') {
|
|
725
|
+
view.mascot = 'waking';
|
|
726
|
+
view.headline = { key: platform === 'mac' ? 'unconfirmedMac' : platform === 'windows' ? 'unconfirmedWindows' : 'unconfirmedLinux', params: {} };
|
|
727
|
+
return view;
|
|
728
|
+
}
|
|
729
|
+
if (state === 'stalled') {
|
|
730
|
+
view.mascot = 'stalled';
|
|
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
|
+
}
|
|
759
|
+
return view;
|
|
760
|
+
}
|
|
761
|
+
if (state === 'failed') {
|
|
762
|
+
view.mascot = 'failed';
|
|
763
|
+
view.button = 'retry';
|
|
764
|
+
const reason = launchOf?.error?.message || '';
|
|
765
|
+
view.headline = { key: 'failed', params: { reason } };
|
|
766
|
+
view.hint = { key: 'failedHint', params: {} };
|
|
767
|
+
view.error = { message: reason, log: launchOf?.log || '' };
|
|
768
|
+
return view;
|
|
769
|
+
}
|
|
770
|
+
if (state === 'starting' && launchOf) {
|
|
771
|
+
view.mascot = 'working';
|
|
772
|
+
const quiet = Math.floor(launchOf.quietMs / 1000);
|
|
773
|
+
const isQuiet = launchOf.quietMs >= limits.quietAfterMs;
|
|
774
|
+
if (launchOf.state === 'started') {
|
|
775
|
+
view.headline = { key: 'answered', params: {} };
|
|
776
|
+
} else if (sup && sup.phase !== 'failed') {
|
|
777
|
+
const megabytes = sup.detail.bytes !== null ? Math.round(sup.detail.bytes / 1048576) : null;
|
|
778
|
+
if (sup.phase === 'snapshot' && sup.detail.files !== null && megabytes !== null) {
|
|
779
|
+
view.headline = { key: 'snapshotSized', params: { files: sup.detail.files, megabytes } };
|
|
780
|
+
} else {
|
|
781
|
+
view.headline = { key: sup.phase, params: {} };
|
|
782
|
+
}
|
|
783
|
+
// Slow on purpose (an install, a download, a backup): silence there is expected.
|
|
784
|
+
const slow = ['dependencies', 'browser', 'build', 'snapshot'].includes(sup.phase);
|
|
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 } };
|
|
790
|
+
} else if (launchOf.step === 'terminal') {
|
|
791
|
+
view.headline = isQuiet ? { key: 'terminalQuiet', params: { seconds: quiet } } : { key: 'terminalOpened', params: {} };
|
|
792
|
+
} else if (launchOf.step === 'spawned') {
|
|
793
|
+
view.headline = { key: 'processStarted', params: {} };
|
|
794
|
+
if (isQuiet) view.hint = { key: 'quiet', params: { seconds: quiet } };
|
|
795
|
+
} else {
|
|
796
|
+
view.headline = { key: 'launcherRunning', params: {} };
|
|
797
|
+
if (isQuiet) view.hint = { key: 'quiet', params: { seconds: quiet } };
|
|
798
|
+
}
|
|
799
|
+
return view;
|
|
800
|
+
}
|
|
801
|
+
return view;
|
|
802
|
+
}
|