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
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// (`agent.assistant.prompt`) in the per-serve config so the model sees it as
|
|
8
8
|
// its system prompt; SSE events are translated to the unified envelope.
|
|
9
9
|
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { dirname, resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
13
|
import { normalizeOpenCodeModel } from '../native-loop-providers.js';
|
|
@@ -46,11 +46,14 @@ export const OPENCODE_ROUTE_GATE_PLUGIN = pathToFileURL(resolve(dirname(fileURLT
|
|
|
46
46
|
*/
|
|
47
47
|
export const OPENCODE_PLAN_AGENT = 'synabun-plan';
|
|
48
48
|
|
|
49
|
+
/** The brain's SynaBun MCP tool-call timeout (ms): as long as the Codex brain's tool_timeout_sec (server.js). */
|
|
50
|
+
export const OPENCODE_BRAIN_MCP_TIMEOUT_MS = 180_000;
|
|
51
|
+
|
|
49
52
|
/**
|
|
50
53
|
* `routeGate` ({ url, session, token }) installs the route-gate plugin in this
|
|
51
54
|
* isolated serve's config only (assistant brains; never the shared sidepanel setup).
|
|
52
55
|
*/
|
|
53
|
-
export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'assistant', model = null, desktopGrant = null, routeGate = null } = {}) {
|
|
56
|
+
export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'assistant', model = null, desktopGrant = null, routeGate = null, brainCredential = null } = {}) {
|
|
54
57
|
const configPath = resolve(xdgRoot, 'opencode', 'config.json');
|
|
55
58
|
if (!existsSync(configPath)) return false;
|
|
56
59
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
@@ -60,7 +63,15 @@ export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'as
|
|
|
60
63
|
// Computer use: an unforgeable per-brain grant unlocks the `computer` group.
|
|
61
64
|
if (desktopGrant) env.SYNABUN_DESKTOP_GRANT = desktopGrant;
|
|
62
65
|
else delete env.SYNABUN_DESKTOP_GRANT;
|
|
66
|
+
// A WhatsApp conversation's brain credential (whatsapp_send_file), for this serve's SynaBun MCP child only.
|
|
67
|
+
// This file names the variable; the value is in the serve's own environment (ensureIsolatedServe), so
|
|
68
|
+
// neither this file (it is in every backup of data/) nor OPENCODE_CONFIG_CONTENT ever holds it.
|
|
69
|
+
if (brainCredential) env.SYNABUN_BRAIN_TOKEN = '{env:SYNABUN_BRAIN_TOKEN}';
|
|
70
|
+
else delete env.SYNABUN_BRAIN_TOKEN;
|
|
63
71
|
config.mcp.SynaBun[envKey] = env;
|
|
72
|
+
// OpenCode gives an MCP tool call 60 s unless the server says otherwise; agent_route may wait on the
|
|
73
|
+
// user's route card and whatsapp_send_file on an upload (130 s): the Codex brain's 180 s here too.
|
|
74
|
+
config.mcp.SynaBun.timeout = OPENCODE_BRAIN_MCP_TIMEOUT_MS;
|
|
64
75
|
}
|
|
65
76
|
config.agent = { ...(config.agent || {}) };
|
|
66
77
|
config.agent.assistant = {
|
|
@@ -89,6 +100,8 @@ export function patchOpenCodeAssistantConfig(xdgRoot, { persona = '', role = 'as
|
|
|
89
100
|
if (routeGate?.url && routeGate?.session && routeGate?.token) plugins.push([OPENCODE_ROUTE_GATE_PLUGIN, { url: routeGate.url, session: routeGate.session, token: routeGate.token, ...(routeGate.remote === true ? { remote: true } : {}) }]);
|
|
90
101
|
if (plugins.length) config.plugin = plugins; else delete config.plugin;
|
|
91
102
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
103
|
+
// It carries this brain's grants (the desktop grant): readable by this user only.
|
|
104
|
+
try { chmodSync(configPath, 0o600); } catch { /* a filesystem without modes */ }
|
|
92
105
|
return true;
|
|
93
106
|
}
|
|
94
107
|
|
|
@@ -203,8 +216,8 @@ export function createOpenCodeBrain({ session, sink, deps = {}, persona = '' } =
|
|
|
203
216
|
if (startPromise) return startPromise;
|
|
204
217
|
startPromise = (async () => {
|
|
205
218
|
const xdgRoot = deps.setupOpencodeSidepanelConfig(termId, brain.mcpProfile || null);
|
|
206
|
-
try { personaInstalled = patchOpenCodeAssistantConfig(xdgRoot, { persona, model: null, desktopGrant: deps.desktopGrant || null, routeGate: deps.routeGate || null }); } catch { personaInstalled = false; }
|
|
207
|
-
entry = await deps.ensureIsolatedServe(termId, { xdgRoot, keepAlive: true, mcpProfile: brain.mcpProfile || null });
|
|
219
|
+
try { personaInstalled = patchOpenCodeAssistantConfig(xdgRoot, { persona, model: null, desktopGrant: deps.desktopGrant || null, routeGate: deps.routeGate || null, brainCredential: deps.brainCredential || null }); } catch { personaInstalled = false; }
|
|
220
|
+
entry = await deps.ensureIsolatedServe(termId, { xdgRoot, keepAlive: true, mcpProfile: brain.mcpProfile || null, brainCredential: deps.brainCredential || null });
|
|
208
221
|
if (!entry?.client) throw new Error('OpenCode isolated serve failed to start');
|
|
209
222
|
client = entry.client;
|
|
210
223
|
await client.waitUntilConnected?.(20_000);
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — Assistant budget planner (per-session split, checkpoints, Jev)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The state behind the budget split (the math is assistant-budget-split.js):
|
|
6
|
+
//
|
|
7
|
+
// - The split of each session, cached on it: made at the session's first
|
|
8
|
+
// actionable route or dispatch (ensureSplit), made again only at a
|
|
9
|
+
// checkpoint (resplit): a route, dispatch or brain turn its provider's
|
|
10
|
+
// share cannot cover (ensureCovered), a change of the budget
|
|
11
|
+
// (onBudgetChanged), a run ending (onRunEnded). Never per tick or per
|
|
12
|
+
// metered step: coverage() and covers() only read.
|
|
13
|
+
// - Jev (surface budget-split) weighs the providers; when it is off,
|
|
14
|
+
// unreachable or answers nothing usable, the deterministic weights are
|
|
15
|
+
// used. The split records which (`source`: jev | fallback) and when.
|
|
16
|
+
// - Re-splits of one session run one at a time; calls that arrive while one
|
|
17
|
+
// waits to start are merged into it.
|
|
18
|
+
//
|
|
19
|
+
// Under No limit nothing is split and every check passes.
|
|
20
|
+
//
|
|
21
|
+
// Every dependency is injected (server.js wires the dispatcher, the runtime,
|
|
22
|
+
// the catalog, the accounts, the config store and Jev); nothing here throws
|
|
23
|
+
// at its callers.
|
|
24
|
+
|
|
25
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { dirname } from 'node:path';
|
|
27
|
+
import { BUDGET_DEFAULT_USD, BUDGET_WARN_RATIO, roundUsd } from './assistant-budget.js';
|
|
28
|
+
import {
|
|
29
|
+
BUDGET_PROVIDERS, MIN_SPEND_USD, billingOf, bucketEligibility, budgetView, coverageOf, coversWork, divideLeft,
|
|
30
|
+
dryProviders, fallbackWeights, jevWeights, listPriceOf, priceLabel, providerSpendOf, rowOf, totalSpentOf,
|
|
31
|
+
} from './assistant-budget-split.js';
|
|
32
|
+
|
|
33
|
+
export const SPLIT_REASONS = Object.freeze(['first_route', 'share_used_up', 'brain_turn', 'budget_changed', 'run_ended']);
|
|
34
|
+
const MAX_SESSIONS = 200;
|
|
35
|
+
const MAX_TASKS = 20;
|
|
36
|
+
// A provider found short again this soon after a re-split for it is not re-split again: the split stands
|
|
37
|
+
// (its runs stop, its tasks move). The brain's own turns re-split again as soon as it spent since.
|
|
38
|
+
const NEED_RETRY_MS = 5_000;
|
|
39
|
+
const RUN_END_DEBOUNCE_MS = 1500;
|
|
40
|
+
|
|
41
|
+
const arr = (value) => (Array.isArray(value) ? value : []);
|
|
42
|
+
const clip = (value, max = 200) => { const text = String(value ?? '').replace(/\s+/g, ' ').trim(); return text.length > max ? `${text.slice(0, max - 1)}…` : text; };
|
|
43
|
+
const iso = (ms) => new Date(ms).toISOString();
|
|
44
|
+
|
|
45
|
+
export function createBudgetPlanner({
|
|
46
|
+
setting = () => ({ unlimited: false, amountUsd: BUDGET_DEFAULT_USD, version: 0 }),
|
|
47
|
+
// (sessionId) => { providers: { [p]: { brainUsd, runsUsd, liveRuns, runningRuns } }, unpricedRuns, liveRuns: [{ provider, model, taskClass }] }
|
|
48
|
+
spendOf = () => ({ providers: {}, unpricedRuns: 0, liveRuns: [] }),
|
|
49
|
+
// (sessionId) => { provider, model, accountId, project } | null
|
|
50
|
+
brainOf = () => null,
|
|
51
|
+
// (sessionId) => is the session still open (a split of an ended one is not redone)
|
|
52
|
+
isOpen = () => true,
|
|
53
|
+
catalog = () => null,
|
|
54
|
+
refreshCatalog = null,
|
|
55
|
+
accounts = () => null,
|
|
56
|
+
routing = () => ({ preferences: {} }),
|
|
57
|
+
routeMode = () => null,
|
|
58
|
+
workers = () => ({}),
|
|
59
|
+
pricing = null,
|
|
60
|
+
// (sessionId) => { [provider]: usd } of this session from the usage ledger, or null
|
|
61
|
+
recentSpend = () => null,
|
|
62
|
+
// assistant-jev.js: { available(), budgetSplit(input, ctx), annotate(logId, outcome) } or null
|
|
63
|
+
judge = null,
|
|
64
|
+
emit = () => {},
|
|
65
|
+
statePath = null,
|
|
66
|
+
now = Date.now,
|
|
67
|
+
log = () => {},
|
|
68
|
+
} = {}) {
|
|
69
|
+
const sessions = new Map(); // id → { split, tasks: [], updatedAt }
|
|
70
|
+
const chains = new Map(); // id → { promise, queued, waiting }
|
|
71
|
+
const needTries = new Map(); // `${id}:${provider}` → { at, version }
|
|
72
|
+
const runEndTimers = new Map();
|
|
73
|
+
let persistTimer = null;
|
|
74
|
+
|
|
75
|
+
// ── persistence ────────────────────────────────────────────────────────
|
|
76
|
+
function load() {
|
|
77
|
+
if (!statePath || !existsSync(statePath)) return;
|
|
78
|
+
try {
|
|
79
|
+
const raw = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
80
|
+
for (const [id, row] of Object.entries(raw?.sessions || {})) {
|
|
81
|
+
if (!row || typeof row !== 'object') continue;
|
|
82
|
+
sessions.set(id, { split: row.split && typeof row.split === 'object' ? row.split : null, tasks: arr(row.tasks).slice(-MAX_TASKS), updatedAt: row.updatedAt || null });
|
|
83
|
+
}
|
|
84
|
+
} catch (error) { log('budget:state-unreadable', error?.message || String(error)); }
|
|
85
|
+
}
|
|
86
|
+
function writeState() {
|
|
87
|
+
if (!statePath) return;
|
|
88
|
+
try {
|
|
89
|
+
const rows = [...sessions.entries()].sort((a, b) => String(b[1].updatedAt || '').localeCompare(String(a[1].updatedAt || ''))).slice(0, MAX_SESSIONS);
|
|
90
|
+
for (const [id] of [...sessions.entries()].filter(([id]) => !rows.some(([kept]) => kept === id))) sessions.delete(id);
|
|
91
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
92
|
+
const tmp = `${statePath}.${process.pid}.tmp`;
|
|
93
|
+
writeFileSync(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(rows) }, null, 2) + '\n', 'utf8');
|
|
94
|
+
renameSync(tmp, statePath);
|
|
95
|
+
} catch (error) { log('budget:state-error', error?.message || String(error)); }
|
|
96
|
+
}
|
|
97
|
+
function persist() {
|
|
98
|
+
if (!statePath || persistTimer) return;
|
|
99
|
+
persistTimer = setTimeout(() => { persistTimer = null; writeState(); }, 300);
|
|
100
|
+
persistTimer.unref?.();
|
|
101
|
+
}
|
|
102
|
+
function rowOfSession(id, { create = false } = {}) {
|
|
103
|
+
let row = sessions.get(id) || null;
|
|
104
|
+
if (!row && create) { row = { split: null, tasks: [], updatedAt: iso(now()) }; sessions.set(id, row); }
|
|
105
|
+
return row;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── reads ──────────────────────────────────────────────────────────────
|
|
109
|
+
function readSetting() {
|
|
110
|
+
try {
|
|
111
|
+
const value = setting() || {};
|
|
112
|
+
return { unlimited: value.unlimited === true, amountUsd: Number(value.amountUsd) > 0 ? Number(value.amountUsd) : BUDGET_DEFAULT_USD, version: Number(value.version) || 0 };
|
|
113
|
+
} catch { return { unlimited: false, amountUsd: BUDGET_DEFAULT_USD, version: 0 }; }
|
|
114
|
+
}
|
|
115
|
+
function limited() { return !readSetting().unlimited; }
|
|
116
|
+
function safe(fn, fallback = null) { try { const value = fn(); return value === undefined ? fallback : value; } catch { return fallback; } }
|
|
117
|
+
function spend(id) { return safe(() => spendOf(id), null) || { providers: {}, unpricedRuns: 0, liveRuns: [] }; }
|
|
118
|
+
function brain(id) { return safe(() => brainOf(id), null); }
|
|
119
|
+
function splitOf(id) { return sessions.get(String(id || ''))?.split || null; }
|
|
120
|
+
function coverage(id) {
|
|
121
|
+
const value = readSetting();
|
|
122
|
+
if (value.unlimited) return null;
|
|
123
|
+
const sessionId = String(id || '');
|
|
124
|
+
return coverageOf({ amountUsd: value.amountUsd, split: splitOf(sessionId), spend: spend(sessionId), brainProvider: brain(sessionId)?.provider || null });
|
|
125
|
+
}
|
|
126
|
+
/** Can `provider` pay for more work in the session now (No limit: always). */
|
|
127
|
+
function covers(id, { provider, forBrain = false, free = false } = {}) {
|
|
128
|
+
if (!id) return true;
|
|
129
|
+
const cov = coverage(id);
|
|
130
|
+
return cov ? coversWork(cov, { provider, forBrain, free }) : true;
|
|
131
|
+
}
|
|
132
|
+
function view(id) {
|
|
133
|
+
const sessionId = String(id || '');
|
|
134
|
+
const value = readSetting();
|
|
135
|
+
const used = spend(sessionId);
|
|
136
|
+
const brainProvider = brain(sessionId)?.provider || null;
|
|
137
|
+
return budgetView({
|
|
138
|
+
sessionId, setting: value, split: value.unlimited ? null : splitOf(sessionId), spend: used, unpricedRuns: used.unpricedRuns || 0, brainProvider,
|
|
139
|
+
coverage: value.unlimited ? null : coverageOf({ amountUsd: value.amountUsd, split: splitOf(sessionId), spend: used, brainProvider }),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Jev's state ────────────────────────────────────────────────────────
|
|
144
|
+
function accountFor(provider, rows, brainInfo) {
|
|
145
|
+
const list = arr(rows?.[provider]);
|
|
146
|
+
if (!list.length) return null;
|
|
147
|
+
const wanted = provider === brainInfo?.provider ? (brainInfo.accountId || 'default')
|
|
148
|
+
: (safe(() => workers()?.[provider === 'codex' ? 'codexAccount' : 'claudeAccount']) || 'default');
|
|
149
|
+
return list.find((row) => row?.id === wanted) || list.find((row) => row?.id === 'default') || list[0];
|
|
150
|
+
}
|
|
151
|
+
function modelInfo(cat, provider, model, account = null) {
|
|
152
|
+
const row = rowOf(cat, provider, model);
|
|
153
|
+
const price = listPriceOf(row, { pricing, provider, model });
|
|
154
|
+
return { id: model || row?.id || null, price: priceLabel(row, price), billing: billingOf(provider, price, account), tier: row?.tier || null };
|
|
155
|
+
}
|
|
156
|
+
function jevInput(id, { value, used, cov, eligible, brainInfo, cat, needs, task, reason, tasks }) {
|
|
157
|
+
const rows = safe(() => accounts(), null);
|
|
158
|
+
const prefs = safe(() => routing()?.preferences, {}) || {};
|
|
159
|
+
const savedRoutes = Object.entries(prefs).filter(([, pref]) => pref?.provider).map(([taskClass, pref]) => {
|
|
160
|
+
const info = modelInfo(cat, pref.provider, pref.model || null, accountFor(pref.provider, rows, brainInfo));
|
|
161
|
+
return { taskClass, provider: pref.provider, model: pref.model || null, effort: pref.effort || null, price: info.price, billing: info.billing };
|
|
162
|
+
});
|
|
163
|
+
const recent = safe(() => recentSpend(id), null) || {};
|
|
164
|
+
const providers = {};
|
|
165
|
+
for (const provider of BUDGET_PROVIDERS) {
|
|
166
|
+
const own = providerSpendOf(used, provider);
|
|
167
|
+
const account = accountFor(provider, rows, brainInfo);
|
|
168
|
+
const routed = savedRoutes.filter((r) => r.provider === provider);
|
|
169
|
+
const models = [];
|
|
170
|
+
for (const r of routed) if (!models.some((m) => m.id === r.model)) models.push({ id: r.model, price: r.price, billing: r.billing, tier: rowOf(cat, provider, r.model)?.tier || null });
|
|
171
|
+
providers[provider] = {
|
|
172
|
+
eligible: eligible[provider]?.eligible !== false, why: eligible[provider]?.reason || null,
|
|
173
|
+
spentUsd: own.spentUsd, brainUsd: own.brainUsd, runsUsd: own.runsUsd,
|
|
174
|
+
shareUsd: cov?.hasSplit ? cov.providers[provider].shareUsd : null, leftUsd: cov?.hasSplit ? cov.providers[provider].leftUsd : null,
|
|
175
|
+
liveRuns: own.liveRuns, runningRuns: own.runningRuns, recentUsd: Number.isFinite(Number(recent[provider])) ? Number(recent[provider]) : null,
|
|
176
|
+
account: account ? { mode: account.mode === 'apiKey' ? 'api key' : account.mode || null, usageLeftPercent: account.usage?.remainingPercent ?? null, limited: account.usage?.limited ?? null } : null,
|
|
177
|
+
models,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const brainModel = brainInfo?.provider ? modelInfo(cat, brainInfo.provider, brainInfo.model || null, accountFor(brainInfo.provider, rows, brainInfo)) : null;
|
|
181
|
+
const spentUsd = totalSpentOf(used);
|
|
182
|
+
return {
|
|
183
|
+
ask: BUDGET_PROVIDERS.filter((p) => eligible[p]?.eligible !== false),
|
|
184
|
+
budget: { amountUsd: value.amountUsd, spentUsd, leftUsd: Math.max(0, value.amountUsd - spentUsd), warnAtUsd: roundUsd(value.amountUsd * BUDGET_WARN_RATIO) },
|
|
185
|
+
checkpoint: { reason, needs },
|
|
186
|
+
brain: brainInfo?.provider ? { provider: brainInfo.provider, model: brainInfo.model || null, price: brainModel?.price || null, billing: brainModel?.billing || null } : null,
|
|
187
|
+
routeMode: safe(() => routeMode(id), null),
|
|
188
|
+
savedRoutes, providers,
|
|
189
|
+
tasks: arr(tasks).map((t) => ({ taskClass: t.taskClass || null, summary: t.summary || null, provider: t.provider || null, model: t.model || null })),
|
|
190
|
+
task: task ? { taskClass: task.taskClass || null, summary: task.summary || null, provider: task.provider || null, model: task.model || null } : null,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── making a split ─────────────────────────────────────────────────────
|
|
195
|
+
async function catalogNow() {
|
|
196
|
+
const cached = safe(() => catalog(), null);
|
|
197
|
+
if (cached || typeof refreshCatalog !== 'function') return cached;
|
|
198
|
+
try { return (await refreshCatalog()) || null; } catch { return null; }
|
|
199
|
+
}
|
|
200
|
+
async function compute(id, want = {}) {
|
|
201
|
+
const value = readSetting();
|
|
202
|
+
if (value.unlimited) return null;
|
|
203
|
+
const reason = SPLIT_REASONS.includes(want.reason) ? want.reason : 'first_route';
|
|
204
|
+
const needs = [...new Set(arr(want.needs).filter((p) => BUDGET_PROVIDERS.includes(p)))];
|
|
205
|
+
const row = rowOfSession(id, { create: true });
|
|
206
|
+
if (want.task) noteTaskRow(row, want.task, { current: true });
|
|
207
|
+
const cat = await catalogNow();
|
|
208
|
+
const used = spend(id);
|
|
209
|
+
const brainInfo = brain(id);
|
|
210
|
+
const brainProvider = BUDGET_PROVIDERS.includes(brainInfo?.provider) ? brainInfo.provider : null;
|
|
211
|
+
const eligible = bucketEligibility({ catalog: cat, accounts: safe(() => accounts(), null), brainProvider });
|
|
212
|
+
const cov = coverageOf({ amountUsd: value.amountUsd, split: row.split, spend: used, brainProvider });
|
|
213
|
+
const leftUsd = Math.max(0, value.amountUsd - totalSpentOf(used));
|
|
214
|
+
const tasks = row.tasks.filter((t) => !t.current);
|
|
215
|
+
const task = want.task || row.tasks.find((t) => t.current) || null;
|
|
216
|
+
let weights = null;
|
|
217
|
+
let jev = null;
|
|
218
|
+
let fallbackWhy = null;
|
|
219
|
+
if (leftUsd < MIN_SPEND_USD) fallbackWhy = 'nothing_left';
|
|
220
|
+
else if (!judge?.budgetSplit) fallbackWhy = 'jev_off';
|
|
221
|
+
else if (typeof judge.available === 'function' && !safe(() => judge.available(), false)) fallbackWhy = 'jev_off';
|
|
222
|
+
else {
|
|
223
|
+
let judged = null;
|
|
224
|
+
try {
|
|
225
|
+
judged = await judge.budgetSplit(jevInput(id, { value, used, cov, eligible, brainInfo, cat, needs, task, reason, tasks }), { sessionId: id, project: brainInfo?.project || null });
|
|
226
|
+
} catch { judged = null; }
|
|
227
|
+
weights = judged ? jevWeights(judged, eligible) : null;
|
|
228
|
+
if (weights) jev = { logId: judged.logId ?? null, scores: judged.scores };
|
|
229
|
+
else fallbackWhy = judged ? 'jev_incomplete' : 'jev_unavailable';
|
|
230
|
+
// A setting changed while Jev answered: this split is for an old budget.
|
|
231
|
+
if (readSetting().version !== value.version) return compute(id, want);
|
|
232
|
+
}
|
|
233
|
+
if (!weights) {
|
|
234
|
+
weights = fallbackWeights({
|
|
235
|
+
eligible, routing: safe(() => routing(), {}) || {}, brain: brainInfo, catalog: cat, pricing,
|
|
236
|
+
tasks, task, liveRuns: arr(used.liveRuns), needs,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// What was spent is read again: Jev took up to its timeout, and money may have moved meanwhile.
|
|
240
|
+
const fresh = spend(id);
|
|
241
|
+
const freshLeft = Math.max(0, value.amountUsd - totalSpentOf(fresh));
|
|
242
|
+
const divided = divideLeft({ leftUsd: freshLeft, weights, eligible, brainProvider });
|
|
243
|
+
const spentAtSplit = Object.fromEntries(BUDGET_PROVIDERS.map((p) => [p, providerSpendOf(fresh, p).spentUsd]));
|
|
244
|
+
const shares = Object.fromEntries(BUDGET_PROVIDERS.map((p) => [p, Number((spentAtSplit[p] + divided.allocCents[p] / 100).toFixed(8))]));
|
|
245
|
+
const split = {
|
|
246
|
+
source: jev ? 'jev' : 'fallback', fallbackWhy: jev ? null : fallbackWhy, reason, at: iso(now()),
|
|
247
|
+
settingVersion: value.version, amountUsd: value.amountUsd,
|
|
248
|
+
leftUsd: divided.leftCents / 100,
|
|
249
|
+
shares, alloc: Object.fromEntries(BUDGET_PROVIDERS.map((p) => [p, divided.allocCents[p] / 100])), spentAtSplit,
|
|
250
|
+
brainProvider, brainFloorUsd: divided.brainFloorCents / 100,
|
|
251
|
+
brainSpentAtSplitUsd: brainProvider ? providerSpendOf(fresh, brainProvider).brainUsd : 0,
|
|
252
|
+
eligible, weights, needs, jev,
|
|
253
|
+
};
|
|
254
|
+
row.split = split;
|
|
255
|
+
row.updatedAt = split.at;
|
|
256
|
+
for (const t of row.tasks) delete t.current;
|
|
257
|
+
persist();
|
|
258
|
+
if (jev?.logId && typeof judge?.annotate === 'function') {
|
|
259
|
+
try { judge.annotate(jev.logId, { applied: true, reason, needs, alloc: split.alloc, brainFloorUsd: split.brainFloorUsd, leftUsd: split.leftUsd }); } catch { /* diagnostics */ }
|
|
260
|
+
}
|
|
261
|
+
log('budget:split', `${id}: ${split.source}${fallbackWhy && !jev ? ` (${fallbackWhy})` : ''} · ${reason} · left $${split.leftUsd.toFixed(2)} · ${BUDGET_PROVIDERS.map((p) => `${p} $${split.alloc[p].toFixed(2)}`).join(', ')}`);
|
|
262
|
+
try { emit(id, view(id), { reason }); } catch { /* the live packet is best effort */ }
|
|
263
|
+
return split;
|
|
264
|
+
}
|
|
265
|
+
function mergeWant(base, next = {}) {
|
|
266
|
+
const order = (reason) => ['share_used_up', 'brain_turn', 'first_route', 'budget_changed', 'run_ended'].indexOf(reason);
|
|
267
|
+
if (!base) return { reason: next.reason || 'first_route', needs: [...new Set(arr(next.needs))], task: next.task || null };
|
|
268
|
+
const reason = order(next.reason) !== -1 && (order(base.reason) === -1 || order(next.reason) < order(base.reason)) ? next.reason : base.reason;
|
|
269
|
+
return { reason, needs: [...new Set([...arr(base.needs), ...arr(next.needs)])], task: next.task || base.task || null };
|
|
270
|
+
}
|
|
271
|
+
/** A checkpoint: divide what is left again. Calls made while one waits to start join it. Never rejects. */
|
|
272
|
+
function resplit(sessionId, want = {}) {
|
|
273
|
+
const id = String(sessionId || '');
|
|
274
|
+
if (!id || !limited()) return Promise.resolve(null);
|
|
275
|
+
let chain = chains.get(id);
|
|
276
|
+
if (!chain) { chain = { promise: Promise.resolve(), queued: null, waiting: null }; chains.set(id, chain); }
|
|
277
|
+
if (chain.queued) { chain.queued = mergeWant(chain.queued, want); return chain.waiting; }
|
|
278
|
+
chain.queued = mergeWant(null, want);
|
|
279
|
+
const waiting = chain.promise.then(() => {
|
|
280
|
+
const next = chain.queued;
|
|
281
|
+
chain.queued = null;
|
|
282
|
+
return compute(id, next);
|
|
283
|
+
}).catch((error) => { log('budget:split-error', `${id}: ${error?.message || String(error)}`); return splitOf(id); });
|
|
284
|
+
chain.waiting = waiting;
|
|
285
|
+
chain.promise = waiting;
|
|
286
|
+
return waiting;
|
|
287
|
+
}
|
|
288
|
+
/** The session's split for the budget as it is now, made when there is none (the first route or dispatch). */
|
|
289
|
+
async function ensureSplit(sessionId, { task = null, reason = null } = {}) {
|
|
290
|
+
const id = String(sessionId || '');
|
|
291
|
+
const value = readSetting();
|
|
292
|
+
if (!id || value.unlimited) return null;
|
|
293
|
+
const current = splitOf(id);
|
|
294
|
+
if (current && current.settingVersion === value.version && current.amountUsd === value.amountUsd) {
|
|
295
|
+
if (task) noteTask(id, task);
|
|
296
|
+
return current;
|
|
297
|
+
}
|
|
298
|
+
return resplit(id, { reason: reason || (current ? 'budget_changed' : 'first_route'), task });
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Can `provider` pay for this work? When its share is used up but the session has money left, the
|
|
302
|
+
* unspent budget is divided again first (a checkpoint: Jev, else the fallback, decides whether the
|
|
303
|
+
* provider gets more; at most once per provider every few seconds). `forBrain`: a brain turn (it may
|
|
304
|
+
* use its reserve). `free`: a free model (only the session limit counts).
|
|
305
|
+
*/
|
|
306
|
+
async function ensureCovered(sessionId, { provider, forBrain = false, free = false, task = null, reason = null } = {}) {
|
|
307
|
+
const id = String(sessionId || '');
|
|
308
|
+
if (!id || !limited()) return true;
|
|
309
|
+
let cov = coverage(id);
|
|
310
|
+
if (coversWork(cov, { provider, forBrain, free })) return true;
|
|
311
|
+
if (cov.leftUsd < MIN_SPEND_USD || free) return false;
|
|
312
|
+
const key = `${id}:${provider}:${forBrain ? 'brain' : 'runs'}`;
|
|
313
|
+
const tried = needTries.get(key);
|
|
314
|
+
const value = readSetting();
|
|
315
|
+
const spentNow = cov.spentUsd;
|
|
316
|
+
// Never a re-split per metered step: a provider the split left short a moment ago stays short.
|
|
317
|
+
if (tried && now() - tried.at < NEED_RETRY_MS && tried.version === value.version && cov.hasSplit && (!forBrain || tried.spent === spentNow)) return false;
|
|
318
|
+
needTries.set(key, { at: now(), version: value.version, spent: spentNow });
|
|
319
|
+
await resplit(id, { reason: reason || (forBrain ? 'brain_turn' : 'share_used_up'), needs: provider ? [provider] : [], task });
|
|
320
|
+
cov = coverage(id);
|
|
321
|
+
return coversWork(cov, { provider, forBrain, free });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── tasks ──────────────────────────────────────────────────────────────
|
|
325
|
+
function noteTaskRow(row, task, { current = false } = {}) {
|
|
326
|
+
if (!task || typeof task !== 'object') return;
|
|
327
|
+
const entry = {
|
|
328
|
+
taskClass: task.taskClass ? String(task.taskClass).slice(0, 20) : null, summary: clip(task.summary || '', 160) || null,
|
|
329
|
+
provider: BUDGET_PROVIDERS.includes(task.provider) ? task.provider : null, model: task.model ? String(task.model).slice(0, 120) : null,
|
|
330
|
+
at: iso(now()), ...(current ? { current: true } : {}),
|
|
331
|
+
};
|
|
332
|
+
const last = row.tasks.at(-1);
|
|
333
|
+
if (last && last.summary === entry.summary && last.taskClass === entry.taskClass && last.provider === entry.provider) { Object.assign(last, entry); return; }
|
|
334
|
+
row.tasks.push(entry);
|
|
335
|
+
if (row.tasks.length > MAX_TASKS) row.tasks.splice(0, row.tasks.length - MAX_TASKS);
|
|
336
|
+
}
|
|
337
|
+
/** A task the session routed (agent_route approved it, or a dispatch started): Jev's next state lists it. */
|
|
338
|
+
function noteTask(sessionId, task) {
|
|
339
|
+
const id = String(sessionId || '');
|
|
340
|
+
if (!id || !task) return;
|
|
341
|
+
const row = rowOfSession(id, { create: true });
|
|
342
|
+
noteTaskRow(row, task);
|
|
343
|
+
row.updatedAt = iso(now());
|
|
344
|
+
persist();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── checkpoints from outside ───────────────────────────────────────────
|
|
348
|
+
/** The budget setting changed: every open session with a split divides what is left again. */
|
|
349
|
+
function onBudgetChanged() {
|
|
350
|
+
needTries.clear();
|
|
351
|
+
if (!limited()) return;
|
|
352
|
+
for (const [id, row] of sessions) {
|
|
353
|
+
if (!row.split || !safe(() => isOpen(id), true)) continue;
|
|
354
|
+
resplit(id, { reason: 'budget_changed' }).catch(() => {});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/** A run of the session ended: what it did not spend goes back into the split (debounced). */
|
|
358
|
+
function onRunEnded(sessionId) {
|
|
359
|
+
const id = String(sessionId || '');
|
|
360
|
+
if (!id || !limited() || !splitOf(id)) return;
|
|
361
|
+
if (runEndTimers.has(id)) clearTimeout(runEndTimers.get(id));
|
|
362
|
+
const timer = setTimeout(() => {
|
|
363
|
+
runEndTimers.delete(id);
|
|
364
|
+
if (safe(() => isOpen(id), true)) resplit(id, { reason: 'run_ended' }).catch(() => {});
|
|
365
|
+
}, RUN_END_DEBOUNCE_MS);
|
|
366
|
+
timer.unref?.();
|
|
367
|
+
runEndTimers.set(id, timer);
|
|
368
|
+
}
|
|
369
|
+
function forget(sessionId) {
|
|
370
|
+
const id = String(sessionId || '');
|
|
371
|
+
sessions.delete(id);
|
|
372
|
+
chains.delete(id);
|
|
373
|
+
persist();
|
|
374
|
+
}
|
|
375
|
+
/** Settled for a test or a shutdown: pending timers cleared, state written now. */
|
|
376
|
+
function flush() {
|
|
377
|
+
for (const timer of runEndTimers.values()) clearTimeout(timer);
|
|
378
|
+
runEndTimers.clear();
|
|
379
|
+
if (persistTimer) { clearTimeout(persistTimer); persistTimer = null; }
|
|
380
|
+
writeState();
|
|
381
|
+
}
|
|
382
|
+
/** What every queued re-split resolves to (tests). */
|
|
383
|
+
function settled(sessionId) { return chains.get(String(sessionId || ''))?.promise || Promise.resolve(splitOf(sessionId)); }
|
|
384
|
+
|
|
385
|
+
load();
|
|
386
|
+
return {
|
|
387
|
+
setting: readSetting, limited, coverage, covers, view, splitOf, dry: (id) => dryProviders(coverage(id)),
|
|
388
|
+
ensureSplit, resplit, ensureCovered, noteTask, onBudgetChanged, onRunEnded, forget, flush, settled,
|
|
389
|
+
};
|
|
390
|
+
}
|