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
|
@@ -12,21 +12,26 @@ import { DispatchError } from './assistant-dispatch.js';
|
|
|
12
12
|
import { openServable } from './assistant-media.js';
|
|
13
13
|
import { designFit, targetMakes } from './assistant-router.js';
|
|
14
14
|
import { normalizeEffort } from './effort-levels.js';
|
|
15
|
-
import {
|
|
15
|
+
import { BUDGET_DEFAULT_USD, BUDGET_MAX_USD, BUDGET_MIN_USD, BUDGET_WARN_RATIO } from './assistant-budget.js';
|
|
16
16
|
import { tooLargeMessage } from './assistant-attachments.js';
|
|
17
17
|
import { getSessionPolicy, requiresHumanApproval } from './remote-policy.js';
|
|
18
18
|
import { clampPlanDispatch, PLAN_DISPATCH_NOTE, runCanChangeCode } from './assistant-plan-permissions.js';
|
|
19
|
+
import { noStore, requireJson, requireLocal, requireLocalHost, sendGuardError } from './http-guards.js';
|
|
20
|
+
|
|
21
|
+
/** The header whatsapp_send_file's caller proves it is the WhatsApp conversation's own brain with (assistant-runtime.js BRAIN_CREDENTIAL_RE). */
|
|
22
|
+
export const BRAIN_CREDENTIAL_HEADER = 'x-synabun-brain-token';
|
|
23
|
+
const SEND_FILE_FIELDS = Object.freeze(['path', 'caption', 'as']);
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
|
-
* How far each provider's spend can be held to
|
|
22
|
-
*
|
|
23
|
-
*
|
|
26
|
+
* How far each provider's spend can be held to the budget (the Budget modal shows it). Spend is
|
|
27
|
+
* checked where the provider lets it be checked; between two checks a run can go past its share by
|
|
28
|
+
* that much.
|
|
24
29
|
*/
|
|
25
30
|
export const BUDGET_ENFORCEMENT = Object.freeze({
|
|
26
|
-
'claude-code': { metered: 'reported',
|
|
27
|
-
opencode: { metered: 'reported',
|
|
28
|
-
codex: { metered: 'estimated',
|
|
29
|
-
brain: { metered: 'reported',
|
|
31
|
+
'claude-code': { metered: 'reported', share: 'Checked between model calls (each call at list price while the turn runs, the reported cost at its end), so one call can pass the share. The Claude CLI also stops a run at its ceiling, or at what the session had left when it started.', session: 'At the budget every run stops.' },
|
|
32
|
+
opencode: { metered: 'reported', share: 'OpenCode reports each step\'s cost; the share is checked at every step, so one step can pass it.', session: 'At the budget every run stops.' },
|
|
33
|
+
codex: { metered: 'estimated', share: 'Codex reports tokens: priced at list price (an API-price estimate; ChatGPT-plan accounts are billed by plan quota) as its rollout records them, so one record or turn can pass the share. A failed turn is priced from its rollout.', session: 'At the budget every run stops.' },
|
|
34
|
+
brain: { metered: 'reported', share: 'Your Assistant\'s own turns draw from its provider\'s share, which keeps a reserve for them; checked before every brain turn. A Claude brain\'s CLI also stops at what that share had left when it started. A brain model with no price is refused.', session: 'At the budget the brain takes no turn until the budget is raised.' },
|
|
30
35
|
});
|
|
31
36
|
|
|
32
37
|
const OWNER_ONLY = { error: 'The assistant is available to the owner only', code: 'GUEST_FORBIDDEN' };
|
|
@@ -89,9 +94,17 @@ export function slimRunView(run) {
|
|
|
89
94
|
* @param {object} deps.dispatcher createAssistantDispatcher()
|
|
90
95
|
* @param {object} [deps.runtime] createAssistantRuntime() (sessions; optional until wired)
|
|
91
96
|
* @param {object} [deps.claudeAccounts] createClaudeAccounts()
|
|
92
|
-
* @param {object} [deps.codexAccounts]
|
|
97
|
+
* @param {object} [deps.codexAccounts] createCodexAccounts() (lib/codex-accounts.js)
|
|
93
98
|
* @param {Function} [deps.buildCatalog] async () => catalog object (models/accounts/projects/profiles)
|
|
94
99
|
* @param {Function} [deps.createLoginTerminal] async ({ env, cwd, profile, accountId }) => { terminalSessionId, profile }
|
|
100
|
+
* @param {Function} [deps.claudeSignIn] async (accountId) => { sessionId, status, error } — Claude Code's own
|
|
101
|
+
* `auth login` in a SynaBun terminal under the account's config directory (lib/provider-panel-terminal-auth.js)
|
|
102
|
+
* @param {Function} [deps.claudeSetMode] async (accountId, mode) => row — what the account's sessions use
|
|
103
|
+
* @param {Function} [deps.claudeAccountInUse] (accountId) => '' | why it cannot be removed now
|
|
104
|
+
* @param {Function} [deps.codexSignIn] async (accountId) => { sessionId, status, error } — `codex login` in a SynaBun
|
|
105
|
+
* terminal under the account's CODEX_HOME (lib/provider-panel-terminal-auth.js)
|
|
106
|
+
* @param {Function} [deps.codexSetMode] async (accountId, mode) => row — what the account's sessions use
|
|
107
|
+
* @param {Function} [deps.codexAccountInUse] (accountId) => '' | why it cannot be removed now
|
|
95
108
|
* @param {object} [deps.attachments] createAttachmentStore() (POST /attachments)
|
|
96
109
|
*/
|
|
97
110
|
export function createAssistantApi({
|
|
@@ -100,8 +113,15 @@ export function createAssistantApi({
|
|
|
100
113
|
usage = null,
|
|
101
114
|
claudeAccounts = null,
|
|
102
115
|
codexAccounts = null,
|
|
116
|
+
accountUsage = null,
|
|
103
117
|
buildCatalog = null,
|
|
104
118
|
createLoginTerminal = null,
|
|
119
|
+
claudeSignIn = null,
|
|
120
|
+
claudeSetMode = null,
|
|
121
|
+
claudeAccountInUse = null,
|
|
122
|
+
codexSignIn = null,
|
|
123
|
+
codexSetMode = null,
|
|
124
|
+
codexAccountInUse = null,
|
|
105
125
|
broadcastSync = () => {},
|
|
106
126
|
isGuestRequest = () => false,
|
|
107
127
|
log = () => {},
|
|
@@ -126,6 +146,12 @@ export function createAssistantApi({
|
|
|
126
146
|
// Why the engine did not start on this server (a string or null), for the settings page.
|
|
127
147
|
userContextError = () => null,
|
|
128
148
|
defaultProject = () => 'global',
|
|
149
|
+
// whatsapp_send_file: the WhatsApp service ({ sendFile }) or a function returning it (it is built
|
|
150
|
+
// after this router), and the port the server listens on (the Host check of that route).
|
|
151
|
+
whatsapp = null,
|
|
152
|
+
port = 3344,
|
|
153
|
+
// The Admin sandbox setting (lib/agent-sandbox-setting.js): one global value for every agent SynaBun starts.
|
|
154
|
+
adminSandbox = null,
|
|
129
155
|
} = {}) {
|
|
130
156
|
if (!dispatcher) throw new Error('createAssistantApi requires a dispatcher');
|
|
131
157
|
const router = Router();
|
|
@@ -275,6 +301,7 @@ export function createAssistantApi({
|
|
|
275
301
|
// ── catalog ────────────────────────────────────────────────────────────────
|
|
276
302
|
router.get('/catalog', async (req, res) => {
|
|
277
303
|
try {
|
|
304
|
+
await accountUsage?.refresh();
|
|
278
305
|
// refresh=1 rebuilds from the providers' endpoints; deep=1 also makes them ask the CLI / app-server again.
|
|
279
306
|
const deep = req.query.deep === '1';
|
|
280
307
|
const force = req.query.refresh === '1' || deep;
|
|
@@ -288,13 +315,21 @@ export function createAssistantApi({
|
|
|
288
315
|
let preferences = {};
|
|
289
316
|
try { preferences = configStore?.routing?.().preferences || {}; } catch {}
|
|
290
317
|
const brief = await catalog.get({ force, deep, view: 'brain', provider, q, preferences });
|
|
291
|
-
|
|
318
|
+
// Accounts are read live (the catalog build can be seconds old): every Claude and Codex account with
|
|
319
|
+
// its mode, whether it is signed in and its identity, and the account a worker gets when a dispatch
|
|
320
|
+
// names none.
|
|
321
|
+
const accounts = {
|
|
322
|
+
...(brief.accounts || {}),
|
|
323
|
+
...(claudeAccounts ? { 'claude-code': accountUsage?.list()?.['claude-code'] || claudeAccounts.listForClient() } : {}),
|
|
324
|
+
...(codexAccounts ? { codex: accountUsage?.list()?.codex || codexAccounts.listForClient() } : {}),
|
|
325
|
+
};
|
|
326
|
+
return res.json({ ok: true, generatedAt: new Date(now()).toISOString(), ...brief, accounts, claudeWorkerAccount: claudeWorkerAccountId(), codexWorkerAccount: codexWorkerAccountId(), limits: dispatcher.limits, runs: dispatcher.list({ activeOnly: true }), totals: dispatcher.totals() });
|
|
292
327
|
}
|
|
293
328
|
// The catalog says how long one of its builds is good for (one without a verified list is redone
|
|
294
329
|
// within seconds), so it is asked every time: this answer is reused only while it hands back the
|
|
295
330
|
// build the answer was made from (a rebuild or invalidate() gives another one).
|
|
296
331
|
const base = typeof buildCatalog === 'function' ? await buildCatalog({ force, deep }) : NO_CATALOG;
|
|
297
|
-
if (!force && catalogCache && catalogCache.base === base && now() - catalogCache.at < 30_000) return res.json(catalogCache.value);
|
|
332
|
+
if (!force && catalogCache && catalogCache.base === base && now() - catalogCache.at < 30_000) return res.json(accountUsage ? { ...catalogCache.value, accounts: { ...catalogCache.value.accounts, ...accountUsage.list() } } : catalogCache.value);
|
|
298
333
|
// Hidden rows stay server-side (view=manage lists them); the lists themselves ship.
|
|
299
334
|
const { hiddenRows: _hiddenRows, ...visible } = base || {};
|
|
300
335
|
const value = {
|
|
@@ -303,9 +338,11 @@ export function createAssistantApi({
|
|
|
303
338
|
...visible,
|
|
304
339
|
accounts: {
|
|
305
340
|
...(base.accounts || {}),
|
|
306
|
-
'claude-code': claudeAccounts ? claudeAccounts.listForClient() : (base.accounts?.['claude-code'] || []),
|
|
307
|
-
codex: codexAccounts ? codexAccounts.listForClient() : (base.accounts?.codex || []),
|
|
341
|
+
'claude-code': accountUsage?.list()?.['claude-code'] || (claudeAccounts ? claudeAccounts.listForClient() : (base.accounts?.['claude-code'] || [])),
|
|
342
|
+
codex: accountUsage?.list()?.codex || (codexAccounts ? codexAccounts.listForClient() : (base.accounts?.codex || [])),
|
|
308
343
|
},
|
|
344
|
+
claudeWorkerAccount: claudeWorkerAccountId(),
|
|
345
|
+
codexWorkerAccount: codexWorkerAccountId(),
|
|
309
346
|
limits: dispatcher.limits,
|
|
310
347
|
runs: dispatcher.list({ activeOnly: true }),
|
|
311
348
|
totals: dispatcher.totals(),
|
|
@@ -469,16 +506,20 @@ export function createAssistantApi({
|
|
|
469
506
|
} catch (error) { sendError(res, error, 400); }
|
|
470
507
|
});
|
|
471
508
|
// ── budget ─────────────────────────────────────────────────────────────────
|
|
509
|
+
/** A session's budget view (dispatcher → budget planner): the setting, spend, and the live split. */
|
|
510
|
+
function sessionBudgetView(sessionId) {
|
|
511
|
+
if (!sessionId) return null;
|
|
512
|
+
try { return dispatcher.sessionBudget?.(String(sessionId)) || null; } catch { return null; }
|
|
513
|
+
}
|
|
472
514
|
function budgetPayload(sessionId = null) {
|
|
473
515
|
const current = configStore.budget();
|
|
474
|
-
const budget = Object.fromEntries(BUDGET_FIELDS.map((field) => [field, current[field]]));
|
|
475
|
-
let session = null;
|
|
476
|
-
if (sessionId) { try { session = dispatcher.sessionBudget?.(String(sessionId)) || null; } catch { session = null; } }
|
|
477
516
|
let pricingStatus = null;
|
|
478
517
|
try { pricingStatus = pricing?.status?.() || null; } catch { pricingStatus = null; }
|
|
479
518
|
return {
|
|
480
|
-
ok: true, version: current.version,
|
|
481
|
-
|
|
519
|
+
ok: true, version: current.version,
|
|
520
|
+
budget: { unlimited: current.unlimited === true, amountUsd: current.amountUsd, version: current.version, updatedAt: current.updatedAt || null, source: current.source || 'saved' },
|
|
521
|
+
defaults: { unlimited: false, amountUsd: BUDGET_DEFAULT_USD }, bounds: { minUsd: BUDGET_MIN_USD, maxUsd: BUDGET_MAX_USD }, warnRatio: BUDGET_WARN_RATIO,
|
|
522
|
+
session: sessionBudgetView(sessionId), pricing: pricingStatus, enforcement: BUDGET_ENFORCEMENT,
|
|
482
523
|
};
|
|
483
524
|
}
|
|
484
525
|
// ── User context (Settings → Automation behavior): the three layers and what a new conversation is told ──
|
|
@@ -525,13 +566,16 @@ export function createAssistantApi({
|
|
|
525
566
|
if (callerContext(req).kind !== 'ui') return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Budget settings are changed from the assistant panel only.' });
|
|
526
567
|
try {
|
|
527
568
|
const body = req.body || {};
|
|
528
|
-
const patch = body.budget && typeof body.budget === 'object' ? body.budget :
|
|
569
|
+
const patch = body.budget && typeof body.budget === 'object' && !Array.isArray(body.budget) ? body.budget : null;
|
|
570
|
+
if (!patch) return res.status(400).json({ ok: false, code: 'BUDGET_INVALID', error: 'Send { budget: { unlimited?, amountUsd? }, version }', message: 'Send { budget: { unlimited?, amountUsd? }, version }', field: 'budget', version: null });
|
|
529
571
|
const expectedVersion = body.version ?? null;
|
|
530
572
|
const saved = configStore.patchBudget(patch, { expectedVersion });
|
|
531
|
-
// Applies now: the
|
|
573
|
+
// Applies now: the planner divides every open session's budget again, the runtime re-checks
|
|
574
|
+
// every session (held mailboxes, the brain's next turn) and every panel hears it.
|
|
532
575
|
try { dispatcher.refreshLimits?.(); } catch {}
|
|
533
|
-
try { runtime
|
|
534
|
-
|
|
576
|
+
if (runtime?.onBudgetChanged) { try { runtime.onBudgetChanged(); } catch {} }
|
|
577
|
+
else { try { dispatcher.budget?.onBudgetChanged?.(); } catch {} }
|
|
578
|
+
broadcastSync({ type: 'assistant:budget-changed', version: saved.version, budget: { unlimited: saved.unlimited, amountUsd: saved.amountUsd, version: saved.version, updatedAt: saved.updatedAt || null } });
|
|
535
579
|
res.json(budgetPayload(body.sessionId || null));
|
|
536
580
|
} catch (error) {
|
|
537
581
|
if (error?.code === 'BUDGET_INVALID' || error?.code === 'VERSION_CONFLICT') {
|
|
@@ -551,6 +595,45 @@ export function createAssistantApi({
|
|
|
551
595
|
try { res.json(runtime.gateCheck({ session: body.session, token: body.token, tool: body.tool, input, inputComplete: body.inputComplete === true, providerSessionId: body.providerSessionId || null, agent: body.agent || null, hello: body.hello === true, host: typeof body.host === 'string' ? body.host : null })); }
|
|
552
596
|
catch (error) { sendError(res, error); }
|
|
553
597
|
});
|
|
598
|
+
// ── whatsapp_send_file (docs/whatsapp.md → Sending files) ──────────────────
|
|
599
|
+
// A local file to the owner, for the WhatsApp conversation whose brain is calling. Who calls is the
|
|
600
|
+
// credential that brain alone holds (X-Synabun-Brain-Token: assistant-runtime.js mintBrainCredential),
|
|
601
|
+
// resolved to its session in constant time; never X-Synabun-Role or X-Synabun-Terminal, which any
|
|
602
|
+
// local process can send. A loopback socket and a localhost Host only (the checks of /api/whatsapp);
|
|
603
|
+
// guests were refused above. Only { path, caption?, as? }. Exactly one send, never retried.
|
|
604
|
+
const portOf = () => (typeof port === 'function' ? port() : port);
|
|
605
|
+
let hostGuard = null;
|
|
606
|
+
const localHostOnly = (req, res, next) => {
|
|
607
|
+
const current = portOf();
|
|
608
|
+
if (!hostGuard || hostGuard.port !== current) {
|
|
609
|
+
try { hostGuard = { port: current, check: requireLocalHost(current) }; } catch { return sendGuardError(res, 500, 'BAD_PORT', 'The file route has no valid port.'); }
|
|
610
|
+
}
|
|
611
|
+
return hostGuard.check(req, res, next);
|
|
612
|
+
};
|
|
613
|
+
router.post('/whatsapp/send-file', noStore, requireLocal, localHostOnly, requireJson, async (req, res) => {
|
|
614
|
+
let sessionId = null;
|
|
615
|
+
try { sessionId = runtime?.sessionForBrainCredential?.(req.get(BRAIN_CREDENTIAL_HEADER)) || null; } catch { sessionId = null; }
|
|
616
|
+
if (!sessionId) return sendGuardError(res, 403, 'BRAIN_CREDENTIAL_REQUIRED', 'Only the WhatsApp conversation\'s own Assistant sends files to the phone: nothing was sent.');
|
|
617
|
+
const body = req.body;
|
|
618
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return sendGuardError(res, 400, 'BAD_REQUEST', 'Send { path, caption?, as? }.');
|
|
619
|
+
const extra = Object.keys(body).find((key) => !SEND_FILE_FIELDS.includes(key));
|
|
620
|
+
if (extra !== undefined) return sendGuardError(res, 400, 'BAD_REQUEST', `Unknown field "${extra.slice(0, 40)}": a file send takes path, caption and as only.`, extra.slice(0, 40));
|
|
621
|
+
let service = null;
|
|
622
|
+
try { service = typeof whatsapp === 'function' ? whatsapp() : whatsapp; } catch { service = null; }
|
|
623
|
+
if (typeof service?.sendFile !== 'function') return sendGuardError(res, 503, 'WHATSAPP_UNAVAILABLE', 'WhatsApp is not available on this computer: nothing was sent.');
|
|
624
|
+
try {
|
|
625
|
+
res.json(await service.sendFile(sessionId, body));
|
|
626
|
+
} catch (error) {
|
|
627
|
+
const status = Number(error?.status) || 500;
|
|
628
|
+
const out = { ok: false, code: error?.code || 'INTERNAL', error: error?.message || String(error) };
|
|
629
|
+
if (error?.field) out.field = error.field;
|
|
630
|
+
if (error?.uncertain === true) out.uncertain = true;
|
|
631
|
+
if (Number.isFinite(error?.retryAfterMs)) out.retryAfterMs = Math.max(0, Math.round(error.retryAfterMs));
|
|
632
|
+
if (status >= 500) log('assistant:whatsapp-send-file', `${sessionId}: ${out.code}${out.uncertain ? ' (delivery unknown)' : ''}`);
|
|
633
|
+
res.status(status).json(out);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
554
637
|
// agent_route: the brain proposes; the router approves, asks, or says pending.
|
|
555
638
|
router.post('/route', orchestrationGuard, async (req, res) => {
|
|
556
639
|
if (!requireRouter(res)) return;
|
|
@@ -772,13 +855,16 @@ export function createAssistantApi({
|
|
|
772
855
|
res.json(brain ? brainWaitView(chat ? chatWaitView(result) : result) : result);
|
|
773
856
|
} catch (error) { sendError(res, error); }
|
|
774
857
|
});
|
|
775
|
-
router.post('/runs/:runId/send', orchestrationGuard, (req, res) => {
|
|
858
|
+
router.post('/runs/:runId/send', orchestrationGuard, async (req, res) => {
|
|
776
859
|
try {
|
|
777
860
|
const body = req.body || {};
|
|
778
861
|
const assistantSessionId = req.assistantCaller.assistantSessionId || dispatcher.get(req.params.runId)?.assistantSessionId || null;
|
|
779
862
|
if (planRefusal(req, res, assistantSessionId, 'send a worker a message')) return;
|
|
780
863
|
if (planState(req, assistantSessionId) === 'plan' && planSendRefusal(res, dispatcher.get(req.params.runId))) return;
|
|
781
864
|
const origin = body.origin === 'user' ? 'user' : (req.assistantCaller.kind === 'ui' ? (body.origin || 'assistant') : 'assistant');
|
|
865
|
+
// A run whose provider share is used up: the session's unspent budget is divided again first, then
|
|
866
|
+
// sendTurn decides (it refuses with BUDGET_SHARE_EXHAUSTED when no money could move).
|
|
867
|
+
try { await dispatcher.coverFollowUp?.(req.params.runId); } catch { /* sendTurn decides on the shares as they are */ }
|
|
782
868
|
res.json(dispatcher.sendTurn(req.params.runId, body.text ?? body.message, { origin, queue: body.queue !== false }));
|
|
783
869
|
} catch (error) { sendError(res, error); }
|
|
784
870
|
});
|
|
@@ -855,6 +941,14 @@ export function createAssistantApi({
|
|
|
855
941
|
res.status(201).json({ ok: true, session });
|
|
856
942
|
} catch (error) { sendError(res, error); }
|
|
857
943
|
});
|
|
944
|
+
// A session's budget and its live split (the spend chip, the Budget modal): the same view the
|
|
945
|
+
// `assistant:budget` packet carries.
|
|
946
|
+
router.get('/sessions/:id/budget', (req, res) => {
|
|
947
|
+
if (!requireRuntime(res)) return;
|
|
948
|
+
const id = req.params.id;
|
|
949
|
+
if (!runtime.getSession(id, { transcript: false })) return res.status(404).json({ ok: false, code: 'SESSION_NOT_FOUND', error: `Unknown assistant session ${id}` });
|
|
950
|
+
try { res.json({ ok: true, budget: sessionBudgetView(id) }); } catch (error) { sendError(res, error); }
|
|
951
|
+
});
|
|
858
952
|
router.get('/sessions/:id/usage', (req, res) => {
|
|
859
953
|
if (!requireRuntime(res)) return;
|
|
860
954
|
const id = req.params.id;
|
|
@@ -870,9 +964,10 @@ export function createAssistantApi({
|
|
|
870
964
|
const session = usage?.sessionView(id) ?? null;
|
|
871
965
|
// Whether work is in progress is the runtime's to say (a turn that has no tokens yet).
|
|
872
966
|
const head = session ? runtime.usageView?.(id, 'current')?.session || null : null;
|
|
873
|
-
return res.json({ ok: true, session: session && head ? { ...session, live: head.live, fidelity: head.fidelity } : session, ...(run ? { run: usage?.runView?.(id, run) ?? null } : {}) });
|
|
967
|
+
return res.json({ ok: true, session: session && head ? { ...session, live: head.live, fidelity: head.fidelity } : session, ...(run ? { run: usage?.runView?.(id, run) ?? null } : {}), budget: sessionBudgetView(id) });
|
|
874
968
|
}
|
|
875
|
-
|
|
969
|
+
// The session budget rides along (agent_usage: the mode and each provider's share / spent / left).
|
|
970
|
+
res.json({ ok: true, usage: runtime.usageView?.(id, task) ?? usage?.taskView(id, task) ?? null, budget: sessionBudgetView(id) });
|
|
876
971
|
} catch (error) { sendError(res, error); }
|
|
877
972
|
});
|
|
878
973
|
router.get('/sessions/:id', (req, res) => {
|
|
@@ -940,25 +1035,51 @@ export function createAssistantApi({
|
|
|
940
1035
|
return (accountId) => {
|
|
941
1036
|
if (dispatcher.accountInUse(provider, accountId)) return `Stop the dispatched ${provider} runs using account ${accountId} first`;
|
|
942
1037
|
if (runtime?.accountInUse?.(provider, accountId)) return `Close the assistant sessions using account ${accountId} first`;
|
|
1038
|
+
if (provider === 'claude-code' && typeof claudeAccountInUse === 'function') return claudeAccountInUse(accountId) || null;
|
|
1039
|
+
if (provider === 'codex' && typeof codexAccountInUse === 'function') return codexAccountInUse(accountId) || null;
|
|
943
1040
|
return null;
|
|
944
1041
|
};
|
|
945
1042
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1043
|
+
// The Claude account a Claude Code worker runs under when its dispatch names none (null = Default).
|
|
1044
|
+
function claudeWorkerAccountId() {
|
|
1045
|
+
try { return configStore?.workers?.().claudeAccount || null; } catch { return null; }
|
|
1046
|
+
}
|
|
1047
|
+
// The same for Codex workers.
|
|
1048
|
+
function codexWorkerAccountId() {
|
|
1049
|
+
try { return configStore?.workers?.().codexAccount || null; } catch { return null; }
|
|
1050
|
+
}
|
|
1051
|
+
const claudeUnavailable = (res) => res.status(503).json({ ok: false, code: 'ACCOUNTS_UNAVAILABLE', error: 'Claude accounts are not available' });
|
|
1052
|
+
// Every Claude account as every surface shows it (lib/claude-accounts.js describe()): mode, signed in,
|
|
1053
|
+
// identity (an email or a masked key, never a key). `?refresh=1` asks Claude Code about each first.
|
|
1054
|
+
router.get('/claude/accounts', async (req, res) => {
|
|
1055
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
1056
|
+
try {
|
|
1057
|
+
if (req.query.refresh === '1' || req.query.refresh === 'true') await claudeAccounts.refreshStatus?.(null, { force: true });
|
|
1058
|
+
else Promise.resolve(claudeAccounts.refreshStatus?.()).catch(() => {});
|
|
1059
|
+
await accountUsage?.refresh('claude-code');
|
|
1060
|
+
res.json({ ok: true, accounts: accountUsage?.list()?.['claude-code'] || claudeAccounts.listForClient(), workerAccountId: claudeWorkerAccountId() });
|
|
1061
|
+
} catch (error) { sendError(res, error); }
|
|
949
1062
|
});
|
|
1063
|
+
// Create an account. `login: true` also opens its Claude sign-in in a SynaBun terminal; otherwise the
|
|
1064
|
+
// caller offers the two ways to sign it in (Claude, or an API key pasted into it).
|
|
950
1065
|
router.post('/claude/accounts', async (req, res) => {
|
|
951
|
-
if (!claudeAccounts) return res
|
|
1066
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
952
1067
|
try {
|
|
953
1068
|
const account = claudeAccounts.create({ label: req.body?.label });
|
|
954
1069
|
let login = null;
|
|
955
|
-
if (req.body?.login
|
|
956
|
-
|
|
957
|
-
}
|
|
1070
|
+
if (req.body?.login === true) login = await startClaudeLogin(account.id);
|
|
1071
|
+
catalogCache = null;
|
|
958
1072
|
res.status(201).json({ ok: true, account: claudeAccounts.listForClient().find((row) => row.id === account.id) || account, login });
|
|
959
1073
|
} catch (error) { sendError(res, error); }
|
|
960
1074
|
});
|
|
961
1075
|
async function startClaudeLogin(accountId) {
|
|
1076
|
+
// Claude Code's own `auth login` under the account's config directory, in a SynaBun terminal (the
|
|
1077
|
+
// panel's sign-in). The account list says when it is signed in (claude:accounts-changed).
|
|
1078
|
+
if (typeof claudeSignIn === 'function') {
|
|
1079
|
+
const terminal = await claudeSignIn(accountId);
|
|
1080
|
+
if (terminal?.status === 'error') throw Object.assign(new Error(terminal.error || 'Could not open the sign-in terminal.'), { status: 500, code: 'LOGIN_FAILED' });
|
|
1081
|
+
return { mode: 'terminal', terminalSessionId: terminal?.sessionId || null, status: terminal?.status || 'pending', profile: 'shell' };
|
|
1082
|
+
}
|
|
962
1083
|
const command = claudeAccounts.loginCommand(accountId);
|
|
963
1084
|
const terminal = await createLoginTerminal({ env: command.env, cwd: command.cwd, profile: 'claude-code', accountId, command: command.command });
|
|
964
1085
|
claudeAccounts.watchLogin(accountId, {
|
|
@@ -970,24 +1091,34 @@ export function createAssistantApi({
|
|
|
970
1091
|
return { mode: 'terminal', terminalSessionId: terminal?.terminalSessionId || terminal?.id || null, profile: terminal?.profile || 'claude-code' };
|
|
971
1092
|
}
|
|
972
1093
|
router.post('/claude/accounts/:id/login', async (req, res) => {
|
|
973
|
-
if (!claudeAccounts) return res
|
|
974
|
-
if (typeof createLoginTerminal !== 'function') return res.status(501).json({ ok: false, code: 'LOGIN_UNAVAILABLE', error: 'Login terminals are not wired' });
|
|
1094
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
1095
|
+
if (typeof claudeSignIn !== 'function' && typeof createLoginTerminal !== 'function') return res.status(501).json({ ok: false, code: 'LOGIN_UNAVAILABLE', error: 'Login terminals are not wired' });
|
|
975
1096
|
try {
|
|
976
|
-
|
|
1097
|
+
const row = claudeAccounts.get(req.params.id);
|
|
1098
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Claude account ${req.params.id}` });
|
|
977
1099
|
res.json({ ok: true, login: await startClaudeLogin(req.params.id) });
|
|
978
1100
|
} catch (error) { sendError(res, error); }
|
|
979
1101
|
});
|
|
980
|
-
|
|
981
|
-
|
|
1102
|
+
// Rename (`label`) and/or choose what the account's sessions use (`mode`: 'subscription' | 'apiKey').
|
|
1103
|
+
router.patch('/claude/accounts/:id', async (req, res) => {
|
|
1104
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
982
1105
|
try {
|
|
983
|
-
const
|
|
1106
|
+
const row = claudeAccounts.get(req.params.id);
|
|
1107
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Claude account ${req.params.id}` });
|
|
1108
|
+
const { label, mode } = req.body || {};
|
|
1109
|
+
if (label === undefined && mode === undefined) return res.status(400).json({ ok: false, code: 'NOTHING_TO_CHANGE', error: 'Send a label or a mode.' });
|
|
1110
|
+
if (label !== undefined) claudeAccounts.rename(req.params.id, label);
|
|
1111
|
+
if (mode !== undefined) {
|
|
1112
|
+
if (typeof claudeSetMode === 'function') await claudeSetMode(req.params.id, mode);
|
|
1113
|
+
else claudeAccounts.setMode(req.params.id, mode);
|
|
1114
|
+
}
|
|
984
1115
|
catalogCache = null;
|
|
985
1116
|
broadcastSync({ type: 'assistant:accounts-changed', provider: 'claude-code', accountId: req.params.id });
|
|
986
|
-
res.json({ ok: true, account });
|
|
1117
|
+
res.json({ ok: true, account: claudeAccounts.describeForClient ? claudeAccounts.describeForClient(req.params.id) : claudeAccounts.get(req.params.id) });
|
|
987
1118
|
} catch (error) { sendError(res, error); }
|
|
988
1119
|
});
|
|
989
1120
|
router.delete('/claude/accounts/:id', (req, res) => {
|
|
990
|
-
if (!claudeAccounts) return res
|
|
1121
|
+
if (!claudeAccounts) return claudeUnavailable(res);
|
|
991
1122
|
try {
|
|
992
1123
|
const result = claudeAccounts.remove(req.params.id, { inUse: accountInUse('claude-code') });
|
|
993
1124
|
catalogCache = null;
|
|
@@ -995,30 +1126,89 @@ export function createAssistantApi({
|
|
|
995
1126
|
res.json({ ok: true, ...result });
|
|
996
1127
|
} catch (error) { sendError(res, error); }
|
|
997
1128
|
});
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1129
|
+
// The account a Claude Code worker uses when its dispatch names none. Signed-out accounts are refused.
|
|
1130
|
+
router.put('/claude/worker-account', (req, res) => {
|
|
1131
|
+
if (!claudeAccounts || !configStore?.patchWorkers) return claudeUnavailable(res);
|
|
1132
|
+
try {
|
|
1133
|
+
const wanted = req.body?.accountId === null || req.body?.accountId === undefined || req.body?.accountId === '' ? 'default' : String(req.body.accountId);
|
|
1134
|
+
const row = claudeAccounts.get(wanted);
|
|
1135
|
+
if (!row || row.id !== wanted) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Claude account ${wanted}` });
|
|
1136
|
+
const unusable = claudeAccounts.unusableReason?.(wanted) || '';
|
|
1137
|
+
if (unusable) return res.status(409).json({ ok: false, code: 'ACCOUNT_SIGNED_OUT', error: unusable });
|
|
1138
|
+
const workers = configStore.patchWorkers({ claudeAccount: wanted });
|
|
1139
|
+
catalogCache = null;
|
|
1140
|
+
broadcastSync({ type: 'assistant:accounts-changed', provider: 'claude-code', accountId: wanted, worker: true });
|
|
1141
|
+
res.json({ ok: true, workerAccountId: workers.claudeAccount });
|
|
1142
|
+
} catch (error) { sendError(res, error); }
|
|
1143
|
+
});
|
|
1144
|
+
const codexUnavailable = (res) => res.status(503).json({ ok: false, code: 'ACCOUNTS_UNAVAILABLE', error: 'Codex accounts are not available' });
|
|
1145
|
+
// Every Codex account as every surface shows it (lib/codex-accounts.js describe()): mode, signed in,
|
|
1146
|
+
// identity (an email or a masked key, never a key). `?refresh=1` asks Codex about each own store first.
|
|
1147
|
+
router.get('/codex/accounts', async (req, res) => {
|
|
1148
|
+
if (!codexAccounts) return codexUnavailable(res);
|
|
1149
|
+
try {
|
|
1150
|
+
if (req.query.refresh === '1' || req.query.refresh === 'true') await codexAccounts.refreshStatus?.(null, { force: true });
|
|
1151
|
+
else Promise.resolve(codexAccounts.refreshStatus?.()).catch(() => {});
|
|
1152
|
+
await accountUsage?.refresh('codex');
|
|
1153
|
+
res.json({ ok: true, accounts: accountUsage?.list()?.codex || codexAccounts.listForClient(), workerAccountId: codexWorkerAccountId() });
|
|
1154
|
+
} catch (error) { sendError(res, error); }
|
|
1001
1155
|
});
|
|
1156
|
+
// `codex login` under the account's CODEX_HOME, in a SynaBun terminal (the panel's sign-in). The account
|
|
1157
|
+
// list says when it is signed in (codex:accounts-changed).
|
|
1158
|
+
async function startCodexLogin(accountId) {
|
|
1159
|
+
if (typeof codexSignIn !== 'function') throw Object.assign(new Error('Login terminals are not wired'), { status: 501, code: 'LOGIN_UNAVAILABLE' });
|
|
1160
|
+
const terminal = await codexSignIn(accountId);
|
|
1161
|
+
if (terminal?.status === 'error') throw Object.assign(new Error(terminal.error || 'Could not open the sign-in terminal.'), { status: 500, code: 'LOGIN_FAILED' });
|
|
1162
|
+
return { mode: 'terminal', terminalSessionId: terminal?.sessionId || null, status: terminal?.status || 'pending', profile: 'shell' };
|
|
1163
|
+
}
|
|
1164
|
+
// Create an account. `login: true` also opens its ChatGPT sign-in in a SynaBun terminal; otherwise the
|
|
1165
|
+
// caller offers the two ways to sign it in (ChatGPT, or an API key pasted into it).
|
|
1166
|
+
router.post('/codex/accounts', async (req, res) => {
|
|
1167
|
+
if (!codexAccounts?.create) return codexUnavailable(res);
|
|
1168
|
+
try {
|
|
1169
|
+
const account = codexAccounts.create({ label: req.body?.label });
|
|
1170
|
+
const login = req.body?.login === true ? await startCodexLogin(account.id) : null;
|
|
1171
|
+
catalogCache = null;
|
|
1172
|
+
res.status(201).json({ ok: true, account: codexAccounts.describeForClient?.(account.id) || account, login });
|
|
1173
|
+
} catch (error) { sendError(res, error); }
|
|
1174
|
+
});
|
|
1175
|
+
// Older pages: create the account and open its ChatGPT sign-in at once.
|
|
1002
1176
|
router.post('/codex/accounts/add-start', async (req, res) => {
|
|
1003
|
-
if (!codexAccounts?.
|
|
1177
|
+
if (!codexAccounts?.create) return codexUnavailable(res);
|
|
1004
1178
|
try {
|
|
1005
|
-
const
|
|
1179
|
+
const account = codexAccounts.create({ label: req.body?.label });
|
|
1180
|
+
const login = await startCodexLogin(account.id).catch((error) => ({ error: error.message }));
|
|
1006
1181
|
catalogCache = null;
|
|
1007
|
-
|
|
1008
|
-
|
|
1182
|
+
res.status(201).json({ ok: true, accountId: account.id, account: codexAccounts.describeForClient?.(account.id) || account, login });
|
|
1183
|
+
} catch (error) { sendError(res, error); }
|
|
1184
|
+
});
|
|
1185
|
+
router.post('/codex/accounts/:id/login', async (req, res) => {
|
|
1186
|
+
if (!codexAccounts) return codexUnavailable(res);
|
|
1187
|
+
try {
|
|
1188
|
+
const row = codexAccounts.describe?.(req.params.id);
|
|
1189
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Codex account ${req.params.id}` });
|
|
1190
|
+
res.json({ ok: true, login: await startCodexLogin(req.params.id) });
|
|
1009
1191
|
} catch (error) { sendError(res, error); }
|
|
1010
1192
|
});
|
|
1011
|
-
|
|
1012
|
-
|
|
1193
|
+
// Rename (`label`) and/or choose what the account's sessions use (`mode`: 'subscription' (ChatGPT) | 'apiKey').
|
|
1194
|
+
router.patch('/codex/accounts/:id', async (req, res) => {
|
|
1195
|
+
if (!codexAccounts) return codexUnavailable(res);
|
|
1013
1196
|
try {
|
|
1014
|
-
const
|
|
1197
|
+
const row = codexAccounts.describe?.(req.params.id);
|
|
1198
|
+
if (!row || row.id !== req.params.id) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Codex account ${req.params.id}` });
|
|
1199
|
+
const { label, mode } = req.body || {};
|
|
1200
|
+
if (label !== undefined) codexAccounts.rename(req.params.id, label);
|
|
1201
|
+
if (mode !== undefined) {
|
|
1202
|
+
if (typeof codexSetMode === 'function') await codexSetMode(req.params.id, mode);
|
|
1203
|
+
else codexAccounts.setMode(req.params.id, mode);
|
|
1204
|
+
}
|
|
1015
1205
|
catalogCache = null;
|
|
1016
1206
|
broadcastSync({ type: 'assistant:accounts-changed', provider: 'codex', accountId: req.params.id });
|
|
1017
|
-
res.json({ ok: true, account });
|
|
1207
|
+
res.json({ ok: true, account: codexAccounts.describeForClient(req.params.id) });
|
|
1018
1208
|
} catch (error) { sendError(res, error); }
|
|
1019
1209
|
});
|
|
1020
1210
|
router.delete('/codex/accounts/:id', (req, res) => {
|
|
1021
|
-
if (!codexAccounts?.remove) return res
|
|
1211
|
+
if (!codexAccounts?.remove) return codexUnavailable(res);
|
|
1022
1212
|
try {
|
|
1023
1213
|
const result = codexAccounts.remove(req.params.id, { inUse: accountInUse('codex') });
|
|
1024
1214
|
catalogCache = null;
|
|
@@ -1026,6 +1216,38 @@ export function createAssistantApi({
|
|
|
1026
1216
|
res.json({ ok: true, ...result });
|
|
1027
1217
|
} catch (error) { sendError(res, error); }
|
|
1028
1218
|
});
|
|
1219
|
+
// The account a Codex worker uses when its dispatch names none. Signed-out accounts are refused.
|
|
1220
|
+
router.put('/codex/worker-account', (req, res) => {
|
|
1221
|
+
if (!codexAccounts || !configStore?.patchWorkers) return codexUnavailable(res);
|
|
1222
|
+
try {
|
|
1223
|
+
const wanted = req.body?.accountId === null || req.body?.accountId === undefined || req.body?.accountId === '' ? 'default' : String(req.body.accountId);
|
|
1224
|
+
const row = codexAccounts.describe?.(wanted);
|
|
1225
|
+
if (!row || row.id !== wanted) return res.status(404).json({ ok: false, code: 'ACCOUNT_NOT_FOUND', error: `Unknown Codex account ${wanted}` });
|
|
1226
|
+
const unusable = codexAccounts.unusableReason?.(wanted) || '';
|
|
1227
|
+
if (unusable) return res.status(409).json({ ok: false, code: 'ACCOUNT_SIGNED_OUT', error: unusable });
|
|
1228
|
+
const workers = configStore.patchWorkers({ codexAccount: wanted });
|
|
1229
|
+
catalogCache = null;
|
|
1230
|
+
broadcastSync({ type: 'assistant:accounts-changed', provider: 'codex', accountId: wanted, worker: true });
|
|
1231
|
+
res.json({ ok: true, workerAccountId: workers.codexAccount });
|
|
1232
|
+
} catch (error) { sendError(res, error); }
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
// ── Admin sandbox (lib/agent-sandbox-setting.js) ───────────────────────────
|
|
1236
|
+
// Whether an agent SynaBun starts may use a sandbox whose setup needs Windows administrator
|
|
1237
|
+
// approval. Global, not per session. The user's setting: an agent cannot change it.
|
|
1238
|
+
router.get('/admin-sandbox', (req, res) => {
|
|
1239
|
+
if (!adminSandbox) return res.status(503).json({ ok: false, code: 'ADMIN_SANDBOX_UNAVAILABLE', error: 'The Admin sandbox setting is not available on this server' });
|
|
1240
|
+
res.json({ ok: true, ...adminSandbox.view() });
|
|
1241
|
+
});
|
|
1242
|
+
router.put('/admin-sandbox', requireJson, (req, res) => {
|
|
1243
|
+
if (!adminSandbox) return res.status(503).json({ ok: false, code: 'ADMIN_SANDBOX_UNAVAILABLE', error: 'The Admin sandbox setting is not available on this server' });
|
|
1244
|
+
if (callerContext(req).kind !== 'ui' || req.get('x-synabun-desktop-grant')) {
|
|
1245
|
+
return res.status(403).json({ ok: false, code: 'UI_ONLY', error: 'Only the user can change the Admin sandbox.' });
|
|
1246
|
+
}
|
|
1247
|
+
const value = req.body?.adminSandbox;
|
|
1248
|
+
if (typeof value !== 'boolean') return res.status(400).json({ ok: false, code: 'INVALID_VALUE', error: 'adminSandbox must be true or false.' });
|
|
1249
|
+
try { res.json({ ok: true, ...adminSandbox.set(value) }); } catch (error) { sendError(res, error); }
|
|
1250
|
+
});
|
|
1029
1251
|
|
|
1030
1252
|
// ── sidepanel tab registry (UI heartbeat) ──────────────────────────────────
|
|
1031
1253
|
function listTabs() {
|
|
@@ -27,12 +27,14 @@ export function claudeSdkMode(approvalMode, planMode) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Never gate these behind a permission card: routing must not wait on an
|
|
30
|
-
// approval prompt,
|
|
31
|
-
// the desktop service, not in the host's permission layer)
|
|
30
|
+
// approval prompt, computer use is fully autonomous (its guards live in
|
|
31
|
+
// the desktop service, not in the host's permission layer), and a file the
|
|
32
|
+
// user asked for on WhatsApp is the turn's reply (the WhatsApp host checks it).
|
|
32
33
|
export const CLAUDE_BRAIN_ALLOWED_TOOLS = Object.freeze([
|
|
33
34
|
'mcp__SynaBun__agent_route', 'mcp__SynaBun__agent_catalog', 'mcp__SynaBun__agent_status', 'mcp__SynaBun__agent_read',
|
|
34
35
|
'mcp__SynaBun__agent_list', 'mcp__SynaBun__agent_wait', 'mcp__SynaBun__agent_usage',
|
|
35
36
|
'mcp__SynaBun__computer', 'mcp__SynaBun__computer_apps', 'mcp__SynaBun__computer_ax', 'mcp__SynaBun__computer_status',
|
|
37
|
+
'mcp__SynaBun__whatsapp_send_file',
|
|
36
38
|
]);
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -85,6 +87,10 @@ export function createClaudeBrain({ session, sink, deps = {}, persona = '', hook
|
|
|
85
87
|
const inner = new deps.ClaudeSession(socket, {
|
|
86
88
|
ownerKey: session.id,
|
|
87
89
|
windowId,
|
|
90
|
+
// The Claude account the brain runs under: the bridge gives its process that
|
|
91
|
+
// account's credential (lib/claude-accounts.js launchFor(): its subscription
|
|
92
|
+
// login or its API key, nothing inherited), and refuses an account that is gone.
|
|
93
|
+
claudeAccountId: brain.accountId || 'default',
|
|
88
94
|
sessionId: providerSessionId,
|
|
89
95
|
model: brain.model || null,
|
|
90
96
|
effort: brain.effort || null,
|
|
@@ -117,6 +123,11 @@ export function createClaudeBrain({ session, sink, deps = {}, persona = '', hook
|
|
|
117
123
|
'X-Synabun-Role': 'assistant', 'X-Synabun-Terminal': session.id, 'X-Synabun-Memory-Session': session.id,
|
|
118
124
|
...(deps.desktopGrant ? { 'X-Synabun-Desktop-Grant': deps.desktopGrant } : {}),
|
|
119
125
|
...mcpHeaders,
|
|
126
|
+
// A WhatsApp conversation's brain credential (whatsapp_send_file). The header names the variable and
|
|
127
|
+
// the CLI expands it from its own environment (below): the MCP config is on its command line, which every
|
|
128
|
+
// account of the computer can read, an environment only this user's processes. The in-process MCP server
|
|
129
|
+
// keeps the value for the request it arrived with, and sends it on the file route alone. Last, so nothing replaces it.
|
|
130
|
+
...(deps.brainCredential ? { 'X-Synabun-Brain-Token': '${SYNABUN_BRAIN_TOKEN}' } : {}),
|
|
120
131
|
},
|
|
121
132
|
// A remote (WhatsApp) session never pre-approves computer use: its PreToolUse hook refuses, asks or allows each call.
|
|
122
133
|
allowedTools,
|
|
@@ -132,6 +143,8 @@ export function createClaudeBrain({ session, sink, deps = {}, persona = '', hook
|
|
|
132
143
|
...(typeof deps.claudeAccountEnv === 'function' ? deps.claudeAccountEnv(brain.accountId) : {}),
|
|
133
144
|
SYNABUN_ASSISTANT_SESSION: String(session.id),
|
|
134
145
|
...(deps.userContextThreads === true ? { SYNABUN_ASSISTANT_THREADS: '1' } : {}),
|
|
146
|
+
// The value of the X-Synabun-Brain-Token header above (a WhatsApp conversation's brain only).
|
|
147
|
+
...(deps.brainCredential ? { SYNABUN_BRAIN_TOKEN: deps.brainCredential } : {}),
|
|
135
148
|
},
|
|
136
149
|
maxBudgetUsd: Number(deps.maxBudgetUsd) > 0 ? Number(deps.maxBudgetUsd) : undefined,
|
|
137
150
|
});
|
|
@@ -119,6 +119,9 @@ export function createCodexBrain({ session, sink, deps = {}, persona = '' } = {}
|
|
|
119
119
|
});
|
|
120
120
|
const socket = createVirtualSocket({
|
|
121
121
|
name: `codex-brain-${session.id}`,
|
|
122
|
+
// A WhatsApp conversation's brain credential rides on the socket object, never in a message: the skin
|
|
123
|
+
// handler gives it to this app-server's SynaBun MCP child through its environment (whatsapp_send_file).
|
|
124
|
+
credential: deps.brainCredential || null,
|
|
122
125
|
onSend: (packet) => {
|
|
123
126
|
if (disposed || !packet || typeof packet !== 'object') return;
|
|
124
127
|
if (packet.type === 'ready') readyResolve?.();
|