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
|
@@ -15,7 +15,17 @@ export const OPEN = 1;
|
|
|
15
15
|
export const CLOSING = 2;
|
|
16
16
|
export const CLOSED = 3;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// A brain's credential (assistant-runtime.js mintBrainCredential), kept beside the socket object
|
|
19
|
+
// that server code built: a client's WebSocket never has one, and nothing a client sends can set
|
|
20
|
+
// it. The Codex skin handler reads it here (brainCredentialOf), never from a message.
|
|
21
|
+
const credentials = new WeakMap();
|
|
22
|
+
|
|
23
|
+
/** The credential the server attached to this in-process socket, or null (always null for a client's socket). */
|
|
24
|
+
export function brainCredentialOf(socket) {
|
|
25
|
+
return (socket && credentials.get(socket)) || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createVirtualSocket({ onSend = () => {}, name = 'virtual', credential = null } = {}) {
|
|
19
29
|
const emitter = new EventEmitter();
|
|
20
30
|
emitter.setMaxListeners(50);
|
|
21
31
|
const socket = {
|
|
@@ -56,5 +66,6 @@ export function createVirtualSocket({ onSend = () => {}, name = 'virtual' } = {}
|
|
|
56
66
|
emit: (...args) => emitter.emit(...args),
|
|
57
67
|
listenerCount: (type) => emitter.listenerCount(type),
|
|
58
68
|
};
|
|
69
|
+
if (typeof credential === 'string' && credential) credentials.set(socket, credential);
|
|
59
70
|
return socket;
|
|
60
71
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
|
-
import { CONN, ERR, LIMITS } from './protocol.js';
|
|
10
|
+
import { CONN, CONN_WARNINGS, ERR, LIMITS, VOICE_MARKER } from './protocol.js';
|
|
11
11
|
import { parseJid, sameAccount } from './identity.js';
|
|
12
12
|
import { redactWa } from './redact.js';
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ export const RUNTIME_EXPORTS = Object.freeze([
|
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
20
|
const QUOTED_MAX = 1000;
|
|
21
|
-
const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
21
|
+
export const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
22
22
|
const VERSION_RECHECK_MS = 6 * 3600_000;
|
|
23
23
|
|
|
24
24
|
function adapterError(code, message, cause) {
|
|
@@ -235,68 +235,103 @@ export function disconnectCode(err) {
|
|
|
235
235
|
return null;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
// Baileys 7.0.0-rc14 (the pinned connector) closes with 500 for more than a
|
|
239
|
+
// bad session: 500 is DisconnectReason.badSession, but it is also the code
|
|
240
|
+
// getErrorCodeFromStreamError (Utils/generics.js) gives any <stream:error>
|
|
241
|
+
// without a code of its own ("Stream Errored (<first child tag>)", the child
|
|
242
|
+
// node in `data`), the code getCodeFromWSError gives a WebSocket error it
|
|
243
|
+
// cannot place ("WebSocket Error (…)"), and the default of a <failure> without
|
|
244
|
+
// a reason ("Connection Failure"). Baileys itself never names a bad session:
|
|
245
|
+
// only WhatsApp's own stream-error child tag can, and only that wipes the
|
|
246
|
+
// saved credentials. Every other 500 is transport noise and is retried.
|
|
247
|
+
const STREAM_ERROR = /^Stream Errored \(/;
|
|
248
|
+
const INVALID_SESSION_TAG = /\b(?:bad[-_ ]?session|not[-_ ]?authori[sz]ed|invalid[-_ ]?session)\b/i;
|
|
249
|
+
|
|
250
|
+
/** WhatsApp said the saved session is invalid: a stream error whose reason tag names it (`<not-authorized/>`). */
|
|
251
|
+
export function invalidSessionSignal(err) {
|
|
252
|
+
const message = typeof err?.message === 'string' ? err.message : '';
|
|
253
|
+
if (!STREAM_ERROR.test(message)) return false;
|
|
254
|
+
const tag = typeof err?.data?.tag === 'string' ? err.data.tag : '';
|
|
255
|
+
return INVALID_SESSION_TAG.test(`${message} ${tag}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Which kind of generic 500 this is (the reason a log line and lastDisconnect carry). */
|
|
259
|
+
function transportReason(err) {
|
|
260
|
+
const message = typeof err?.message === 'string' ? err.message : '';
|
|
261
|
+
if (STREAM_ERROR.test(message)) return 'stream_error';
|
|
262
|
+
if (/^WebSocket Error \(/.test(message)) return 'websocket_error';
|
|
263
|
+
if (/^Connection Failure\b/.test(message)) return 'connection_failure';
|
|
264
|
+
return 'status_500';
|
|
265
|
+
}
|
|
266
|
+
|
|
238
267
|
/**
|
|
239
268
|
* Map a lastDisconnect error to what the host does next, by DisconnectReason
|
|
240
269
|
* NAME (408 is both connectionLost and timedOut; the enum's reverse map says
|
|
241
|
-
* timedOut).
|
|
242
|
-
*
|
|
270
|
+
* timedOut). A saved session never gives up on its own: only 401 (logged out)
|
|
271
|
+
* and an explicitly invalid session stop it, and both wipe the credentials.
|
|
272
|
+
* 440 and 403 retry from the longest delay (`delay: 'long'`) and carry a
|
|
273
|
+
* `warning` the service shows (CONN_WARNINGS). While linking (no saved
|
|
274
|
+
* session) anything but 515, 405, 401 and 403 means the QR or code ran out.
|
|
275
|
+
* @returns {{action:'reconnect_now'|'refresh_version'|'backoff'|'stop', state:string, wipe:boolean, reason:string, code:number|null,
|
|
276
|
+
* delay?:'long', warning?:'replaced'|'forbidden'}}
|
|
243
277
|
*/
|
|
244
278
|
export function classifyDisconnect(err, { linking = false, DisconnectReason = {} } = {}) {
|
|
245
279
|
const code = disconnectCode(err);
|
|
246
280
|
const name = code !== null && typeof DisconnectReason[code] === 'string' ? DisconnectReason[code] : null;
|
|
247
|
-
const out = (action, state, wipe, reason) => ({ action, state, wipe, reason, code });
|
|
281
|
+
const out = (action, state, wipe, reason, extra = null) => ({ action, state, wipe, reason, code, ...(extra || {}) });
|
|
282
|
+
// 500: an explicit bad session, or noise (a generic stream / WebSocket / failure error).
|
|
283
|
+
const reason = name === 'badSession' ? (invalidSessionSignal(err) ? 'badSession' : transportReason(err)) : name;
|
|
248
284
|
if (code === 405) return out('refresh_version', linking ? CONN.CONNECTING : CONN.RECONNECTING, false, 'outdated_version');
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
default:
|
|
262
|
-
// timedOut / connectionLost / connectionClosed / unavailableService /
|
|
263
|
-
// anything unknown. While linking that means the QR or code ran out:
|
|
264
|
-
// stop instead of looping through fresh QR codes.
|
|
265
|
-
if (linking) return out('stop', CONN.LINK_EXPIRED, true, name || 'link_failed');
|
|
266
|
-
return out('backoff', CONN.RECONNECTING, false, name || (code === null ? 'closed' : `status_${code}`));
|
|
285
|
+
if (name === 'restartRequired') return out('reconnect_now', CONN.CONNECTING, false, name);
|
|
286
|
+
if (name === 'loggedOut') return out('stop', CONN.LOGGED_OUT, true, name);
|
|
287
|
+
if (linking) {
|
|
288
|
+
if (name === 'forbidden') return out('stop', CONN.FORBIDDEN, false, name);
|
|
289
|
+
// A timeout or a closed stream means the QR / code ran out: stop instead of looping through fresh QR codes.
|
|
290
|
+
return out('stop', CONN.LINK_EXPIRED, true, reason || 'link_failed');
|
|
291
|
+
}
|
|
292
|
+
if (reason === 'badSession') return out('stop', CONN.LOGGED_OUT, true, reason);
|
|
293
|
+
const warning = CONN_WARNINGS[code];
|
|
294
|
+
if (warning) {
|
|
295
|
+
// Two copies of one session would kick each other every few seconds, and a refused account is not hammered.
|
|
296
|
+
return out('backoff', CONN.RECONNECTING, false, name || `status_${code}`, { delay: 'long', warning });
|
|
267
297
|
}
|
|
298
|
+
// timedOut / connectionLost / connectionClosed / unavailableService / multideviceMismatch /
|
|
299
|
+
// a generic 500 / anything unknown: keep the credentials and try again.
|
|
300
|
+
return out('backoff', CONN.RECONNECTING, false, reason || (code === null ? 'closed' : `status_${code}`));
|
|
268
301
|
}
|
|
269
302
|
|
|
270
303
|
/**
|
|
271
|
-
* Reconnect delays 2/
|
|
272
|
-
*
|
|
304
|
+
* Reconnect delays 2/4/8/16/32/64/128/256/300 s, each ±20% and never above
|
|
305
|
+
* 300 s; the ladder starts over after 10 minutes connected (noteStable). It
|
|
306
|
+
* never gives up. `next({ long: true })` (440, 403) goes to the top at once and
|
|
307
|
+
* stays there until that stable connection: after a session another copy took
|
|
308
|
+
* or an account WhatsApp refused, any drop waits the long delay (a refused
|
|
309
|
+
* account may fail in other ways too, and two copies must not trade quick
|
|
310
|
+
* retries), the same 10 minutes that end the warning Settings shows.
|
|
273
311
|
*/
|
|
274
312
|
export function createReconnectPolicy({
|
|
275
|
-
now = () => Date.now(),
|
|
276
313
|
rng = Math.random,
|
|
277
|
-
scheduleSec = [2,
|
|
314
|
+
scheduleSec = [2, 4, 8, 16, 32, 64, 128, 256, 300],
|
|
278
315
|
jitter = 0.2,
|
|
279
|
-
|
|
280
|
-
windowMs = 10 * 60_000,
|
|
316
|
+
maxMs = 300_000,
|
|
281
317
|
} = {}) {
|
|
282
318
|
let idx = 0;
|
|
283
|
-
let attempts =
|
|
319
|
+
let attempts = 0;
|
|
284
320
|
return {
|
|
285
|
-
/** @returns {{delayMs:number}
|
|
286
|
-
next() {
|
|
287
|
-
|
|
288
|
-
attempts = attempts.filter((a) => t - a < windowMs);
|
|
289
|
-
if (attempts.length >= limit) return { giveUp: true };
|
|
290
|
-
attempts.push(t);
|
|
321
|
+
/** @returns {{delayMs:number}} */
|
|
322
|
+
next({ long = false } = {}) {
|
|
323
|
+
if (long) idx = Math.max(idx, scheduleSec.length - 1);
|
|
291
324
|
const base = scheduleSec[Math.min(idx, scheduleSec.length - 1)] * 1000;
|
|
292
325
|
idx += 1;
|
|
326
|
+
attempts += 1;
|
|
293
327
|
const factor = 1 + (rng() * 2 - 1) * jitter;
|
|
294
|
-
return { delayMs: Math.max(0, Math.round(base * factor)) };
|
|
328
|
+
return { delayMs: Math.min(maxMs, Math.max(0, Math.round(base * factor))) };
|
|
295
329
|
},
|
|
296
330
|
/** Connected long enough: start the ladder over. */
|
|
297
331
|
noteStable() { idx = 0; },
|
|
298
|
-
reset() { idx = 0; attempts =
|
|
299
|
-
|
|
332
|
+
reset() { idx = 0; attempts = 0; },
|
|
333
|
+
/** Retries scheduled since the last reset (an explicit connect). */
|
|
334
|
+
get attempts() { return attempts; },
|
|
300
335
|
};
|
|
301
336
|
}
|
|
302
337
|
|
|
@@ -323,8 +358,8 @@ const DROP_TYPES = Object.freeze({
|
|
|
323
358
|
stickerPackMessage: 'sticker',
|
|
324
359
|
});
|
|
325
360
|
|
|
361
|
+
// audioMessage (voice notes and audio files) is kind 'audio': transcribed on this computer (transcription.js).
|
|
326
362
|
const UNSUPPORTED = Object.freeze({
|
|
327
|
-
audioMessage: 'audio',
|
|
328
363
|
videoMessage: 'video',
|
|
329
364
|
ptvMessage: 'video',
|
|
330
365
|
documentMessage: 'document',
|
|
@@ -347,7 +382,7 @@ function captionOf(inner) {
|
|
|
347
382
|
|
|
348
383
|
/**
|
|
349
384
|
* Classify the content of one message without downloading anything.
|
|
350
|
-
* @returns {{drop:string} | {type:string, kind:'text'|'image'|'unsupported', inner:object|null,
|
|
385
|
+
* @returns {{drop:string} | {type:string, kind:'text'|'image'|'audio'|'unsupported', inner:object|null,
|
|
351
386
|
* contextInfo:object|null, text:string, unsupportedType:string|null}}
|
|
352
387
|
*/
|
|
353
388
|
export function describeContent(msg, mod) {
|
|
@@ -370,6 +405,9 @@ export function describeContent(msg, mod) {
|
|
|
370
405
|
if (type === 'imageMessage') {
|
|
371
406
|
return { type, kind: 'image', inner, contextInfo, text: captionOf(inner), unsupportedType: null };
|
|
372
407
|
}
|
|
408
|
+
if (type === 'audioMessage') {
|
|
409
|
+
return { type, kind: 'audio', inner, contextInfo, text: captionOf(inner), unsupportedType: null };
|
|
410
|
+
}
|
|
373
411
|
return { type, kind: 'unsupported', inner, contextInfo, text: captionOf(inner), unsupportedType: UNSUPPORTED[type] || 'other' };
|
|
374
412
|
}
|
|
375
413
|
|
|
@@ -401,13 +439,65 @@ function withTimeout(promise, ms, label) {
|
|
|
401
439
|
|
|
402
440
|
const MSG_ID_RE = /^[A-Za-z0-9._:+/=-]{1,128}$/;
|
|
403
441
|
|
|
442
|
+
/** A size or duration a message declares about itself (a number, a protobuf Long, a string), or null when it says none. */
|
|
443
|
+
function declared(value) {
|
|
444
|
+
if (value === null || value === undefined || value === '') return null;
|
|
445
|
+
let n = NaN;
|
|
446
|
+
if (typeof value === 'number') n = value;
|
|
447
|
+
else if (typeof value === 'bigint' || typeof value === 'string') n = Number(value);
|
|
448
|
+
else if (typeof value?.toNumber === 'function') { try { n = value.toNumber(); } catch { n = NaN; } }
|
|
449
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* A voice note or audio file (kind 'audio'), only for a message host-core already accepted:
|
|
454
|
+
* → { transcript, recorded } or { unsupported: { type: 'audio', reason } }. `recorded`: a voice note
|
|
455
|
+
* the sender recorded in WhatsApp (ptt), not an audio file attached to the chat. Nothing is downloaded while
|
|
456
|
+
* transcription is off or cannot run here, or when the message says it is over a limit.
|
|
457
|
+
* transcriber transcription.js createTranscriber() (enabled, available(), transcribe())
|
|
458
|
+
* stale sent so long ago that the bridge will skip it anyway: not worth transcribing
|
|
459
|
+
*/
|
|
460
|
+
async function readAudio(msg, { mod, sock, logger, inner, transcriber, stale }) {
|
|
461
|
+
const refuse = (reason) => ({ unsupported: { type: 'audio', reason } });
|
|
462
|
+
if (!transcriber || !transcriber.enabled) return refuse('transcription_disabled');
|
|
463
|
+
let ready = false;
|
|
464
|
+
try { ready = transcriber.available() === true; } catch { ready = false; }
|
|
465
|
+
if (!ready) return refuse('transcription_unavailable');
|
|
466
|
+
if ((declared(inner?.fileLength) ?? 0) > LIMITS.MAX_AUDIO_BYTES) return refuse('too_large');
|
|
467
|
+
if ((declared(inner?.seconds) ?? 0) > LIMITS.MAX_AUDIO_SECONDS) return refuse('too_long');
|
|
468
|
+
if (stale) return refuse('stale');
|
|
469
|
+
let bytes;
|
|
470
|
+
try {
|
|
471
|
+
const buf = await withTimeout(
|
|
472
|
+
mod.downloadMediaMessage(msg, 'buffer', {}, { logger, reuploadRequest: sock?.updateMediaMessage }),
|
|
473
|
+
DOWNLOAD_TIMEOUT_MS,
|
|
474
|
+
'audio download',
|
|
475
|
+
);
|
|
476
|
+
bytes = Buffer.isBuffer(buf) ? buf : Buffer.from(buf || []);
|
|
477
|
+
} catch {
|
|
478
|
+
return refuse('transcription_failed');
|
|
479
|
+
}
|
|
480
|
+
if (bytes.length > LIMITS.MAX_AUDIO_BYTES) return refuse('too_large');
|
|
481
|
+
if (!bytes.length) return refuse('transcription_failed');
|
|
482
|
+
let result = null;
|
|
483
|
+
try { result = await transcriber.transcribe(bytes); } catch { result = null; }
|
|
484
|
+
if (result?.ok && typeof result.text === 'string' && result.text.trim()) return { transcript: result.text.trim(), recorded: inner?.ptt === true };
|
|
485
|
+
// Turned off while the note waited its turn: said as such, not as a failure.
|
|
486
|
+
return refuse(['too_long', 'too_large', 'transcription_disabled'].includes(result?.reason) ? result.reason : 'transcription_failed');
|
|
487
|
+
}
|
|
488
|
+
|
|
404
489
|
/**
|
|
405
490
|
* Turn an accepted message into the parts of an InboundMessage (never a JID).
|
|
406
|
-
*
|
|
491
|
+
* A transcribed voice note: `text` is VOICE_MARKER + the transcript, then "\n" + the caption when
|
|
492
|
+
* there is one (the transcript first when the text is cut), and `voice: true`; `audioFile: true` when
|
|
493
|
+
* it is an audio file rather than a note recorded in WhatsApp (its words may be anyone's: the bridge
|
|
494
|
+
* treats it like a picture).
|
|
495
|
+
* @returns {Promise<{text:string, images:Array, unsupported:null|{type:string, reason?:string}, forwarded:boolean,
|
|
496
|
+
* quoted:null|{id:string,text:string,fromBot:boolean}, voice:boolean, audioFile:boolean}>}
|
|
407
497
|
*/
|
|
408
|
-
export async function extractInbound(msg, { mod, sock, logger, sentIds, described, text }) {
|
|
498
|
+
export async function extractInbound(msg, { mod, sock, logger, sentIds, described, text, transcriber = null, audioStale = false }) {
|
|
409
499
|
const d = described;
|
|
410
|
-
|
|
500
|
+
let body = String(text ?? d.text ?? '').slice(0, LIMITS.MAX_INBOUND_TEXT);
|
|
411
501
|
const ctx = d.contextInfo;
|
|
412
502
|
const forwarded = !!(ctx && (ctx.isForwarded || Number(ctx.forwardingScore) > 0));
|
|
413
503
|
let quoted = null;
|
|
@@ -420,7 +510,21 @@ export async function extractInbound(msg, { mod, sock, logger, sentIds, describe
|
|
|
420
510
|
}
|
|
421
511
|
const images = [];
|
|
422
512
|
let unsupported = null;
|
|
513
|
+
let voice = false;
|
|
514
|
+
let audioFile = false;
|
|
423
515
|
if (d.kind === 'unsupported') unsupported = { type: d.unsupportedType };
|
|
516
|
+
if (d.kind === 'audio') {
|
|
517
|
+
const audio = await readAudio(msg, { mod, sock, logger, inner: d.inner, transcriber, stale: audioStale });
|
|
518
|
+
if (audio.transcript) {
|
|
519
|
+
const caption = String(text ?? d.text ?? '');
|
|
520
|
+
body = `${VOICE_MARKER} ${audio.transcript}${caption.trim() ? `\n${caption}` : ''}`.slice(0, LIMITS.MAX_INBOUND_TEXT);
|
|
521
|
+
voice = true;
|
|
522
|
+
audioFile = !audio.recorded;
|
|
523
|
+
} else {
|
|
524
|
+
// The same unsupported-audio shape as before, with why; a caption still gets through.
|
|
525
|
+
unsupported = audio.unsupported;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
424
528
|
if (d.kind === 'image') {
|
|
425
529
|
const mime = String(d.inner?.mimetype || '').split(';')[0].trim().toLowerCase();
|
|
426
530
|
const declared = Number(d.inner?.fileLength ?? 0);
|
|
@@ -443,7 +547,23 @@ export async function extractInbound(msg, { mod, sock, logger, sentIds, describe
|
|
|
443
547
|
}
|
|
444
548
|
}
|
|
445
549
|
}
|
|
446
|
-
return { text: body, images, unsupported, forwarded, quoted };
|
|
550
|
+
return { text: body, images, unsupported, forwarded, quoted, voice, audioFile };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* The sendMessage content for a file prepared by local-file.js (prepareLocalFile): a document
|
|
555
|
+
* (mimetype + fileName, the caption when there is one), a photo or a video (the caption), or audio
|
|
556
|
+
* (never a caption, never a voice note). `jpegThumbnail: null` keeps Baileys from building a preview,
|
|
557
|
+
* which for a video means running FFmpeg through a shell: SynaBun never decodes the file it sends.
|
|
558
|
+
*/
|
|
559
|
+
export function fileMessage(file) {
|
|
560
|
+
const caption = typeof file?.caption === 'string' && file.caption ? { caption: file.caption } : {};
|
|
561
|
+
switch (file?.type) {
|
|
562
|
+
case 'image': return { image: file.data, mimetype: file.mimetype, jpegThumbnail: null, ...caption };
|
|
563
|
+
case 'video': return { video: file.data, mimetype: file.mimetype, jpegThumbnail: null, ...caption };
|
|
564
|
+
case 'audio': return { audio: file.data, mimetype: file.mimetype, ptt: false };
|
|
565
|
+
default: return { document: file.data, mimetype: file.mimetype, fileName: file.fileName || 'file', ...caption };
|
|
566
|
+
}
|
|
447
567
|
}
|
|
448
568
|
|
|
449
569
|
/** The minimal message Baileys needs to quote an owner message in a reply. */
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
// owner's messages; a synabun.mailbox event is RUNNING(mailbox), a turn the
|
|
14
14
|
// CLI starts itself RUNNING(background), and a turn nobody here submitted (a
|
|
15
15
|
// prompt typed on the desktop) RUNNING(external) — tracked, never forwarded.
|
|
16
|
+
// A task the router continues on another model (a synabun.continuation event:
|
|
17
|
+
// a route decided automatically, by a remembered route, or on a card) answers
|
|
18
|
+
// where its prompt came from: RUNNING(followup) for a prompt from the phone,
|
|
19
|
+
// whose answer is sent once as the reply (not an update, whatever "Forward
|
|
20
|
+
// background results" says), RUNNING(external) for any other. A follow-up is
|
|
21
|
+
// how a turn's answer is delivered, nothing more: what may run unasked or be
|
|
22
|
+
// approved is the runtime's (a continuation is its own turn, never the owner's
|
|
23
|
+
// message: computer use asks once in it) and the owner's typed answers.
|
|
16
24
|
// The turn object exists before submit is called: brain events can arrive
|
|
17
25
|
// before it resolves.
|
|
18
26
|
//
|
|
@@ -23,8 +31,9 @@
|
|
|
23
31
|
// closes WITHOUT being granted (denied, cancelled) and the message goes to the
|
|
24
32
|
// Assistant as the next prompt. An approval is only ever granted by a "yes"
|
|
25
33
|
// the bridge matched on a message the owner typed, aimed at that request while
|
|
26
|
-
// it is open: never by the model, by a forwarded message, by a
|
|
27
|
-
//
|
|
34
|
+
// it is open: never by the model, by a forwarded message, by a voice note (its
|
|
35
|
+
// transcript is an ordinary message, `voice: true`), by a reply that quotes
|
|
36
|
+
// something else or by anything that merely looks like an answer.
|
|
28
37
|
// Messages that arrive while a turn runs wait as one merged prompt and go in
|
|
29
38
|
// as soon as it ends; a queue watch lets go of a turn whose end never came.
|
|
30
39
|
// A request that appears while such a message waits closes at once, unseen and
|
|
@@ -87,14 +96,28 @@ export const BRIDGE_LIMITS = Object.freeze({
|
|
|
87
96
|
policyTickMs: 30_000,
|
|
88
97
|
});
|
|
89
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Why a voice note was not transcribed (the host's unsupported.reason), told to the phone once per
|
|
101
|
+
* reason every unsupportedEveryMs. Anything else unsupported gets the generic line.
|
|
102
|
+
*/
|
|
103
|
+
export const AUDIO_NOT_TRANSCRIBED = Object.freeze({
|
|
104
|
+
transcription_disabled: 'Voice notes are not transcribed: that is turned off in Settings → WhatsApp on your computer. Type your message instead.',
|
|
105
|
+
transcription_unavailable: 'I can\'t transcribe voice notes yet: this computer needs whisper.cpp, its speech model and FFmpeg (Settings → WhatsApp says what is missing). Type your message instead.',
|
|
106
|
+
too_large: 'That voice note is too large for me to transcribe (20 MB at most). Send a shorter one, or type it.',
|
|
107
|
+
too_long: 'That voice note is too long for me to transcribe (10 minutes at most). Send a shorter one, or type it.',
|
|
108
|
+
stale: 'That voice note was sent a while ago, so I did not transcribe it. Send it again if you still need it.',
|
|
109
|
+
transcription_failed: 'I couldn\'t transcribe that voice note. Try again, or type your message.',
|
|
110
|
+
});
|
|
111
|
+
|
|
90
112
|
/** Send refusals that mean "not now": the reply waits in the outbox for the next connect. */
|
|
91
113
|
const RESEND_ON_CONNECT = /DISCONNECT|NOT_CONNECTED|OFFLINE|RECONNECT|CLOSED|TIMEOUT|HOST_UNAVAILABLE/i;
|
|
92
114
|
/**
|
|
93
115
|
* What happens to a reply the transport refused: 'connect' (it waits for the
|
|
94
116
|
* next connect), 'later' (THROTTLED: sent again after its retryAfterMs) or
|
|
95
|
-
* 'drop'. PAUSED (an owner anomaly paused sending) and HELD (
|
|
96
|
-
*
|
|
97
|
-
*
|
|
117
|
+
* 'drop'. PAUSED (an owner anomaly paused sending) and HELD (a connector
|
|
118
|
+
* process of another SynaBun version, down until a restart; a crash is not
|
|
119
|
+
* HELD: it reads HOST_UNAVAILABLE and the reply waits) drop it: the service
|
|
120
|
+
* records why in the activity log and the Settings tab shows the state.
|
|
98
121
|
*/
|
|
99
122
|
export function outboxPlan(code) {
|
|
100
123
|
const value = String(code || '');
|
|
@@ -104,7 +127,7 @@ export function outboxPlan(code) {
|
|
|
104
127
|
|
|
105
128
|
const DEFAULT_CONFIG = Object.freeze({
|
|
106
129
|
enabled: true, level: 'ask', progress: 'key', forwardBackground: true, replyLabel: null, maxMessages: 3,
|
|
107
|
-
rotation: 'idle6h', strictWorkerApprovals: false, computerUse: false, mode: 'self', paused: false, pausedBy: null,
|
|
130
|
+
rotation: 'idle6h', strictWorkerApprovals: false, computerUse: false, mode: 'self', paused: false, pausedBy: null,
|
|
108
131
|
});
|
|
109
132
|
const ALREADY_ANSWERED = new Set(['CONTROL_UNKNOWN', 'ROUTE_ALREADY_DECIDED', 'ROUTE_NOT_FOUND', 'CLARIFY_ALREADY_ANSWERED', 'CLARIFY_NOT_FOUND', 'PERMISSION_ALREADY_RESOLVED', 'NO_PENDING_REQUEST', 'RUN_NOT_FOUND']);
|
|
110
133
|
const ROUTE_DONE_PHASES = new Set(['decided', 'auto', 'expired', 'declined', 'cancelled']);
|
|
@@ -191,6 +214,8 @@ export function createWhatsAppBridge({
|
|
|
191
214
|
outboxTimer: null, // a THROTTLED reply's retry
|
|
192
215
|
staleSkipped: 0,
|
|
193
216
|
staleTimer: null,
|
|
217
|
+
stoppedAt: 0, // when the owner's last /stop was sent (its message time)
|
|
218
|
+
stopLateNoted: false, // one note per /stop for what was sent before it and arrived after it
|
|
194
219
|
unsupportedAt: new Map(),
|
|
195
220
|
minuteWindow: { start: 0, count: 0, noticed: false },
|
|
196
221
|
dayWindow: { start: 0, count: 0, noticed: false },
|
|
@@ -435,14 +460,16 @@ export function createWhatsAppBridge({
|
|
|
435
460
|
state.brainApplied = { sessionId, signature };
|
|
436
461
|
persistBridgeState();
|
|
437
462
|
}
|
|
463
|
+
// Autonomous has no time limit on WhatsApp: the policy carries no autonomousUntil (lib/remote-policy.js effectiveLevel).
|
|
464
|
+
// WhatsApp turned off on this computer (`enabled: false`, which also stops the connector) ends computer use like the switch.
|
|
438
465
|
function remoteFromConfig(cfg = readConfig()) {
|
|
439
|
-
return { channel: 'whatsapp', level: cfg.level || 'ask', strictWorkerApprovals: cfg.strictWorkerApprovals === true,
|
|
466
|
+
return { channel: 'whatsapp', level: cfg.level || 'ask', strictWorkerApprovals: cfg.strictWorkerApprovals === true, paused: state.paused, computerUse: cfg.computerUse === true && cfg.enabled !== false && state.computerEnded !== true };
|
|
440
467
|
}
|
|
441
468
|
/** The policy of one of our sessions: computer use is the current conversation's alone. */
|
|
442
469
|
function remoteFor(id, base = remoteFromConfig()) {
|
|
443
470
|
return id === state.sessionId ? base : { ...base, computerUse: false };
|
|
444
471
|
}
|
|
445
|
-
const policySignature = (remote) => JSON.stringify([remote.level, remote.strictWorkerApprovals, remote.
|
|
472
|
+
const policySignature = (remote) => JSON.stringify([remote.level, remote.strictWorkerApprovals, remote.paused, remote.computerUse]);
|
|
446
473
|
async function createSession() {
|
|
447
474
|
const rt = runtime();
|
|
448
475
|
if (!rt?.createSession) throw Object.assign(new Error('The Assistant is not available right now.'), { code: 'RUNTIME_UNAVAILABLE' });
|
|
@@ -500,9 +527,8 @@ export function createWhatsAppBridge({
|
|
|
500
527
|
// ── policy ─────────────────────────────────────────────────────────────────
|
|
501
528
|
/**
|
|
502
529
|
* Keep the level of every WhatsApp session in line with the config (level,
|
|
503
|
-
* strict approvals,
|
|
504
|
-
*
|
|
505
|
-
* runtime re-apply the clamp and stop runs above it.
|
|
530
|
+
* strict approvals, pause, computer use). A change re-registers, which makes
|
|
531
|
+
* the runtime re-apply the clamp and stop runs above it.
|
|
506
532
|
*/
|
|
507
533
|
function syncPolicies() {
|
|
508
534
|
const cfg = readConfig();
|
|
@@ -652,7 +678,8 @@ export function createWhatsAppBridge({
|
|
|
652
678
|
if (turn.kind === 'followup') state.followUp = null;
|
|
653
679
|
else { presence('paused'); state.inflight = null; persistBridgeState(); react(turn.sourceIds, 'done'); }
|
|
654
680
|
} else if (turn.kind === 'followup') {
|
|
655
|
-
// The task the phone decided on a card
|
|
681
|
+
// The task the phone decided on a card, or that its prompt carried on to another model: its answer
|
|
682
|
+
// is a reply, not an update. Said once: the turn is gone already, a repeated end finds nothing.
|
|
656
683
|
state.followUp = null;
|
|
657
684
|
const text = finalText(turn);
|
|
658
685
|
if (outcome === 'failed') await say(`Something went wrong: ${String(message || turn.resultError || 'the turn failed').slice(0, 400)}`, { maxChunks: 1 });
|
|
@@ -767,6 +794,12 @@ export function createWhatsAppBridge({
|
|
|
767
794
|
// Our own prompt (the WhatsApp turn exists already); any other is someone else's turn.
|
|
768
795
|
if (ev.origin !== 'whatsapp' && !state.turns.has(sessionId)) makeTurn(sessionId, 'external');
|
|
769
796
|
return;
|
|
797
|
+
case 'synabun.continuation':
|
|
798
|
+
// The router carries a task on (another model, the same conversation), before any of its output.
|
|
799
|
+
// A prompt from the phone: its answer is the reply to the phone. Anything else stays as before:
|
|
800
|
+
// someone else's turn, unless a card the phone saw was just decided (ensureTurn).
|
|
801
|
+
if (!state.turns.has(sessionId)) ensureTurn(sessionId, ev.origin === 'whatsapp' ? 'followup' : 'external');
|
|
802
|
+
return;
|
|
770
803
|
case 'synabun.mailbox': {
|
|
771
804
|
const items = Array.isArray(ev.items) ? ev.items : [];
|
|
772
805
|
const turn = state.turns.get(sessionId);
|
|
@@ -1031,6 +1064,10 @@ export function createWhatsAppBridge({
|
|
|
1031
1064
|
case 'help': await say(HELP_TEXT, { maxChunks: 2 }); return;
|
|
1032
1065
|
case 'status': await say(statusText(statusSnapshot()), { maxChunks: 1 }); return;
|
|
1033
1066
|
case 'stop': {
|
|
1067
|
+
// What was sent before this /stop and arrives after it (a voice note still being transcribed, and
|
|
1068
|
+
// what waited behind it) is not acted on, as /stop clears what already waits in line.
|
|
1069
|
+
state.stoppedAt = Math.max(state.stoppedAt, tsMs(message.ts, now()));
|
|
1070
|
+
state.stopLateNoted = false;
|
|
1034
1071
|
const { stoppedRuns, clearedQueue } = await stopEverything({ reason: 'phone' });
|
|
1035
1072
|
const n = stoppedRuns + clearedQueue;
|
|
1036
1073
|
await say(`Stopped. Cancelled ${n} task${n === 1 ? '' : 's'}.`, { maxChunks: 1 });
|
|
@@ -1067,18 +1104,14 @@ export function createWhatsAppBridge({
|
|
|
1067
1104
|
function statusSnapshot() {
|
|
1068
1105
|
const view = state.sessionId ? sessionView(state.sessionId) : null;
|
|
1069
1106
|
const turn = state.sessionId ? state.turns.get(state.sessionId) : null;
|
|
1070
|
-
const rt = runtime();
|
|
1071
|
-
let brainCapUsd = null;
|
|
1072
|
-
try { brainCapUsd = rt?.brainCapUsd?.() ?? null; } catch { brainCapUsd = null; }
|
|
1073
1107
|
const cfg = readConfig();
|
|
1074
1108
|
const current = currentCard();
|
|
1075
1109
|
return {
|
|
1076
1110
|
connected: isConnected(), sessionId: state.sessionId, title: view?.title || null, brain: view?.brain || null,
|
|
1077
1111
|
running: !!turn || !!view?.running, turnKind: turn?.kind || null, queued: state.queue.length,
|
|
1078
1112
|
pendingCards: state.cards.size, currentCard: current ? (current.kind === 'route' ? 'where to run a task' : current.kind === 'computer' ? 'controlling your Mac' : current.kind) : null,
|
|
1079
|
-
budget: view?.budget || null, brainUsd: view?.costUsd ?? null,
|
|
1113
|
+
budget: view?.budget || null, brainUsd: view?.costUsd ?? null,
|
|
1080
1114
|
level: state.sessionId ? levelOf(state.sessionId) : cfg.level, paused: state.paused, pausedBy: state.pausedBy,
|
|
1081
|
-
autonomousUntil: cfg.autonomousUntil || null,
|
|
1082
1115
|
};
|
|
1083
1116
|
}
|
|
1084
1117
|
/** /stop and stopAll: the turn, the session's runs, open cards and the queue. */
|
|
@@ -1168,6 +1201,16 @@ export function createWhatsAppBridge({
|
|
|
1168
1201
|
// `backlog`): never acted on, only counted for one note. Checked first, so a
|
|
1169
1202
|
// backlog neither uses up the rate limits nor counts as activity.
|
|
1170
1203
|
if (now() - tsMs(message.ts, now()) > L.staleMs) { noteStale(); return; }
|
|
1204
|
+
// Sent before the owner's last /stop, delivered after it (a typed /stop does not wait for a voice note
|
|
1205
|
+
// being transcribed): /stop already stopped it. A command still runs. What WhatsApp held while SynaBun was
|
|
1206
|
+
// offline arrives in order and is never overtaken (a backlog is never sent ahead), so it is not affected.
|
|
1207
|
+
if (state.stoppedAt && message.backlog !== true && tsMs(message.ts, now()) < state.stoppedAt && !parseCommand(message)) {
|
|
1208
|
+
if (!state.stopLateNoted) {
|
|
1209
|
+
state.stopLateNoted = true;
|
|
1210
|
+
await say('A message you sent before /stop arrived after it, so I did not act on it. Send it again if you still need it.', { maxChunks: 1 });
|
|
1211
|
+
}
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1171
1214
|
if (rateLimited()) return;
|
|
1172
1215
|
const activityBefore = state.lastActivityAt;
|
|
1173
1216
|
state.lastActivityAt = now();
|
|
@@ -1178,10 +1221,12 @@ export function createWhatsAppBridge({
|
|
|
1178
1221
|
const images = Array.isArray(message.images) ? message.images : [];
|
|
1179
1222
|
if (message.unsupported && !text.trim() && !images.length) {
|
|
1180
1223
|
const type = String(message.unsupported.type || 'this kind of');
|
|
1181
|
-
const
|
|
1224
|
+
const audio = type === 'audio' && Object.hasOwn(AUDIO_NOT_TRANSCRIBED, String(message.unsupported.reason)) ? AUDIO_NOT_TRANSCRIBED[message.unsupported.reason] : null;
|
|
1225
|
+
const key = audio ? `audio:${message.unsupported.reason}` : type;
|
|
1226
|
+
const last = state.unsupportedAt.get(key) || 0;
|
|
1182
1227
|
if (now() - last >= L.unsupportedEveryMs) {
|
|
1183
|
-
state.unsupportedAt.set(
|
|
1184
|
-
await say(`I can read text and pictures for now; ${type} messages are not supported yet.`, { maxChunks: 1 });
|
|
1228
|
+
state.unsupportedAt.set(key, now());
|
|
1229
|
+
await say(audio || `I can read text and pictures for now; ${type} messages are not supported yet.`, { maxChunks: 1 });
|
|
1185
1230
|
}
|
|
1186
1231
|
return;
|
|
1187
1232
|
}
|
|
@@ -1198,8 +1243,8 @@ export function createWhatsAppBridge({
|
|
|
1198
1243
|
}
|
|
1199
1244
|
// Something is waiting for the owner: a clear answer answers it; anything else closes it
|
|
1200
1245
|
// without a grant and carries on as the next prompt. Only words the owner typed can answer:
|
|
1201
|
-
// never a forwarded message, a picture or "//text".
|
|
1202
|
-
const answerable = !images.length && command?.kind !== 'literal' && message.forwarded !== true;
|
|
1246
|
+
// never a forwarded message, a picture, a voice note (its transcript) or "//text".
|
|
1247
|
+
const answerable = !images.length && command?.kind !== 'literal' && message.forwarded !== true && message.voice !== true;
|
|
1203
1248
|
if (await replyToPending(message, { answerable })) return;
|
|
1204
1249
|
if (rotationDue(activityBefore)) state.rotateOnNextSubmit = true;
|
|
1205
1250
|
// A request raised between that look and here is older than this message too (onCard only sees
|
|
@@ -1271,7 +1316,7 @@ export function createWhatsAppBridge({
|
|
|
1271
1316
|
if (!list.length) return;
|
|
1272
1317
|
let composed = null;
|
|
1273
1318
|
try { composed = inbound?.composePrompt ? inbound.composePrompt(list, { rng }) : null; } catch (error) { log('whatsapp:bridge', `compose: ${error?.message || error}`); }
|
|
1274
|
-
if (!composed) composed = { text: list.map((part) => String(part.text || '')).filter(Boolean).join('\n\n'), images: list.flatMap((part) => part.images || []), sourceIds: list.map((part) => part.id).filter(Boolean), untrusted: list.some((part) => part.forwarded || part.quoted) };
|
|
1319
|
+
if (!composed) composed = { text: list.map((part) => String(part.text || '')).filter(Boolean).join('\n\n'), images: list.flatMap((part) => part.images || []), sourceIds: list.map((part) => part.id).filter(Boolean), untrusted: list.some((part) => part.forwarded || part.quoted || part.audioFile === true) };
|
|
1275
1320
|
const images = Array.isArray(composed.images) ? composed.images.filter(Boolean) : [];
|
|
1276
1321
|
const job = {
|
|
1277
1322
|
text: String(composed.text || ''), images,
|
|
@@ -1402,22 +1447,21 @@ export function createWhatsAppBridge({
|
|
|
1402
1447
|
}
|
|
1403
1448
|
persistBridgeState();
|
|
1404
1449
|
react(job.sourceIds, 'failed');
|
|
1405
|
-
if (code === 'SESSION_BUDGET_EXCEEDED' || code === '
|
|
1450
|
+
if (code === 'SESSION_BUDGET_EXCEEDED' || code === 'BRAIN_SHARE_EXHAUSTED') { await say(budgetText(code, sessionId, result.message), { maxChunks: 1 }); return; }
|
|
1406
1451
|
if (code === 'EMPTY_PROMPT') return;
|
|
1407
1452
|
await say(`Could not start that: ${String(result.message || code).slice(0, 400)}`, { maxChunks: 1 });
|
|
1408
1453
|
}
|
|
1409
|
-
/** Spend against the
|
|
1454
|
+
/** Spend against the budget, and the two ways on: /new or a higher budget. Never a silent new session. */
|
|
1410
1455
|
function budgetText(code, sessionId, fallback) {
|
|
1411
1456
|
const view = sessionView(sessionId);
|
|
1457
|
+
const budget = view?.budget || null;
|
|
1412
1458
|
let line = '';
|
|
1413
|
-
if (code === 'SESSION_BUDGET_EXCEEDED' &&
|
|
1414
|
-
line = `This conversation spent ${usd(
|
|
1415
|
-
} else if (code === '
|
|
1416
|
-
|
|
1417
|
-
try { cap = runtime()?.brainCapUsd?.() ?? null; } catch { cap = null; }
|
|
1418
|
-
line = cap !== null ? `The Assistant spent ${usd(view?.costUsd)} of its ${usd(cap)} cap in this conversation.` : '';
|
|
1459
|
+
if (code === 'SESSION_BUDGET_EXCEEDED' && budget && Number.isFinite(Number(budget.amountUsd))) {
|
|
1460
|
+
line = `This conversation spent ${usd(budget.totalUsd)} of its ${usd(budget.amountUsd)} budget.`;
|
|
1461
|
+
} else if (code === 'BRAIN_SHARE_EXHAUSTED' && budget && Number.isFinite(Number(budget.amountUsd))) {
|
|
1462
|
+
line = `The Assistant's share of this conversation's ${usd(budget.amountUsd)} budget is used up.`;
|
|
1419
1463
|
}
|
|
1420
|
-
return `${line || String(fallback || 'This conversation reached its budget
|
|
1464
|
+
return `${line || String(fallback || 'This conversation reached its budget.')} Send /new to start a fresh conversation, or raise the budget (or choose No limit) in SynaBun.`;
|
|
1421
1465
|
}
|
|
1422
1466
|
|
|
1423
1467
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -1484,14 +1528,14 @@ export function createWhatsAppBridge({
|
|
|
1484
1528
|
persistBridgeState();
|
|
1485
1529
|
}
|
|
1486
1530
|
|
|
1487
|
-
// Keep the level in line with the config (
|
|
1531
|
+
// Keep the level in line with the config (a change that reached it without a refresh).
|
|
1488
1532
|
state.policyTimer = T.setInterval(() => { try { runtime(); syncPolicies(); } catch {} }, L.policyTickMs);
|
|
1489
1533
|
state.policyTimer?.unref?.();
|
|
1490
1534
|
runtime();
|
|
1491
1535
|
|
|
1492
1536
|
return {
|
|
1493
1537
|
onInbound, onConnection, status, newSession, pause, resume, stopAll, shutdown,
|
|
1494
|
-
// Settings changed (level, strict approvals,
|
|
1538
|
+
// Settings changed (level, strict approvals, computer use): re-apply now instead of at the next tick.
|
|
1495
1539
|
refresh: () => { state.policySeen.clear(); runtime(); syncPolicies(); return status(); },
|
|
1496
1540
|
endComputerUse,
|
|
1497
1541
|
_internals: { state, finalText, syncPolicies, drain, onPacket, brainTarget, moveOn },
|