synabun 2.0.0 → 2.0.7
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 +97 -0
- package/NOTICE +5 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +59 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +153 -18
- 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 +26 -3
- package/hooks/claude-code/stop.mjs +5 -5
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/start-launcher.js +88 -1
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +2126 -209
- package/neural-interface/i18n/pt-BR.json +2123 -207
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +316 -57
- package/neural-interface/lib/assistant-brains/claude.js +19 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +91 -14
- package/neural-interface/lib/assistant-dispatch.js +494 -138
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +73 -12
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +488 -59
- 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 +183 -0
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +123 -9
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +33 -5
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -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/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -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-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +234 -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/install-details.js +145 -0
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-loop-providers.js +26 -8
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +701 -2
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/install-app.html +9 -6
- package/neural-interface/public/offline.html +19 -3
- package/neural-interface/public/onboarding.html +107 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +50 -14
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
- package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
- package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
- package/neural-interface/public/shared/assistant/asst-render.js +197 -23
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +95 -343
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -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-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +186 -2
- package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +61 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -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/git-bash-view.js +145 -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 +161 -165
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- 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 +131 -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 +22 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +32 -2
- package/neural-interface/public/shared/pwa-install.css +3 -3
- package/neural-interface/public/shared/pwa-install.js +100 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -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 +33 -4
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/start-bridge.js +238 -19
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +1710 -770
- package/neural-interface/public/shared/synabun-mascot.js +68 -58
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +369 -229
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- 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-git-bash.js +124 -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 +10 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +187 -19
- package/neural-interface/public/shared/ui-menubar.js +25 -33
- 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 +48 -22
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +509 -490
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +22 -0
- package/neural-interface/public/shared/ui-terminal.js +40 -5
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- 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-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- 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/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +253 -0
- package/neural-interface/public/sw.js +2 -2
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -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/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +2085 -1629
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +5 -8
- package/setup.js +53 -15
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- 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/skills/synabun/synabunicon.png +0 -0
|
@@ -12,24 +12,31 @@ import { DispatchError } from './assistant-dispatch.js';
|
|
|
12
12
|
import { openServable } from './assistant-media.js';
|
|
13
13
|
import { designFit, targetMakes } from './assistant-router.js';
|
|
14
14
|
import { normalizeEffort } from './effort-levels.js';
|
|
15
|
-
import {
|
|
15
|
+
import { BUDGET_DEFAULT_USD, BUDGET_MAX_USD, BUDGET_MIN_USD, BUDGET_WARN_RATIO } from './assistant-budget.js';
|
|
16
16
|
import { tooLargeMessage } from './assistant-attachments.js';
|
|
17
17
|
import { getSessionPolicy, requiresHumanApproval } from './remote-policy.js';
|
|
18
18
|
import { clampPlanDispatch, PLAN_DISPATCH_NOTE, runCanChangeCode } from './assistant-plan-permissions.js';
|
|
19
|
+
import { noStore, requireJson, requireLocal, requireLocalHost, sendGuardError } from './http-guards.js';
|
|
20
|
+
|
|
21
|
+
/** The header whatsapp_send_file's caller proves it is the WhatsApp conversation's own brain with (assistant-runtime.js BRAIN_CREDENTIAL_RE). */
|
|
22
|
+
export const BRAIN_CREDENTIAL_HEADER = 'x-synabun-brain-token';
|
|
23
|
+
const SEND_FILE_FIELDS = Object.freeze(['path', 'caption', 'as']);
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
|
-
* How far each provider's spend can be held to
|
|
22
|
-
*
|
|
23
|
-
*
|
|
26
|
+
* How far each provider's spend can be held to the budget (the Budget modal shows it). Spend is
|
|
27
|
+
* checked where the provider lets it be checked; between two checks a run can go past its share by
|
|
28
|
+
* that much.
|
|
24
29
|
*/
|
|
25
30
|
export const BUDGET_ENFORCEMENT = Object.freeze({
|
|
26
|
-
'claude-code': { metered: 'reported',
|
|
27
|
-
opencode: { metered: 'reported',
|
|
28
|
-
codex: { metered: 'estimated',
|
|
29
|
-
brain: { metered: 'reported',
|
|
31
|
+
'claude-code': { metered: 'reported', share: 'Checked between model calls (each call at list price while the turn runs, the reported cost at its end), so one call can pass the share. The Claude CLI also stops a run at its ceiling, or at what the session had left when it started.', session: 'At the budget every run stops.' },
|
|
32
|
+
opencode: { metered: 'reported', share: 'OpenCode reports each step\'s cost; the share is checked at every step, so one step can pass it.', session: 'At the budget every run stops.' },
|
|
33
|
+
codex: { metered: 'estimated', share: 'Codex reports tokens: priced at list price (an API-price estimate; ChatGPT-plan accounts are billed by plan quota) as its rollout records them, so one record or turn can pass the share. A failed turn is priced from its rollout.', session: 'At the budget every run stops.' },
|
|
34
|
+
brain: { metered: 'reported', share: 'Your Assistant\'s own turns draw from its provider\'s share, which keeps a reserve for them; checked before every brain turn. A Claude brain\'s CLI also stops at what that share had left when it started. A brain model with no price is refused.', session: 'At the budget the brain takes no turn until the budget is raised.' },
|
|
30
35
|
});
|
|
31
36
|
|
|
32
37
|
const OWNER_ONLY = { error: 'The assistant is available to the owner only', code: 'GUEST_FORBIDDEN' };
|
|
38
|
+
// What GET /catalog is made from when no catalog builder is wired: one object, so its answer is reused like any build's.
|
|
39
|
+
const NO_CATALOG = Object.freeze({});
|
|
33
40
|
|
|
34
41
|
function sendError(res, error, fallbackStatus = 500) {
|
|
35
42
|
if (error instanceof DispatchError || (error && error.code && error.status)) {
|
|
@@ -87,9 +94,17 @@ export function slimRunView(run) {
|
|
|
87
94
|
* @param {object} deps.dispatcher createAssistantDispatcher()
|
|
88
95
|
* @param {object} [deps.runtime] createAssistantRuntime() (sessions; optional until wired)
|
|
89
96
|
* @param {object} [deps.claudeAccounts] createClaudeAccounts()
|
|
90
|
-
* @param {object} [deps.codexAccounts]
|
|
97
|
+
* @param {object} [deps.codexAccounts] createCodexAccounts() (lib/codex-accounts.js)
|
|
91
98
|
* @param {Function} [deps.buildCatalog] async () => catalog object (models/accounts/projects/profiles)
|
|
92
99
|
* @param {Function} [deps.createLoginTerminal] async ({ env, cwd, profile, accountId }) => { terminalSessionId, profile }
|
|
100
|
+
* @param {Function} [deps.claudeSignIn] async (accountId) => { sessionId, status, error } — Claude Code's own
|
|
101
|
+
* `auth login` in a SynaBun terminal under the account's config directory (lib/provider-panel-terminal-auth.js)
|
|
102
|
+
* @param {Function} [deps.claudeSetMode] async (accountId, mode) => row — what the account's sessions use
|
|
103
|
+
* @param {Function} [deps.claudeAccountInUse] (accountId) => '' | why it cannot be removed now
|
|
104
|
+
* @param {Function} [deps.codexSignIn] async (accountId) => { sessionId, status, error } — `codex login` in a SynaBun
|
|
105
|
+
* terminal under the account's CODEX_HOME (lib/provider-panel-terminal-auth.js)
|
|
106
|
+
* @param {Function} [deps.codexSetMode] async (accountId, mode) => row — what the account's sessions use
|
|
107
|
+
* @param {Function} [deps.codexAccountInUse] (accountId) => '' | why it cannot be removed now
|
|
93
108
|
* @param {object} [deps.attachments] createAttachmentStore() (POST /attachments)
|
|
94
109
|
*/
|
|
95
110
|
export function createAssistantApi({
|
|
@@ -98,8 +113,15 @@ export function createAssistantApi({
|
|
|
98
113
|
usage = null,
|
|
99
114
|
claudeAccounts = null,
|
|
100
115
|
codexAccounts = null,
|
|
116
|
+
accountUsage = null,
|
|
101
117
|
buildCatalog = null,
|
|
102
118
|
createLoginTerminal = null,
|
|
119
|
+
claudeSignIn = null,
|
|
120
|
+
claudeSetMode = null,
|
|
121
|
+
claudeAccountInUse = null,
|
|
122
|
+
codexSignIn = null,
|
|
123
|
+
codexSetMode = null,
|
|
124
|
+
codexAccountInUse = null,
|
|
103
125
|
broadcastSync = () => {},
|
|
104
126
|
isGuestRequest = () => false,
|
|
105
127
|
log = () => {},
|
|
@@ -119,6 +141,15 @@ export function createAssistantApi({
|
|
|
119
141
|
// Remote sessions (lib/remote-policy.js): the session's policy, or null. Defaults
|
|
120
142
|
// to the runtime's view, else the process-wide registry.
|
|
121
143
|
sessionPolicy = null,
|
|
144
|
+
// The user-context engine (assistant-user-context.js) and the project a new conversation starts in.
|
|
145
|
+
userContext = null,
|
|
146
|
+
// Why the engine did not start on this server (a string or null), for the settings page.
|
|
147
|
+
userContextError = () => null,
|
|
148
|
+
defaultProject = () => 'global',
|
|
149
|
+
// whatsapp_send_file: the WhatsApp service ({ sendFile }) or a function returning it (it is built
|
|
150
|
+
// after this router), and the port the server listens on (the Host check of that route).
|
|
151
|
+
whatsapp = null,
|
|
152
|
+
port = 3344,
|
|
122
153
|
} = {}) {
|
|
123
154
|
if (!dispatcher) throw new Error('createAssistantApi requires a dispatcher');
|
|
124
155
|
const router = Router();
|
|
@@ -268,22 +299,35 @@ export function createAssistantApi({
|
|
|
268
299
|
// ── catalog ────────────────────────────────────────────────────────────────
|
|
269
300
|
router.get('/catalog', async (req, res) => {
|
|
270
301
|
try {
|
|
271
|
-
|
|
302
|
+
await accountUsage?.refresh();
|
|
303
|
+
// refresh=1 rebuilds from the providers' endpoints; deep=1 also makes them ask the CLI / app-server again.
|
|
304
|
+
const deep = req.query.deep === '1';
|
|
305
|
+
const force = req.query.refresh === '1' || deep;
|
|
272
306
|
// Brain view / search: compact rows (OpenCode can list hundreds of models).
|
|
273
307
|
const view = req.query.view === 'brain' ? 'brain' : req.query.view === 'manage' ? 'manage' : 'full';
|
|
274
308
|
const provider = req.query.provider ? String(req.query.provider) : null;
|
|
275
309
|
const q = req.query.q || req.query.search ? String(req.query.q || req.query.search) : null;
|
|
276
310
|
// 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 })) });
|
|
311
|
+
if (view === 'manage' && catalog?.get) return res.json({ ok: true, ...(await catalog.get({ force, deep, view: 'manage', provider })) });
|
|
278
312
|
if (catalog?.get && (view === 'brain' || provider || q)) {
|
|
279
313
|
let preferences = {};
|
|
280
314
|
try { preferences = configStore?.routing?.().preferences || {}; } catch {}
|
|
281
|
-
const brief = await catalog.get({ force, view: 'brain', provider, q, preferences });
|
|
282
|
-
|
|
315
|
+
const brief = await catalog.get({ force, deep, view: 'brain', provider, q, preferences });
|
|
316
|
+
// Accounts are read live (the catalog build can be seconds old): every Claude and Codex account with
|
|
317
|
+
// its mode, whether it is signed in and its identity, and the account a worker gets when a dispatch
|
|
318
|
+
// names none.
|
|
319
|
+
const accounts = {
|
|
320
|
+
...(brief.accounts || {}),
|
|
321
|
+
...(claudeAccounts ? { 'claude-code': accountUsage?.list()?.['claude-code'] || claudeAccounts.listForClient() } : {}),
|
|
322
|
+
...(codexAccounts ? { codex: accountUsage?.list()?.codex || codexAccounts.listForClient() } : {}),
|
|
323
|
+
};
|
|
324
|
+
return res.json({ ok: true, generatedAt: new Date(now()).toISOString(), ...brief, accounts, claudeWorkerAccount: claudeWorkerAccountId(), codexWorkerAccount: codexWorkerAccountId(), limits: dispatcher.limits, runs: dispatcher.list({ activeOnly: true }), totals: dispatcher.totals() });
|
|
283
325
|
}
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
326
|
+
// The catalog says how long one of its builds is good for (one without a verified list is redone
|
|
327
|
+
// within seconds), so it is asked every time: this answer is reused only while it hands back the
|
|
328
|
+
// build the answer was made from (a rebuild or invalidate() gives another one).
|
|
329
|
+
const base = typeof buildCatalog === 'function' ? await buildCatalog({ force, deep }) : NO_CATALOG;
|
|
330
|
+
if (!force && catalogCache && catalogCache.base === base && now() - catalogCache.at < 30_000) return res.json(accountUsage ? { ...catalogCache.value, accounts: { ...catalogCache.value.accounts, ...accountUsage.list() } } : catalogCache.value);
|
|
287
331
|
// Hidden rows stay server-side (view=manage lists them); the lists themselves ship.
|
|
288
332
|
const { hiddenRows: _hiddenRows, ...visible } = base || {};
|
|
289
333
|
const value = {
|
|
@@ -292,9 +336,11 @@ export function createAssistantApi({
|
|
|
292
336
|
...visible,
|
|
293
337
|
accounts: {
|
|
294
338
|
...(base.accounts || {}),
|
|
295
|
-
'claude-code': claudeAccounts ? claudeAccounts.listForClient() : (base.accounts?.['claude-code'] || []),
|
|
296
|
-
codex: codexAccounts ? codexAccounts.listForClient() : (base.accounts?.codex || []),
|
|
339
|
+
'claude-code': accountUsage?.list()?.['claude-code'] || (claudeAccounts ? claudeAccounts.listForClient() : (base.accounts?.['claude-code'] || [])),
|
|
340
|
+
codex: accountUsage?.list()?.codex || (codexAccounts ? codexAccounts.listForClient() : (base.accounts?.codex || [])),
|
|
297
341
|
},
|
|
342
|
+
claudeWorkerAccount: claudeWorkerAccountId(),
|
|
343
|
+
codexWorkerAccount: codexWorkerAccountId(),
|
|
298
344
|
limits: dispatcher.limits,
|
|
299
345
|
runs: dispatcher.list({ activeOnly: true }),
|
|
300
346
|
totals: dispatcher.totals(),
|
|
@@ -458,18 +504,57 @@ export function createAssistantApi({
|
|
|
458
504
|
} catch (error) { sendError(res, error, 400); }
|
|
459
505
|
});
|
|
460
506
|
// ── budget ─────────────────────────────────────────────────────────────────
|
|
507
|
+
/** A session's budget view (dispatcher → budget planner): the setting, spend, and the live split. */
|
|
508
|
+
function sessionBudgetView(sessionId) {
|
|
509
|
+
if (!sessionId) return null;
|
|
510
|
+
try { return dispatcher.sessionBudget?.(String(sessionId)) || null; } catch { return null; }
|
|
511
|
+
}
|
|
461
512
|
function budgetPayload(sessionId = null) {
|
|
462
513
|
const current = configStore.budget();
|
|
463
|
-
const budget = Object.fromEntries(BUDGET_FIELDS.map((field) => [field, current[field]]));
|
|
464
|
-
let session = null;
|
|
465
|
-
if (sessionId) { try { session = dispatcher.sessionBudget?.(String(sessionId)) || null; } catch { session = null; } }
|
|
466
514
|
let pricingStatus = null;
|
|
467
515
|
try { pricingStatus = pricing?.status?.() || null; } catch { pricingStatus = null; }
|
|
468
516
|
return {
|
|
469
|
-
ok: true, version: current.version,
|
|
470
|
-
|
|
517
|
+
ok: true, version: current.version,
|
|
518
|
+
budget: { unlimited: current.unlimited === true, amountUsd: current.amountUsd, version: current.version, updatedAt: current.updatedAt || null, source: current.source || 'saved' },
|
|
519
|
+
defaults: { unlimited: false, amountUsd: BUDGET_DEFAULT_USD }, bounds: { minUsd: BUDGET_MIN_USD, maxUsd: BUDGET_MAX_USD }, warnRatio: BUDGET_WARN_RATIO,
|
|
520
|
+
session: sessionBudgetView(sessionId), pricing: pricingStatus, enforcement: BUDGET_ENFORCEMENT,
|
|
471
521
|
};
|
|
472
522
|
}
|
|
523
|
+
// ── User context (Settings → Automation behavior): the three layers and what a new conversation is told ──
|
|
524
|
+
function userContextPayload(req) {
|
|
525
|
+
let fallback = 'global';
|
|
526
|
+
try { fallback = String(defaultProject() || 'global'); } catch { fallback = 'global'; }
|
|
527
|
+
const project = String(req.query?.project || '').trim().slice(0, 200) || fallback;
|
|
528
|
+
return { ok: true, settings: configStore.userContext(), capabilities: userContext.capabilities(), preview: userContext.preview({ project }) };
|
|
529
|
+
}
|
|
530
|
+
const userContextReady = (res) => {
|
|
531
|
+
if (userContext && configStore?.userContext) return true;
|
|
532
|
+
let reason = null;
|
|
533
|
+
try { reason = typeof userContextError === 'function' ? userContextError() : userContextError; } catch { reason = null; }
|
|
534
|
+
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 });
|
|
535
|
+
return false;
|
|
536
|
+
};
|
|
537
|
+
router.get('/user-context', (req, res) => {
|
|
538
|
+
if (!userContextReady(res)) return;
|
|
539
|
+
try { res.json(userContextPayload(req)); } catch (error) { sendError(res, error); }
|
|
540
|
+
});
|
|
541
|
+
router.put('/user-context', (req, res) => {
|
|
542
|
+
if (!userContextReady(res)) return;
|
|
543
|
+
if (!configStore.patchUserContext) return res.status(503).json({ ok: false, code: 'CONFIG_UNAVAILABLE', error: 'User context settings are read-only' });
|
|
544
|
+
if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'User context settings are changed from Settings only.' });
|
|
545
|
+
try {
|
|
546
|
+
const body = req.body || {};
|
|
547
|
+
const { project: _project, ...patch } = body.settings && typeof body.settings === 'object' ? body.settings : body;
|
|
548
|
+
configStore.patchUserContext(patch);
|
|
549
|
+
// Read live by the engine: the next turn (and the next brain start) follows it.
|
|
550
|
+
broadcastSync({ type: 'assistant:user-context-changed', settings: configStore.userContext() });
|
|
551
|
+
res.json(userContextPayload(req));
|
|
552
|
+
} catch (error) {
|
|
553
|
+
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 });
|
|
554
|
+
sendError(res, error, 400);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
473
558
|
router.get('/budget', (req, res) => {
|
|
474
559
|
if (!configStore?.budget) return res.status(503).json({ ok: false, code: 'CONFIG_UNAVAILABLE', error: 'Budget settings are not available' });
|
|
475
560
|
try { res.json(budgetPayload(req.query.sessionId || null)); } catch (error) { sendError(res, error); }
|
|
@@ -479,13 +564,16 @@ export function createAssistantApi({
|
|
|
479
564
|
if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Budget settings are changed from the assistant panel only.' });
|
|
480
565
|
try {
|
|
481
566
|
const body = req.body || {};
|
|
482
|
-
const patch = body.budget && typeof body.budget === 'object' ? body.budget :
|
|
567
|
+
const patch = body.budget && typeof body.budget === 'object' && !Array.isArray(body.budget) ? body.budget : null;
|
|
568
|
+
if (!patch) return res.status(400).json({ ok: false, code: 'BUDGET_INVALID', error: 'Send { budget: { unlimited?, amountUsd? }, version }', message: 'Send { budget: { unlimited?, amountUsd? }, version }', field: 'budget', version: null });
|
|
483
569
|
const expectedVersion = body.version ?? null;
|
|
484
570
|
const saved = configStore.patchBudget(patch, { expectedVersion });
|
|
485
|
-
// Applies now: the
|
|
571
|
+
// Applies now: the planner divides every open session's budget again, the runtime re-checks
|
|
572
|
+
// every session (held mailboxes, the brain's next turn) and every panel hears it.
|
|
486
573
|
try { dispatcher.refreshLimits?.(); } catch {}
|
|
487
|
-
try { runtime
|
|
488
|
-
|
|
574
|
+
if (runtime?.onBudgetChanged) { try { runtime.onBudgetChanged(); } catch {} }
|
|
575
|
+
else { try { dispatcher.budget?.onBudgetChanged?.(); } catch {} }
|
|
576
|
+
broadcastSync({ type: 'assistant:budget-changed', version: saved.version, budget: { unlimited: saved.unlimited, amountUsd: saved.amountUsd, version: saved.version, updatedAt: saved.updatedAt || null } });
|
|
489
577
|
res.json(budgetPayload(body.sessionId || null));
|
|
490
578
|
} catch (error) {
|
|
491
579
|
if (error?.code === 'BUDGET_INVALID' || error?.code === 'VERSION_CONFLICT') {
|
|
@@ -505,6 +593,45 @@ export function createAssistantApi({
|
|
|
505
593
|
try { res.json(runtime.gateCheck({ session: body.session, token: body.token, tool: body.tool, input, inputComplete: body.inputComplete === true, providerSessionId: body.providerSessionId || null, agent: body.agent || null, hello: body.hello === true, host: typeof body.host === 'string' ? body.host : null })); }
|
|
506
594
|
catch (error) { sendError(res, error); }
|
|
507
595
|
});
|
|
596
|
+
// ── whatsapp_send_file (docs/whatsapp.md → Sending files) ──────────────────
|
|
597
|
+
// A local file to the owner, for the WhatsApp conversation whose brain is calling. Who calls is the
|
|
598
|
+
// credential that brain alone holds (X-Synabun-Brain-Token: assistant-runtime.js mintBrainCredential),
|
|
599
|
+
// resolved to its session in constant time; never X-Synabun-Role or X-Synabun-Terminal, which any
|
|
600
|
+
// local process can send. A loopback socket and a localhost Host only (the checks of /api/whatsapp);
|
|
601
|
+
// guests were refused above. Only { path, caption?, as? }. Exactly one send, never retried.
|
|
602
|
+
const portOf = () => (typeof port === 'function' ? port() : port);
|
|
603
|
+
let hostGuard = null;
|
|
604
|
+
const localHostOnly = (req, res, next) => {
|
|
605
|
+
const current = portOf();
|
|
606
|
+
if (!hostGuard || hostGuard.port !== current) {
|
|
607
|
+
try { hostGuard = { port: current, check: requireLocalHost(current) }; } catch { return sendGuardError(res, 500, 'BAD_PORT', 'The file route has no valid port.'); }
|
|
608
|
+
}
|
|
609
|
+
return hostGuard.check(req, res, next);
|
|
610
|
+
};
|
|
611
|
+
router.post('/whatsapp/send-file', noStore, requireLocal, localHostOnly, requireJson, async (req, res) => {
|
|
612
|
+
let sessionId = null;
|
|
613
|
+
try { sessionId = runtime?.sessionForBrainCredential?.(req.get(BRAIN_CREDENTIAL_HEADER)) || null; } catch { sessionId = null; }
|
|
614
|
+
if (!sessionId) return sendGuardError(res, 403, 'BRAIN_CREDENTIAL_REQUIRED', 'Only the WhatsApp conversation\'s own Assistant sends files to the phone: nothing was sent.');
|
|
615
|
+
const body = req.body;
|
|
616
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return sendGuardError(res, 400, 'BAD_REQUEST', 'Send { path, caption?, as? }.');
|
|
617
|
+
const extra = Object.keys(body).find((key) => !SEND_FILE_FIELDS.includes(key));
|
|
618
|
+
if (extra !== undefined) return sendGuardError(res, 400, 'BAD_REQUEST', `Unknown field "${extra.slice(0, 40)}": a file send takes path, caption and as only.`, extra.slice(0, 40));
|
|
619
|
+
let service = null;
|
|
620
|
+
try { service = typeof whatsapp === 'function' ? whatsapp() : whatsapp; } catch { service = null; }
|
|
621
|
+
if (typeof service?.sendFile !== 'function') return sendGuardError(res, 503, 'WHATSAPP_UNAVAILABLE', 'WhatsApp is not available on this computer: nothing was sent.');
|
|
622
|
+
try {
|
|
623
|
+
res.json(await service.sendFile(sessionId, body));
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const status = Number(error?.status) || 500;
|
|
626
|
+
const out = { ok: false, code: error?.code || 'INTERNAL', error: error?.message || String(error) };
|
|
627
|
+
if (error?.field) out.field = error.field;
|
|
628
|
+
if (error?.uncertain === true) out.uncertain = true;
|
|
629
|
+
if (Number.isFinite(error?.retryAfterMs)) out.retryAfterMs = Math.max(0, Math.round(error.retryAfterMs));
|
|
630
|
+
if (status >= 500) log('assistant:whatsapp-send-file', `${sessionId}: ${out.code}${out.uncertain ? ' (delivery unknown)' : ''}`);
|
|
631
|
+
res.status(status).json(out);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
508
635
|
// agent_route: the brain proposes; the router approves, asks, or says pending.
|
|
509
636
|
router.post('/route', orchestrationGuard, async (req, res) => {
|
|
510
637
|
if (!requireRouter(res)) return;
|
|
@@ -726,13 +853,16 @@ export function createAssistantApi({
|
|
|
726
853
|
res.json(brain ? brainWaitView(chat ? chatWaitView(result) : result) : result);
|
|
727
854
|
} catch (error) { sendError(res, error); }
|
|
728
855
|
});
|
|
729
|
-
router.post('/runs/:runId/send', orchestrationGuard, (req, res) => {
|
|
856
|
+
router.post('/runs/:runId/send', orchestrationGuard, async (req, res) => {
|
|
730
857
|
try {
|
|
731
858
|
const body = req.body || {};
|
|
732
859
|
const assistantSessionId = req.assistantCaller.assistantSessionId || dispatcher.get(req.params.runId)?.assistantSessionId || null;
|
|
733
860
|
if (planRefusal(req, res, assistantSessionId, 'send a worker a message')) return;
|
|
734
861
|
if (planState(req, assistantSessionId) === 'plan' && planSendRefusal(res, dispatcher.get(req.params.runId))) return;
|
|
735
862
|
const origin = body.origin === 'user' ? 'user' : (req.assistantCaller.kind === 'ui' ? (body.origin || 'assistant') : 'assistant');
|
|
863
|
+
// A run whose provider share is used up: the session's unspent budget is divided again first, then
|
|
864
|
+
// sendTurn decides (it refuses with BUDGET_SHARE_EXHAUSTED when no money could move).
|
|
865
|
+
try { await dispatcher.coverFollowUp?.(req.params.runId); } catch { /* sendTurn decides on the shares as they are */ }
|
|
736
866
|
res.json(dispatcher.sendTurn(req.params.runId, body.text ?? body.message, { origin, queue: body.queue !== false }));
|
|
737
867
|
} catch (error) { sendError(res, error); }
|
|
738
868
|
});
|
|
@@ -779,6 +909,12 @@ export function createAssistantApi({
|
|
|
779
909
|
router.post('/runs/:runId/complete', orchestrationGuard, (req, res) => {
|
|
780
910
|
try { res.json(dispatcher.complete(req.params.runId)); } catch (error) { sendError(res, error); }
|
|
781
911
|
});
|
|
912
|
+
// The user closed the sidepanel tab of a worker that finished its task: completed, never "stopped
|
|
913
|
+
// by the user" (409 RUN_NOT_FINISHED for any other run, which the tab then stops as before).
|
|
914
|
+
router.post('/runs/:runId/tab-closed', (req, res) => {
|
|
915
|
+
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.' });
|
|
916
|
+
try { res.json(dispatcher.completeClosedTab(req.params.runId)); } catch (error) { sendError(res, error); }
|
|
917
|
+
});
|
|
782
918
|
router.post('/runs/:runId/focus', (req, res) => {
|
|
783
919
|
const run = dispatcher.get(req.params.runId);
|
|
784
920
|
if (!run) return res.status(404).json({ ok: false, code: 'RUN_NOT_FOUND', error: `Unknown run ${req.params.runId}` });
|
|
@@ -803,6 +939,14 @@ export function createAssistantApi({
|
|
|
803
939
|
res.status(201).json({ ok: true, session });
|
|
804
940
|
} catch (error) { sendError(res, error); }
|
|
805
941
|
});
|
|
942
|
+
// A session's budget and its live split (the spend chip, the Budget modal): the same view the
|
|
943
|
+
// `assistant:budget` packet carries.
|
|
944
|
+
router.get('/sessions/:id/budget', (req, res) => {
|
|
945
|
+
if (!requireRuntime(res)) return;
|
|
946
|
+
const id = req.params.id;
|
|
947
|
+
if (!runtime.getSession(id, { transcript: false })) return res.status(404).json({ ok: false, code: 'SESSION_NOT_FOUND', error: `Unknown assistant session ${id}` });
|
|
948
|
+
try { res.json({ ok: true, budget: sessionBudgetView(id) }); } catch (error) { sendError(res, error); }
|
|
949
|
+
});
|
|
806
950
|
router.get('/sessions/:id/usage', (req, res) => {
|
|
807
951
|
if (!requireRuntime(res)) return;
|
|
808
952
|
const id = req.params.id;
|
|
@@ -818,9 +962,10 @@ export function createAssistantApi({
|
|
|
818
962
|
const session = usage?.sessionView(id) ?? null;
|
|
819
963
|
// Whether work is in progress is the runtime's to say (a turn that has no tokens yet).
|
|
820
964
|
const head = session ? runtime.usageView?.(id, 'current')?.session || null : null;
|
|
821
|
-
return res.json({ ok: true, session: session && head ? { ...session, live: head.live, fidelity: head.fidelity } : session, ...(run ? { run: usage?.runView?.(id, run) ?? null } : {}) });
|
|
965
|
+
return res.json({ ok: true, session: session && head ? { ...session, live: head.live, fidelity: head.fidelity } : session, ...(run ? { run: usage?.runView?.(id, run) ?? null } : {}), budget: sessionBudgetView(id) });
|
|
822
966
|
}
|
|
823
|
-
|
|
967
|
+
// The session budget rides along (agent_usage: the mode and each provider's share / spent / left).
|
|
968
|
+
res.json({ ok: true, usage: runtime.usageView?.(id, task) ?? usage?.taskView(id, task) ?? null, budget: sessionBudgetView(id) });
|
|
824
969
|
} catch (error) { sendError(res, error); }
|
|
825
970
|
});
|
|
826
971
|
router.get('/sessions/:id', (req, res) => {
|
|
@@ -835,6 +980,10 @@ export function createAssistantApi({
|
|
|
835
980
|
if (req.body && 'computerUse' in req.body && (req.get('x-synabun-terminal') || req.get('x-synabun-desktop-grant'))) {
|
|
836
981
|
return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the user can switch computer use on or off.' });
|
|
837
982
|
}
|
|
983
|
+
// Test density is the user's setting too: an agent cannot raise or lower it.
|
|
984
|
+
if (req.body && 'testDensity' in req.body && (req.get('x-synabun-terminal') || req.get('x-synabun-desktop-grant'))) {
|
|
985
|
+
return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the user can change the Test density.' });
|
|
986
|
+
}
|
|
838
987
|
try {
|
|
839
988
|
const session = await runtime.updateSession(req.params.id, req.body || {});
|
|
840
989
|
if (!session) return res.status(404).json({ ok: false, code: 'SESSION_NOT_FOUND', error: `Unknown assistant session ${req.params.id}` });
|
|
@@ -884,25 +1033,51 @@ export function createAssistantApi({
|
|
|
884
1033
|
return (accountId) => {
|
|
885
1034
|
if (dispatcher.accountInUse(provider, accountId)) return `Stop the dispatched ${provider} runs using account ${accountId} first`;
|
|
886
1035
|
if (runtime?.accountInUse?.(provider, accountId)) return `Close the assistant sessions using account ${accountId} first`;
|
|
1036
|
+
if (provider === 'claude-code' && typeof claudeAccountInUse === 'function') return claudeAccountInUse(accountId) || null;
|
|
1037
|
+
if (provider === 'codex' && typeof codexAccountInUse === 'function') return codexAccountInUse(accountId) || null;
|
|
887
1038
|
return null;
|
|
888
1039
|
};
|
|
889
1040
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1041
|
+
// The Claude account a Claude Code worker runs under when its dispatch names none (null = Default).
|
|
1042
|
+
function claudeWorkerAccountId() {
|
|
1043
|
+
try { return configStore?.workers?.().claudeAccount || null; } catch { return null; }
|
|
1044
|
+
}
|
|
1045
|
+
// The same for Codex workers.
|
|
1046
|
+
function codexWorkerAccountId() {
|
|
1047
|
+
try { return configStore?.workers?.().codexAccount || null; } catch { return null; }
|
|
1048
|
+
}
|
|
1049
|
+
const claudeUnavailable = (res) => res.status(503).json({ ok: false, code: 'ACCOUNTS_UNAVAILABLE', error: 'Claude accounts are not available' });
|
|
1050
|
+
// Every Claude account as every surface shows it (lib/claude-accounts.js describe()): mode, signed in,
|
|
1051
|
+
// identity (an email or a masked key, never a key). `?refresh=1` asks Claude Code about each first.
|
|
1052
|
+
router.get('/claude/accounts', async (req, res) => {
|
|
1053
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
1054
|
+
try {
|
|
1055
|
+
if (req.query.refresh === '1' || req.query.refresh === 'true') await claudeAccounts.refreshStatus?.(null, { force: true });
|
|
1056
|
+
else Promise.resolve(claudeAccounts.refreshStatus?.()).catch(() => {});
|
|
1057
|
+
await accountUsage?.refresh('claude-code');
|
|
1058
|
+
res.json({ ok: true, accounts: accountUsage?.list()?.['claude-code'] || claudeAccounts.listForClient(), workerAccountId: claudeWorkerAccountId() });
|
|
1059
|
+
} catch (error) { sendError(res, error); }
|
|
893
1060
|
});
|
|
1061
|
+
// Create an account. `login: true` also opens its Claude sign-in in a SynaBun terminal; otherwise the
|
|
1062
|
+
// caller offers the two ways to sign it in (Claude, or an API key pasted into it).
|
|
894
1063
|
router.post('/claude/accounts', async (req, res) => {
|
|
895
|
-
if (!claudeAccounts) return res
|
|
1064
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
896
1065
|
try {
|
|
897
1066
|
const account = claudeAccounts.create({ label: req.body?.label });
|
|
898
1067
|
let login = null;
|
|
899
|
-
if (req.body?.login
|
|
900
|
-
|
|
901
|
-
}
|
|
1068
|
+
if (req.body?.login === true) login = await startClaudeLogin(account.id);
|
|
1069
|
+
catalogCache = null;
|
|
902
1070
|
res.status(201).json({ ok: true, account: claudeAccounts.listForClient().find((row) => row.id === account.id) || account, login });
|
|
903
1071
|
} catch (error) { sendError(res, error); }
|
|
904
1072
|
});
|
|
905
1073
|
async function startClaudeLogin(accountId) {
|
|
1074
|
+
// Claude Code's own `auth login` under the account's config directory, in a SynaBun terminal (the
|
|
1075
|
+
// panel's sign-in). The account list says when it is signed in (claude:accounts-changed).
|
|
1076
|
+
if (typeof claudeSignIn === 'function') {
|
|
1077
|
+
const terminal = await claudeSignIn(accountId);
|
|
1078
|
+
if (terminal?.status === 'error') throw Object.assign(new Error(terminal.error || 'Could not open the sign-in terminal.'), { status: 500, code: 'LOGIN_FAILED' });
|
|
1079
|
+
return { mode: 'terminal', terminalSessionId: terminal?.sessionId || null, status: terminal?.status || 'pending', profile: 'shell' };
|
|
1080
|
+
}
|
|
906
1081
|
const command = claudeAccounts.loginCommand(accountId);
|
|
907
1082
|
const terminal = await createLoginTerminal({ env: command.env, cwd: command.cwd, profile: 'claude-code', accountId, command: command.command });
|
|
908
1083
|
claudeAccounts.watchLogin(accountId, {
|
|
@@ -914,24 +1089,34 @@ export function createAssistantApi({
|
|
|
914
1089
|
return { mode: 'terminal', terminalSessionId: terminal?.terminalSessionId || terminal?.id || null, profile: terminal?.profile || 'claude-code' };
|
|
915
1090
|
}
|
|
916
1091
|
router.post('/claude/accounts/:id/login', async (req, res) => {
|
|
917
|
-
if (!claudeAccounts) return res
|
|
918
|
-
if (typeof createLoginTerminal !== 'function') return res.status(501).json({ ok: false, code: 'LOGIN_UNAVAILABLE', error: 'Login terminals are not wired' });
|
|
1092
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
1093
|
+
if (typeof claudeSignIn !== 'function' && typeof createLoginTerminal !== 'function') return res.status(501).json({ ok: false, code: 'LOGIN_UNAVAILABLE', error: 'Login terminals are not wired' });
|
|
919
1094
|
try {
|
|
920
|
-
|
|
1095
|
+
const row = claudeAccounts.get(req.params.id);
|
|
1096
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Claude account ${req.params.id}` });
|
|
921
1097
|
res.json({ ok: true, login: await startClaudeLogin(req.params.id) });
|
|
922
1098
|
} catch (error) { sendError(res, error); }
|
|
923
1099
|
});
|
|
924
|
-
|
|
925
|
-
|
|
1100
|
+
// Rename (`label`) and/or choose what the account's sessions use (`mode`: 'subscription' | 'apiKey').
|
|
1101
|
+
router.patch('/claude/accounts/:id', async (req, res) => {
|
|
1102
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
926
1103
|
try {
|
|
927
|
-
const
|
|
1104
|
+
const row = claudeAccounts.get(req.params.id);
|
|
1105
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Claude account ${req.params.id}` });
|
|
1106
|
+
const { label, mode } = req.body || {};
|
|
1107
|
+
if (label === undefined && mode === undefined) return res.status(400).json({ ok: false, code: 'NOTHING_TO_CHANGE', error: 'Send a label or a mode.' });
|
|
1108
|
+
if (label !== undefined) claudeAccounts.rename(req.params.id, label);
|
|
1109
|
+
if (mode !== undefined) {
|
|
1110
|
+
if (typeof claudeSetMode === 'function') await claudeSetMode(req.params.id, mode);
|
|
1111
|
+
else claudeAccounts.setMode(req.params.id, mode);
|
|
1112
|
+
}
|
|
928
1113
|
catalogCache = null;
|
|
929
1114
|
broadcastSync({ type: 'assistant:accounts-changed', provider: 'claude-code', accountId: req.params.id });
|
|
930
|
-
res.json({ ok: true, account });
|
|
1115
|
+
res.json({ ok: true, account: claudeAccounts.describeForClient ? claudeAccounts.describeForClient(req.params.id) : claudeAccounts.get(req.params.id) });
|
|
931
1116
|
} catch (error) { sendError(res, error); }
|
|
932
1117
|
});
|
|
933
1118
|
router.delete('/claude/accounts/:id', (req, res) => {
|
|
934
|
-
if (!claudeAccounts) return res
|
|
1119
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
935
1120
|
try {
|
|
936
1121
|
const result = claudeAccounts.remove(req.params.id, { inUse: accountInUse('claude-code') });
|
|
937
1122
|
catalogCache = null;
|
|
@@ -939,30 +1124,89 @@ export function createAssistantApi({
|
|
|
939
1124
|
res.json({ ok: true, ...result });
|
|
940
1125
|
} catch (error) { sendError(res, error); }
|
|
941
1126
|
});
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1127
|
+
// The account a Claude Code worker uses when its dispatch names none. Signed-out accounts are refused.
|
|
1128
|
+
router.put('/claude/worker-account', (req, res) => {
|
|
1129
|
+
if (!claudeAccounts || !configStore?.patchWorkers) return claudeUnavailable(res);
|
|
1130
|
+
try {
|
|
1131
|
+
const wanted = req.body?.accountId === null || req.body?.accountId === undefined || req.body?.accountId === '' ? 'default' : String(req.body.accountId);
|
|
1132
|
+
const row = claudeAccounts.get(wanted);
|
|
1133
|
+
if (!row || row.id !== wanted) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Claude account ${wanted}` });
|
|
1134
|
+
const unusable = claudeAccounts.unusableReason?.(wanted) || '';
|
|
1135
|
+
if (unusable) return res.status(409).json({ ok: false, code: 'ACCOUNT_SIGNED_OUT', error: unusable });
|
|
1136
|
+
const workers = configStore.patchWorkers({ claudeAccount: wanted });
|
|
1137
|
+
catalogCache = null;
|
|
1138
|
+
broadcastSync({ type: 'assistant:accounts-changed', provider: 'claude-code', accountId: wanted, worker: true });
|
|
1139
|
+
res.json({ ok: true, workerAccountId: workers.claudeAccount });
|
|
1140
|
+
} catch (error) { sendError(res, error); }
|
|
1141
|
+
});
|
|
1142
|
+
const codexUnavailable = (res) => res.status(503).json({ ok: false, code: 'ACCOUNTS_UNAVAILABLE', error: 'Codex accounts are not available' });
|
|
1143
|
+
// Every Codex account as every surface shows it (lib/codex-accounts.js describe()): mode, signed in,
|
|
1144
|
+
// identity (an email or a masked key, never a key). `?refresh=1` asks Codex about each own store first.
|
|
1145
|
+
router.get('/codex/accounts', async (req, res) => {
|
|
1146
|
+
if (!codexAccounts) return codexUnavailable(res);
|
|
1147
|
+
try {
|
|
1148
|
+
if (req.query.refresh === '1' || req.query.refresh === 'true') await codexAccounts.refreshStatus?.(null, { force: true });
|
|
1149
|
+
else Promise.resolve(codexAccounts.refreshStatus?.()).catch(() => {});
|
|
1150
|
+
await accountUsage?.refresh('codex');
|
|
1151
|
+
res.json({ ok: true, accounts: accountUsage?.list()?.codex || codexAccounts.listForClient(), workerAccountId: codexWorkerAccountId() });
|
|
1152
|
+
} catch (error) { sendError(res, error); }
|
|
945
1153
|
});
|
|
1154
|
+
// `codex login` under the account's CODEX_HOME, in a SynaBun terminal (the panel's sign-in). The account
|
|
1155
|
+
// list says when it is signed in (codex:accounts-changed).
|
|
1156
|
+
async function startCodexLogin(accountId) {
|
|
1157
|
+
if (typeof codexSignIn !== 'function') throw Object.assign(new Error('Login terminals are not wired'), { status: 501, code: 'LOGIN_UNAVAILABLE' });
|
|
1158
|
+
const terminal = await codexSignIn(accountId);
|
|
1159
|
+
if (terminal?.status === 'error') throw Object.assign(new Error(terminal.error || 'Could not open the sign-in terminal.'), { status: 500, code: 'LOGIN_FAILED' });
|
|
1160
|
+
return { mode: 'terminal', terminalSessionId: terminal?.sessionId || null, status: terminal?.status || 'pending', profile: 'shell' };
|
|
1161
|
+
}
|
|
1162
|
+
// Create an account. `login: true` also opens its ChatGPT sign-in in a SynaBun terminal; otherwise the
|
|
1163
|
+
// caller offers the two ways to sign it in (ChatGPT, or an API key pasted into it).
|
|
1164
|
+
router.post('/codex/accounts', async (req, res) => {
|
|
1165
|
+
if (!codexAccounts?.create) return codexUnavailable(res);
|
|
1166
|
+
try {
|
|
1167
|
+
const account = codexAccounts.create({ label: req.body?.label });
|
|
1168
|
+
const login = req.body?.login === true ? await startCodexLogin(account.id) : null;
|
|
1169
|
+
catalogCache = null;
|
|
1170
|
+
res.status(201).json({ ok: true, account: codexAccounts.describeForClient?.(account.id) || account, login });
|
|
1171
|
+
} catch (error) { sendError(res, error); }
|
|
1172
|
+
});
|
|
1173
|
+
// Older pages: create the account and open its ChatGPT sign-in at once.
|
|
946
1174
|
router.post('/codex/accounts/add-start', async (req, res) => {
|
|
947
|
-
if (!codexAccounts?.
|
|
1175
|
+
if (!codexAccounts?.create) return codexUnavailable(res);
|
|
948
1176
|
try {
|
|
949
|
-
const
|
|
1177
|
+
const account = codexAccounts.create({ label: req.body?.label });
|
|
1178
|
+
const login = await startCodexLogin(account.id).catch((error) => ({ error: error.message }));
|
|
950
1179
|
catalogCache = null;
|
|
951
|
-
|
|
952
|
-
|
|
1180
|
+
res.status(201).json({ ok: true, accountId: account.id, account: codexAccounts.describeForClient?.(account.id) || account, login });
|
|
1181
|
+
} catch (error) { sendError(res, error); }
|
|
1182
|
+
});
|
|
1183
|
+
router.post('/codex/accounts/:id/login', async (req, res) => {
|
|
1184
|
+
if (!codexAccounts) return codexUnavailable(res);
|
|
1185
|
+
try {
|
|
1186
|
+
const row = codexAccounts.describe?.(req.params.id);
|
|
1187
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Codex account ${req.params.id}` });
|
|
1188
|
+
res.json({ ok: true, login: await startCodexLogin(req.params.id) });
|
|
953
1189
|
} catch (error) { sendError(res, error); }
|
|
954
1190
|
});
|
|
955
|
-
|
|
956
|
-
|
|
1191
|
+
// Rename (`label`) and/or choose what the account's sessions use (`mode`: 'subscription' (ChatGPT) | 'apiKey').
|
|
1192
|
+
router.patch('/codex/accounts/:id', async (req, res) => {
|
|
1193
|
+
if (!codexAccounts) return codexUnavailable(res);
|
|
957
1194
|
try {
|
|
958
|
-
const
|
|
1195
|
+
const row = codexAccounts.describe?.(req.params.id);
|
|
1196
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Codex account ${req.params.id}` });
|
|
1197
|
+
const { label, mode } = req.body || {};
|
|
1198
|
+
if (label !== undefined) codexAccounts.rename(req.params.id, label);
|
|
1199
|
+
if (mode !== undefined) {
|
|
1200
|
+
if (typeof codexSetMode === 'function') await codexSetMode(req.params.id, mode);
|
|
1201
|
+
else codexAccounts.setMode(req.params.id, mode);
|
|
1202
|
+
}
|
|
959
1203
|
catalogCache = null;
|
|
960
1204
|
broadcastSync({ type: 'assistant:accounts-changed', provider: 'codex', accountId: req.params.id });
|
|
961
|
-
res.json({ ok: true, account });
|
|
1205
|
+
res.json({ ok: true, account: codexAccounts.describeForClient(req.params.id) });
|
|
962
1206
|
} catch (error) { sendError(res, error); }
|
|
963
1207
|
});
|
|
964
1208
|
router.delete('/codex/accounts/:id', (req, res) => {
|
|
965
|
-
if (!codexAccounts?.remove) return res
|
|
1209
|
+
if (!codexAccounts?.remove) return codexUnavailable(res);
|
|
966
1210
|
try {
|
|
967
1211
|
const result = codexAccounts.remove(req.params.id, { inUse: accountInUse('codex') });
|
|
968
1212
|
catalogCache = null;
|
|
@@ -970,6 +1214,21 @@ export function createAssistantApi({
|
|
|
970
1214
|
res.json({ ok: true, ...result });
|
|
971
1215
|
} catch (error) { sendError(res, error); }
|
|
972
1216
|
});
|
|
1217
|
+
// The account a Codex worker uses when its dispatch names none. Signed-out accounts are refused.
|
|
1218
|
+
router.put('/codex/worker-account', (req, res) => {
|
|
1219
|
+
if (!codexAccounts || !configStore?.patchWorkers) return codexUnavailable(res);
|
|
1220
|
+
try {
|
|
1221
|
+
const wanted = req.body?.accountId === null || req.body?.accountId === undefined || req.body?.accountId === '' ? 'default' : String(req.body.accountId);
|
|
1222
|
+
const row = codexAccounts.describe?.(wanted);
|
|
1223
|
+
if (!row || row.id !== wanted) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Codex account ${wanted}` });
|
|
1224
|
+
const unusable = codexAccounts.unusableReason?.(wanted) || '';
|
|
1225
|
+
if (unusable) return res.status(409).json({ ok: false, code: 'ACCOUNT_SIGNED_OUT', error: unusable });
|
|
1226
|
+
const workers = configStore.patchWorkers({ codexAccount: wanted });
|
|
1227
|
+
catalogCache = null;
|
|
1228
|
+
broadcastSync({ type: 'assistant:accounts-changed', provider: 'codex', accountId: wanted, worker: true });
|
|
1229
|
+
res.json({ ok: true, workerAccountId: workers.codexAccount });
|
|
1230
|
+
} catch (error) { sendError(res, error); }
|
|
1231
|
+
});
|
|
973
1232
|
|
|
974
1233
|
// ── sidepanel tab registry (UI heartbeat) ──────────────────────────────────
|
|
975
1234
|
function listTabs() {
|