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
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
// owner's messages; a synabun.mailbox event is RUNNING(mailbox), a turn the
|
|
14
14
|
// CLI starts itself RUNNING(background), and a turn nobody here submitted (a
|
|
15
15
|
// prompt typed on the desktop) RUNNING(external) — tracked, never forwarded.
|
|
16
|
+
// A task the router continues on another model (a synabun.continuation event:
|
|
17
|
+
// a route decided automatically, by a remembered route, or on a card) answers
|
|
18
|
+
// where its prompt came from: RUNNING(followup) for a prompt from the phone,
|
|
19
|
+
// whose answer is sent once as the reply (not an update, whatever "Forward
|
|
20
|
+
// background results" says), RUNNING(external) for any other. A follow-up is
|
|
21
|
+
// how a turn's answer is delivered, nothing more: what may run unasked or be
|
|
22
|
+
// approved is the runtime's (a continuation is its own turn, never the owner's
|
|
23
|
+
// message: computer use asks once in it) and the owner's typed answers.
|
|
16
24
|
// The turn object exists before submit is called: brain events can arrive
|
|
17
25
|
// before it resolves.
|
|
18
26
|
//
|
|
@@ -23,8 +31,9 @@
|
|
|
23
31
|
// closes WITHOUT being granted (denied, cancelled) and the message goes to the
|
|
24
32
|
// Assistant as the next prompt. An approval is only ever granted by a "yes"
|
|
25
33
|
// the bridge matched on a message the owner typed, aimed at that request while
|
|
26
|
-
// it is open: never by the model, by a forwarded message, by a
|
|
27
|
-
//
|
|
34
|
+
// it is open: never by the model, by a forwarded message, by a voice note (its
|
|
35
|
+
// transcript is an ordinary message, `voice: true`), by a reply that quotes
|
|
36
|
+
// something else or by anything that merely looks like an answer.
|
|
28
37
|
// Messages that arrive while a turn runs wait as one merged prompt and go in
|
|
29
38
|
// as soon as it ends; a queue watch lets go of a turn whose end never came.
|
|
30
39
|
// A request that appears while such a message waits closes at once, unseen and
|
|
@@ -87,14 +96,28 @@ export const BRIDGE_LIMITS = Object.freeze({
|
|
|
87
96
|
policyTickMs: 30_000,
|
|
88
97
|
});
|
|
89
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Why a voice note was not transcribed (the host's unsupported.reason), told to the phone once per
|
|
101
|
+
* reason every unsupportedEveryMs. Anything else unsupported gets the generic line.
|
|
102
|
+
*/
|
|
103
|
+
export const AUDIO_NOT_TRANSCRIBED = Object.freeze({
|
|
104
|
+
transcription_disabled: 'Voice notes are not transcribed: that is turned off in Settings → WhatsApp on your computer. Type your message instead.',
|
|
105
|
+
transcription_unavailable: 'I can\'t transcribe voice notes yet: this computer needs whisper.cpp, its speech model and FFmpeg (Settings → WhatsApp says what is missing). Type your message instead.',
|
|
106
|
+
too_large: 'That voice note is too large for me to transcribe (20 MB at most). Send a shorter one, or type it.',
|
|
107
|
+
too_long: 'That voice note is too long for me to transcribe (10 minutes at most). Send a shorter one, or type it.',
|
|
108
|
+
stale: 'That voice note was sent a while ago, so I did not transcribe it. Send it again if you still need it.',
|
|
109
|
+
transcription_failed: 'I couldn\'t transcribe that voice note. Try again, or type your message.',
|
|
110
|
+
});
|
|
111
|
+
|
|
90
112
|
/** Send refusals that mean "not now": the reply waits in the outbox for the next connect. */
|
|
91
113
|
const RESEND_ON_CONNECT = /DISCONNECT|NOT_CONNECTED|OFFLINE|RECONNECT|CLOSED|TIMEOUT|HOST_UNAVAILABLE/i;
|
|
92
114
|
/**
|
|
93
115
|
* What happens to a reply the transport refused: 'connect' (it waits for the
|
|
94
116
|
* next connect), 'later' (THROTTLED: sent again after its retryAfterMs) or
|
|
95
|
-
* 'drop'. PAUSED (an owner anomaly paused sending) and HELD (
|
|
96
|
-
*
|
|
97
|
-
*
|
|
117
|
+
* 'drop'. PAUSED (an owner anomaly paused sending) and HELD (a connector
|
|
118
|
+
* process of another SynaBun version, down until a restart; a crash is not
|
|
119
|
+
* HELD: it reads HOST_UNAVAILABLE and the reply waits) drop it: the service
|
|
120
|
+
* records why in the activity log and the Settings tab shows the state.
|
|
98
121
|
*/
|
|
99
122
|
export function outboxPlan(code) {
|
|
100
123
|
const value = String(code || '');
|
|
@@ -104,7 +127,7 @@ export function outboxPlan(code) {
|
|
|
104
127
|
|
|
105
128
|
const DEFAULT_CONFIG = Object.freeze({
|
|
106
129
|
enabled: true, level: 'ask', progress: 'key', forwardBackground: true, replyLabel: null, maxMessages: 3,
|
|
107
|
-
rotation: 'idle6h', strictWorkerApprovals: false, computerUse: false, mode: 'self', paused: false, pausedBy: null,
|
|
130
|
+
rotation: 'idle6h', strictWorkerApprovals: false, computerUse: false, mode: 'self', paused: false, pausedBy: null,
|
|
108
131
|
});
|
|
109
132
|
const ALREADY_ANSWERED = new Set(['CONTROL_UNKNOWN', 'ROUTE_ALREADY_DECIDED', 'ROUTE_NOT_FOUND', 'CLARIFY_ALREADY_ANSWERED', 'CLARIFY_NOT_FOUND', 'PERMISSION_ALREADY_RESOLVED', 'NO_PENDING_REQUEST', 'RUN_NOT_FOUND']);
|
|
110
133
|
const ROUTE_DONE_PHASES = new Set(['decided', 'auto', 'expired', 'declined', 'cancelled']);
|
|
@@ -191,6 +214,8 @@ export function createWhatsAppBridge({
|
|
|
191
214
|
outboxTimer: null, // a THROTTLED reply's retry
|
|
192
215
|
staleSkipped: 0,
|
|
193
216
|
staleTimer: null,
|
|
217
|
+
stoppedAt: 0, // when the owner's last /stop was sent (its message time)
|
|
218
|
+
stopLateNoted: false, // one note per /stop for what was sent before it and arrived after it
|
|
194
219
|
unsupportedAt: new Map(),
|
|
195
220
|
minuteWindow: { start: 0, count: 0, noticed: false },
|
|
196
221
|
dayWindow: { start: 0, count: 0, noticed: false },
|
|
@@ -435,14 +460,16 @@ export function createWhatsAppBridge({
|
|
|
435
460
|
state.brainApplied = { sessionId, signature };
|
|
436
461
|
persistBridgeState();
|
|
437
462
|
}
|
|
463
|
+
// Autonomous has no time limit on WhatsApp: the policy carries no autonomousUntil (lib/remote-policy.js effectiveLevel).
|
|
464
|
+
// WhatsApp turned off on this computer (`enabled: false`, which also stops the connector) ends computer use like the switch.
|
|
438
465
|
function remoteFromConfig(cfg = readConfig()) {
|
|
439
|
-
return { channel: 'whatsapp', level: cfg.level || 'ask', strictWorkerApprovals: cfg.strictWorkerApprovals === true,
|
|
466
|
+
return { channel: 'whatsapp', level: cfg.level || 'ask', strictWorkerApprovals: cfg.strictWorkerApprovals === true, paused: state.paused, computerUse: cfg.computerUse === true && cfg.enabled !== false && state.computerEnded !== true };
|
|
440
467
|
}
|
|
441
468
|
/** The policy of one of our sessions: computer use is the current conversation's alone. */
|
|
442
469
|
function remoteFor(id, base = remoteFromConfig()) {
|
|
443
470
|
return id === state.sessionId ? base : { ...base, computerUse: false };
|
|
444
471
|
}
|
|
445
|
-
const policySignature = (remote) => JSON.stringify([remote.level, remote.strictWorkerApprovals, remote.
|
|
472
|
+
const policySignature = (remote) => JSON.stringify([remote.level, remote.strictWorkerApprovals, remote.paused, remote.computerUse]);
|
|
446
473
|
async function createSession() {
|
|
447
474
|
const rt = runtime();
|
|
448
475
|
if (!rt?.createSession) throw Object.assign(new Error('The Assistant is not available right now.'), { code: 'RUNTIME_UNAVAILABLE' });
|
|
@@ -500,9 +527,8 @@ export function createWhatsAppBridge({
|
|
|
500
527
|
// ── policy ─────────────────────────────────────────────────────────────────
|
|
501
528
|
/**
|
|
502
529
|
* Keep the level of every WhatsApp session in line with the config (level,
|
|
503
|
-
* strict approvals,
|
|
504
|
-
*
|
|
505
|
-
* runtime re-apply the clamp and stop runs above it.
|
|
530
|
+
* strict approvals, pause, computer use). A change re-registers, which makes
|
|
531
|
+
* the runtime re-apply the clamp and stop runs above it.
|
|
506
532
|
*/
|
|
507
533
|
function syncPolicies() {
|
|
508
534
|
const cfg = readConfig();
|
|
@@ -652,7 +678,8 @@ export function createWhatsAppBridge({
|
|
|
652
678
|
if (turn.kind === 'followup') state.followUp = null;
|
|
653
679
|
else { presence('paused'); state.inflight = null; persistBridgeState(); react(turn.sourceIds, 'done'); }
|
|
654
680
|
} else if (turn.kind === 'followup') {
|
|
655
|
-
// The task the phone decided on a card
|
|
681
|
+
// The task the phone decided on a card, or that its prompt carried on to another model: its answer
|
|
682
|
+
// is a reply, not an update. Said once: the turn is gone already, a repeated end finds nothing.
|
|
656
683
|
state.followUp = null;
|
|
657
684
|
const text = finalText(turn);
|
|
658
685
|
if (outcome === 'failed') await say(`Something went wrong: ${String(message || turn.resultError || 'the turn failed').slice(0, 400)}`, { maxChunks: 1 });
|
|
@@ -767,6 +794,12 @@ export function createWhatsAppBridge({
|
|
|
767
794
|
// Our own prompt (the WhatsApp turn exists already); any other is someone else's turn.
|
|
768
795
|
if (ev.origin !== 'whatsapp' && !state.turns.has(sessionId)) makeTurn(sessionId, 'external');
|
|
769
796
|
return;
|
|
797
|
+
case 'synabun.continuation':
|
|
798
|
+
// The router carries a task on (another model, the same conversation), before any of its output.
|
|
799
|
+
// A prompt from the phone: its answer is the reply to the phone. Anything else stays as before:
|
|
800
|
+
// someone else's turn, unless a card the phone saw was just decided (ensureTurn).
|
|
801
|
+
if (!state.turns.has(sessionId)) ensureTurn(sessionId, ev.origin === 'whatsapp' ? 'followup' : 'external');
|
|
802
|
+
return;
|
|
770
803
|
case 'synabun.mailbox': {
|
|
771
804
|
const items = Array.isArray(ev.items) ? ev.items : [];
|
|
772
805
|
const turn = state.turns.get(sessionId);
|
|
@@ -1031,6 +1064,10 @@ export function createWhatsAppBridge({
|
|
|
1031
1064
|
case 'help': await say(HELP_TEXT, { maxChunks: 2 }); return;
|
|
1032
1065
|
case 'status': await say(statusText(statusSnapshot()), { maxChunks: 1 }); return;
|
|
1033
1066
|
case 'stop': {
|
|
1067
|
+
// What was sent before this /stop and arrives after it (a voice note still being transcribed, and
|
|
1068
|
+
// what waited behind it) is not acted on, as /stop clears what already waits in line.
|
|
1069
|
+
state.stoppedAt = Math.max(state.stoppedAt, tsMs(message.ts, now()));
|
|
1070
|
+
state.stopLateNoted = false;
|
|
1034
1071
|
const { stoppedRuns, clearedQueue } = await stopEverything({ reason: 'phone' });
|
|
1035
1072
|
const n = stoppedRuns + clearedQueue;
|
|
1036
1073
|
await say(`Stopped. Cancelled ${n} task${n === 1 ? '' : 's'}.`, { maxChunks: 1 });
|
|
@@ -1067,18 +1104,14 @@ export function createWhatsAppBridge({
|
|
|
1067
1104
|
function statusSnapshot() {
|
|
1068
1105
|
const view = state.sessionId ? sessionView(state.sessionId) : null;
|
|
1069
1106
|
const turn = state.sessionId ? state.turns.get(state.sessionId) : null;
|
|
1070
|
-
const rt = runtime();
|
|
1071
|
-
let brainCapUsd = null;
|
|
1072
|
-
try { brainCapUsd = rt?.brainCapUsd?.() ?? null; } catch { brainCapUsd = null; }
|
|
1073
1107
|
const cfg = readConfig();
|
|
1074
1108
|
const current = currentCard();
|
|
1075
1109
|
return {
|
|
1076
1110
|
connected: isConnected(), sessionId: state.sessionId, title: view?.title || null, brain: view?.brain || null,
|
|
1077
1111
|
running: !!turn || !!view?.running, turnKind: turn?.kind || null, queued: state.queue.length,
|
|
1078
1112
|
pendingCards: state.cards.size, currentCard: current ? (current.kind === 'route' ? 'where to run a task' : current.kind === 'computer' ? 'controlling your Mac' : current.kind) : null,
|
|
1079
|
-
budget: view?.budget || null, brainUsd: view?.costUsd ?? null,
|
|
1113
|
+
budget: view?.budget || null, brainUsd: view?.costUsd ?? null,
|
|
1080
1114
|
level: state.sessionId ? levelOf(state.sessionId) : cfg.level, paused: state.paused, pausedBy: state.pausedBy,
|
|
1081
|
-
autonomousUntil: cfg.autonomousUntil || null,
|
|
1082
1115
|
};
|
|
1083
1116
|
}
|
|
1084
1117
|
/** /stop and stopAll: the turn, the session's runs, open cards and the queue. */
|
|
@@ -1168,6 +1201,16 @@ export function createWhatsAppBridge({
|
|
|
1168
1201
|
// `backlog`): never acted on, only counted for one note. Checked first, so a
|
|
1169
1202
|
// backlog neither uses up the rate limits nor counts as activity.
|
|
1170
1203
|
if (now() - tsMs(message.ts, now()) > L.staleMs) { noteStale(); return; }
|
|
1204
|
+
// Sent before the owner's last /stop, delivered after it (a typed /stop does not wait for a voice note
|
|
1205
|
+
// being transcribed): /stop already stopped it. A command still runs. What WhatsApp held while SynaBun was
|
|
1206
|
+
// offline arrives in order and is never overtaken (a backlog is never sent ahead), so it is not affected.
|
|
1207
|
+
if (state.stoppedAt && message.backlog !== true && tsMs(message.ts, now()) < state.stoppedAt && !parseCommand(message)) {
|
|
1208
|
+
if (!state.stopLateNoted) {
|
|
1209
|
+
state.stopLateNoted = true;
|
|
1210
|
+
await say('A message you sent before /stop arrived after it, so I did not act on it. Send it again if you still need it.', { maxChunks: 1 });
|
|
1211
|
+
}
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1171
1214
|
if (rateLimited()) return;
|
|
1172
1215
|
const activityBefore = state.lastActivityAt;
|
|
1173
1216
|
state.lastActivityAt = now();
|
|
@@ -1178,10 +1221,12 @@ export function createWhatsAppBridge({
|
|
|
1178
1221
|
const images = Array.isArray(message.images) ? message.images : [];
|
|
1179
1222
|
if (message.unsupported && !text.trim() && !images.length) {
|
|
1180
1223
|
const type = String(message.unsupported.type || 'this kind of');
|
|
1181
|
-
const
|
|
1224
|
+
const audio = type === 'audio' && Object.hasOwn(AUDIO_NOT_TRANSCRIBED, String(message.unsupported.reason)) ? AUDIO_NOT_TRANSCRIBED[message.unsupported.reason] : null;
|
|
1225
|
+
const key = audio ? `audio:${message.unsupported.reason}` : type;
|
|
1226
|
+
const last = state.unsupportedAt.get(key) || 0;
|
|
1182
1227
|
if (now() - last >= L.unsupportedEveryMs) {
|
|
1183
|
-
state.unsupportedAt.set(
|
|
1184
|
-
await say(`I can read text and pictures for now; ${type} messages are not supported yet.`, { maxChunks: 1 });
|
|
1228
|
+
state.unsupportedAt.set(key, now());
|
|
1229
|
+
await say(audio || `I can read text and pictures for now; ${type} messages are not supported yet.`, { maxChunks: 1 });
|
|
1185
1230
|
}
|
|
1186
1231
|
return;
|
|
1187
1232
|
}
|
|
@@ -1198,8 +1243,8 @@ export function createWhatsAppBridge({
|
|
|
1198
1243
|
}
|
|
1199
1244
|
// Something is waiting for the owner: a clear answer answers it; anything else closes it
|
|
1200
1245
|
// without a grant and carries on as the next prompt. Only words the owner typed can answer:
|
|
1201
|
-
// never a forwarded message, a picture or "//text".
|
|
1202
|
-
const answerable = !images.length && command?.kind !== 'literal' && message.forwarded !== true;
|
|
1246
|
+
// never a forwarded message, a picture, a voice note (its transcript) or "//text".
|
|
1247
|
+
const answerable = !images.length && command?.kind !== 'literal' && message.forwarded !== true && message.voice !== true;
|
|
1203
1248
|
if (await replyToPending(message, { answerable })) return;
|
|
1204
1249
|
if (rotationDue(activityBefore)) state.rotateOnNextSubmit = true;
|
|
1205
1250
|
// A request raised between that look and here is older than this message too (onCard only sees
|
|
@@ -1271,7 +1316,7 @@ export function createWhatsAppBridge({
|
|
|
1271
1316
|
if (!list.length) return;
|
|
1272
1317
|
let composed = null;
|
|
1273
1318
|
try { composed = inbound?.composePrompt ? inbound.composePrompt(list, { rng }) : null; } catch (error) { log('whatsapp:bridge', `compose: ${error?.message || error}`); }
|
|
1274
|
-
if (!composed) composed = { text: list.map((part) => String(part.text || '')).filter(Boolean).join('\n\n'), images: list.flatMap((part) => part.images || []), sourceIds: list.map((part) => part.id).filter(Boolean), untrusted: list.some((part) => part.forwarded || part.quoted) };
|
|
1319
|
+
if (!composed) composed = { text: list.map((part) => String(part.text || '')).filter(Boolean).join('\n\n'), images: list.flatMap((part) => part.images || []), sourceIds: list.map((part) => part.id).filter(Boolean), untrusted: list.some((part) => part.forwarded || part.quoted || part.audioFile === true) };
|
|
1275
1320
|
const images = Array.isArray(composed.images) ? composed.images.filter(Boolean) : [];
|
|
1276
1321
|
const job = {
|
|
1277
1322
|
text: String(composed.text || ''), images,
|
|
@@ -1402,22 +1447,21 @@ export function createWhatsAppBridge({
|
|
|
1402
1447
|
}
|
|
1403
1448
|
persistBridgeState();
|
|
1404
1449
|
react(job.sourceIds, 'failed');
|
|
1405
|
-
if (code === 'SESSION_BUDGET_EXCEEDED' || code === '
|
|
1450
|
+
if (code === 'SESSION_BUDGET_EXCEEDED' || code === 'BRAIN_SHARE_EXHAUSTED') { await say(budgetText(code, sessionId, result.message), { maxChunks: 1 }); return; }
|
|
1406
1451
|
if (code === 'EMPTY_PROMPT') return;
|
|
1407
1452
|
await say(`Could not start that: ${String(result.message || code).slice(0, 400)}`, { maxChunks: 1 });
|
|
1408
1453
|
}
|
|
1409
|
-
/** Spend against the
|
|
1454
|
+
/** Spend against the budget, and the two ways on: /new or a higher budget. Never a silent new session. */
|
|
1410
1455
|
function budgetText(code, sessionId, fallback) {
|
|
1411
1456
|
const view = sessionView(sessionId);
|
|
1457
|
+
const budget = view?.budget || null;
|
|
1412
1458
|
let line = '';
|
|
1413
|
-
if (code === 'SESSION_BUDGET_EXCEEDED' &&
|
|
1414
|
-
line = `This conversation spent ${usd(
|
|
1415
|
-
} else if (code === '
|
|
1416
|
-
|
|
1417
|
-
try { cap = runtime()?.brainCapUsd?.() ?? null; } catch { cap = null; }
|
|
1418
|
-
line = cap !== null ? `The Assistant spent ${usd(view?.costUsd)} of its ${usd(cap)} cap in this conversation.` : '';
|
|
1459
|
+
if (code === 'SESSION_BUDGET_EXCEEDED' && budget && Number.isFinite(Number(budget.amountUsd))) {
|
|
1460
|
+
line = `This conversation spent ${usd(budget.totalUsd)} of its ${usd(budget.amountUsd)} budget.`;
|
|
1461
|
+
} else if (code === 'BRAIN_SHARE_EXHAUSTED' && budget && Number.isFinite(Number(budget.amountUsd))) {
|
|
1462
|
+
line = `The Assistant's share of this conversation's ${usd(budget.amountUsd)} budget is used up.`;
|
|
1419
1463
|
}
|
|
1420
|
-
return `${line || String(fallback || 'This conversation reached its budget
|
|
1464
|
+
return `${line || String(fallback || 'This conversation reached its budget.')} Send /new to start a fresh conversation, or raise the budget (or choose No limit) in SynaBun.`;
|
|
1421
1465
|
}
|
|
1422
1466
|
|
|
1423
1467
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -1484,14 +1528,14 @@ export function createWhatsAppBridge({
|
|
|
1484
1528
|
persistBridgeState();
|
|
1485
1529
|
}
|
|
1486
1530
|
|
|
1487
|
-
// Keep the level in line with the config (
|
|
1531
|
+
// Keep the level in line with the config (a change that reached it without a refresh).
|
|
1488
1532
|
state.policyTimer = T.setInterval(() => { try { runtime(); syncPolicies(); } catch {} }, L.policyTickMs);
|
|
1489
1533
|
state.policyTimer?.unref?.();
|
|
1490
1534
|
runtime();
|
|
1491
1535
|
|
|
1492
1536
|
return {
|
|
1493
1537
|
onInbound, onConnection, status, newSession, pause, resume, stopAll, shutdown,
|
|
1494
|
-
// Settings changed (level, strict approvals,
|
|
1538
|
+
// Settings changed (level, strict approvals, computer use): re-apply now instead of at the next tick.
|
|
1495
1539
|
refresh: () => { state.policySeen.clear(); runtime(); syncPolicies(); return status(); },
|
|
1496
1540
|
endComputerUse,
|
|
1497
1541
|
_internals: { state, finalText, syncPolicies, drain, onPacket, brainTarget, moveOn },
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// SynaBun — WhatsApp Link: the owner's slash commands
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
4
|
//
|
|
5
|
-
// Pure parsing and fixed texts. A command is the owner's own text —
|
|
6
|
-
// forwarded or quoted message — that starts with
|
|
7
|
-
// (case-insensitive). Commands run at once, busy or paused.
|
|
8
|
-
// a prompt; "//x" sends the literal "/x". No command raises the level or
|
|
5
|
+
// Pure parsing and fixed texts. A command is the owner's own typed text —
|
|
6
|
+
// never a forwarded or quoted message, never a voice note — that starts with
|
|
7
|
+
// one of COMMANDS (case-insensitive). Commands run at once, busy or paused.
|
|
8
|
+
// Any other "/x" is a prompt; "//x" sends the literal "/x". No command raises the level or
|
|
9
9
|
// changes a setting: those names get a fixed refusal (they would otherwise
|
|
10
10
|
// reach the brain's CLI as its own slash commands).
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ const REFUSED = new Set([
|
|
|
21
21
|
|
|
22
22
|
export const HELP_TEXT = [
|
|
23
23
|
'*SynaBun on WhatsApp*',
|
|
24
|
-
'Write to me like you would in SynaBun. Pictures work too.',
|
|
24
|
+
'Write to me like you would in SynaBun. Pictures work too, and voice notes when this computer can transcribe them.',
|
|
25
25
|
'',
|
|
26
26
|
'/status — what I am doing, spend and level',
|
|
27
27
|
'/stop — stop the current task, its agents and anything waiting for your answer',
|
|
@@ -31,7 +31,7 @@ export const HELP_TEXT = [
|
|
|
31
31
|
'/cards — ask again what is still waiting for your answer',
|
|
32
32
|
'/help — this list',
|
|
33
33
|
'',
|
|
34
|
-
'When I ask something, just answer in your own words. An approval takes a plain yes or no; any other message approves nothing and carries on the conversation. Start with // to send text that begins with a slash.',
|
|
34
|
+
'When I ask something, just answer in your own words. An approval takes a plain yes or no, typed; any other message (a voice note too) approves nothing and carries on the conversation. Start with // to send text that begins with a slash.',
|
|
35
35
|
'Levels, models and other settings change only in SynaBun on your computer (Settings → WhatsApp).',
|
|
36
36
|
].join('\n');
|
|
37
37
|
|
|
@@ -40,10 +40,10 @@ export const SETTINGS_REFUSAL = 'Settings change only in SynaBun on your compute
|
|
|
40
40
|
/**
|
|
41
41
|
* An inbound message → { kind: 'command', name, args } | { kind: 'refused', name }
|
|
42
42
|
* | { kind: 'literal', text } ("//x" → "/x") | null (not a command: a prompt).
|
|
43
|
-
* Only the owner's own text counts: forwarded or quoted messages are prompts.
|
|
43
|
+
* Only the owner's own typed text counts: forwarded or quoted messages and voice notes are prompts.
|
|
44
44
|
*/
|
|
45
45
|
export function parseCommand(message) {
|
|
46
|
-
if (!message || message.owner === false || message.forwarded || message.quoted) return null;
|
|
46
|
+
if (!message || message.owner === false || message.forwarded || message.quoted || message.voice === true) return null;
|
|
47
47
|
const text = String(message.text ?? '').trim();
|
|
48
48
|
if (!text.startsWith('/')) return null;
|
|
49
49
|
if (text.startsWith('//')) return { kind: 'literal', text: text.slice(1) };
|
|
@@ -66,8 +66,8 @@ function levelLabel(level) {
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* /status text from a snapshot: { connected, sessionId, title, brain:{provider,model},
|
|
69
|
-
* running, turnKind, queued, pendingCards, currentCard, budget:{totalUsd,
|
|
70
|
-
* brainUsd,
|
|
69
|
+
* running, turnKind, queued, pendingCards, currentCard, budget:{ unlimited, totalUsd, amountUsd, leftUsd },
|
|
70
|
+
* brainUsd, level, paused, pausedBy }. Autonomous has no time limit: it holds until it is turned off in Settings.
|
|
71
71
|
*/
|
|
72
72
|
export function statusText(s = {}) {
|
|
73
73
|
const lines = ['*SynaBun status*'];
|
|
@@ -83,10 +83,9 @@ export function statusText(s = {}) {
|
|
|
83
83
|
: 'idle';
|
|
84
84
|
lines.push(`Now: ${doing}${s.queued ? ` · ${s.queued} message${s.queued === 1 ? '' : 's'} queued` : ''}`);
|
|
85
85
|
if (s.pendingCards) lines.push(`Waiting for you: ${s.pendingCards} question${s.pendingCards === 1 ? '' : 's'} or approval${s.pendingCards === 1 ? '' : 's'}${s.currentCard ? ` (${s.currentCard})` : ''} — /cards asks again`);
|
|
86
|
-
if (s.budget
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
lines.push(level);
|
|
86
|
+
if (s.budget?.unlimited === true) lines.push(`Spend: ${usd(s.budget.totalUsd)} (no limit)`);
|
|
87
|
+
else if (s.budget && Number.isFinite(Number(s.budget.amountUsd))) lines.push(`Spend: ${usd(s.budget.totalUsd)} of the ${usd(s.budget.amountUsd)} budget${Number.isFinite(Number(s.brainUsd)) ? ` (brain ${usd(s.brainUsd)})` : ''}`);
|
|
88
|
+
lines.push(`Level: ${levelLabel(s.level)}${s.level === 'autonomous' ? ' until turned off' : ''}`);
|
|
90
89
|
if (s.paused) lines.push(`Paused${s.pausedBy === 'desktop' ? ' from your computer' : ''} — ${s.pausedBy === 'desktop' ? 'resume it in SynaBun' : 'send /resume'}`);
|
|
91
90
|
return lines.join('\n');
|
|
92
91
|
}
|
|
@@ -10,12 +10,17 @@
|
|
|
10
10
|
// Two kinds of fields:
|
|
11
11
|
// user what Settings edits (update(), validated, versioned: a stale
|
|
12
12
|
// expectedVersion is a 409 like lib/assistant-config.js)
|
|
13
|
-
// service what SynaBun owns (mode, the conversation, the bound owner,
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// service what SynaBun owns (mode, the conversation, the bound owner,
|
|
14
|
+
// pause) — written by the service and the bridge through
|
|
15
|
+
// write(), refused in update().
|
|
16
16
|
// Raising the level to 'autonomous' never applies here: update() needs
|
|
17
17
|
// confirmEscalation and answers {pending:true}; the service applies it after
|
|
18
|
-
// the phone answers "ALLOW <code>". Lowering applies at once.
|
|
18
|
+
// the phone answers "ALLOW <code>". Lowering applies at once. Autonomous then
|
|
19
|
+
// stays on until the owner lowers it in Settings, restarts included: there is
|
|
20
|
+
// no time limit. `autonomousUntil` is a legacy field (the end of the 8-hour
|
|
21
|
+
// window of 2.0.5 and before): a level 'autonomous' stored with one was turned
|
|
22
|
+
// on under that rule and is read as 'ask' (migrateLegacyAutonomous), so an
|
|
23
|
+
// update never puts anyone back into Autonomous.
|
|
19
24
|
// `computerUse` is the owner's switch for controlling this Mac from a
|
|
20
25
|
// WhatsApp conversation: off by default, a plain boolean, changed only through
|
|
21
26
|
// update() (Settings on this computer: the guarded PUT /api/whatsapp/config).
|
|
@@ -25,9 +30,19 @@
|
|
|
25
30
|
// Assistant) or { provider, model, effort }. Its shape is checked here; the
|
|
26
31
|
// service checks the model and the effort against the Assistant's catalog
|
|
27
32
|
// before it calls update() (lib/whatsapp/brain.js).
|
|
33
|
+
// Voice notes (lib/whatsapp/transcription.js): `transcriptionEnabled`, the
|
|
34
|
+
// spoken `transcriptionLanguage` ('auto' or a 2-3 letter code) and three
|
|
35
|
+
// paths, '' meaning "find it by itself": the whisper-cli program, the ggml
|
|
36
|
+
// model and FFmpeg. A path is the absolute path of an existing regular file
|
|
37
|
+
// (a program executable), checked when update() stores it and again by the
|
|
38
|
+
// host right before it runs; it is never looked up on PATH. Only update()
|
|
39
|
+
// (Settings on this computer) sets a path: a service write may only clear one,
|
|
40
|
+
// so nothing but the person at this computer chooses what the host runs.
|
|
28
41
|
|
|
29
42
|
import { LEVELS } from '../remote-policy.js';
|
|
30
43
|
import { cleanBrainChoice } from './brain.js';
|
|
44
|
+
import { TRANSCRIPTION_LANGUAGE_RE } from './protocol.js';
|
|
45
|
+
import { checkToolPath } from './transcription.js';
|
|
31
46
|
|
|
32
47
|
export const WHATSAPP_CONFIG_KEY = 'whatsapp_config';
|
|
33
48
|
|
|
@@ -44,6 +59,11 @@ export const USER_DEFAULTS = Object.freeze({
|
|
|
44
59
|
strictWorkerApprovals: false,
|
|
45
60
|
computerUse: false,
|
|
46
61
|
brain: null,
|
|
62
|
+
transcriptionEnabled: true,
|
|
63
|
+
transcriptionLanguage: 'auto',
|
|
64
|
+
transcriptionEnginePath: '',
|
|
65
|
+
transcriptionModelPath: '',
|
|
66
|
+
transcriptionFfmpegPath: '',
|
|
47
67
|
});
|
|
48
68
|
|
|
49
69
|
export const SERVICE_DEFAULTS = Object.freeze({
|
|
@@ -51,7 +71,7 @@ export const SERVICE_DEFAULTS = Object.freeze({
|
|
|
51
71
|
sessionId: null,
|
|
52
72
|
previousSessionIds: Object.freeze([]),
|
|
53
73
|
owner: null, // { masked, boundAt, via } — never a number
|
|
54
|
-
autonomousUntil: null, //
|
|
74
|
+
autonomousUntil: null, // legacy: the end of 2.0.5's 8-hour Autonomous window; always null now (migrateLegacyAutonomous)
|
|
55
75
|
paused: false,
|
|
56
76
|
pausedBy: null, // 'desktop' | 'phone'
|
|
57
77
|
version: 0,
|
|
@@ -68,6 +88,11 @@ export const ROTATIONS = Object.freeze(['idle6h', 'daily', 'never']);
|
|
|
68
88
|
export const SELF_TRIGGERS = Object.freeze(['all', 'prefix']);
|
|
69
89
|
export const MODES = Object.freeze(['self', 'dedicated']);
|
|
70
90
|
export const REPLY_LABEL_MAX = 24;
|
|
91
|
+
/** The voice-note settings the host gets (service prefsOf → protocol.js PREFS_SPEC). */
|
|
92
|
+
export const TRANSCRIPTION_FIELDS = Object.freeze(['transcriptionEnabled', 'transcriptionLanguage', 'transcriptionEnginePath', 'transcriptionModelPath', 'transcriptionFfmpegPath']);
|
|
93
|
+
/** The path fields and what each must name (transcription.js checkToolPath). */
|
|
94
|
+
export const TOOL_PATH_FIELDS = Object.freeze({ transcriptionEnginePath: 'program', transcriptionModelPath: 'file', transcriptionFfmpegPath: 'program' });
|
|
95
|
+
const TOOL_PATH_MAX = 4096;
|
|
71
96
|
|
|
72
97
|
const RANK = Object.freeze({ 'read-only': 0, ask: 1, autonomous: 2 });
|
|
73
98
|
|
|
@@ -81,6 +106,28 @@ function configError(field, message, { code = 'CONFIG_INVALID', status = 400 } =
|
|
|
81
106
|
return error;
|
|
82
107
|
}
|
|
83
108
|
|
|
109
|
+
/** A tool path's shape: '' (find it by itself) or an absolute path of one line; undefined when it has neither shape. */
|
|
110
|
+
function cleanToolPath(value) {
|
|
111
|
+
if (value === null || value === undefined) return '';
|
|
112
|
+
if (typeof value !== 'string') return undefined;
|
|
113
|
+
const text = value.trim();
|
|
114
|
+
if (!text) return '';
|
|
115
|
+
if (text.length > TOOL_PATH_MAX || /[\u0000-\u001f\u007f]/.test(text)) return undefined;
|
|
116
|
+
return checkToolPath(text, 'program', { fs: SHAPE_ONLY_FS }) === 'not_absolute' ? undefined : text;
|
|
117
|
+
}
|
|
118
|
+
// checkToolPath against nothing on disk: only its shape rules run (absolute, not a network path).
|
|
119
|
+
const SHAPE_ONLY_FS = Object.freeze({ statSync: () => { throw new Error('shape only'); }, accessSync: () => {}, constants: {} });
|
|
120
|
+
|
|
121
|
+
const TOOL_PATH_PROBLEMS = Object.freeze({
|
|
122
|
+
not_absolute: 'must be an absolute path (it is never looked up on PATH)',
|
|
123
|
+
network: 'must be a path on this computer, not on a network share',
|
|
124
|
+
missing: 'names a file that does not exist',
|
|
125
|
+
not_file: 'must name a regular file',
|
|
126
|
+
not_executable: process.platform === 'win32' ? 'must name a program (.exe)' : 'names a file that is not executable (chmod +x it)',
|
|
127
|
+
unreadable: 'names a file this account cannot read',
|
|
128
|
+
empty: 'names an empty file',
|
|
129
|
+
});
|
|
130
|
+
|
|
84
131
|
/** A reply label: printable, one line, 1-24 characters; '' → null (the mode's default label). */
|
|
85
132
|
function cleanLabel(value) {
|
|
86
133
|
if (value === null || value === undefined) return null;
|
|
@@ -102,6 +149,8 @@ const CHECKS = {
|
|
|
102
149
|
activityText: (v) => typeof v === 'boolean',
|
|
103
150
|
strictWorkerApprovals: (v) => typeof v === 'boolean',
|
|
104
151
|
computerUse: (v) => typeof v === 'boolean',
|
|
152
|
+
transcriptionEnabled: (v) => typeof v === 'boolean',
|
|
153
|
+
transcriptionLanguage: (v) => typeof v === 'string' && TRANSCRIPTION_LANGUAGE_RE.test(v),
|
|
105
154
|
};
|
|
106
155
|
const MESSAGES = {
|
|
107
156
|
enabled: 'enabled must be true or false',
|
|
@@ -116,10 +165,21 @@ const MESSAGES = {
|
|
|
116
165
|
strictWorkerApprovals: 'strictWorkerApprovals must be true or false',
|
|
117
166
|
computerUse: 'computerUse must be true or false',
|
|
118
167
|
brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or opencode',
|
|
168
|
+
transcriptionEnabled: 'transcriptionEnabled must be true or false',
|
|
169
|
+
transcriptionLanguage: "transcriptionLanguage must be 'auto' or a 2-3 letter lowercase language code, such as en or pt",
|
|
170
|
+
transcriptionEnginePath: 'transcriptionEnginePath must be empty (find whisper-cli by itself) or the absolute path of the whisper-cli program',
|
|
171
|
+
transcriptionModelPath: 'transcriptionModelPath must be empty (ggml-small.bin in the transcription folder) or the absolute path of a whisper.cpp model file',
|
|
172
|
+
transcriptionFfmpegPath: 'transcriptionFfmpegPath must be empty (find ffmpeg by itself) or the absolute path of the ffmpeg program',
|
|
119
173
|
};
|
|
120
174
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
175
|
+
/** Whether a tool path can be used on this computer: null, or why not (transcription.js checkToolPath). */
|
|
176
|
+
const defaultCheckPath = (value, kind) => checkToolPath(value, kind);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Validate a user patch (Settings fields only). → the cleaned patch; throws 400 CONFIG_INVALID {field}.
|
|
180
|
+
* `checkPath(value, kind)` → null or a problem: how a tool path is checked on disk (tests pass a fake).
|
|
181
|
+
*/
|
|
182
|
+
export function validateConfigPatch(patch = {}, { checkPath = defaultCheckPath } = {}) {
|
|
123
183
|
if (!isPlainObject(patch)) throw configError('config', 'config must be an object');
|
|
124
184
|
const out = {};
|
|
125
185
|
for (const [key, value] of Object.entries(patch)) {
|
|
@@ -138,6 +198,14 @@ export function validateConfigPatch(patch = {}) {
|
|
|
138
198
|
out.brain = brain;
|
|
139
199
|
continue;
|
|
140
200
|
}
|
|
201
|
+
if (TOOL_PATH_FIELDS[key]) {
|
|
202
|
+
const toolPath = cleanToolPath(value);
|
|
203
|
+
if (toolPath === undefined) throw configError(key, MESSAGES[key]);
|
|
204
|
+
const problem = toolPath ? checkPath(toolPath, TOOL_PATH_FIELDS[key]) : null;
|
|
205
|
+
if (problem) throw configError(key, `${key} ${TOOL_PATH_PROBLEMS[problem] || 'names a file that cannot be used'}: ${toolPath}`);
|
|
206
|
+
out[key] = toolPath;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
141
209
|
if (!CHECKS[key](value)) throw configError(key, MESSAGES[key]);
|
|
142
210
|
out[key] = value;
|
|
143
211
|
}
|
|
@@ -157,13 +225,28 @@ function cleanOwner(value) {
|
|
|
157
225
|
return { masked, boundAt: toMs(value.boundAt), via: value.via === 'claim' ? 'claim' : 'self_confirm' };
|
|
158
226
|
}
|
|
159
227
|
|
|
228
|
+
/**
|
|
229
|
+
* A level 'autonomous' stored with an `autonomousUntil` (any value but null) was turned on under
|
|
230
|
+
* the 8-hour rule of 2.0.5 and before, whether that window is still open or not: it does not
|
|
231
|
+
* become Autonomous without a time limit. → the stored value as level 'ask' with no
|
|
232
|
+
* autonomousUntil, or null when `stored` is not such a value. Autonomous stored with
|
|
233
|
+
* autonomousUntil null is today's state and stays.
|
|
234
|
+
*/
|
|
235
|
+
export function migrateLegacyAutonomous(stored) {
|
|
236
|
+
if (!isPlainObject(stored) || stored.level !== 'autonomous') return null;
|
|
237
|
+
if (stored.autonomousUntil === null || stored.autonomousUntil === undefined) return null;
|
|
238
|
+
return { ...stored, level: 'ask', autonomousUntil: null };
|
|
239
|
+
}
|
|
240
|
+
|
|
160
241
|
/** Defaults + stored values; a bad stored value falls back to its default, field by field. */
|
|
161
242
|
export function effectiveConfig(stored = {}) {
|
|
162
|
-
const raw = isPlainObject(stored) ? stored : {};
|
|
243
|
+
const raw = isPlainObject(stored) ? migrateLegacyAutonomous(stored) || stored : {};
|
|
163
244
|
const out = {};
|
|
164
245
|
for (const key of USER_FIELDS) {
|
|
165
246
|
if (key === 'replyLabel') { const label = cleanLabel(raw.replyLabel); out.replyLabel = label === undefined ? null : label; continue; }
|
|
166
247
|
if (key === 'brain') { const brain = cleanBrainChoice(raw.brain); out.brain = brain === undefined ? null : brain; continue; }
|
|
248
|
+
// A stored path keeps its shape only: whether the file still works is the host's check, right before it runs.
|
|
249
|
+
if (TOOL_PATH_FIELDS[key]) { const toolPath = cleanToolPath(raw[key]); out[key] = toolPath === undefined ? '' : toolPath; continue; }
|
|
167
250
|
out[key] = raw[key] !== undefined && CHECKS[key](raw[key]) ? raw[key] : USER_DEFAULTS[key];
|
|
168
251
|
}
|
|
169
252
|
out.mode = MODES.includes(raw.mode) ? raw.mode : SERVICE_DEFAULTS.mode;
|
|
@@ -172,7 +255,7 @@ export function effectiveConfig(stored = {}) {
|
|
|
172
255
|
? raw.previousSessionIds.map((id) => (typeof id === 'string' ? id : id?.id)).filter((id) => typeof id === 'string' && id).slice(0, 10)
|
|
173
256
|
: [];
|
|
174
257
|
out.owner = cleanOwner(raw.owner);
|
|
175
|
-
out.autonomousUntil =
|
|
258
|
+
out.autonomousUntil = null; // legacy: Autonomous has no time limit (migrateLegacyAutonomous)
|
|
176
259
|
out.paused = raw.paused === true;
|
|
177
260
|
out.pausedBy = out.paused && (raw.pausedBy === 'desktop' || raw.pausedBy === 'phone') ? raw.pausedBy : null;
|
|
178
261
|
out.version = Number.isSafeInteger(raw.version) && raw.version >= 0 ? raw.version : 0;
|
|
@@ -181,18 +264,23 @@ export function effectiveConfig(stored = {}) {
|
|
|
181
264
|
return out;
|
|
182
265
|
}
|
|
183
266
|
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
267
|
+
/**
|
|
268
|
+
* The configured level ('ask' when it is not a known one). Autonomous has no time limit: it
|
|
269
|
+
* holds until the owner lowers it. A legacy 8-hour Autonomous (migrateLegacyAutonomous) is 'ask'.
|
|
270
|
+
*/
|
|
271
|
+
export function currentLevel(config) {
|
|
272
|
+
if (migrateLegacyAutonomous(config)) return 'ask';
|
|
273
|
+
return LEVELS.includes(config?.level) ? config.level : 'ask';
|
|
188
274
|
}
|
|
189
275
|
|
|
190
276
|
/**
|
|
191
277
|
* @param {object} deps
|
|
192
278
|
* @param {(key:string)=>string|null} deps.get getKvConfig
|
|
193
279
|
* @param {(key:string, value:string)=>void} deps.set setKvConfig
|
|
280
|
+
* @param {() => void} [deps.onLegacyAutonomous] a legacy 8-hour Autonomous was stored as Ask (once, after it was saved)
|
|
281
|
+
* @param {(value:string, kind:'program'|'file') => string|null} [deps.checkPath] how update() checks a tool path on disk
|
|
194
282
|
*/
|
|
195
|
-
export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.now, log = () => {} } = {}) {
|
|
283
|
+
export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.now, log = () => {}, onLegacyAutonomous = null, checkPath = defaultCheckPath } = {}) {
|
|
196
284
|
let cache = null; // { at, value }
|
|
197
285
|
|
|
198
286
|
function readStored() {
|
|
@@ -202,12 +290,34 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
202
290
|
if (!raw) return {};
|
|
203
291
|
try {
|
|
204
292
|
const value = JSON.parse(raw);
|
|
205
|
-
return isPlainObject(value) ? value : {};
|
|
293
|
+
return isPlainObject(value) ? migrateStored(value) : {};
|
|
206
294
|
} catch (error) {
|
|
207
295
|
log('whatsapp:config-corrupt', error?.message || String(error));
|
|
208
296
|
return {};
|
|
209
297
|
}
|
|
210
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* A legacy 8-hour Autonomous (migrateLegacyAutonomous) found by any read is stored as Ask at
|
|
301
|
+
* once, with the version bumped (an open Settings tab reloads before it saves), and reported
|
|
302
|
+
* once. A store that cannot be written still reads it as Ask, and tries again at the next read.
|
|
303
|
+
*/
|
|
304
|
+
function migrateStored(value) {
|
|
305
|
+
const migrated = migrateLegacyAutonomous(value);
|
|
306
|
+
if (!migrated) return value;
|
|
307
|
+
const version = Number.isSafeInteger(value.version) && value.version >= 0 ? value.version : 0;
|
|
308
|
+
const next = { ...migrated, version: version + 1 };
|
|
309
|
+
try {
|
|
310
|
+
if (typeof set !== 'function') return next;
|
|
311
|
+
set(WHATSAPP_CONFIG_KEY, JSON.stringify(next));
|
|
312
|
+
} catch (error) {
|
|
313
|
+
log('whatsapp:config-unwritable', `the earlier Autonomous could not be stored as Ask: ${error?.message || String(error)}`);
|
|
314
|
+
return next;
|
|
315
|
+
}
|
|
316
|
+
cache = null;
|
|
317
|
+
log('whatsapp:config-migrated', 'Autonomous from an earlier version (8-hour window) ended; the level is Ask');
|
|
318
|
+
try { onLegacyAutonomous?.(); } catch {}
|
|
319
|
+
return next;
|
|
320
|
+
}
|
|
211
321
|
function persist(value) {
|
|
212
322
|
if (typeof set === 'function') set(WHATSAPP_CONFIG_KEY, JSON.stringify(value));
|
|
213
323
|
cache = null;
|
|
@@ -221,8 +331,8 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
221
331
|
|
|
222
332
|
/**
|
|
223
333
|
* Service-owned write: service fields, internal state and user fields the
|
|
224
|
-
* service decides (the confirmed autonomous level
|
|
225
|
-
*
|
|
334
|
+
* service decides (the confirmed autonomous level). A change to a user field
|
|
335
|
+
* bumps the version so an open Settings tab reloads first.
|
|
226
336
|
*/
|
|
227
337
|
function write(patch = {}) {
|
|
228
338
|
if (!isPlainObject(patch)) return read();
|
|
@@ -239,6 +349,10 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
239
349
|
if (key === 'version') continue;
|
|
240
350
|
// Computer use is turned on by the person at this computer only (update()); the service may only turn it off.
|
|
241
351
|
if (key === 'computerUse' && value !== false) { log('whatsapp:config-ignored', 'computerUse is turned on in Settings only'); continue; }
|
|
352
|
+
// What the host runs (whisper-cli, FFmpeg) and the model it loads are chosen in Settings only; a service write may clear them.
|
|
353
|
+
if (TOOL_PATH_FIELDS[key] && value !== '' && value !== null) { log('whatsapp:config-ignored', `${key} is set in Settings only`); continue; }
|
|
354
|
+
// A legacy field: Autonomous has no time limit, so nothing writes an expiry (only null, which clears it).
|
|
355
|
+
if (key === 'autonomousUntil' && value !== null) { log('whatsapp:config-ignored', 'autonomousUntil is a legacy field: Autonomous has no time limit'); continue; }
|
|
242
356
|
next[key] = value;
|
|
243
357
|
if (USER_FIELDS.includes(key) && JSON.stringify(effectiveConfig(next)[key]) !== JSON.stringify(before[key])) userChanged = true;
|
|
244
358
|
}
|
|
@@ -253,7 +367,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
253
367
|
* level waits for the phone (the service holds the escalation).
|
|
254
368
|
*/
|
|
255
369
|
function update(patch = {}, { expectedVersion = null, confirmEscalation = false } = {}) {
|
|
256
|
-
const clean = validateConfigPatch(patch);
|
|
370
|
+
const clean = validateConfigPatch(patch, { checkPath });
|
|
257
371
|
const stored = readStored();
|
|
258
372
|
const current = effectiveConfig(stored);
|
|
259
373
|
if (expectedVersion !== null && expectedVersion !== undefined && Number(expectedVersion) !== current.version) {
|
|
@@ -263,7 +377,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
263
377
|
}
|
|
264
378
|
let pending = false;
|
|
265
379
|
if (clean.level !== undefined) {
|
|
266
|
-
const effective = currentLevel(current
|
|
380
|
+
const effective = currentLevel(current);
|
|
267
381
|
if (clean.level === 'autonomous') {
|
|
268
382
|
if (effective !== 'autonomous') {
|
|
269
383
|
if (confirmEscalation !== true) throw configError('level', 'Autonomous needs a second confirmation on this computer, then an ALLOW code from your phone.', { code: 'CONFIRM_REQUIRED' });
|
|
@@ -271,7 +385,7 @@ export function createWhatsAppConfigStore({ get, set, ttlMs = 3000, now = Date.n
|
|
|
271
385
|
}
|
|
272
386
|
delete clean.level;
|
|
273
387
|
} else if (RANK[clean.level] < RANK[effective] || clean.level !== current.level) {
|
|
274
|
-
// Lowering
|
|
388
|
+
// Lowering applies at once (and clears the legacy autonomousUntil).
|
|
275
389
|
clean.autonomousUntil = null;
|
|
276
390
|
}
|
|
277
391
|
}
|