synabun 2.0.5 → 2.0.9
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 +78 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +13 -0
- package/docs/whatsapp.md +151 -16
- package/hooks/claude-code/post-plan.mjs +6 -30
- package/hooks/claude-code/session-start.mjs +1 -4
- package/hooks/claude-code/shared.mjs +112 -75
- 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/package-processes.js +670 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/runtime-dependencies.js +225 -0
- 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/browser-bench.d.ts +1 -1
- package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
- package/mcp-server/dist/services/browser-bench.js +1 -1
- package/mcp-server/dist/services/browser-bench.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/categories.d.ts +35 -2
- package/mcp-server/dist/services/categories.d.ts.map +1 -1
- package/mcp-server/dist/services/categories.js +77 -33
- package/mcp-server/dist/services/categories.js.map +1 -1
- 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/desktop-bench.d.ts +7 -7
- package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
- package/mcp-server/dist/services/desktop-bench.js +1 -1
- package/mcp-server/dist/services/desktop-bench.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 +11 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +35 -14
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/bluesky.js +1 -1
- package/mcp-server/dist/tools/bluesky.js.map +1 -1
- package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
- package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-advanced.js +1 -1
- package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-batch.js +1 -1
- package/mcp-server/dist/tools/browser-batch.js.map +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
- package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
- package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-interact.js +1 -1
- package/mcp-server/dist/tools/browser-interact.js.map +1 -1
- package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
- package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-navigate.js +1 -1
- package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
- package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
- package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-observe.js +1 -1
- package/mcp-server/dist/tools/browser-observe.js.map +1 -1
- package/mcp-server/dist/tools/card-tools.d.ts +1 -1
- package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/card-tools.js +1 -1
- package/mcp-server/dist/tools/card-tools.js.map +1 -1
- package/mcp-server/dist/tools/category.d.ts +1 -1
- package/mcp-server/dist/tools/category.d.ts.map +1 -1
- package/mcp-server/dist/tools/category.js +5 -1
- package/mcp-server/dist/tools/category.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +1 -1
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +1 -1
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/computer.d.ts +1 -1
- package/mcp-server/dist/tools/computer.d.ts.map +1 -1
- package/mcp-server/dist/tools/computer.js +1 -1
- package/mcp-server/dist/tools/computer.js.map +1 -1
- package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
- package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-channel.js +1 -1
- package/mcp-server/dist/tools/discord-channel.js.map +1 -1
- package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
- package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-guild.js +1 -1
- package/mcp-server/dist/tools/discord-guild.js.map +1 -1
- package/mcp-server/dist/tools/discord-member.d.ts +1 -1
- package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-member.js +1 -1
- package/mcp-server/dist/tools/discord-member.js.map +1 -1
- package/mcp-server/dist/tools/discord-message.d.ts +1 -1
- package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-message.js +1 -1
- package/mcp-server/dist/tools/discord-message.js.map +1 -1
- package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
- package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-onboarding.js +1 -1
- package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
- package/mcp-server/dist/tools/discord-role.d.ts +1 -1
- package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-role.js +1 -1
- package/mcp-server/dist/tools/discord-role.js.map +1 -1
- package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
- package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-thread.js +1 -1
- package/mcp-server/dist/tools/discord-thread.js.map +1 -1
- package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
- package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-webhook.js +1 -1
- package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
- package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
- package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
- package/mcp-server/dist/tools/fb-groups.js +1 -1
- package/mcp-server/dist/tools/fb-groups.js.map +1 -1
- package/mcp-server/dist/tools/forget.d.ts +1 -1
- package/mcp-server/dist/tools/forget.d.ts.map +1 -1
- package/mcp-server/dist/tools/forget.js +1 -1
- package/mcp-server/dist/tools/forget.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.d.ts +1 -1
- package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +1 -1
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/experience.js +1 -1
- package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
- package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/indexing.js +1 -1
- package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
- package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/inspect.js +1 -1
- package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
- package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/navigate.js +1 -1
- package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
- package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/performance.js +1 -1
- package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
- package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/settings.js +1 -1
- package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/image-tools.js.map +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
- package/mcp-server/dist/tools/loop.d.ts +1 -1
- package/mcp-server/dist/tools/loop.d.ts.map +1 -1
- package/mcp-server/dist/tools/loop.js +1 -1
- package/mcp-server/dist/tools/loop.js.map +1 -1
- package/mcp-server/dist/tools/memories.d.ts +1 -1
- package/mcp-server/dist/tools/memories.d.ts.map +1 -1
- package/mcp-server/dist/tools/memories.js +1 -1
- package/mcp-server/dist/tools/memories.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +1 -1
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/profile.d.ts +1 -1
- package/mcp-server/dist/tools/profile.d.ts.map +1 -1
- package/mcp-server/dist/tools/profile.js +1 -1
- package/mcp-server/dist/tools/profile.js.map +1 -1
- package/mcp-server/dist/tools/recall.d.ts +1 -1
- package/mcp-server/dist/tools/recall.d.ts.map +1 -1
- package/mcp-server/dist/tools/recall.js +1 -1
- package/mcp-server/dist/tools/recall.js.map +1 -1
- package/mcp-server/dist/tools/reflect.d.ts +1 -1
- package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
- package/mcp-server/dist/tools/reflect.js +1 -1
- package/mcp-server/dist/tools/reflect.js.map +1 -1
- package/mcp-server/dist/tools/remember.d.ts +1 -1
- package/mcp-server/dist/tools/remember.d.ts.map +1 -1
- package/mcp-server/dist/tools/remember.js +13 -11
- package/mcp-server/dist/tools/remember.js.map +1 -1
- package/mcp-server/dist/tools/restore.d.ts +1 -1
- package/mcp-server/dist/tools/restore.d.ts.map +1 -1
- package/mcp-server/dist/tools/restore.js +1 -1
- package/mcp-server/dist/tools/restore.js.map +1 -1
- package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
- package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/style-guide-tools.js +1 -1
- package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
- package/mcp-server/dist/tools/sync.d.ts +1 -1
- package/mcp-server/dist/tools/sync.d.ts.map +1 -1
- package/mcp-server/dist/tools/sync.js +1 -1
- package/mcp-server/dist/tools/sync.js.map +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
- package/mcp-server/dist/tools/utils.d.ts +1 -1
- package/mcp-server/dist/tools/utils.d.ts.map +1 -1
- package/mcp-server/dist/tools/utils.js +1 -1
- package/mcp-server/dist/tools/utils.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/tools/whiteboard-tools.d.ts +3 -3
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/analytics.js +1 -1
- package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
- package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/download.js +1 -1
- package/mcp-server/dist/tools/youtube/download.js.map +1 -1
- package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/media.js +1 -1
- package/mcp-server/dist/tools/youtube/media.js.map +1 -1
- package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/metadata.js +1 -1
- package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
- package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/navigate.js +1 -1
- package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
- package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/playlist.js +1 -1
- package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
- package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/schedule.js +1 -1
- package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
- package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/source.js +1 -1
- package/mcp-server/dist/tools/youtube/source.js.map +1 -1
- package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/upload.js +4 -9
- package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
- package/mcp-server/dist/tui.js +18 -5
- 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 +3 -3
- package/mcp-server/package.json +3 -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/browser-bench.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/categories.ts +77 -32
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/desktop-bench.ts +1 -1
- 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 +34 -15
- package/mcp-server/src/tools/bluesky.ts +1 -1
- package/mcp-server/src/tools/browser-advanced.ts +1 -1
- package/mcp-server/src/tools/browser-batch.ts +1 -1
- package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
- package/mcp-server/src/tools/browser-interact.ts +1 -1
- package/mcp-server/src/tools/browser-navigate.ts +1 -1
- package/mcp-server/src/tools/browser-observe.ts +1 -1
- package/mcp-server/src/tools/card-tools.ts +1 -1
- package/mcp-server/src/tools/category.ts +6 -1
- package/mcp-server/src/tools/choice.ts +1 -1
- package/mcp-server/src/tools/computer.ts +1 -1
- package/mcp-server/src/tools/discord-channel.ts +1 -1
- package/mcp-server/src/tools/discord-guild.ts +1 -1
- package/mcp-server/src/tools/discord-member.ts +1 -1
- package/mcp-server/src/tools/discord-message.ts +1 -1
- package/mcp-server/src/tools/discord-onboarding.ts +1 -1
- package/mcp-server/src/tools/discord-role.ts +1 -1
- package/mcp-server/src/tools/discord-thread.ts +1 -1
- package/mcp-server/src/tools/discord-webhook.ts +1 -1
- package/mcp-server/src/tools/fb-groups.ts +1 -1
- package/mcp-server/src/tools/forget.ts +1 -1
- package/mcp-server/src/tools/git-tools.ts +1 -1
- package/mcp-server/src/tools/gsc/experience.ts +1 -1
- package/mcp-server/src/tools/gsc/indexing.ts +1 -1
- package/mcp-server/src/tools/gsc/inspect.ts +1 -1
- package/mcp-server/src/tools/gsc/navigate.ts +1 -1
- package/mcp-server/src/tools/gsc/performance.ts +1 -1
- package/mcp-server/src/tools/gsc/settings.ts +1 -1
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
- package/mcp-server/src/tools/loop.ts +1 -1
- package/mcp-server/src/tools/memories.ts +1 -1
- package/mcp-server/src/tools/morelogin.ts +1 -1
- package/mcp-server/src/tools/profile.ts +1 -1
- package/mcp-server/src/tools/recall.ts +1 -1
- package/mcp-server/src/tools/reflect.ts +1 -1
- package/mcp-server/src/tools/remember.ts +13 -12
- package/mcp-server/src/tools/restore.ts +1 -1
- package/mcp-server/src/tools/style-guide-tools.ts +1 -1
- package/mcp-server/src/tools/sync.ts +1 -1
- package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
- package/mcp-server/src/tools/utils.ts +1 -1
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
- package/mcp-server/src/tools/youtube/analytics.ts +1 -1
- package/mcp-server/src/tools/youtube/download.ts +1 -1
- package/mcp-server/src/tools/youtube/media.ts +1 -1
- package/mcp-server/src/tools/youtube/metadata.ts +1 -1
- package/mcp-server/src/tools/youtube/navigate.ts +1 -1
- package/mcp-server/src/tools/youtube/playlist.ts +1 -1
- package/mcp-server/src/tools/youtube/schedule.ts +1 -1
- package/mcp-server/src/tools/youtube/source.ts +1 -1
- package/mcp-server/src/tools/youtube/upload.ts +4 -12
- package/mcp-server/src/tui.ts +24 -10
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +645 -57
- package/neural-interface/i18n/pt-BR.json +644 -56
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
- package/neural-interface/lib/agent-sandbox-setting.js +57 -0
- package/neural-interface/lib/assistant-api.js +274 -52
- package/neural-interface/lib/assistant-brains/claude.js +15 -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-config.js +62 -14
- package/neural-interface/lib/assistant-dispatch.js +432 -125
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-persona.js +68 -11
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
- 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 +312 -48
- package/neural-interface/lib/assistant-user-writer.js +4 -2
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +90 -3
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +3 -3
- 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/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/codex-account-auth.js +180 -0
- package/neural-interface/lib/codex-accounts.js +502 -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 +122 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/exec-loop-command.js +3 -1
- 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-read.js +34 -2
- package/neural-interface/lib/install-details.js +163 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/native-loop-providers.js +22 -4
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +96 -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/session-indexer.js +3 -1
- package/neural-interface/lib/session-title-generator.js +6 -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/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 +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/offline.html +16 -1
- package/neural-interface/public/onboarding.html +96 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +542 -0
- package/neural-interface/public/shared/account-usage.js +79 -0
- package/neural-interface/public/shared/account-view.js +336 -0
- package/neural-interface/public/shared/agent-runtime-options.js +28 -8
- package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +39 -2
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +288 -32
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
- package/neural-interface/public/shared/assistant/asst-menu.js +219 -80
- package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +87 -16
- package/neural-interface/public/shared/assistant/asst-render.js +38 -7
- package/neural-interface/public/shared/assistant/asst-route.js +72 -14
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +1 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +160 -29
- 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 +49 -108
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +172 -111
- package/neural-interface/public/shared/cdx/cdx-tabs.js +264 -664
- 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/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +46 -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-panel.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +1 -1
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/html-shell.js +1 -0
- 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 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +36 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +169 -2
- package/neural-interface/public/shared/pwa-install-page.js +3 -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-ia.js +15 -0
- package/neural-interface/public/shared/showcase-privacy.js +490 -0
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +281 -32
- package/neural-interface/public/shared/synabun-mascot.js +48 -2
- package/neural-interface/public/shared/ui-claude-panel.js +262 -217
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-explorer.js +2 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-loading.js +30 -6
- package/neural-interface/public/shared/ui-menubar.js +13 -3
- package/neural-interface/public/shared/ui-navbar.js +3 -1
- 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 +55 -3
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +8 -0
- package/neural-interface/public/shared/ui-terminal.js +17 -0
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-update.js +598 -7
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +2 -0
- package/neural-interface/public/sw.js +1 -1
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/scripts/rebuild-pty.js +14 -2
- package/neural-interface/server.js +1384 -679
- package/npm-shrinkwrap.json +4424 -0
- package/package.json +57 -2
- package/postinstall.js +61 -47
- package/preuninstall.js +8 -35
- package/setup.js +80 -47
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/schedule.md +1 -1
- package/updater.cmd +2 -2
- package/updater.mjs +97 -3
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// rehydration, per-turn auto-recall, dispatch mailbox delivery, and the memory
|
|
9
9
|
// obligation nudges. All server dependencies are injected.
|
|
10
10
|
|
|
11
|
-
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
12
12
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, resolve } from 'node:path';
|
|
14
14
|
import { createClaudeBrain } from './assistant-brains/claude.js';
|
|
@@ -16,14 +16,16 @@ import { createCodexBrain } from './assistant-brains/codex.js';
|
|
|
16
16
|
import { createOpenCodeBrain } from './assistant-brains/opencode.js';
|
|
17
17
|
import { buildAssistantPersona, formatMailbox, reconcileRouteItems } from './assistant-persona.js';
|
|
18
18
|
import { normalizeTestDensity, testDensityStamp } from './assistant-test-density.js';
|
|
19
|
-
import { codexUsageCostUsd } from './assistant-budget.js';
|
|
19
|
+
import { codexUsageCostUsd, floorCents } from './assistant-budget.js';
|
|
20
|
+
import { MIN_SPEND_USD } from './assistant-budget-split.js';
|
|
20
21
|
import { CLAUDE_ESTIMATE_SOURCE, createClaudeMeter, createCodexMeter, createOpenCodeMeter, totalTokens, withTotal, zeroTokens } from './assistant-usage.js';
|
|
21
22
|
import { claudeTokensCostUsd } from './assistant-pricing.js';
|
|
22
23
|
import { normalizeRouteMode } from './assistant-config.js';
|
|
23
24
|
import { TASK_CLASS_META } from './assistant-router.js';
|
|
25
|
+
import { chooseUsageAccount } from './account-usage.js';
|
|
24
26
|
import { defaultIsHidden, enabledStandIn, findModel, providerAllHidden, selectSheetModels } from './assistant-catalog.js';
|
|
25
27
|
import { normalizeEffort } from './effort-levels.js';
|
|
26
|
-
import { claudeRouteAvailability, createRouteGate, mcpStatusRouteAvailability, ROUTE_GATE_LOOP_MESSAGE } from './assistant-route-gate.js';
|
|
28
|
+
import { claudeRouteAvailability, createRouteGate, isReplyTool, mcpStatusRouteAvailability, ROUTE_GATE_LOOP_MESSAGE } from './assistant-route-gate.js';
|
|
27
29
|
import { claudeReadOnlyPermission, planDenyMessage, planToolDecision, readOnlyDenyMessage, readOnlyToolDecision } from './assistant-plan-permissions.js';
|
|
28
30
|
import { codexRouteGateBootstrap } from './assistant-route-gate-codex.js';
|
|
29
31
|
import { proposedPlanText } from './assistant-envelope.js';
|
|
@@ -37,17 +39,49 @@ export const DEFAULT_SESSION_TITLE = 'SynaBun';
|
|
|
37
39
|
export function isDefaultSessionTitle(title) { return !title || /^(?:SynaBun|Assistant(?: \d+)?)$/.test(String(title).trim()); }
|
|
38
40
|
const MAILBOX_REASONS = new Set(['turn_completed', 'needs_input', 'permission_request', 'failed', 'interrupted', 'stalled', 'budget_warning', 'budget_exceeded', 'budget_unpriced', 'queue_started', 'user_intervened', 'stopped']);
|
|
39
41
|
// A stopped run reaches the brain when the user stopped it or it ran out of money.
|
|
40
|
-
const MAILBOX_STOP_REASONS = new Set(['user', 'budget_cap', 'session_budget_cap', 'budget_unpriced']);
|
|
42
|
+
const MAILBOX_STOP_REASONS = new Set(['user', 'budget_cap', 'session_budget_cap', 'budget_unpriced', 'budget_share']);
|
|
43
|
+
/** The `[SynaBun Budget]` line: a change of the session budget reaches the brain as one line before its next turn. */
|
|
44
|
+
export const BUDGET_HEADER = '[SynaBun Budget]';
|
|
45
|
+
/**
|
|
46
|
+
* What the brain is told when the budget changed after its prompt was built (setting: { unlimited,
|
|
47
|
+
* amountUsd }; money: the session's budget view, for what is spent and left). The key changes only
|
|
48
|
+
* with the setting, so spending never repeats the line.
|
|
49
|
+
*/
|
|
50
|
+
export function budgetStamp(setting, money = null) {
|
|
51
|
+
if (!setting) return { key: null, text: '' };
|
|
52
|
+
const usdText = (value) => `$${(Number(value) || 0).toFixed(2)}`;
|
|
53
|
+
if (setting.unlimited === true) {
|
|
54
|
+
return { key: 'unlimited', text: `${BUDGET_HEADER} The user switched this session to No limit: there is no budget now, no provider shares and no stops for spend. Do not pass budget_usd; tasks route by the saved routes as before. Spend is still metered (agent_usage).` };
|
|
55
|
+
}
|
|
56
|
+
const amount = Number(setting.amountUsd) || 0;
|
|
57
|
+
const spent = money && Number.isFinite(Number(money.totalUsd)) ? Number(money.totalUsd) : null;
|
|
58
|
+
const status = spent === null ? '' : ` ${usdText(spent)} spent, ${usdText(Math.max(0, amount - spent))} left.`;
|
|
59
|
+
return {
|
|
60
|
+
key: `limited:${amount.toFixed(2)}`,
|
|
61
|
+
text: `${BUDGET_HEADER} The user set this session's budget to ${usdText(amount)} (your own turns plus every run, at list price; one warning at 80%).${status} SynaBun divides what is left across the providers: each provider's runs draw from its share and your turns from your provider's share (agent_usage shows the split). When a share is used up, agent_route moves a task to a provider with money left, so saved routes yield to the budget. budget_usd is optional: a ceiling for one run inside its share.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
41
64
|
const usd = (value) => `$${(Number(value) || 0).toFixed(2)}`;
|
|
42
65
|
const TRANSCRIPT_EVENT_TYPES = new Set(['assistant', 'user', 'result', 'system']);
|
|
43
66
|
// Below the autonomous level a remote session's brain asks before any tool the
|
|
44
67
|
// read-only classifier would refuse. These stay unasked: questions and plans (they reach
|
|
45
68
|
// the user anyway), in-session bookkeeping, subagent launches (their own calls
|
|
46
|
-
// are asked), memory writes,
|
|
47
|
-
|
|
69
|
+
// are asked), memory writes, agent_dispatch (its route card is the approval), and
|
|
70
|
+
// whatsapp_send_file: delivering a file the user asked for is a reply, like the final text
|
|
71
|
+
// (any host's name for it: isReplyTool).
|
|
72
|
+
export const REMOTE_ASK_EXEMPT = new Set([
|
|
48
73
|
'AskUserQuestion', 'ExitPlanMode', 'EnterPlanMode', 'TodoWrite', 'TodoRead', 'ToolSearch', 'Task', 'Agent',
|
|
49
74
|
'mcp__SynaBun__remember', 'mcp__SynaBun__reflect', 'mcp__SynaBun__category', 'mcp__SynaBun__agent_dispatch',
|
|
75
|
+
'mcp__SynaBun__whatsapp_send_file',
|
|
50
76
|
]);
|
|
77
|
+
/**
|
|
78
|
+
* The credential of one brain launch of a WhatsApp conversation (whatsapp_send_file): it reaches
|
|
79
|
+
* that brain's SynaBun MCP server only (Claude: an MCP header; Codex and OpenCode: the MCP child's
|
|
80
|
+
* environment), which sends it on POST /api/assistant/whatsapp/send-file and nowhere else. Random,
|
|
81
|
+
* in memory only (never on the record, in a log, the persona or a transcript), gone with the brain.
|
|
82
|
+
*/
|
|
83
|
+
export const BRAIN_CREDENTIAL_RE = /^sbb_[A-Za-z0-9_-]{43}$/;
|
|
84
|
+
export function mintBrainCredential() { return `sbb_${randomBytes(32).toString('base64url')}`; }
|
|
51
85
|
// An OpenCode brain of a remote session reaches the web through these on its own
|
|
52
86
|
// (no SynaBun browser, no approval): the gate plugin refuses them.
|
|
53
87
|
const REMOTE_OPENCODE_WEB_TOOLS = new Set(['webfetch', 'websearch', 'codesearch']);
|
|
@@ -156,6 +190,9 @@ export function mergeHooks(a, b) {
|
|
|
156
190
|
export function createAssistantRuntime({
|
|
157
191
|
dispatcher = null,
|
|
158
192
|
claudeAccounts = null,
|
|
193
|
+
// lib/codex-accounts.js: a Codex brain whose account cannot run is refused before it starts.
|
|
194
|
+
codexAccounts = null,
|
|
195
|
+
accountUsage = null,
|
|
159
196
|
ClaudeSession = null,
|
|
160
197
|
sdkVersion = null,
|
|
161
198
|
handleCodexSkinWebSocket = null,
|
|
@@ -184,8 +221,11 @@ export function createAssistantRuntime({
|
|
|
184
221
|
codexBin = () => 'codex',
|
|
185
222
|
codexGateBootstrap = codexRouteGateBootstrap,
|
|
186
223
|
// Codex list prices (assistant-budget.js createModelPricing). When given, a
|
|
187
|
-
// brain whose spend cannot be priced is refused (
|
|
224
|
+
// brain whose spend cannot be priced is refused under a budget (it fails closed).
|
|
188
225
|
pricing = null,
|
|
226
|
+
// The session budget (assistant-budget-planner.js): the setting, the provider shares, the
|
|
227
|
+
// checkpoints. Defaults to the dispatcher's own planner.
|
|
228
|
+
budget: budgetPlanner = null,
|
|
189
229
|
usage = null,
|
|
190
230
|
// The meters of assistant-usage.js, by provider (tests swap one in).
|
|
191
231
|
meterFactories = null,
|
|
@@ -206,6 +246,8 @@ export function createAssistantRuntime({
|
|
|
206
246
|
// (capability) → boolean, the verifier of lib/remote-policy.js createPhoneAuthority(): is this the
|
|
207
247
|
// capability of the one WhatsApp bridge? Without a verifier nothing can grant computer use remotely.
|
|
208
248
|
phoneAuthority = null,
|
|
249
|
+
// The Admin sandbox setting (lib/agent-sandbox-setting.js): global, shown in every session view.
|
|
250
|
+
adminSandbox = null,
|
|
209
251
|
} = {}) {
|
|
210
252
|
if (!dataDir) throw new Error('createAssistantRuntime requires dataDir');
|
|
211
253
|
const settings = {
|
|
@@ -220,7 +262,6 @@ export function createAssistantRuntime({
|
|
|
220
262
|
retention: 200,
|
|
221
263
|
defaultBrain: config.defaultBrain || {},
|
|
222
264
|
personaExtra: config.persona?.extra || '',
|
|
223
|
-
maxBudgetUsd: Number(config.brains?.claude?.maxBudgetUsd) || 10,
|
|
224
265
|
// A brain's approval or question card waits this long, then it is declined
|
|
225
266
|
// (0 = wait forever). A pending card holds the turn and the mailbox.
|
|
226
267
|
approvalTimeoutMs: Math.max(0, Number(config.approvalTimeoutMinutes ?? 10) * 60_000) || 0,
|
|
@@ -301,7 +342,9 @@ export function createAssistantRuntime({
|
|
|
301
342
|
return {
|
|
302
343
|
record, brain: null, brainProvider: null, brainMeter: null, brainMeterProvider: null, turnTaskId: null, turnTasks: [], sockets: new Set(), buffer: [], running: false, pendingControls: new Map(), controlTimers: new Map(),
|
|
303
344
|
mailbox: [], mailboxTimer: null, obligations: new Set(), rememberedThisTurn: false, disposeTimer: null, personaText: '',
|
|
304
|
-
|
|
345
|
+
// The last human prompt and where it came from ('ui', 'whatsapp', 'api'): a route continuation
|
|
346
|
+
// answers there (lastPromptOrigin; the transcript's after a restart).
|
|
347
|
+
lastPrompt: '', lastPromptOrigin: null, mailboxTurn: false, nudgedRuns: new Set(),
|
|
305
348
|
// The provider's usage limit as last reported (limitNotice), or null: one state, never a list.
|
|
306
349
|
limit: null,
|
|
307
350
|
// Human prompts so far: one request cycle each (assistant-clarify.js briefs).
|
|
@@ -309,6 +352,8 @@ export function createAssistantRuntime({
|
|
|
309
352
|
// Claude brains: the turns sent, keyed by their final text, for the in-process recall hook.
|
|
310
353
|
turnMeta: new Map(),
|
|
311
354
|
desktopGrant: null,
|
|
355
|
+
// A WhatsApp conversation's brain credential (mintBrainCredential): this launch only, never persisted.
|
|
356
|
+
brainCredential: null,
|
|
312
357
|
// Route gate (assistant-route-gate.js): one per session while routing is wired.
|
|
313
358
|
// gatePluginAt: when the OpenCode serve loaded the gate plugin (until then, reactive).
|
|
314
359
|
// gateNudge: { reason, gen } while a reactive refusal waits for its turn to end.
|
|
@@ -863,7 +908,9 @@ export function createAssistantRuntime({
|
|
|
863
908
|
out.testDensity = testDensityOf(record);
|
|
864
909
|
out.testDensityExplicit = normalizeTestDensity(record.testDensity, null);
|
|
865
910
|
// `rename`: this server keeps a rename on the record (userRenamed), so a panel writes it here.
|
|
866
|
-
|
|
911
|
+
// `adminSandbox`: the global setting is served here (PUT /api/assistant/admin-sandbox).
|
|
912
|
+
out.features = { routing: !!router, computer: computerSupported(), testDensity: true, rename: true, adminSandbox: !!adminSandbox };
|
|
913
|
+
if (adminSandbox) { try { out.adminSandbox = adminSandbox.enabled() === true; } catch { out.adminSandbox = false; } }
|
|
867
914
|
out.budget = sessionMoney(record.id);
|
|
868
915
|
out.remoteLevel = policy ? effectiveLevel(policy, { untrusted: live.turnUntrusted === true, now: now() }) : null;
|
|
869
916
|
}
|
|
@@ -1142,7 +1189,7 @@ export function createAssistantRuntime({
|
|
|
1142
1189
|
fidelity: row.fidelity || settled?.fidelity, reason: row.reason ?? settled?.reason ?? null, source: row.source || settled?.source,
|
|
1143
1190
|
});
|
|
1144
1191
|
if (estimate && costUsd > 0) {
|
|
1145
|
-
live
|
|
1192
|
+
addBrainCost(live, costUsd, 'claude-code');
|
|
1146
1193
|
try { dispatcher?.checkSession?.(id); } catch {}
|
|
1147
1194
|
sendCost(live);
|
|
1148
1195
|
}
|
|
@@ -1200,10 +1247,10 @@ export function createAssistantRuntime({
|
|
|
1200
1247
|
// session restarted (resume, brain switch), so it counts in full.
|
|
1201
1248
|
const last = Number(live.record.lastReportedCost) || 0;
|
|
1202
1249
|
const delta = cost >= last ? cost - last : cost;
|
|
1203
|
-
live.
|
|
1250
|
+
addBrainCost(live, delta, live.brainProvider || live.record.brain?.provider || null);
|
|
1204
1251
|
live.record.lastReportedCost = cost;
|
|
1205
1252
|
live.record.costBasis = event.cost_basis || 'reported';
|
|
1206
|
-
// The brain's spend counts toward the session
|
|
1253
|
+
// The brain's spend counts toward the session budget: warn, or stop the session's runs.
|
|
1207
1254
|
try { dispatcher?.checkSession?.(id); } catch {}
|
|
1208
1255
|
sendCost(live);
|
|
1209
1256
|
}
|
|
@@ -1680,13 +1727,54 @@ export function createAssistantRuntime({
|
|
|
1680
1727
|
}
|
|
1681
1728
|
/** The brain's own spend in a session (the dispatcher adds it to the session total). */
|
|
1682
1729
|
function brainSpend(sessionId) { return Number(sessions.get(sessionId)?.record.costUsd) || 0; }
|
|
1683
|
-
/**
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1730
|
+
/**
|
|
1731
|
+
* Book the brain's spend: its session total and the provider it ran on (the budget counts the
|
|
1732
|
+
* brain's turns in that provider's share).
|
|
1733
|
+
*/
|
|
1734
|
+
function addBrainCost(live, usdAmount, provider) {
|
|
1735
|
+
const added = Number(usdAmount);
|
|
1736
|
+
if (!Number.isFinite(added) || added <= 0) return;
|
|
1737
|
+
live.record.costUsd = Number(((Number(live.record.costUsd) || 0) + added).toFixed(4));
|
|
1738
|
+
const key = BRAIN_PROVIDERS.has(provider) ? provider : (live.record.brain?.provider || 'claude-code');
|
|
1739
|
+
const rows = live.record.costByProvider && typeof live.record.costByProvider === 'object' ? live.record.costByProvider : {};
|
|
1740
|
+
rows[key] = Number(((Number(rows[key]) || 0) + added).toFixed(8));
|
|
1741
|
+
live.record.costByProvider = rows;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* The brain's spend by the provider it ran on. A session recorded before this was kept books what
|
|
1745
|
+
* no provider holds on the brain's provider.
|
|
1746
|
+
*/
|
|
1747
|
+
function brainSpendByProvider(sessionId) {
|
|
1748
|
+
const record = sessions.get(sessionId)?.record;
|
|
1749
|
+
if (!record) return {};
|
|
1750
|
+
const rows = record.costByProvider && typeof record.costByProvider === 'object' ? { ...record.costByProvider } : {};
|
|
1751
|
+
const known = Object.values(rows).reduce((sum, value) => sum + (Number(value) || 0), 0);
|
|
1752
|
+
const rest = (Number(record.costUsd) || 0) - known;
|
|
1753
|
+
if (rest > 0.00005) {
|
|
1754
|
+
const key = BRAIN_PROVIDERS.has(record.brain?.provider) ? record.brain.provider : 'claude-code';
|
|
1755
|
+
rows[key] = (Number(rows[key]) || 0) + rest;
|
|
1756
|
+
}
|
|
1757
|
+
return rows;
|
|
1758
|
+
}
|
|
1759
|
+
/** The session budget: the injected planner, else the dispatcher's. */
|
|
1760
|
+
function planner() { return budgetPlanner || dispatcher?.budget || null; }
|
|
1761
|
+
/** The budget setting now ({ unlimited, amountUsd, version }), or null when nothing keeps one. */
|
|
1762
|
+
function budgetSettingNow() {
|
|
1763
|
+
try { const value = planner()?.setting?.() || configStore?.budget?.() || null; return value ? { unlimited: value.unlimited === true, amountUsd: Number(value.amountUsd) || 0, version: Number(value.version) || 0 } : null; } catch { return null; }
|
|
1764
|
+
}
|
|
1765
|
+
/** The "[SynaBun Budget]" line when the setting changed since the brain last saw it (then marked seen), else ''. */
|
|
1766
|
+
function budgetLine(live) {
|
|
1767
|
+
try {
|
|
1768
|
+
const stamp = budgetStamp(budgetSettingNow(), sessionMoney(live.record.id));
|
|
1769
|
+
if (!stamp.key) return '';
|
|
1770
|
+
if (live.budgetStampKey === undefined) { live.budgetStampKey = stamp.key; return ''; }
|
|
1771
|
+
if (live.budgetStampKey === stamp.key) return '';
|
|
1772
|
+
live.budgetStampKey = stamp.key;
|
|
1773
|
+
return stamp.text;
|
|
1774
|
+
} catch { return ''; }
|
|
1687
1775
|
}
|
|
1688
1776
|
function sessionMoney(sessionId) {
|
|
1689
|
-
try { return dispatcher?.sessionBudget?.(sessionId) || null; } catch { return null; }
|
|
1777
|
+
try { return dispatcher?.sessionBudget?.(sessionId) || planner()?.view?.(sessionId) || null; } catch { return null; }
|
|
1690
1778
|
}
|
|
1691
1779
|
/**
|
|
1692
1780
|
* How the brain's spend is known: 'reported' (Claude; OpenCode with a list
|
|
@@ -1711,34 +1799,91 @@ export function createAssistantRuntime({
|
|
|
1711
1799
|
function sendCost(live) {
|
|
1712
1800
|
const budget = sessionMoney(live.record.id);
|
|
1713
1801
|
const basis = brainCostBasis(live);
|
|
1714
|
-
sendToSockets(live, { type: 'assistant:cost', sessionUsd: live.record.costUsd, dispatchUsd: dispatchSpend(live.record.id), budget,
|
|
1802
|
+
sendToSockets(live, { type: 'assistant:cost', sessionUsd: live.record.costUsd, dispatchUsd: dispatchSpend(live.record.id), budget, brainMetered: basis !== null, brainCostBasis: basis });
|
|
1715
1803
|
}
|
|
1716
1804
|
/**
|
|
1717
|
-
*
|
|
1718
|
-
*
|
|
1719
|
-
|
|
1805
|
+
* The session's budget as it is now, live: `assistant:budget` to the session's sockets and the sync
|
|
1806
|
+
* channel (every window), after a split or a change of the budget. `reason`: what made it.
|
|
1807
|
+
*/
|
|
1808
|
+
function sendBudget(sessionId, budget = null, { reason = null } = {}) {
|
|
1809
|
+
const live = sessions.get(String(sessionId || ''));
|
|
1810
|
+
if (!live) return;
|
|
1811
|
+
const view = budget || sessionMoney(live.record.id);
|
|
1812
|
+
if (!view) return;
|
|
1813
|
+
const packet = { type: 'assistant:budget', sessionId: live.record.id, reason: reason || null, budget: view };
|
|
1814
|
+
// A view, not an event (like usage): the open sockets only, never buffered (attach sends a fresh one).
|
|
1815
|
+
const raw = JSON.stringify(packet);
|
|
1816
|
+
for (const ws of [...live.sockets]) { if (ws.readyState === 1) { try { ws.send(raw); } catch {} } }
|
|
1817
|
+
try { broadcastSync({ ...packet, assistantSessionId: live.record.id }); } catch {}
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Why the brain may not start a turn, checked before every turn (a turn already running finishes):
|
|
1821
|
+
* an unpriced brain model, the session's budget spent, or no money left in the brain's provider
|
|
1822
|
+
* share (runTurn divides the session's unspent budget again first). Nothing under No limit.
|
|
1720
1823
|
*/
|
|
1721
1824
|
function brainBudgetBlock(live) {
|
|
1722
|
-
|
|
1825
|
+
const money = sessionMoney(live.record.id);
|
|
1826
|
+
if (!money || money.unlimited) return null;
|
|
1827
|
+
// Fail closed: a brain whose spend has no price could spend past the budget.
|
|
1723
1828
|
if (pricing && brainCostBasis(live) === null) {
|
|
1724
1829
|
const model = live.record.brain?.model || brainCapabilities(live.record)?.id || 'its default model';
|
|
1725
|
-
return { code: 'BRAIN_UNPRICED', message: `No price is known for the ${live.record.brain?.provider || ''} brain model ${model}, so the budget
|
|
1830
|
+
return { code: 'BRAIN_UNPRICED', message: `No price is known for the ${live.record.brain?.provider || ''} brain model ${model}, so the session budget cannot hold its spend. Pick a priced or free model for the brain, or switch the budget to No limit.` };
|
|
1726
1831
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1832
|
+
if (money.exceeded) {
|
|
1833
|
+
return { code: 'SESSION_BUDGET_EXCEEDED', message: `This session spent ${usd(money.totalUsd)} of its ${usd(money.amountUsd)} budget (brain ${usd(money.brainUsd)} + agents ${usd(money.dispatchUsd)}). Raise the budget or switch to No limit in ⋯ → Budget… to continue.` };
|
|
1834
|
+
}
|
|
1835
|
+
const provider = live.record.brain?.provider || null;
|
|
1836
|
+
const covered = planner()?.covers?.(live.record.id, { provider, forBrain: true });
|
|
1837
|
+
if (covered === false) {
|
|
1838
|
+
return { code: 'BRAIN_SHARE_EXHAUSTED', share: true, message: `The ${provider} share of this session's budget is used up, so the brain cannot take another turn (${usd(money.leftUsd)} of the ${usd(money.amountUsd)} budget left in other providers' shares). Raise the budget or switch to No limit in ⋯ → Budget… to continue.` };
|
|
1730
1839
|
}
|
|
1731
|
-
const cap = brainCapUsd();
|
|
1732
|
-
const spent = Number(live.record.costUsd) || 0;
|
|
1733
|
-
if (spent >= cap) return { code: 'BRAIN_BUDGET_EXCEEDED', message: `The brain spent ${usd(spent)} in this session, at its cap of ${usd(cap)}. Raise the brain cap in ⋯ → Budget… to continue.` };
|
|
1734
1840
|
return null;
|
|
1735
1841
|
}
|
|
1736
|
-
/**
|
|
1842
|
+
/**
|
|
1843
|
+
* The planner divided a session's budget again (budget planner `emit`): the panels hear it, and a
|
|
1844
|
+
* mailbox the brain's empty share held goes now.
|
|
1845
|
+
*/
|
|
1846
|
+
function onBudgetSplit(sessionId, budget = null, { reason = null } = {}) {
|
|
1847
|
+
sendBudget(sessionId, budget, { reason });
|
|
1848
|
+
const live = sessions.get(String(sessionId || ''));
|
|
1849
|
+
if (live && live.mailbox.length && !brainBudgetBlock(live)) { live.budgetHoldNotified = false; live.budgetShareTriedAt = 0; scheduleMailbox(live, 0); }
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Before a brain turn: when the brain's provider share is used up but the session has money left,
|
|
1853
|
+
* the session's unspent budget is divided again (its reserve comes back with the new split).
|
|
1854
|
+
*/
|
|
1855
|
+
async function coverBrainTurn(live) {
|
|
1856
|
+
const budget = planner();
|
|
1857
|
+
if (!budget?.ensureCovered || !budget.limited?.()) return true;
|
|
1858
|
+
try { return await budget.ensureCovered(live.record.id, { provider: live.record.brain?.provider || null, forBrain: true, reason: 'brain_turn' }); }
|
|
1859
|
+
catch { return false; }
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* The Claude brain CLI's own stop (--max-budget-usd): what its provider's share has left for it when
|
|
1863
|
+
* it starts, never more than the session has left. null under No limit (no CLI cap at all).
|
|
1864
|
+
*/
|
|
1865
|
+
function brainCliCap(live) {
|
|
1866
|
+
const money = sessionMoney(live.record.id);
|
|
1867
|
+
if (!money || money.unlimited) return null;
|
|
1868
|
+
let left = Number(money.leftUsd) || 0;
|
|
1869
|
+
try {
|
|
1870
|
+
const cov = planner()?.coverage?.(live.record.id);
|
|
1871
|
+
if (cov && Number.isFinite(Number(cov.brainLeftUsd))) left = Math.min(left, Number(cov.brainLeftUsd));
|
|
1872
|
+
} catch { /* the session's own left */ }
|
|
1873
|
+
return Math.max(MIN_SPEND_USD, floorCents(left) / 100);
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* The budget changed (Assistant → Budget): the planner divides each open session's budget again,
|
|
1877
|
+
* held mailbox items may go now, and every open panel refreshes its chip and split.
|
|
1878
|
+
*/
|
|
1737
1879
|
function onBudgetChanged() {
|
|
1880
|
+
try { planner()?.onBudgetChanged?.(); } catch {}
|
|
1738
1881
|
for (const live of sessions.values()) {
|
|
1739
1882
|
if (live.record.status === 'ended') continue;
|
|
1883
|
+
live.budgetShareTriedAt = 0;
|
|
1740
1884
|
try { dispatcher?.checkSession?.(live.record.id); } catch {}
|
|
1741
1885
|
sendCost(live);
|
|
1886
|
+
sendBudget(live.record.id, null, { reason: 'budget_changed' });
|
|
1742
1887
|
if (live.mailbox.length && !brainBudgetBlock(live)) { live.budgetHoldNotified = false; scheduleMailbox(live, 0); }
|
|
1743
1888
|
}
|
|
1744
1889
|
}
|
|
@@ -1754,6 +1899,10 @@ export function createAssistantRuntime({
|
|
|
1754
1899
|
try { setupState = desktop?.setupState?.() || null; } catch {}
|
|
1755
1900
|
// This prompt states the level: a "[SynaBun Tests]" line follows only a later change.
|
|
1756
1901
|
live.testDensityStamp = testDensityStamp(testDensityOf(live.record));
|
|
1902
|
+
// …and the budget: a "[SynaBun Budget]" line follows only a later change of the setting.
|
|
1903
|
+
const budgetSetting = budgetSettingNow();
|
|
1904
|
+
const money = sessionMoney(live.record.id);
|
|
1905
|
+
live.budgetStampKey = budgetStamp(budgetSetting, money).key;
|
|
1757
1906
|
return buildAssistantPersona({
|
|
1758
1907
|
assistantSessionId: live.record.id,
|
|
1759
1908
|
brain,
|
|
@@ -1773,6 +1922,8 @@ export function createAssistantRuntime({
|
|
|
1773
1922
|
computer: { available: computerSupported(), enabled: computerEffective(live.record), setupState, remote: computerDecision(live, { untrusted: false }) },
|
|
1774
1923
|
taskClasses: TASK_CLASS_META,
|
|
1775
1924
|
testDensity: testDensityOf(live.record),
|
|
1925
|
+
// The session budget as the brain starts: the setting and, with a budget, the split so far.
|
|
1926
|
+
budget: budgetSetting ? { ...budgetSetting, view: money } : null,
|
|
1776
1927
|
clarify: !!clarifier,
|
|
1777
1928
|
channel: live.record.channel || live.record.remote?.channel || null,
|
|
1778
1929
|
});
|
|
@@ -1823,11 +1974,15 @@ export function createAssistantRuntime({
|
|
|
1823
1974
|
live.computerGrantLive = false;
|
|
1824
1975
|
} catch (error) { log('assistant:desktop-grant-error', error?.message || String(error)); }
|
|
1825
1976
|
}
|
|
1977
|
+
// whatsapp_send_file: one credential per brain launch of a WhatsApp conversation (BRAIN_CREDENTIAL_RE),
|
|
1978
|
+
// for that brain's SynaBun MCP server alone. Every other session's brain gets none.
|
|
1979
|
+
live.brainCredential = (live.record.channel || live.record.remote?.channel) === 'whatsapp' ? mintBrainCredential() : null;
|
|
1980
|
+
const cliCap = brain.provider === 'claude-code' ? brainCliCap(live) : null;
|
|
1826
1981
|
const deps = {
|
|
1827
1982
|
ClaudeSession, sdkVersion, PACKAGE_ROOT, handleCodexSkinWebSocket, ensureIsolatedServe, stopIsolatedServe, setupOpencodeSidepanelConfig,
|
|
1828
1983
|
claudeAccountEnv: (accountId) => (claudeAccounts?.envFor?.(accountId) || {}),
|
|
1829
|
-
// The Claude CLI caps this process at what the
|
|
1830
|
-
maxBudgetUsd:
|
|
1984
|
+
// The Claude CLI caps this process at what the brain's provider share has left (none under No limit).
|
|
1985
|
+
maxBudgetUsd: cliCap ?? undefined,
|
|
1831
1986
|
// A Codex brain reports tokens: priced at the list price of its model (estimated).
|
|
1832
1987
|
priceCodexUsage: (usage) => {
|
|
1833
1988
|
const price = codexBrainPrice(live);
|
|
@@ -1843,6 +1998,7 @@ export function createAssistantRuntime({
|
|
|
1843
1998
|
// Also a resume into another session: what the meter counts as the main loop follows it.
|
|
1844
1999
|
onOpenCodeRoot: (sessionId) => { live.record.providerSessionId = sessionId; metered('meter-state', () => live.brainMeter?.setRoot?.(sessionId)); saveMeter(live); },
|
|
1845
2000
|
desktopGrant: live.desktopGrant || null,
|
|
2001
|
+
brainCredential: live.brainCredential || null,
|
|
1846
2002
|
// A remote session's level (lib/remote-policy.js): the Claude brain allows
|
|
1847
2003
|
// bypass only at autonomous. null for a desktop session.
|
|
1848
2004
|
remoteLevel: remote ? effectiveLevel(remote, { now: now() }) : null,
|
|
@@ -1881,6 +2037,9 @@ export function createAssistantRuntime({
|
|
|
1881
2037
|
const claude = brain.provider === 'claude-code';
|
|
1882
2038
|
const hooks = mergeHooks(mergeHooks(mergeHooks(memoryHooks, claude && gate ? gateHooks(live) : null), claude ? browserHooks(live) : null), claude && (remote || live.record.remote) ? remoteHooks(live) : null);
|
|
1883
2039
|
live.brainProvider = brain.provider;
|
|
2040
|
+
// What the CLI cap was sized from: a later change of the budget restarts a Claude brain (runTurn).
|
|
2041
|
+
live.brainCliCap = cliCap;
|
|
2042
|
+
live.brainBudgetVersion = budgetSettingNow()?.version ?? null;
|
|
1884
2043
|
startMeter(live, brain.provider);
|
|
1885
2044
|
const created = factory({ session: live.record, sink, deps, persona, hooks });
|
|
1886
2045
|
live.brain = created;
|
|
@@ -1912,6 +2071,8 @@ export function createAssistantRuntime({
|
|
|
1912
2071
|
if (identity.providerThreadId) live.record.providerThreadId = identity.providerThreadId;
|
|
1913
2072
|
} catch {}
|
|
1914
2073
|
live.brain = null;
|
|
2074
|
+
// Its credential dies with it: a file send presenting it is refused from here on.
|
|
2075
|
+
live.brainCredential = null;
|
|
1915
2076
|
if (live.brainProvider === 'codex') pollCodex(live, { force: true });
|
|
1916
2077
|
// No further turn will book them: a Claude brain's provisional tokens are booked as an estimate,
|
|
1917
2078
|
// any other brain's go (Codex was just polled, OpenCode settled at its turn's end).
|
|
@@ -2054,8 +2215,10 @@ export function createAssistantRuntime({
|
|
|
2054
2215
|
}
|
|
2055
2216
|
}
|
|
2056
2217
|
// `authority`: the WhatsApp bridge's capability; it alone makes a prompt count as the owner's own
|
|
2057
|
-
// plain message from the phone (fromPhone). `origin` below is audit text and
|
|
2058
|
-
//
|
|
2218
|
+
// plain message from the phone (fromPhone). `origin` below is audit text and where an answer goes
|
|
2219
|
+
// (a continuation's, to the chat its prompt came from); it grants nothing.
|
|
2220
|
+
// `origin`: who wrote a human prompt ('ui', 'whatsapp', 'api'), or for a route continuation
|
|
2221
|
+
// (`work.kind` 'continuation') who wrote the prompt it carries on; `untrusted`: its
|
|
2059
2222
|
// text carries content the owner did not write (a remote session caps it at ask).
|
|
2060
2223
|
// `offerPlan`: a system turn that revises a plan still ends with a plan card.
|
|
2061
2224
|
// `work`: the scheduler entry this system turn is; `request`: { id, reply } of a
|
|
@@ -2082,13 +2245,26 @@ export function createAssistantRuntime({
|
|
|
2082
2245
|
try { await catalog.full(); } catch {}
|
|
2083
2246
|
if (cancelled()) return dropTurn(live, request);
|
|
2084
2247
|
}
|
|
2085
|
-
|
|
2248
|
+
let budgetBlock = brainBudgetBlock(live);
|
|
2249
|
+
// The brain's provider share is used up but the session has money left: it is divided again first.
|
|
2250
|
+
if (budgetBlock?.share && await coverBrainTurn(live)) {
|
|
2251
|
+
if (cancelled()) return dropTurn(live, request);
|
|
2252
|
+
budgetBlock = brainBudgetBlock(live);
|
|
2253
|
+
}
|
|
2086
2254
|
if (budgetBlock) return refuse(live, budgetBlock.code, budgetBlock.message, { request });
|
|
2255
|
+
// A Claude brain whose account cannot run (signed out, its key unreadable, removed) is refused with
|
|
2256
|
+
// why, before a process starts under it: the CLI would only answer "not logged in" mid-turn.
|
|
2257
|
+
if (!live.brain && ['claude-code', 'codex'].includes(live.record.brain?.provider)) {
|
|
2258
|
+
const unusable = live.record.brain.provider === 'codex' ? codexAccountUnusable(live.record.brain.accountId) : claudeAccountUnusable(live.record.brain.accountId);
|
|
2259
|
+
if (unusable) return refuse(live, 'ACCOUNT_SIGNED_OUT', `${unusable} Pick another account in the brain picker, or sign it in from its Account menu → Manage….`, { request });
|
|
2260
|
+
}
|
|
2087
2261
|
// A remote session: a human prompt sets the trust of this turn and the ones it leads to.
|
|
2088
2262
|
const remote = remotePolicyFor(live);
|
|
2089
2263
|
if (!system) live.turnUntrusted = untrusted === true;
|
|
2090
|
-
// A Claude brain its CLI cap stopped restarts with the budget it has now
|
|
2091
|
-
|
|
2264
|
+
// A Claude brain its CLI cap stopped restarts with the budget it has now, and so does one whose
|
|
2265
|
+
// budget setting changed since it started (a raise, a cut, No limit: no CLI cap at all).
|
|
2266
|
+
const budgetMoved = live.brainProvider === 'claude-code' && live.brainBudgetVersion !== undefined && live.brainBudgetVersion !== (budgetSettingNow()?.version ?? null);
|
|
2267
|
+
if ((live.brainCapHit || budgetMoved) && live.brain && !live.running) {
|
|
2092
2268
|
live.brainCapHit = false;
|
|
2093
2269
|
await disposeBrain(live, 'budget');
|
|
2094
2270
|
if (cancelled()) return dropTurn(live, request);
|
|
@@ -2123,6 +2299,7 @@ export function createAssistantRuntime({
|
|
|
2123
2299
|
let turnRecalled = null;
|
|
2124
2300
|
if (!system) {
|
|
2125
2301
|
live.lastPrompt = prompt;
|
|
2302
|
+
live.lastPromptOrigin = origin ? String(origin) : null;
|
|
2126
2303
|
// A prompt from elsewhere (WhatsApp, the API) is news to the panel: it goes live too.
|
|
2127
2304
|
const promptPacket = { type: 'event', event: { type: 'synabun.user_prompt', text: prompt, images: images.length, ...(origin && origin !== 'ui' ? { origin: String(origin) } : {}) } };
|
|
2128
2305
|
appendTranscript(live.record.id, { at: iso(now()), packet: promptPacket });
|
|
@@ -2170,6 +2347,9 @@ export function createAssistantRuntime({
|
|
|
2170
2347
|
live.testDensityStamp = tests;
|
|
2171
2348
|
}
|
|
2172
2349
|
} catch {}
|
|
2350
|
+
// The budget changed since the brain last saw it: one "[SynaBun Budget]" line, in the same place.
|
|
2351
|
+
const budgetNote = budgetLine(live);
|
|
2352
|
+
if (budgetNote) finalText = finalText.endsWith(prompt) ? `${finalText.slice(0, finalText.length - prompt.length)}${budgetNote}\n\n${prompt}` : `${budgetNote}\n\n${finalText}`;
|
|
2173
2353
|
// A new request cycle. Open clarify questions make this message the
|
|
2174
2354
|
// user's reply (assistant-clarify.js); otherwise the last brief retires.
|
|
2175
2355
|
live.promptCycle += 1;
|
|
@@ -2206,6 +2386,14 @@ export function createAssistantRuntime({
|
|
|
2206
2386
|
}
|
|
2207
2387
|
// The last check: from here on the turn is the brain's.
|
|
2208
2388
|
if (cancelled()) return dropTurn(live, request);
|
|
2389
|
+
// A route continuation starts: before any of its output, every host and observer hears where the
|
|
2390
|
+
// prompt it carries on came from (the WhatsApp bridge answers the chat that started the task).
|
|
2391
|
+
// It stays a system turn: no user_prompt, no new request cycle, nothing granted (computer use asks).
|
|
2392
|
+
if (system && work?.kind === 'continuation') {
|
|
2393
|
+
const continued = { type: 'event', event: { type: 'synabun.continuation', origin: origin ? String(origin) : null } };
|
|
2394
|
+
appendTranscript(live.record.id, { at: iso(now()), packet: continued });
|
|
2395
|
+
sendToSockets(live, continued);
|
|
2396
|
+
}
|
|
2209
2397
|
if (usage) {
|
|
2210
2398
|
taskId ||= live.record.taskId || ensureTaskId(live.record.id);
|
|
2211
2399
|
// record.taskId is the latest human task: a system turn that carries its own names only its turn.
|
|
@@ -2215,8 +2403,8 @@ export function createAssistantRuntime({
|
|
|
2215
2403
|
else live.turnTaskId = taskId;
|
|
2216
2404
|
scheduleUsage(live.record.id);
|
|
2217
2405
|
}
|
|
2218
|
-
// A remote session runs every turn at what its level allows now (an
|
|
2219
|
-
//
|
|
2406
|
+
// A remote session runs every turn at what its level allows now (a pause, an
|
|
2407
|
+
// untrusted prompt): the modes go to the brain with the turn.
|
|
2220
2408
|
if (remote) live.record.brain = clampedBrain(live, live.record.brain);
|
|
2221
2409
|
// A Codex / OpenCode plan turn ends with the plan, not an approval request: the runtime asks.
|
|
2222
2410
|
// (runQuery already closed the plan card this turn replaces.)
|
|
@@ -2353,10 +2541,24 @@ export function createAssistantRuntime({
|
|
|
2353
2541
|
try { mark = dispatcher?.deliveryState?.(item.run.runId) || null; } catch { mark = null; }
|
|
2354
2542
|
return !!mark && Number(mark.turn) >= turn;
|
|
2355
2543
|
}
|
|
2356
|
-
/**
|
|
2544
|
+
/**
|
|
2545
|
+
* Out of money: the events wait (a raise or No limit re-schedules them), said once. The brain's
|
|
2546
|
+
* provider share used up while the session has money left: the budget is divided again, then the
|
|
2547
|
+
* mailbox goes.
|
|
2548
|
+
*/
|
|
2357
2549
|
function mailboxAllowed(live) {
|
|
2358
2550
|
const budgetBlock = brainBudgetBlock(live);
|
|
2359
2551
|
if (!budgetBlock) return true;
|
|
2552
|
+
if (budgetBlock.share && !live.budgetCovering && !(live.budgetShareTriedAt && now() - live.budgetShareTriedAt < 10_000)) {
|
|
2553
|
+
live.budgetCovering = true;
|
|
2554
|
+
coverBrainTurn(live).then((covered) => {
|
|
2555
|
+
live.budgetCovering = false;
|
|
2556
|
+
live.budgetShareTriedAt = covered ? 0 : now();
|
|
2557
|
+
if (live.mailbox.length) scheduleMailbox(live, 0);
|
|
2558
|
+
}).catch(() => { live.budgetCovering = false; live.budgetShareTriedAt = now(); });
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
if (live.budgetCovering) return false;
|
|
2360
2562
|
if (!live.budgetHoldNotified) {
|
|
2361
2563
|
live.budgetHoldNotified = true;
|
|
2362
2564
|
sendToSockets(live, { type: 'error', code: budgetBlock.code, message: `${budgetBlock.message} ${live.mailbox.length} agent event${live.mailbox.length === 1 ? '' : 's'} will reach the brain after that.` });
|
|
@@ -2387,7 +2589,9 @@ export function createAssistantRuntime({
|
|
|
2387
2589
|
let sessionRuns = [];
|
|
2388
2590
|
try { sessionRuns = unread.some((item) => item?.kind === 'route_decided') ? (dispatcher?.list?.({ assistantSessionId: live.record.id, limit: 500 }) || []) : []; } catch { sessionRuns = []; }
|
|
2389
2591
|
const items = reconcileRouteItems(unread, sessionRuns);
|
|
2390
|
-
|
|
2592
|
+
// A budget change since the brain last saw it rides first (a raise is what released a held mailbox).
|
|
2593
|
+
const budgetNote = budgetLine(live);
|
|
2594
|
+
const text = budgetNote ? `${budgetNote}\n\n${formatMailbox(items)}` : formatMailbox(items);
|
|
2391
2595
|
sendToSockets(live, { type: 'event', event: { type: 'synabun.mailbox', items } });
|
|
2392
2596
|
appendTranscript(live.record.id, { at: iso(now()), packet: { type: 'event', event: { type: 'synabun.mailbox', items } } });
|
|
2393
2597
|
live.mailboxTurn = true;
|
|
@@ -2480,15 +2684,25 @@ export function createAssistantRuntime({
|
|
|
2480
2684
|
* `fallback`: the UI's start path — a disabled model starts on its enabled stand-in (reported back) instead of refusing.
|
|
2481
2685
|
* `channel`: where the conversation happens ('whatsapp'); the persona adapts to it.
|
|
2482
2686
|
* `internal.remote` ({ channel, level, strictWorkerApprovals, autonomousUntil, computerUse }): an in-process
|
|
2483
|
-
* caller (the WhatsApp bridge) starts a remote session
|
|
2687
|
+
* caller (the WhatsApp bridge) starts a remote session (`autonomousUntil` limits Autonomous on a channel
|
|
2688
|
+
* other than WhatsApp only: on WhatsApp it has no time limit). REST callers pass only the body, so
|
|
2484
2689
|
* they cannot. The record is flagged before it is persisted: after a restart without a new
|
|
2485
2690
|
* registration the session reads as read-only.
|
|
2486
2691
|
*/
|
|
2692
|
+
async function resolveAutoAccount(brain) {
|
|
2693
|
+
if (brain.accountId !== 'auto' || !['claude-code', 'codex'].includes(brain.provider)) return brain;
|
|
2694
|
+
await accountUsage?.refresh(brain.provider);
|
|
2695
|
+
const rows = accountUsage?.list()?.[brain.provider] || [];
|
|
2696
|
+
const key = brain.provider === 'codex' ? 'codexAccount' : 'claudeAccount';
|
|
2697
|
+
const { row } = chooseUsageAccount(rows, 'auto', configStore?.workers?.()?.[key] || 'default');
|
|
2698
|
+
if (!row) throw Object.assign(new Error('Auto requires a signed-in subscription account.'), { code: 'ACCOUNT_NO_SUBSCRIPTION', status: 409 });
|
|
2699
|
+
return { ...brain, accountId: row.id };
|
|
2700
|
+
}
|
|
2487
2701
|
async function createSession({ brain = {}, label = null, cwd = null, windowId = null, fallback = false, channel = null } = {}, internal = {}) {
|
|
2488
2702
|
const id = `assistant-${randomUUID()}`;
|
|
2489
2703
|
const requested = normalizeBrain({ ...brain, cwd: brain?.cwd || cwd || settings.defaultBrain.cwd || PACKAGE_ROOT }, settings.defaultBrain, { catalog: catalog?.peek?.() || null });
|
|
2490
2704
|
const enabled = await enabledBrain(requested, { fallback: fallback === true, named: !!brain?.model });
|
|
2491
|
-
const normalized = enabled.brain;
|
|
2705
|
+
const normalized = await resolveAutoAccount(enabled.brain);
|
|
2492
2706
|
const record = {
|
|
2493
2707
|
id, brain: normalized, title: label ? clip(label, 60) : DEFAULT_SESSION_TITLE, project: detectProject(normalized.cwd) || 'global',
|
|
2494
2708
|
createdAt: iso(now()), updatedAt: iso(now()), lastActiveAt: null, status: 'idle', providerSessionId: null, providerThreadId: null,
|
|
@@ -2556,12 +2770,13 @@ export function createAssistantRuntime({
|
|
|
2556
2770
|
const switching = BRAIN_PROVIDERS.has(patch.brain.provider) && patch.brain.provider !== live.record.brain.provider;
|
|
2557
2771
|
const base = switching && !patch.brain.model ? { ...live.record.brain, model: null } : live.record.brain;
|
|
2558
2772
|
let next = normalizeBrain({ ...base, ...patch.brain, model: patch.brain.model || base.model }, base, { catalog: catalog?.peek?.() || null });
|
|
2773
|
+
next = await resolveAutoAccount(next);
|
|
2559
2774
|
const modelChanged = next.model !== live.record.brain.model || next.provider !== live.record.brain.provider;
|
|
2560
2775
|
if (modelChanged) next = (await enabledBrain(next)).brain;
|
|
2561
2776
|
// A remote session keeps what was asked for and runs what its level allows.
|
|
2562
2777
|
if ('permissionMode' in patch.brain || 'planMode' in patch.brain) next = clampModeWrite(live, next);
|
|
2563
2778
|
else next = clampedBrain(live, next);
|
|
2564
|
-
const providerChanged = next.provider !== live.record.brain.provider || next.accountId !== live.record.brain.accountId || next.cwd !== live.record.brain.cwd || next.mcpProfile !== live.record.brain.mcpProfile;
|
|
2779
|
+
const providerChanged = patch.restartBrain === true || next.provider !== live.record.brain.provider || next.accountId !== live.record.brain.accountId || next.cwd !== live.record.brain.cwd || next.mcpProfile !== live.record.brain.mcpProfile;
|
|
2565
2780
|
// A usage limit is the account's: another provider or account starts without the notice.
|
|
2566
2781
|
if (next.provider !== live.record.brain.provider || next.accountId !== live.record.brain.accountId) setLimit(live, null);
|
|
2567
2782
|
live.record.brain = next;
|
|
@@ -2661,6 +2876,13 @@ export function createAssistantRuntime({
|
|
|
2661
2876
|
}
|
|
2662
2877
|
return null;
|
|
2663
2878
|
}
|
|
2879
|
+
// Why the Claude account a brain names cannot run now ('' when it can, or when that is not known yet).
|
|
2880
|
+
function claudeAccountUnusable(accountId) {
|
|
2881
|
+
try { return claudeAccounts?.unusableReason?.(accountId || 'default') || ''; } catch { return ''; }
|
|
2882
|
+
}
|
|
2883
|
+
function codexAccountUnusable(accountId) {
|
|
2884
|
+
try { return codexAccounts?.unusableReason?.(accountId || 'default') || ''; } catch { return ''; }
|
|
2885
|
+
}
|
|
2664
2886
|
function accountInUse(provider, accountId) {
|
|
2665
2887
|
for (const live of sessions.values()) {
|
|
2666
2888
|
if (live.brain && live.record.brain?.provider === provider && (live.record.brain?.accountId || 'default') === accountId) return true;
|
|
@@ -2687,6 +2909,23 @@ export function createAssistantRuntime({
|
|
|
2687
2909
|
}
|
|
2688
2910
|
/** Something runs, or the approved plan's turn or a compact is waiting to (a prompt would be refused). */
|
|
2689
2911
|
function isBusy(id) { const live = sessions.get(String(id || '')); return sessionBusy(live) || (!!live && !!workReserved(live)); }
|
|
2912
|
+
/**
|
|
2913
|
+
* The session whose live brain holds this credential (whatsapp_send_file's caller identity:
|
|
2914
|
+
* BRAIN_CREDENTIAL_RE), or null. Only a well-formed value is compared, in constant time and
|
|
2915
|
+
* against every live brain, so neither a match nor its position shows in the time taken.
|
|
2916
|
+
*/
|
|
2917
|
+
function sessionForBrainCredential(value) {
|
|
2918
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
2919
|
+
if (!BRAIN_CREDENTIAL_RE.test(text)) return null;
|
|
2920
|
+
const given = createHash('sha256').update(text).digest();
|
|
2921
|
+
let found = null;
|
|
2922
|
+
for (const [id, live] of sessions) {
|
|
2923
|
+
if (!live.brain || typeof live.brainCredential !== 'string') continue;
|
|
2924
|
+
const held = createHash('sha256').update(live.brainCredential).digest();
|
|
2925
|
+
if (timingSafeEqual(given, held) && found === null) found = id;
|
|
2926
|
+
}
|
|
2927
|
+
return found;
|
|
2928
|
+
}
|
|
2690
2929
|
/** The session's remote policy (lib/remote-policy.js), or null. */
|
|
2691
2930
|
function sessionPolicy(id) { const live = sessions.get(String(id || '')); return live ? remotePolicyFor(live) : null; }
|
|
2692
2931
|
/** Where the session's conversation happens: 'whatsapp' (WhatsApp Link), or null for the panel. */
|
|
@@ -2904,6 +3143,8 @@ export function createAssistantRuntime({
|
|
|
2904
3143
|
try { ws.send(JSON.stringify(engine)); } catch {}
|
|
2905
3144
|
try { ws.send(JSON.stringify({ type: 'assistant:session', session: view(live), reason: 'attached' })); } catch {}
|
|
2906
3145
|
if (usage) try { ws.send(JSON.stringify({ type: 'assistant:usage', ...usageView(live.record.id) })); } catch {}
|
|
3146
|
+
// The session budget and its split as they are now.
|
|
3147
|
+
try { const budgetNow = sessionMoney(live.record.id); if (budgetNow) ws.send(JSON.stringify({ type: 'assistant:budget', sessionId: live.record.id, reason: 'attached', budget: budgetNow })); } catch {}
|
|
2907
3148
|
// The limit notice as it is now (null clears one this panel still shows from before).
|
|
2908
3149
|
sendLimit(live, [ws]);
|
|
2909
3150
|
const replay = live.buffer.splice(0, live.buffer.length);
|
|
@@ -3056,6 +3297,13 @@ export function createAssistantRuntime({
|
|
|
3056
3297
|
}
|
|
3057
3298
|
|
|
3058
3299
|
// ── router sinks (wired lazily in server.js; see assistant-router.js) ──────
|
|
3300
|
+
/** What the budget planner needs of a session: its brain, project, and whether it is still open. */
|
|
3301
|
+
function budgetSession(id) {
|
|
3302
|
+
const live = sessions.get(String(id || ''));
|
|
3303
|
+
if (!live) return null;
|
|
3304
|
+
const brain = live.record.brain || {};
|
|
3305
|
+
return { provider: brain.provider || null, model: brain.model || null, accountId: brain.accountId || null, project: live.record.project || null, open: live.record.status !== 'ended' };
|
|
3306
|
+
}
|
|
3059
3307
|
function routerSession(id) {
|
|
3060
3308
|
const live = sessions.get(id);
|
|
3061
3309
|
if (!live) return null;
|
|
@@ -3130,7 +3378,8 @@ export function createAssistantRuntime({
|
|
|
3130
3378
|
const label = row?.label || target.model || 'the chosen model';
|
|
3131
3379
|
const summary = route?.summary || '';
|
|
3132
3380
|
const text = `[SynaBun Router] The user chose to run ${summary ? `"${summary}"` : 'this task'} here on ${label}${target.effort ? ` (effort ${target.effort})` : ''}. Continue that task now on this model, from where the conversation left off.`;
|
|
3133
|
-
|
|
3381
|
+
// `origin`: where the prompt this task came from was written, so the continuation answers there.
|
|
3382
|
+
queueWork(live, { kind: 'continuation', gen, options: { text, turnModel: target.model || null, turnEffort: target.effort || null, gateOpen: true, origin: lastPromptOrigin(live) } });
|
|
3134
3383
|
return true;
|
|
3135
3384
|
}
|
|
3136
3385
|
function routerSinks() {
|
|
@@ -3157,6 +3406,19 @@ export function createAssistantRuntime({
|
|
|
3157
3406
|
}
|
|
3158
3407
|
return '';
|
|
3159
3408
|
}
|
|
3409
|
+
/**
|
|
3410
|
+
* Where the last human prompt came from ('ui', 'whatsapp', 'api'): the transcript's after a restart
|
|
3411
|
+
* (a prompt journaled without an origin was typed in the panel). null when there is none.
|
|
3412
|
+
*/
|
|
3413
|
+
function lastPromptOrigin(live) {
|
|
3414
|
+
if (live.lastPromptOrigin) return live.lastPromptOrigin;
|
|
3415
|
+
const entries = readTranscript(live.record.id, 200);
|
|
3416
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
3417
|
+
const event = entries[i]?.packet?.event;
|
|
3418
|
+
if (event?.type === 'synabun.user_prompt') return event.origin ? String(event.origin) : 'ui';
|
|
3419
|
+
}
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3160
3422
|
function clarifySession(id) {
|
|
3161
3423
|
const live = sessions.get(id);
|
|
3162
3424
|
if (!live) return null;
|
|
@@ -3372,7 +3634,7 @@ export function createAssistantRuntime({
|
|
|
3372
3634
|
? { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', permissionDecisionReason: 'Computer use is on for this WhatsApp turn.' } }
|
|
3373
3635
|
: { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask', permissionDecisionReason: 'The user is asked once per task before the Mac is controlled from WhatsApp.' } };
|
|
3374
3636
|
}
|
|
3375
|
-
if (remoteLevel(live) === 'autonomous' || REMOTE_ASK_EXEMPT.has(tool)) return {};
|
|
3637
|
+
if (remoteLevel(live) === 'autonomous' || REMOTE_ASK_EXEMPT.has(tool) || isReplyTool(tool)) return {};
|
|
3376
3638
|
if (claudeReadOnlyPermission(tool, toolInput) !== 'deny') return {};
|
|
3377
3639
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask', permissionDecisionReason: `This session is driven from ${live.record.remote?.channel === 'whatsapp' || !live.record.remote?.channel ? 'WhatsApp' : live.record.remote.channel}: the user approves ${tool} first.` } };
|
|
3378
3640
|
}],
|
|
@@ -3498,9 +3760,11 @@ export function createAssistantRuntime({
|
|
|
3498
3760
|
testDensityFor, routerSession, routerSend, routerEvent, routerMailbox, routerContinue, routerSinks, routerRouted, routerRouteFailed, routerComputerApproved, gateCheck, isPlanning, isPlanReadOnly,
|
|
3499
3761
|
clarifySession, clarifyEvent, clarifyGate, clarifySinks,
|
|
3500
3762
|
getComputerUse, getComputerTurn, supersedeComputerApproval, abortTurn, notifySession,
|
|
3501
|
-
brainSpend,
|
|
3763
|
+
brainSpend, brainSpendByProvider, budgetSession, onBudgetChanged, onBudgetSplit, sendBudget, currentTask, usageView,
|
|
3502
3764
|
// Programmatic surface (the WhatsApp bridge) and remote sessions.
|
|
3503
3765
|
observe, submit, stopTurn, answerControl, answerDispatchControl, pendingControls, isBusy, sessionPolicy, sessionChannel,
|
|
3766
|
+
// whatsapp_send_file's caller identity (POST /api/assistant/whatsapp/send-file).
|
|
3767
|
+
sessionForBrainCredential,
|
|
3504
3768
|
_internals: { sessions, ensureBrain, disposeBrain, runQuery, enqueueMailbox, deliverMailbox, settings, ensureGate, onBrainPacket, onTurnFinished, remoteHooks, packetObservers, userContext },
|
|
3505
3769
|
};
|
|
3506
3770
|
}
|