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
|
@@ -30,6 +30,8 @@ export const BUDGET_ENFORCEMENT = Object.freeze({
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
const OWNER_ONLY = { error: 'The assistant is available to the owner only', code: 'GUEST_FORBIDDEN' };
|
|
33
|
+
// What GET /catalog is made from when no catalog builder is wired: one object, so its answer is reused like any build's.
|
|
34
|
+
const NO_CATALOG = Object.freeze({});
|
|
33
35
|
|
|
34
36
|
function sendError(res, error, fallbackStatus = 500) {
|
|
35
37
|
if (error instanceof DispatchError || (error && error.code && error.status)) {
|
|
@@ -119,6 +121,11 @@ export function createAssistantApi({
|
|
|
119
121
|
// Remote sessions (lib/remote-policy.js): the session's policy, or null. Defaults
|
|
120
122
|
// to the runtime's view, else the process-wide registry.
|
|
121
123
|
sessionPolicy = null,
|
|
124
|
+
// The user-context engine (assistant-user-context.js) and the project a new conversation starts in.
|
|
125
|
+
userContext = null,
|
|
126
|
+
// Why the engine did not start on this server (a string or null), for the settings page.
|
|
127
|
+
userContextError = () => null,
|
|
128
|
+
defaultProject = () => 'global',
|
|
122
129
|
} = {}) {
|
|
123
130
|
if (!dispatcher) throw new Error('createAssistantApi requires a dispatcher');
|
|
124
131
|
const router = Router();
|
|
@@ -268,22 +275,26 @@ export function createAssistantApi({
|
|
|
268
275
|
// ── catalog ────────────────────────────────────────────────────────────────
|
|
269
276
|
router.get('/catalog', async (req, res) => {
|
|
270
277
|
try {
|
|
271
|
-
|
|
278
|
+
// refresh=1 rebuilds from the providers' endpoints; deep=1 also makes them ask the CLI / app-server again.
|
|
279
|
+
const deep = req.query.deep === '1';
|
|
280
|
+
const force = req.query.refresh === '1' || deep;
|
|
272
281
|
// Brain view / search: compact rows (OpenCode can list hundreds of models).
|
|
273
282
|
const view = req.query.view === 'brain' ? 'brain' : req.query.view === 'manage' ? 'manage' : 'full';
|
|
274
283
|
const provider = req.query.provider ? String(req.query.provider) : null;
|
|
275
284
|
const q = req.query.q || req.query.search ? String(req.query.q || req.query.search) : null;
|
|
276
285
|
// The Models manager: every row with hidden: true|false.
|
|
277
|
-
if (view === 'manage' && catalog?.get) return res.json({ ok: true, ...(await catalog.get({ force, view: 'manage', provider })) });
|
|
286
|
+
if (view === 'manage' && catalog?.get) return res.json({ ok: true, ...(await catalog.get({ force, deep, view: 'manage', provider })) });
|
|
278
287
|
if (catalog?.get && (view === 'brain' || provider || q)) {
|
|
279
288
|
let preferences = {};
|
|
280
289
|
try { preferences = configStore?.routing?.().preferences || {}; } catch {}
|
|
281
|
-
const brief = await catalog.get({ force, view: 'brain', provider, q, preferences });
|
|
290
|
+
const brief = await catalog.get({ force, deep, view: 'brain', provider, q, preferences });
|
|
282
291
|
return res.json({ ok: true, generatedAt: new Date(now()).toISOString(), ...brief, limits: dispatcher.limits, runs: dispatcher.list({ activeOnly: true }), totals: dispatcher.totals() });
|
|
283
292
|
}
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
293
|
+
// The catalog says how long one of its builds is good for (one without a verified list is redone
|
|
294
|
+
// within seconds), so it is asked every time: this answer is reused only while it hands back the
|
|
295
|
+
// build the answer was made from (a rebuild or invalidate() gives another one).
|
|
296
|
+
const base = typeof buildCatalog === 'function' ? await buildCatalog({ force, deep }) : NO_CATALOG;
|
|
297
|
+
if (!force && catalogCache && catalogCache.base === base && now() - catalogCache.at < 30_000) return res.json(catalogCache.value);
|
|
287
298
|
// Hidden rows stay server-side (view=manage lists them); the lists themselves ship.
|
|
288
299
|
const { hiddenRows: _hiddenRows, ...visible } = base || {};
|
|
289
300
|
const value = {
|
|
@@ -470,6 +481,41 @@ export function createAssistantApi({
|
|
|
470
481
|
bounds: { minUsd: BUDGET_MIN_USD, maxUsd: BUDGET_MAX_USD }, session, pricing: pricingStatus, enforcement: BUDGET_ENFORCEMENT,
|
|
471
482
|
};
|
|
472
483
|
}
|
|
484
|
+
// ── User context (Settings → Automation behavior): the three layers and what a new conversation is told ──
|
|
485
|
+
function userContextPayload(req) {
|
|
486
|
+
let fallback = 'global';
|
|
487
|
+
try { fallback = String(defaultProject() || 'global'); } catch { fallback = 'global'; }
|
|
488
|
+
const project = String(req.query?.project || '').trim().slice(0, 200) || fallback;
|
|
489
|
+
return { ok: true, settings: configStore.userContext(), capabilities: userContext.capabilities(), preview: userContext.preview({ project }) };
|
|
490
|
+
}
|
|
491
|
+
const userContextReady = (res) => {
|
|
492
|
+
if (userContext && configStore?.userContext) return true;
|
|
493
|
+
let reason = null;
|
|
494
|
+
try { reason = typeof userContextError === 'function' ? userContextError() : userContextError; } catch { reason = null; }
|
|
495
|
+
res.status(503).json({ ok: false, code: 'USER_CONTEXT_UNAVAILABLE', error: 'User context is not available on this server', reason: reason ? String(reason).slice(0, 300) : null });
|
|
496
|
+
return false;
|
|
497
|
+
};
|
|
498
|
+
router.get('/user-context', (req, res) => {
|
|
499
|
+
if (!userContextReady(res)) return;
|
|
500
|
+
try { res.json(userContextPayload(req)); } catch (error) { sendError(res, error); }
|
|
501
|
+
});
|
|
502
|
+
router.put('/user-context', (req, res) => {
|
|
503
|
+
if (!userContextReady(res)) return;
|
|
504
|
+
if (!configStore.patchUserContext) return res.status(503).json({ ok: false, code: 'CONFIG_UNAVAILABLE', error: 'User context settings are read-only' });
|
|
505
|
+
if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'User context settings are changed from Settings only.' });
|
|
506
|
+
try {
|
|
507
|
+
const body = req.body || {};
|
|
508
|
+
const { project: _project, ...patch } = body.settings && typeof body.settings === 'object' ? body.settings : body;
|
|
509
|
+
configStore.patchUserContext(patch);
|
|
510
|
+
// Read live by the engine: the next turn (and the next brain start) follows it.
|
|
511
|
+
broadcastSync({ type: 'assistant:user-context-changed', settings: configStore.userContext() });
|
|
512
|
+
res.json(userContextPayload(req));
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (error?.code === 'USER_CONTEXT_INVALID') return res.status(400).json({ ok: false, code: error.code, error: error.message, message: error.message, field: error.field || null });
|
|
515
|
+
sendError(res, error, 400);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
473
519
|
router.get('/budget', (req, res) => {
|
|
474
520
|
if (!configStore?.budget) return res.status(503).json({ ok: false, code: 'CONFIG_UNAVAILABLE', error: 'Budget settings are not available' });
|
|
475
521
|
try { res.json(budgetPayload(req.query.sessionId || null)); } catch (error) { sendError(res, error); }
|
|
@@ -779,6 +825,12 @@ export function createAssistantApi({
|
|
|
779
825
|
router.post('/runs/:runId/complete', orchestrationGuard, (req, res) => {
|
|
780
826
|
try { res.json(dispatcher.complete(req.params.runId)); } catch (error) { sendError(res, error); }
|
|
781
827
|
});
|
|
828
|
+
// The user closed the sidepanel tab of a worker that finished its task: completed, never "stopped
|
|
829
|
+
// by the user" (409 RUN_NOT_FINISHED for any other run, which the tab then stops as before).
|
|
830
|
+
router.post('/runs/:runId/tab-closed', (req, res) => {
|
|
831
|
+
if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the sidepanel that closed the tab reports it.' });
|
|
832
|
+
try { res.json(dispatcher.completeClosedTab(req.params.runId)); } catch (error) { sendError(res, error); }
|
|
833
|
+
});
|
|
782
834
|
router.post('/runs/:runId/focus', (req, res) => {
|
|
783
835
|
const run = dispatcher.get(req.params.runId);
|
|
784
836
|
if (!run) return res.status(404).json({ ok: false, code: 'RUN_NOT_FOUND', error: `Unknown run ${req.params.runId}` });
|
|
@@ -835,6 +887,10 @@ export function createAssistantApi({
|
|
|
835
887
|
if (req.body && 'computerUse' in req.body && (req.get('x-synabun-terminal') || req.get('x-synabun-desktop-grant'))) {
|
|
836
888
|
return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the user can switch computer use on or off.' });
|
|
837
889
|
}
|
|
890
|
+
// Test density is the user's setting too: an agent cannot raise or lower it.
|
|
891
|
+
if (req.body && 'testDensity' in req.body && (req.get('x-synabun-terminal') || req.get('x-synabun-desktop-grant'))) {
|
|
892
|
+
return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the user can change the Test density.' });
|
|
893
|
+
}
|
|
838
894
|
try {
|
|
839
895
|
const session = await runtime.updateSession(req.params.id, req.body || {});
|
|
840
896
|
if (!session) return res.status(404).json({ ok: false, code: 'SESSION_NOT_FOUND', error: `Unknown assistant session ${req.params.id}` });
|
|
@@ -125,9 +125,13 @@ export function createClaudeBrain({ session, sink, deps = {}, persona = '', hook
|
|
|
125
125
|
// prompt-submit.mjs steps aside (it injected every memory twice and paid Jev
|
|
126
126
|
// twice) and stop.mjs skips the claim check (a relayed worker "tests pass"
|
|
127
127
|
// has its evidence in the worker's turn, not here). Hooks read it from env.
|
|
128
|
+
// SYNABUN_ASSISTANT_THREADS: the user-context engine keeps this conversation's
|
|
129
|
+
// thread record (its nudges layer is on), so pre-compact.mjs asks for no
|
|
130
|
+
// conversation memory of its own.
|
|
128
131
|
env: {
|
|
129
132
|
...(typeof deps.claudeAccountEnv === 'function' ? deps.claudeAccountEnv(brain.accountId) : {}),
|
|
130
133
|
SYNABUN_ASSISTANT_SESSION: String(session.id),
|
|
134
|
+
...(deps.userContextThreads === true ? { SYNABUN_ASSISTANT_THREADS: '1' } : {}),
|
|
131
135
|
},
|
|
132
136
|
maxBudgetUsd: Number(deps.maxBudgetUsd) > 0 ? Number(deps.maxBudgetUsd) : undefined,
|
|
133
137
|
});
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
// planner) and the router can pick cheap-first, send image work to a model that
|
|
16
16
|
// can see, and image / video creation to a model that makes them. Everything
|
|
17
17
|
// I/O is injected; parsers are pure.
|
|
18
|
+
//
|
|
19
|
+
// Which models exist is never decided here. Each provider's rows are the list
|
|
20
|
+
// its own model selector reads (/api/claude/models, /api/codex/models,
|
|
21
|
+
// /api/opencode/providers/full); the static tables (prices, tier hints, the 1M
|
|
22
|
+
// rule) only describe a row. A model the provider lists and no table knows is
|
|
23
|
+
// offered with what is unknown left unknown; a model only a table knows is
|
|
24
|
+
// never offered. `sources` says how each provider's list was obtained.
|
|
18
25
|
|
|
19
26
|
import { sortEfforts, variantNames } from './effort-levels.js';
|
|
20
27
|
import { listPrice } from './assistant-pricing.js';
|
|
@@ -82,6 +89,43 @@ function resolvedKey(row) {
|
|
|
82
89
|
return `${baseModelId(row.upstream)}${contextSuffix(row.id)}`.toLowerCase();
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
// ── moving aliases ───────────────────────────────────────────────────────────
|
|
93
|
+
// "opus", "haiku", "default" name whatever model Claude Code points them at, and
|
|
94
|
+
// that changes with a CLI update. A hidden alias is pinned to the model it named
|
|
95
|
+
// when it was hidden (lib/assistant-hidden-models.js); these say what a row names.
|
|
96
|
+
|
|
97
|
+
/** A model id without its snapshot date ("claude-haiku-4-5-20251001", "…@20251001"), lower-case: what makes two ids one model. */
|
|
98
|
+
export function modelIdentity(id) {
|
|
99
|
+
return baseModelId(id).trim().toLowerCase().replace(/[-@]\d{8}$/, '');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* What a moving alias row names right now: { model, variant } ("opus[1m]" →
|
|
104
|
+
* { model: 'claude-opus-5-5', variant: '1m' }), lower-case. null for a row that
|
|
105
|
+
* is its own model (a concrete id, every Codex row), for OpenCode (its
|
|
106
|
+
* `upstream` is the provider, not a model) and whenever the provider did not
|
|
107
|
+
* name a versioned model behind the alias (the static stand-in list, an alias
|
|
108
|
+
* resolving to another alias): nothing is guessed.
|
|
109
|
+
*/
|
|
110
|
+
export function aliasTargetOf(row) {
|
|
111
|
+
if (!row?.id || row.provider === 'opencode' || String(row.id).includes('/')) return null;
|
|
112
|
+
const model = baseModelId(row.upstream || '').trim().toLowerCase();
|
|
113
|
+
if (!model || !/\d/.test(model) || modelIdentity(model) === modelIdentity(row.id)) return null;
|
|
114
|
+
return { model, variant: contextVariantOf(row.id) };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The id of the model a pin or an alias target names, context included ("claude-opus-5-5[1m]"), lower-case. */
|
|
118
|
+
export function pinnedModelId(pin) {
|
|
119
|
+
const model = baseModelId(pin?.model || '').trim().toLowerCase();
|
|
120
|
+
return model ? `${model}${pin.variant ? `[${String(pin.variant).toLowerCase()}]` : ''}` : '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Do a pin and an alias target name the same model at the same context window? (A new snapshot date is the same model.) */
|
|
124
|
+
export function sameModelPin(a, b) {
|
|
125
|
+
return !!a && !!b && !!modelIdentity(a.model) && modelIdentity(a.model) === modelIdentity(b.model)
|
|
126
|
+
&& String(a.variant || '').toLowerCase() === String(b.variant || '').toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
|
|
85
129
|
/**
|
|
86
130
|
* Every Claude row that can run at 1M gets a "[1m]" sibling right after it,
|
|
87
131
|
* unless the CLI already lists one: the CLI accepts "<id>[1m]" for those
|
|
@@ -305,6 +349,9 @@ export function rowIsHidden(catalog, row) {
|
|
|
305
349
|
* "opus[1m]"). Context variants hide independently: hiding "opus" leaves
|
|
306
350
|
* "opus[1m]" alone and the other way round; same for "gpt-x" / "gpt-x[extended]".
|
|
307
351
|
* OpenCode: the full "provider/model" id, or a bare model id.
|
|
352
|
+
* An entry with a pin record (catalog.hiddenPins: an alias hidden as the model
|
|
353
|
+
* it named, or that model's own id once the alias moved on) hides its own row
|
|
354
|
+
* only: it never reaches another alias through the resolved model.
|
|
308
355
|
*/
|
|
309
356
|
export function hiddenModelId(catalog, provider, id, { row = null } = {}) {
|
|
310
357
|
if (!provider || !id) return null;
|
|
@@ -322,7 +369,8 @@ export function hiddenModelId(catalog, provider, id, { row = null } = {}) {
|
|
|
322
369
|
.find((r) => String(r?.id || '').toLowerCase() === want);
|
|
323
370
|
const resolved = resolvedKey(own);
|
|
324
371
|
if (!resolved || resolved === want) return null;
|
|
325
|
-
|
|
372
|
+
const pins = catalog?.hiddenPins?.[provider] || null;
|
|
373
|
+
return hidden.find((h) => String(h).toLowerCase() === resolved && !pins?.[resolved]) || null;
|
|
326
374
|
}
|
|
327
375
|
|
|
328
376
|
/**
|
|
@@ -442,7 +490,9 @@ function ctxText(ctx) {
|
|
|
442
490
|
|
|
443
491
|
/** One routing-sheet line: `id · tier · $in/$out · vision · image-out · video-out · ctx · efforts`. */
|
|
444
492
|
export function formatModelLine(row) {
|
|
445
|
-
|
|
493
|
+
// Only Codex bills a plan; any other row without a price is a model no table prices yet.
|
|
494
|
+
const planBilled = row.provider === 'codex' || !!row.listPrice;
|
|
495
|
+
const parts = [row.id, row.tier || '?', row.price || planBilled ? priceText(row.price, row.listPrice) : 'price unknown'];
|
|
446
496
|
if (row.vision) parts.push('vision');
|
|
447
497
|
if (rowMakes(row, 'image')) parts.push('image-out');
|
|
448
498
|
if (rowMakes(row, 'video')) parts.push('video-out');
|
|
@@ -549,6 +599,12 @@ export function compactRow(row) {
|
|
|
549
599
|
* @param {()=>object} [deps.readFilter] → { opencode:[globs] } (routing.catalogFilter)
|
|
550
600
|
* @param {()=>object} [deps.readHidden] → { 'claude-code': [], codex: [], opencode: [] } ids the user
|
|
551
601
|
* hid (Assistant → Models; OpenCode's list is Settings → OpenCode's). A bare array = OpenCode ids.
|
|
602
|
+
* @param {(args:{provider:string, rows:object[], source:string})=>{ids:string[], pins:object}|null} [deps.reconcileHidden]
|
|
603
|
+
* settles a provider's hidden list against the rows just listed (hidden aliases are pinned to a model:
|
|
604
|
+
* lib/assistant-hidden-models.js `reconcile`); its ids and pins replace readHidden's for that build
|
|
605
|
+
* @param {(change:{providers:string[], generatedAt:string})=>void} [deps.onChange] a build listed other
|
|
606
|
+
* models than the build before it (hiding has its own event and does not count)
|
|
607
|
+
* @param {number} [deps.retryMs] how soon a build is redone while Claude Code or Codex gave no verified list
|
|
552
608
|
*/
|
|
553
609
|
export function createAssistantCatalog({
|
|
554
610
|
fetchJson,
|
|
@@ -557,24 +613,53 @@ export function createAssistantCatalog({
|
|
|
557
613
|
listAccounts = () => ({}),
|
|
558
614
|
readFilter = () => ({}),
|
|
559
615
|
readHidden = () => [],
|
|
616
|
+
reconcileHidden = null,
|
|
617
|
+
onChange = null,
|
|
560
618
|
ttlMs = 30_000,
|
|
619
|
+
retryMs = 5_000,
|
|
561
620
|
now = Date.now,
|
|
562
621
|
log = () => {},
|
|
563
622
|
} = {}) {
|
|
564
623
|
if (typeof fetchJson !== 'function') throw new Error('createAssistantCatalog requires fetchJson');
|
|
565
624
|
let cache = null;
|
|
566
625
|
let inflight = null;
|
|
626
|
+
// The last verified list Claude Code and Codex gave: what their rows are built
|
|
627
|
+
// from while the provider does not answer, or answers with its static stand-in.
|
|
628
|
+
const lastLive = {};
|
|
629
|
+
let degradedBuilds = 0;
|
|
630
|
+
let epoch = 0; // bumped by invalidate(): a build that began before it is not kept
|
|
631
|
+
let lastListed = null; // provider → signature of the rows the last build listed
|
|
632
|
+
|
|
633
|
+
const pickArray = (value, keys = []) => {
|
|
634
|
+
if (Array.isArray(value)) return value;
|
|
635
|
+
for (const key of keys) if (Array.isArray(value?.[key])) return value[key];
|
|
636
|
+
return [];
|
|
637
|
+
};
|
|
567
638
|
|
|
568
|
-
|
|
639
|
+
/**
|
|
640
|
+
* One provider's answer → the list to build rows from and how it was obtained:
|
|
641
|
+
* 'live' (the provider answered), 'stale' (it did not: its last verified list),
|
|
642
|
+
* 'fallback' (never verified: the endpoint's static stand-in), 'unavailable'.
|
|
643
|
+
*/
|
|
644
|
+
function settle(provider, payload) {
|
|
645
|
+
const list = pickArray(payload, ['models']);
|
|
646
|
+
const verified = !!payload && payload.ok !== false && payload.source !== 'fallback' && list.length > 0;
|
|
647
|
+
if (verified) { lastLive[provider] = list; return { list, state: 'live' }; }
|
|
648
|
+
if (lastLive[provider]) return { list: lastLive[provider], state: 'stale' };
|
|
649
|
+
return list.length ? { list, state: 'fallback' } : { list: [], state: 'unavailable' };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function listedSignature(rows) {
|
|
653
|
+
return JSON.stringify(arr(rows).map((row) => [row.id, row.label, row.upstream || '', row.status || '', row.contextWindow || 0, arr(row.efforts).join(',')]));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function build({ deep = false } = {}) {
|
|
657
|
+
// `deep` also drops the providers' own discovery caches (a CLI or app-server round trip).
|
|
658
|
+
const fresh = deep ? '?refresh=1' : '';
|
|
569
659
|
const [claudeModels, codexModels, opencodeProviders, projects, mcpProfile] = await Promise.all([
|
|
570
|
-
fetchJson(
|
|
660
|
+
fetchJson(`/api/claude/models${fresh}`), fetchJson(`/api/codex/models${fresh}`), fetchJson('/api/opencode/providers/full'),
|
|
571
661
|
fetchJson('/api/projects'), fetchJson('/api/mcp/profile'),
|
|
572
662
|
]);
|
|
573
|
-
const pickArray = (value, keys = []) => {
|
|
574
|
-
if (Array.isArray(value)) return value;
|
|
575
|
-
for (const key of keys) if (Array.isArray(value?.[key])) return value[key];
|
|
576
|
-
return [];
|
|
577
|
-
};
|
|
578
663
|
const filter = (() => { try { return readFilter() || {}; } catch { return {}; } })();
|
|
579
664
|
const presets = mcpProfile?.presets && typeof mcpProfile.presets === 'object'
|
|
580
665
|
? Object.entries(mcpProfile.presets).map(([name, preset]) => ({ name, label: preset?.label || name, groups: preset?.groups || [], tools: preset?.tools || null }))
|
|
@@ -590,12 +675,34 @@ export function createAssistantCatalog({
|
|
|
590
675
|
if (Array.isArray(raw)) raw = { opencode: raw };
|
|
591
676
|
return Object.fromEntries(CATALOG_PROVIDERS.map((p) => [p, arr(raw[p]).map(String)]));
|
|
592
677
|
})();
|
|
678
|
+
const claude = settle('claude-code', claudeModels);
|
|
679
|
+
const codex = settle('codex', codexModels);
|
|
680
|
+
// OpenCode is read from its serve on every build; while it is down its models are unknown, not remembered.
|
|
681
|
+
const sources = {
|
|
682
|
+
'claude-code': claude.state,
|
|
683
|
+
codex: codex.state,
|
|
684
|
+
opencode: opencodeProviders && opencodeProviders.ok !== false ? 'live' : 'unavailable',
|
|
685
|
+
};
|
|
686
|
+
for (const p of ['claude-code', 'codex']) if (sources[p] !== 'live') log('catalog:source', `${p}: ${sources[p]}`);
|
|
593
687
|
const all = {
|
|
594
|
-
'claude-code': withClaudeContextVariants(normalizeClaudeRows(
|
|
595
|
-
codex: normalizeCodexRows(
|
|
688
|
+
'claude-code': withClaudeContextVariants(normalizeClaudeRows(claude.list, { pricing: claudePricing })),
|
|
689
|
+
codex: normalizeCodexRows(codex.list, { priceOf: codexPrice }),
|
|
596
690
|
opencode: parseOpenCodeProviders(opencodeProviders, { filter: filter.opencode || null }),
|
|
597
691
|
};
|
|
598
|
-
|
|
692
|
+
// A hidden alias hides the model it named when it was hidden. With the rows at hand the
|
|
693
|
+
// store settles that: an alias that moved on is shown again and its old model stays hidden.
|
|
694
|
+
const hiddenPins = Object.fromEntries(CATALOG_PROVIDERS.map((p) => [p, {}]));
|
|
695
|
+
if (typeof reconcileHidden === 'function') {
|
|
696
|
+
for (const p of CATALOG_PROVIDERS) {
|
|
697
|
+
try {
|
|
698
|
+
const settled = reconcileHidden({ provider: p, rows: all[p], source: sources[p] });
|
|
699
|
+
if (!settled || !Array.isArray(settled.ids)) continue;
|
|
700
|
+
hidden[p] = settled.ids.map(String);
|
|
701
|
+
hiddenPins[p] = settled.pins && typeof settled.pins === 'object' ? settled.pins : {};
|
|
702
|
+
} catch (error) { log('catalog:hidden-pins-error', `${p}: ${error?.message || String(error)}`); }
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const lookup = { hidden, hiddenPins };
|
|
599
706
|
const models = {};
|
|
600
707
|
const hiddenRows = {};
|
|
601
708
|
const listedCounts = {};
|
|
@@ -606,11 +713,20 @@ export function createAssistantCatalog({
|
|
|
606
713
|
for (const row of all[p]) (hidden[p].length && hiddenModelId(lookup, p, row.id, { row }) ? hiddenRows[p] : models[p]).push(row);
|
|
607
714
|
}
|
|
608
715
|
const opencode = models.opencode;
|
|
716
|
+
const listedNow = Object.fromEntries(CATALOG_PROVIDERS.map((p) => [p, listedSignature(all[p])]));
|
|
717
|
+
const changed = lastListed ? CATALOG_PROVIDERS.filter((p) => listedNow[p] !== lastListed[p]) : [];
|
|
718
|
+
lastListed = listedNow;
|
|
609
719
|
return {
|
|
610
720
|
generatedAt: new Date(now()).toISOString(),
|
|
721
|
+
// Providers whose listed models differ from the build before (not part of the value's meaning: full() reports it).
|
|
722
|
+
changedProviders: changed,
|
|
611
723
|
models,
|
|
612
724
|
hidden,
|
|
725
|
+
// provider → { hidden id (lower-case) → { model, variant, label, at, … } }: the entries that hide one row only.
|
|
726
|
+
hiddenPins,
|
|
613
727
|
hiddenRows,
|
|
728
|
+
// How each provider's list was obtained: live | stale | fallback | unavailable.
|
|
729
|
+
sources,
|
|
614
730
|
listed: listedCounts,
|
|
615
731
|
// The provider answered with an authoritative list, even an empty one (OpenCode's `connected`).
|
|
616
732
|
known: { opencode: openCodeConnectedKnown(opencodeProviders) },
|
|
@@ -630,28 +746,57 @@ export function createAssistantCatalog({
|
|
|
630
746
|
};
|
|
631
747
|
}
|
|
632
748
|
|
|
633
|
-
|
|
634
|
-
|
|
749
|
+
/**
|
|
750
|
+
* `force` rebuilds now from the providers' endpoints (each answers from its own
|
|
751
|
+
* discovery cache, exactly as it does for its panel's selector); `deep` makes
|
|
752
|
+
* those endpoints ask the CLI / app-server again as well.
|
|
753
|
+
*/
|
|
754
|
+
async function full({ force = false, deep = false } = {}) {
|
|
755
|
+
if (!force && !deep && cache && now() - cache.at < cache.ttl) return cache.value;
|
|
635
756
|
if (inflight) return inflight;
|
|
636
|
-
|
|
757
|
+
const started = epoch;
|
|
758
|
+
inflight = build({ deep }).then((built) => {
|
|
759
|
+
const { changedProviders, ...value } = built;
|
|
760
|
+
// No verified list from Claude Code or Codex: ask again soon, backing off to the
|
|
761
|
+
// normal lifetime (a provider that is not installed must not be spawned every few seconds).
|
|
762
|
+
const degraded = ['claude-code', 'codex'].some((p) => value.sources[p] !== 'live');
|
|
763
|
+
degradedBuilds = degraded ? degradedBuilds + 1 : 0;
|
|
764
|
+
const ttl = degraded ? Math.min(ttlMs, retryMs * 2 ** (degradedBuilds - 1)) : ttlMs;
|
|
765
|
+
cache = { at: now(), value, ttl: started === epoch ? ttl : 0 };
|
|
766
|
+
if (changedProviders.length && typeof onChange === 'function') {
|
|
767
|
+
try { onChange({ providers: changedProviders, generatedAt: value.generatedAt }); } catch (error) { log('catalog:onchange-error', error?.message || String(error)); }
|
|
768
|
+
}
|
|
769
|
+
return value;
|
|
770
|
+
}).finally(() => { inflight = null; });
|
|
637
771
|
return inflight;
|
|
638
772
|
}
|
|
639
773
|
|
|
640
|
-
/**
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
774
|
+
/**
|
|
775
|
+
* Every row, hidden ones included, per provider, and how each provider's list was obtained:
|
|
776
|
+
* { rows, sources, generatedAt } of one build, so what a row says is never read against another build's source.
|
|
777
|
+
*/
|
|
778
|
+
async function manageSnapshot({ force = false, deep = false } = {}) {
|
|
779
|
+
const value = await full({ force, deep });
|
|
780
|
+
return {
|
|
781
|
+
rows: Object.fromEntries(CATALOG_PROVIDERS.map((p) => [p, [...arr(value.models?.[p]), ...arr(value.hiddenRows?.[p])]])),
|
|
782
|
+
sources: { ...value.sources },
|
|
783
|
+
generatedAt: value.generatedAt,
|
|
784
|
+
};
|
|
644
785
|
}
|
|
645
786
|
|
|
787
|
+
/** Every row, hidden ones included, per provider (the Models manager, id resolution). */
|
|
788
|
+
async function manageRows(options = {}) { return (await manageSnapshot(options)).rows; }
|
|
789
|
+
|
|
646
790
|
/**
|
|
647
791
|
* view 'full' (default): every visible row. view 'brain': compact rows,
|
|
648
792
|
* OpenCode capped; `provider` + `q` narrow the list (agent_catalog search).
|
|
649
793
|
* view 'manage': every row with hidden: true|false and hiddenBy (the list
|
|
650
794
|
* entry that hides it), plus `archived`: every hidden id per provider with
|
|
651
|
-
* the rows it covers; listed:false = no current row (still restorable)
|
|
795
|
+
* the rows it covers; listed:false = no current row (still restorable); `pin`
|
|
796
|
+
* = the model an alias entry was hidden as (see hiddenPins).
|
|
652
797
|
*/
|
|
653
|
-
async function get({ force = false, view = 'full', provider = null, q = null, brain = null, preferences = {} } = {}) {
|
|
654
|
-
const value = await full({ force });
|
|
798
|
+
async function get({ force = false, deep = false, view = 'full', provider = null, q = null, brain = null, preferences = {} } = {}) {
|
|
799
|
+
const value = await full({ force, deep });
|
|
655
800
|
if (view === 'manage') {
|
|
656
801
|
const models = {};
|
|
657
802
|
const archived = {};
|
|
@@ -672,10 +817,11 @@ export function createAssistantCatalog({
|
|
|
672
817
|
});
|
|
673
818
|
archived[p] = arr(value.hidden?.[p]).map((id) => {
|
|
674
819
|
const covered = covers.get(String(id).toLowerCase()) || [];
|
|
675
|
-
|
|
820
|
+
const pin = value.hiddenPins?.[p]?.[String(id).toLowerCase()] || null;
|
|
821
|
+
return { id, listed: covered.length > 0, group: p === 'opencode' ? String(id).split('/')[0] : null, rows: covered, ...(pin ? { pin } : {}) };
|
|
676
822
|
});
|
|
677
823
|
}
|
|
678
|
-
return { view: 'manage', generatedAt: value.generatedAt, models, archived, hidden: value.hidden, listed: value.listed };
|
|
824
|
+
return { view: 'manage', generatedAt: value.generatedAt, models, archived, hidden: value.hidden, hiddenPins: value.hiddenPins, listed: value.listed, sources: value.sources };
|
|
679
825
|
}
|
|
680
826
|
if (view !== 'brain' && !provider && !q) return value;
|
|
681
827
|
const needle = q ? String(q).toLowerCase() : null;
|
|
@@ -690,7 +836,7 @@ export function createAssistantCatalog({
|
|
|
690
836
|
const sheet = selectSheetModels(value, { brain, preferences, max: 40 });
|
|
691
837
|
for (const p of CATALOG_PROVIDERS) models[p] = (p === 'opencode' ? sheet[p] : value.models[p]).map(compactRow);
|
|
692
838
|
}
|
|
693
|
-
const { opencodeHidden, hidden, hiddenRows, ...rest } = value;
|
|
839
|
+
const { opencodeHidden, hidden, hiddenPins, hiddenRows, ...rest } = value;
|
|
694
840
|
const hiddenCount = Object.fromEntries(CATALOG_PROVIDERS.map((p) => [p, arr(hiddenRows?.[p]).length]));
|
|
695
841
|
return { ...rest, view: 'brain', models, opencodeTotal: value.opencodeTotal, hiddenCount, opencodeHiddenCount: arr(opencodeHidden).length };
|
|
696
842
|
}
|
|
@@ -725,7 +871,13 @@ export function createAssistantCatalog({
|
|
|
725
871
|
return { vision: row.vision ?? null, tier: row.tier || null, price: row.price || null, label: row.label || row.id, contextWindow: row.contextWindow || null, source: hit?.match || 'default', id: row.id };
|
|
726
872
|
}
|
|
727
873
|
|
|
728
|
-
function invalidate() { cache = null; }
|
|
874
|
+
function invalidate() { cache = null; epoch += 1; }
|
|
875
|
+
/** A provider's own list changed: drop the copy and build the next one now (onChange reports the difference). */
|
|
876
|
+
function refresh() {
|
|
877
|
+
invalidate();
|
|
878
|
+
const pending = inflight; // began before the change: let it end, then build again
|
|
879
|
+
return pending ? pending.catch(() => null).then(() => full()) : full();
|
|
880
|
+
}
|
|
729
881
|
|
|
730
|
-
return { get, full, peek, find, hiddenId, manageRows, brainInfo, invalidate };
|
|
882
|
+
return { get, full, peek, find, hiddenId, manageRows, manageSnapshot, brainInfo, invalidate, refresh };
|
|
731
883
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// The assistant's user-editable knobs live in one JSON file: `defaultBrain`,
|
|
6
6
|
// `persona`, `limits`, `memory` (read by the other assistant modules),
|
|
7
|
-
// `routing
|
|
7
|
+
// `routing`, the budget caps (owned here; see assistant-budget.js) and
|
|
8
|
+
// `userContext` (the three layers of assistant-user-context.js). Knobs
|
|
8
9
|
// are data: the file is re-read when its mtime changes, so a PUT from the
|
|
9
10
|
// routes or budget editor, or a hand edit, applies without a restart. Writes
|
|
10
11
|
// are atomic (tmp + rename) and keep every unknown key.
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
13
14
|
import { dirname } from 'node:path';
|
|
14
15
|
import { effectiveBudget, validateBudgetPatch, writeBudgetInto } from './assistant-budget.js';
|
|
16
|
+
import { normalizeUserContext, USER_CONTEXT_DEFAULTS } from './assistant-user-context-budgets.js';
|
|
15
17
|
|
|
16
18
|
export const ROUTE_MODES = ['always-ask', 'ask-unsure', 'never'];
|
|
17
19
|
export const PREFERENCE_KINDS = ['direct', 'dispatch'];
|
|
@@ -145,6 +147,22 @@ export function effectiveRouting(raw = {}) {
|
|
|
145
147
|
};
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
export const USER_CONTEXT_KEYS = Object.freeze(Object.keys(USER_CONTEXT_DEFAULTS));
|
|
151
|
+
|
|
152
|
+
/** A partial `userContext` patch: booleans only, known layers only. */
|
|
153
|
+
export function validateUserContextPatch(patch = {}) {
|
|
154
|
+
const fail = (field, message) => { const error = new Error(message); error.code = 'USER_CONTEXT_INVALID'; error.field = field; error.status = 400; return error; };
|
|
155
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) throw fail('userContext', 'userContext must be an object');
|
|
156
|
+
const out = {};
|
|
157
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
158
|
+
if (!USER_CONTEXT_KEYS.includes(key)) throw fail(key, `Unknown userContext setting "${key}"`);
|
|
159
|
+
if (typeof value !== 'boolean') throw fail(key, `userContext.${key} must be true or false`);
|
|
160
|
+
out[key] = value;
|
|
161
|
+
}
|
|
162
|
+
if (!Object.keys(out).length) throw fail('userContext', `Nothing to change: send one of ${USER_CONTEXT_KEYS.join(', ')}`);
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
148
166
|
export function createAssistantConfigStore({ path, log = () => {}, now = Date.now } = {}) {
|
|
149
167
|
if (!path) throw new Error('createAssistantConfigStore requires path');
|
|
150
168
|
let cached = null; // { mtimeMs, value }
|
|
@@ -224,9 +242,20 @@ export function createAssistantConfigStore({ path, log = () => {}, now = Date.no
|
|
|
224
242
|
return patchRouting({ preferences: { [key]: value } });
|
|
225
243
|
}
|
|
226
244
|
|
|
245
|
+
/** The user-context layers (assistant-user-context.js): knowledge, nudges, trace. All on by default; read live. */
|
|
246
|
+
function userContext() { return normalizeUserContext(read().userContext); }
|
|
247
|
+
/** Switch layers (a validated partial patch). Every unknown key of the file is kept. */
|
|
248
|
+
function patchUserContext(patch = {}) {
|
|
249
|
+
const clean = validateUserContextPatch(patch);
|
|
250
|
+
const current = read();
|
|
251
|
+
const stored = current.userContext && typeof current.userContext === 'object' && !Array.isArray(current.userContext) ? current.userContext : {};
|
|
252
|
+
write({ ...current, userContext: { ...stored, ...clean, updatedAt: new Date(now()).toISOString() } });
|
|
253
|
+
return userContext();
|
|
254
|
+
}
|
|
255
|
+
|
|
227
256
|
function onChange(fn) { listeners.add(fn); return () => listeners.delete(fn); }
|
|
228
257
|
|
|
229
|
-
return { read, routing, version, patchRouting, setPreference, budget, budgetVersion, patchBudget, onChange, path };
|
|
258
|
+
return { read, routing, version, patchRouting, setPreference, budget, budgetVersion, patchBudget, userContext, patchUserContext, onChange, path };
|
|
230
259
|
}
|
|
231
260
|
|
|
232
261
|
export function fileExists(path) { try { return existsSync(path); } catch { return false; } }
|