@0xmaxma/claude-gateway 1.8.14 โ 2.0.0
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/README.md +28 -1613
- package/config.template.json +7 -1
- package/dist/agent/runner.d.ts +377 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +680 -24
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/skill-learning/index.d.ts +6 -4
- package/dist/agent/skill-learning/index.d.ts.map +1 -1
- package/dist/agent/skill-learning/index.js +3 -3
- package/dist/agent/skill-learning/index.js.map +1 -1
- package/dist/agent/skill-learning/notifier.d.ts +3 -4
- package/dist/agent/skill-learning/notifier.d.ts.map +1 -1
- package/dist/agent/skill-learning/notifier.js +15 -13
- package/dist/agent/skill-learning/notifier.js.map +1 -1
- package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
- package/dist/agent/skill-learning/reviewer.js +3 -1
- package/dist/agent/skill-learning/reviewer.js.map +1 -1
- package/dist/api/connectors-router.d.ts.map +1 -1
- package/dist/api/connectors-router.js +110 -4
- package/dist/api/connectors-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts +1 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +13 -1
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-webhook-router.d.ts +2 -2
- package/dist/api/line-webhook-router.d.ts.map +1 -1
- package/dist/api/line-webhook-router.js +264 -230
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +135 -10
- package/dist/api/router.js.map +1 -1
- package/dist/api/share-router.d.ts.map +1 -1
- package/dist/api/share-router.js +29 -10
- package/dist/api/share-router.js.map +1 -1
- package/dist/api/slack-client.d.ts +2 -0
- package/dist/api/slack-client.d.ts.map +1 -1
- package/dist/api/slack-client.js +6 -2
- package/dist/api/slack-client.js.map +1 -1
- package/dist/api/slack-webhook-router.d.ts +3 -0
- package/dist/api/slack-webhook-router.d.ts.map +1 -1
- package/dist/api/slack-webhook-router.js +226 -131
- package/dist/api/slack-webhook-router.js.map +1 -1
- package/dist/api/voice-router.d.ts +21 -0
- package/dist/api/voice-router.d.ts.map +1 -0
- package/dist/api/voice-router.js +372 -0
- package/dist/api/voice-router.js.map +1 -0
- package/dist/api/voice-settings-router.d.ts +5 -0
- package/dist/api/voice-settings-router.d.ts.map +1 -0
- package/dist/api/voice-settings-router.js +318 -0
- package/dist/api/voice-settings-router.js.map +1 -0
- package/dist/apps/agent-container-migration.d.ts +11 -0
- package/dist/apps/agent-container-migration.d.ts.map +1 -0
- package/dist/apps/agent-container-migration.js +85 -0
- package/dist/apps/agent-container-migration.js.map +1 -0
- package/dist/apps/installer.d.ts +1 -1
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +2 -1
- package/dist/apps/installer.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +5 -0
- package/dist/cli/commands/tasks.d.ts.map +1 -0
- package/dist/cli/commands/tasks.js +92 -0
- package/dist/cli/commands/tasks.js.map +1 -0
- package/dist/cli/http-client.d.ts +2 -0
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +8 -3
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/config-write-lock.d.ts +2 -0
- package/dist/config/config-write-lock.d.ts.map +1 -1
- package/dist/config/config-write-lock.js +5 -0
- package/dist/config/config-write-lock.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +87 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/config/migrator.d.ts.map +1 -1
- package/dist/config/migrator.js +12 -1
- package/dist/config/migrator.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +6 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/connectors/resolve.d.ts.map +1 -1
- package/dist/connectors/resolve.js +3 -1
- package/dist/connectors/resolve.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/cron/manager.d.ts.map +1 -1
- package/dist/cron/manager.js +21 -2
- package/dist/cron/manager.js.map +1 -1
- package/dist/discord/receiver.d.ts +2 -1
- package/dist/discord/receiver.d.ts.map +1 -1
- package/dist/discord/receiver.js +5 -1
- package/dist/discord/receiver.js.map +1 -1
- package/dist/history/db.d.ts +3 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +21 -1
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +2 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/index.js +48 -9
- package/dist/index.js.map +1 -1
- package/dist/orchestration/bounded-queue.d.ts +15 -0
- package/dist/orchestration/bounded-queue.d.ts.map +1 -0
- package/dist/orchestration/bounded-queue.js +58 -0
- package/dist/orchestration/bounded-queue.js.map +1 -0
- package/dist/orchestration/bridge.d.ts +55 -0
- package/dist/orchestration/bridge.d.ts.map +1 -0
- package/dist/orchestration/bridge.js +234 -0
- package/dist/orchestration/bridge.js.map +1 -0
- package/dist/orchestration/browser-routing.d.ts +7 -0
- package/dist/orchestration/browser-routing.d.ts.map +1 -0
- package/dist/orchestration/browser-routing.js +22 -0
- package/dist/orchestration/browser-routing.js.map +1 -0
- package/dist/orchestration/capabilities.d.ts +49 -0
- package/dist/orchestration/capabilities.d.ts.map +1 -0
- package/dist/orchestration/capabilities.js +417 -0
- package/dist/orchestration/capabilities.js.map +1 -0
- package/dist/orchestration/capacity.d.ts +14 -0
- package/dist/orchestration/capacity.d.ts.map +1 -0
- package/dist/orchestration/capacity.js +52 -0
- package/dist/orchestration/capacity.js.map +1 -0
- package/dist/orchestration/channel-activity.d.ts +21 -0
- package/dist/orchestration/channel-activity.d.ts.map +1 -0
- package/dist/orchestration/channel-activity.js +72 -0
- package/dist/orchestration/channel-activity.js.map +1 -0
- package/dist/orchestration/channel-controls.d.ts +32 -0
- package/dist/orchestration/channel-controls.d.ts.map +1 -0
- package/dist/orchestration/channel-controls.js +105 -0
- package/dist/orchestration/channel-controls.js.map +1 -0
- package/dist/orchestration/channel-input-media.d.ts +11 -0
- package/dist/orchestration/channel-input-media.d.ts.map +1 -0
- package/dist/orchestration/channel-input-media.js +54 -0
- package/dist/orchestration/channel-input-media.js.map +1 -0
- package/dist/orchestration/channel-media.d.ts +4 -0
- package/dist/orchestration/channel-media.d.ts.map +1 -0
- package/dist/orchestration/channel-media.js +70 -0
- package/dist/orchestration/channel-media.js.map +1 -0
- package/dist/orchestration/channel-speech.d.ts +7 -0
- package/dist/orchestration/channel-speech.d.ts.map +1 -0
- package/dist/orchestration/channel-speech.js +65 -0
- package/dist/orchestration/channel-speech.js.map +1 -0
- package/dist/orchestration/cli-skills.d.ts +12 -0
- package/dist/orchestration/cli-skills.d.ts.map +1 -0
- package/dist/orchestration/cli-skills.js +132 -0
- package/dist/orchestration/cli-skills.js.map +1 -0
- package/dist/orchestration/config.d.ts +256 -0
- package/dist/orchestration/config.d.ts.map +1 -0
- package/dist/orchestration/config.js +110 -0
- package/dist/orchestration/config.js.map +1 -0
- package/dist/orchestration/container.d.ts +21 -0
- package/dist/orchestration/container.d.ts.map +1 -0
- package/dist/orchestration/container.js +195 -0
- package/dist/orchestration/container.js.map +1 -0
- package/dist/orchestration/control-delivery.d.ts +5 -0
- package/dist/orchestration/control-delivery.d.ts.map +1 -0
- package/dist/orchestration/control-delivery.js +50 -0
- package/dist/orchestration/control-delivery.js.map +1 -0
- package/dist/orchestration/conversation-intake.d.ts +32 -0
- package/dist/orchestration/conversation-intake.d.ts.map +1 -0
- package/dist/orchestration/conversation-intake.js +92 -0
- package/dist/orchestration/conversation-intake.js.map +1 -0
- package/dist/orchestration/decisions.d.ts +27 -0
- package/dist/orchestration/decisions.d.ts.map +1 -0
- package/dist/orchestration/decisions.js +148 -0
- package/dist/orchestration/decisions.js.map +1 -0
- package/dist/orchestration/delivery.d.ts +30 -0
- package/dist/orchestration/delivery.d.ts.map +1 -0
- package/dist/orchestration/delivery.js +219 -0
- package/dist/orchestration/delivery.js.map +1 -0
- package/dist/orchestration/display-stream.d.ts +4 -0
- package/dist/orchestration/display-stream.d.ts.map +1 -0
- package/dist/orchestration/display-stream.js +77 -0
- package/dist/orchestration/display-stream.js.map +1 -0
- package/dist/orchestration/events.d.ts +30 -0
- package/dist/orchestration/events.d.ts.map +1 -0
- package/dist/orchestration/events.js +65 -0
- package/dist/orchestration/events.js.map +1 -0
- package/dist/orchestration/execution-observation.d.ts +38 -0
- package/dist/orchestration/execution-observation.d.ts.map +1 -0
- package/dist/orchestration/execution-observation.js +25 -0
- package/dist/orchestration/execution-observation.js.map +1 -0
- package/dist/orchestration/file-delivery.d.ts +16 -0
- package/dist/orchestration/file-delivery.d.ts.map +1 -0
- package/dist/orchestration/file-delivery.js +139 -0
- package/dist/orchestration/file-delivery.js.map +1 -0
- package/dist/orchestration/gateway-config.d.ts +17 -0
- package/dist/orchestration/gateway-config.d.ts.map +1 -0
- package/dist/orchestration/gateway-config.js +128 -0
- package/dist/orchestration/gateway-config.js.map +1 -0
- package/dist/orchestration/history.d.ts +13 -0
- package/dist/orchestration/history.d.ts.map +1 -0
- package/dist/orchestration/history.js +48 -0
- package/dist/orchestration/history.js.map +1 -0
- package/dist/orchestration/identity.d.ts +6 -0
- package/dist/orchestration/identity.d.ts.map +1 -0
- package/dist/orchestration/identity.js +13 -0
- package/dist/orchestration/identity.js.map +1 -0
- package/dist/orchestration/inference-errors.d.ts +3 -0
- package/dist/orchestration/inference-errors.d.ts.map +1 -0
- package/dist/orchestration/inference-errors.js +16 -0
- package/dist/orchestration/inference-errors.js.map +1 -0
- package/dist/orchestration/input-images.d.ts +12 -0
- package/dist/orchestration/input-images.d.ts.map +1 -0
- package/dist/orchestration/input-images.js +66 -0
- package/dist/orchestration/input-images.js.map +1 -0
- package/dist/orchestration/instance-lock.d.ts +5 -0
- package/dist/orchestration/instance-lock.d.ts.map +1 -0
- package/dist/orchestration/instance-lock.js +33 -0
- package/dist/orchestration/instance-lock.js.map +1 -0
- package/dist/orchestration/line-loading.d.ts +21 -0
- package/dist/orchestration/line-loading.d.ts.map +1 -0
- package/dist/orchestration/line-loading.js +89 -0
- package/dist/orchestration/line-loading.js.map +1 -0
- package/dist/orchestration/media.d.ts +3 -0
- package/dist/orchestration/media.d.ts.map +1 -0
- package/dist/orchestration/media.js +46 -0
- package/dist/orchestration/media.js.map +1 -0
- package/dist/orchestration/migrations/schema.d.ts +3 -0
- package/dist/orchestration/migrations/schema.d.ts.map +1 -0
- package/dist/orchestration/migrations/schema.js +80 -0
- package/dist/orchestration/migrations/schema.js.map +1 -0
- package/dist/orchestration/notification-mailbox.d.ts +5 -0
- package/dist/orchestration/notification-mailbox.d.ts.map +1 -0
- package/dist/orchestration/notification-mailbox.js +29 -0
- package/dist/orchestration/notification-mailbox.js.map +1 -0
- package/dist/orchestration/process-activity.d.ts +23 -0
- package/dist/orchestration/process-activity.d.ts.map +1 -0
- package/dist/orchestration/process-activity.js +95 -0
- package/dist/orchestration/process-activity.js.map +1 -0
- package/dist/orchestration/process-supervisor.d.ts +15 -0
- package/dist/orchestration/process-supervisor.d.ts.map +1 -0
- package/dist/orchestration/process-supervisor.js +92 -0
- package/dist/orchestration/process-supervisor.js.map +1 -0
- package/dist/orchestration/process-turn.d.ts +35 -0
- package/dist/orchestration/process-turn.d.ts.map +1 -0
- package/dist/orchestration/process-turn.js +246 -0
- package/dist/orchestration/process-turn.js.map +1 -0
- package/dist/orchestration/progress-review.d.ts +28 -0
- package/dist/orchestration/progress-review.d.ts.map +1 -0
- package/dist/orchestration/progress-review.js +48 -0
- package/dist/orchestration/progress-review.js.map +1 -0
- package/dist/orchestration/recovery.d.ts +8 -0
- package/dist/orchestration/recovery.d.ts.map +1 -0
- package/dist/orchestration/recovery.js +35 -0
- package/dist/orchestration/recovery.js.map +1 -0
- package/dist/orchestration/reply-context.d.ts +7 -0
- package/dist/orchestration/reply-context.d.ts.map +1 -0
- package/dist/orchestration/reply-context.js +54 -0
- package/dist/orchestration/reply-context.js.map +1 -0
- package/dist/orchestration/runtime.d.ts +232 -0
- package/dist/orchestration/runtime.d.ts.map +1 -0
- package/dist/orchestration/runtime.js +962 -0
- package/dist/orchestration/runtime.js.map +1 -0
- package/dist/orchestration/skills.d.ts +17 -0
- package/dist/orchestration/skills.d.ts.map +1 -0
- package/dist/orchestration/skills.js +32 -0
- package/dist/orchestration/skills.js.map +1 -0
- package/dist/orchestration/source-policy.d.ts +6 -0
- package/dist/orchestration/source-policy.d.ts.map +1 -0
- package/dist/orchestration/source-policy.js +16 -0
- package/dist/orchestration/source-policy.js.map +1 -0
- package/dist/orchestration/speech.d.ts +24 -0
- package/dist/orchestration/speech.d.ts.map +1 -0
- package/dist/orchestration/speech.js +61 -0
- package/dist/orchestration/speech.js.map +1 -0
- package/dist/orchestration/stop-controls.d.ts +30 -0
- package/dist/orchestration/stop-controls.d.ts.map +1 -0
- package/dist/orchestration/stop-controls.js +79 -0
- package/dist/orchestration/stop-controls.js.map +1 -0
- package/dist/orchestration/store.d.ts +86 -0
- package/dist/orchestration/store.d.ts.map +1 -0
- package/dist/orchestration/store.js +275 -0
- package/dist/orchestration/store.js.map +1 -0
- package/dist/orchestration/task-controls.d.ts +130 -0
- package/dist/orchestration/task-controls.d.ts.map +1 -0
- package/dist/orchestration/task-controls.js +55 -0
- package/dist/orchestration/task-controls.js.map +1 -0
- package/dist/orchestration/task-files.d.ts +29 -0
- package/dist/orchestration/task-files.d.ts.map +1 -0
- package/dist/orchestration/task-files.js +200 -0
- package/dist/orchestration/task-files.js.map +1 -0
- package/dist/orchestration/task-report.d.ts +7 -0
- package/dist/orchestration/task-report.d.ts.map +1 -0
- package/dist/orchestration/task-report.js +33 -0
- package/dist/orchestration/task-report.js.map +1 -0
- package/dist/orchestration/tasks/checkpoint-hook.d.ts +19 -0
- package/dist/orchestration/tasks/checkpoint-hook.d.ts.map +1 -0
- package/dist/orchestration/tasks/checkpoint-hook.js +49 -0
- package/dist/orchestration/tasks/checkpoint-hook.js.map +1 -0
- package/dist/orchestration/tasks/cleanup.d.ts +18 -0
- package/dist/orchestration/tasks/cleanup.d.ts.map +1 -0
- package/dist/orchestration/tasks/cleanup.js +113 -0
- package/dist/orchestration/tasks/cleanup.js.map +1 -0
- package/dist/orchestration/tasks/driver.d.ts +23 -0
- package/dist/orchestration/tasks/driver.d.ts.map +1 -0
- package/dist/orchestration/tasks/driver.js +209 -0
- package/dist/orchestration/tasks/driver.js.map +1 -0
- package/dist/orchestration/tasks/failure.d.ts +5 -0
- package/dist/orchestration/tasks/failure.d.ts.map +1 -0
- package/dist/orchestration/tasks/failure.js +17 -0
- package/dist/orchestration/tasks/failure.js.map +1 -0
- package/dist/orchestration/tasks/pool.d.ts +18 -0
- package/dist/orchestration/tasks/pool.d.ts.map +1 -0
- package/dist/orchestration/tasks/pool.js +73 -0
- package/dist/orchestration/tasks/pool.js.map +1 -0
- package/dist/orchestration/tasks/scheduler.d.ts +38 -0
- package/dist/orchestration/tasks/scheduler.d.ts.map +1 -0
- package/dist/orchestration/tasks/scheduler.js +160 -0
- package/dist/orchestration/tasks/scheduler.js.map +1 -0
- package/dist/orchestration/tasks/service.d.ts +109 -0
- package/dist/orchestration/tasks/service.d.ts.map +1 -0
- package/dist/orchestration/tasks/service.js +697 -0
- package/dist/orchestration/tasks/service.js.map +1 -0
- package/dist/orchestration/tasks/shared-workspace.d.ts +25 -0
- package/dist/orchestration/tasks/shared-workspace.d.ts.map +1 -0
- package/dist/orchestration/tasks/shared-workspace.js +162 -0
- package/dist/orchestration/tasks/shared-workspace.js.map +1 -0
- package/dist/orchestration/tasks/task-directive.d.ts +6 -0
- package/dist/orchestration/tasks/task-directive.d.ts.map +1 -0
- package/dist/orchestration/tasks/task-directive.js +41 -0
- package/dist/orchestration/tasks/task-directive.js.map +1 -0
- package/dist/orchestration/tasks/timing.d.ts +15 -0
- package/dist/orchestration/tasks/timing.d.ts.map +1 -0
- package/dist/orchestration/tasks/timing.js +48 -0
- package/dist/orchestration/tasks/timing.js.map +1 -0
- package/dist/orchestration/tasks/tool-repetition.d.ts +3 -0
- package/dist/orchestration/tasks/tool-repetition.d.ts.map +1 -0
- package/dist/orchestration/tasks/tool-repetition.js +30 -0
- package/dist/orchestration/tasks/tool-repetition.js.map +1 -0
- package/dist/orchestration/tasks/workspace.d.ts +24 -0
- package/dist/orchestration/tasks/workspace.d.ts.map +1 -0
- package/dist/orchestration/tasks/workspace.js +147 -0
- package/dist/orchestration/tasks/workspace.js.map +1 -0
- package/dist/orchestration/telegram-speech.d.ts +17 -0
- package/dist/orchestration/telegram-speech.d.ts.map +1 -0
- package/dist/orchestration/telegram-speech.js +55 -0
- package/dist/orchestration/telegram-speech.js.map +1 -0
- package/dist/orchestration/telegram-tool-status.d.ts +23 -0
- package/dist/orchestration/telegram-tool-status.d.ts.map +1 -0
- package/dist/orchestration/telegram-tool-status.js +207 -0
- package/dist/orchestration/telegram-tool-status.js.map +1 -0
- package/dist/orchestration/telegram-voices.d.ts +42 -0
- package/dist/orchestration/telegram-voices.d.ts.map +1 -0
- package/dist/orchestration/telegram-voices.js +70 -0
- package/dist/orchestration/telegram-voices.js.map +1 -0
- package/dist/orchestration/tool-activity.d.ts +10 -0
- package/dist/orchestration/tool-activity.d.ts.map +1 -0
- package/dist/orchestration/tool-activity.js +38 -0
- package/dist/orchestration/tool-activity.js.map +1 -0
- package/dist/orchestration/types.d.ts +177 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +13 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/orchestration/voice-reply-policy.d.ts +8 -0
- package/dist/orchestration/voice-reply-policy.d.ts.map +1 -0
- package/dist/orchestration/voice-reply-policy.js +46 -0
- package/dist/orchestration/voice-reply-policy.js.map +1 -0
- package/dist/orchestration/worker-shares.d.ts +8 -0
- package/dist/orchestration/worker-shares.d.ts.map +1 -0
- package/dist/orchestration/worker-shares.js +59 -0
- package/dist/orchestration/worker-shares.js.map +1 -0
- package/dist/orchestration/workflow.d.ts +121 -0
- package/dist/orchestration/workflow.d.ts.map +1 -0
- package/dist/orchestration/workflow.js +69 -0
- package/dist/orchestration/workflow.js.map +1 -0
- package/dist/session/input-image.d.ts +10 -0
- package/dist/session/input-image.d.ts.map +1 -0
- package/dist/session/input-image.js +3 -0
- package/dist/session/input-image.js.map +1 -0
- package/dist/session/process.d.ts +15 -3
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +143 -35
- package/dist/session/process.js.map +1 -1
- package/dist/session/runtime-profile.d.ts +36 -0
- package/dist/session/runtime-profile.d.ts.map +1 -0
- package/dist/session/runtime-profile.js +63 -0
- package/dist/session/runtime-profile.js.map +1 -0
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +23 -1
- package/dist/session/store.js.map +1 -1
- package/dist/share/share-store.d.ts +3 -1
- package/dist/share/share-store.d.ts.map +1 -1
- package/dist/share/share-store.js +13 -2
- package/dist/share/share-store.js.map +1 -1
- package/dist/shared/line-request-order.d.ts +2 -0
- package/dist/shared/line-request-order.d.ts.map +1 -0
- package/dist/shared/line-request-order.js +16 -0
- package/dist/shared/line-request-order.js.map +1 -0
- package/dist/shared/task-elapsed.d.ts +3 -0
- package/dist/shared/task-elapsed.d.ts.map +1 -0
- package/dist/shared/task-elapsed.js +12 -0
- package/dist/shared/task-elapsed.js.map +1 -0
- package/dist/shared/task-updated-time.d.ts +3 -0
- package/dist/shared/task-updated-time.d.ts.map +1 -0
- package/dist/shared/task-updated-time.js +15 -0
- package/dist/shared/task-updated-time.js.map +1 -0
- package/dist/skills/loader.d.ts +2 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/telegram/chunks.d.ts +19 -0
- package/dist/telegram/chunks.d.ts.map +1 -0
- package/dist/telegram/chunks.js +129 -0
- package/dist/telegram/chunks.js.map +1 -0
- package/dist/telegram/markdown.d.ts.map +1 -1
- package/dist/telegram/markdown.js +18 -3
- package/dist/telegram/markdown.js.map +1 -1
- package/dist/telegram/receiver.d.ts +2 -1
- package/dist/telegram/receiver.d.ts.map +1 -1
- package/dist/telegram/receiver.js +6 -1
- package/dist/telegram/receiver.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/web-ui.d.ts.map +1 -1
- package/dist/ui/web-ui.js +31 -5
- package/dist/ui/web-ui.js.map +1 -1
- package/dist/voice/diagnostics.d.ts +29 -0
- package/dist/voice/diagnostics.d.ts.map +1 -0
- package/dist/voice/diagnostics.js +37 -0
- package/dist/voice/diagnostics.js.map +1 -0
- package/dist/voice/errors.d.ts +19 -0
- package/dist/voice/errors.d.ts.map +1 -0
- package/dist/voice/errors.js +133 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/line-audio.d.ts +4 -0
- package/dist/voice/line-audio.d.ts.map +1 -0
- package/dist/voice/line-audio.js +27 -0
- package/dist/voice/line-audio.js.map +1 -0
- package/dist/voice/managed-quota.d.ts +5 -0
- package/dist/voice/managed-quota.d.ts.map +1 -0
- package/dist/voice/managed-quota.js +35 -0
- package/dist/voice/managed-quota.js.map +1 -0
- package/dist/voice/mp3.d.ts +3 -0
- package/dist/voice/mp3.d.ts.map +1 -0
- package/dist/voice/mp3.js +30 -0
- package/dist/voice/mp3.js.map +1 -0
- package/dist/voice/notes.d.ts +9 -0
- package/dist/voice/notes.d.ts.map +1 -0
- package/dist/voice/notes.js +68 -0
- package/dist/voice/notes.js.map +1 -0
- package/dist/voice/playback.d.ts +22 -0
- package/dist/voice/playback.d.ts.map +1 -0
- package/dist/voice/playback.js +52 -0
- package/dist/voice/playback.js.map +1 -0
- package/dist/voice/preview.d.ts +6 -0
- package/dist/voice/preview.d.ts.map +1 -0
- package/dist/voice/preview.js +44 -0
- package/dist/voice/preview.js.map +1 -0
- package/dist/voice/protocol.d.ts +10 -0
- package/dist/voice/protocol.d.ts.map +1 -0
- package/dist/voice/protocol.js +30 -0
- package/dist/voice/protocol.js.map +1 -0
- package/dist/voice/providers/cartesia-tts.d.ts +26 -0
- package/dist/voice/providers/cartesia-tts.d.ts.map +1 -0
- package/dist/voice/providers/cartesia-tts.js +95 -0
- package/dist/voice/providers/cartesia-tts.js.map +1 -0
- package/dist/voice/providers/deepgram-stt.d.ts +20 -0
- package/dist/voice/providers/deepgram-stt.d.ts.map +1 -0
- package/dist/voice/providers/deepgram-stt.js +66 -0
- package/dist/voice/providers/deepgram-stt.js.map +1 -0
- package/dist/voice/providers/elevenlabs-stt.d.ts +20 -0
- package/dist/voice/providers/elevenlabs-stt.d.ts.map +1 -0
- package/dist/voice/providers/elevenlabs-stt.js +92 -0
- package/dist/voice/providers/elevenlabs-stt.js.map +1 -0
- package/dist/voice/providers/elevenlabs-tts.d.ts +26 -0
- package/dist/voice/providers/elevenlabs-tts.d.ts.map +1 -0
- package/dist/voice/providers/elevenlabs-tts.js +82 -0
- package/dist/voice/providers/elevenlabs-tts.js.map +1 -0
- package/dist/voice/providers/encoded-audio-tts.d.ts +21 -0
- package/dist/voice/providers/encoded-audio-tts.d.ts.map +1 -0
- package/dist/voice/providers/encoded-audio-tts.js +175 -0
- package/dist/voice/providers/encoded-audio-tts.js.map +1 -0
- package/dist/voice/providers/fake.d.ts +39 -0
- package/dist/voice/providers/fake.d.ts.map +1 -0
- package/dist/voice/providers/fake.js +41 -0
- package/dist/voice/providers/fake.js.map +1 -0
- package/dist/voice/providers/gemini-live-stt.d.ts +21 -0
- package/dist/voice/providers/gemini-live-stt.d.ts.map +1 -0
- package/dist/voice/providers/gemini-live-stt.js +158 -0
- package/dist/voice/providers/gemini-live-stt.js.map +1 -0
- package/dist/voice/providers/gemini.d.ts +60 -0
- package/dist/voice/providers/gemini.d.ts.map +1 -0
- package/dist/voice/providers/gemini.js +124 -0
- package/dist/voice/providers/gemini.js.map +1 -0
- package/dist/voice/providers/model-ref.d.ts +9 -0
- package/dist/voice/providers/model-ref.d.ts.map +1 -0
- package/dist/voice/providers/model-ref.js +31 -0
- package/dist/voice/providers/model-ref.js.map +1 -0
- package/dist/voice/providers/openrouter.d.ts +39 -0
- package/dist/voice/providers/openrouter.d.ts.map +1 -0
- package/dist/voice/providers/openrouter.js +154 -0
- package/dist/voice/providers/openrouter.js.map +1 -0
- package/dist/voice/providers/paxalabs-stt.d.ts +12 -0
- package/dist/voice/providers/paxalabs-stt.d.ts.map +1 -0
- package/dist/voice/providers/paxalabs-stt.js +41 -0
- package/dist/voice/providers/paxalabs-stt.js.map +1 -0
- package/dist/voice/providers/paxalabs-tts.d.ts +23 -0
- package/dist/voice/providers/paxalabs-tts.d.ts.map +1 -0
- package/dist/voice/providers/paxalabs-tts.js +80 -0
- package/dist/voice/providers/paxalabs-tts.js.map +1 -0
- package/dist/voice/providers/recorded-stt.d.ts +34 -0
- package/dist/voice/providers/recorded-stt.d.ts.map +1 -0
- package/dist/voice/providers/recorded-stt.js +59 -0
- package/dist/voice/providers/recorded-stt.js.map +1 -0
- package/dist/voice/providers/registry.d.ts +10 -0
- package/dist/voice/providers/registry.d.ts.map +1 -0
- package/dist/voice/providers/registry.js +77 -0
- package/dist/voice/providers/registry.js.map +1 -0
- package/dist/voice/providers/socket.d.ts +10 -0
- package/dist/voice/providers/socket.d.ts.map +1 -0
- package/dist/voice/providers/socket.js +91 -0
- package/dist/voice/providers/socket.js.map +1 -0
- package/dist/voice/providers/upstream.d.ts +9 -0
- package/dist/voice/providers/upstream.d.ts.map +1 -0
- package/dist/voice/providers/upstream.js +51 -0
- package/dist/voice/providers/upstream.js.map +1 -0
- package/dist/voice/providers/voice-catalog.d.ts +17 -0
- package/dist/voice/providers/voice-catalog.d.ts.map +1 -0
- package/dist/voice/providers/voice-catalog.js +167 -0
- package/dist/voice/providers/voice-catalog.js.map +1 -0
- package/dist/voice/queue.d.ts +2 -0
- package/dist/voice/queue.d.ts.map +1 -0
- package/dist/voice/queue.js +6 -0
- package/dist/voice/queue.js.map +1 -0
- package/dist/voice/session.d.ts +86 -0
- package/dist/voice/session.d.ts.map +1 -0
- package/dist/voice/session.js +464 -0
- package/dist/voice/session.js.map +1 -0
- package/dist/voice/turn-manager.d.ts +27 -0
- package/dist/voice/turn-manager.d.ts.map +1 -0
- package/dist/voice/turn-manager.js +75 -0
- package/dist/voice/turn-manager.js.map +1 -0
- package/dist/voice/types.d.ts +78 -0
- package/dist/voice/types.d.ts.map +1 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/wav.d.ts +2 -0
- package/dist/voice/wav.d.ts.map +1 -0
- package/dist/voice/wav.js +21 -0
- package/dist/voice/wav.js.map +1 -0
- package/dist/wechat/ilink-client.d.ts +13 -0
- package/dist/wechat/ilink-client.d.ts.map +1 -1
- package/dist/wechat/ilink-client.js +19 -0
- package/dist/wechat/ilink-client.js.map +1 -1
- package/dist/whatsapp/manager.d.ts.map +1 -1
- package/dist/whatsapp/manager.js +51 -1
- package/dist/whatsapp/manager.js.map +1 -1
- package/mcp/capability-catalog.ts +206 -0
- package/mcp/modules.ts +56 -0
- package/mcp/server.ts +18 -37
- package/mcp/tools/browser/module.ts +1 -1
- package/mcp/tools/browser/skills/open-browser/SKILL.md +5 -3
- package/mcp/tools/discord/commands.ts +17 -1
- package/mcp/tools/discord/inbound.ts +11 -0
- package/mcp/tools/discord/module.ts +42 -2
- package/mcp/tools/discord/receiver-server.ts +10 -0
- package/mcp/tools/discord/types.ts +4 -1
- package/mcp/tools/image/module.ts +3 -1
- package/mcp/tools/receiver-spool.test.ts +28 -0
- package/mcp/tools/receiver-spool.ts +56 -0
- package/mcp/tools/share-file/module.ts +9 -1
- package/mcp/tools/shared/share-client.ts +19 -0
- package/mcp/tools/tasks/module.ts +35 -0
- package/mcp/tools/telegram/media-group.ts +21 -0
- package/mcp/tools/telegram/receiver-server.ts +232 -11
- package/mcp/tools/telegram/selection-confirmation.ts +20 -0
- package/mcp/tools/telegram/task-browser.ts +117 -0
- package/mcp/tools/telegram/task-detail.ts +36 -0
- package/mcp/tools/telegram/task-elapsed.ts +1 -0
- package/mcp/tools/telegram/typing.ts +6 -116
- package/mcp/tools/video/module.ts +1 -1
- package/mcp/types.ts +5 -0
- package/package.json +2 -1
- package/resource/claude_gateway.jpeg +0 -0
- package/scripts/orchestration/bench-store.cjs +30 -0
- package/scripts/orchestration/inspect.cjs +16 -0
- package/scripts/orchestration/probe-profile.cjs +67 -0
- package/scripts/orchestration/reconcile.cjs +21 -0
- package/scripts/orchestration/smoke-elevenlabs.cjs +90 -0
- package/scripts/orchestration/smoke-media.cjs +70 -0
- package/scripts/orchestration/smoke-text.cjs +101 -0
- package/scripts/orchestration/smoke-vision.cjs +49 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mediaGroupKey, mergeMediaGroup, type ChannelInput } from './telegram/media-group';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdirSync, readdirSync, openSync, closeSync, writeFileSync, fsyncSync, renameSync, readFileSync, unlinkSync, statSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
/** Receiver-local handoff journal. Persist before returning to the platform
|
|
7
|
+
* client; only remove after the gateway confirms its SQLite admission. */
|
|
8
|
+
export class ReceiverSpool {
|
|
9
|
+
private active = false;
|
|
10
|
+
private readonly timer: ReturnType<typeof setInterval>;
|
|
11
|
+
constructor(private readonly directory: string, private readonly callback: string, private readonly request: typeof fetch = fetch, private readonly mediaGroupWaitMs = 2000) {
|
|
12
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
13
|
+
this.timer = setInterval(() => { void this.flush(); }, 1000); this.timer.unref();
|
|
14
|
+
void this.flush();
|
|
15
|
+
}
|
|
16
|
+
enqueue(input: unknown): void {
|
|
17
|
+
const group=mediaGroupKey(input as ChannelInput);
|
|
18
|
+
if(group){
|
|
19
|
+
let prior:ChannelInput|undefined;
|
|
20
|
+
try{prior=JSON.parse(readFileSync(join(this.directory,`${group}.json`),'utf8'));}catch{}
|
|
21
|
+
input=mergeMediaGroup(prior,input as ChannelInput);
|
|
22
|
+
}
|
|
23
|
+
const payload = JSON.stringify(input);
|
|
24
|
+
if (Buffer.byteLength(payload) > 131072) throw new Error('Ingress payload too large');
|
|
25
|
+
if (readdirSync(this.directory).filter(file => file.endsWith('.json')).length >= 1000) throw new Error('Ingress spool full');
|
|
26
|
+
const key = group ?? createHash('sha256').update(payload).digest('hex');
|
|
27
|
+
const temporary = join(this.directory, `${key}.${randomUUID()}.tmp`);
|
|
28
|
+
const descriptor = openSync(temporary, 'wx', 0o600);
|
|
29
|
+
try { writeFileSync(descriptor, payload); fsyncSync(descriptor); } finally { closeSync(descriptor); }
|
|
30
|
+
renameSync(temporary, join(this.directory, `${key}.json`));
|
|
31
|
+
this.syncDirectory();
|
|
32
|
+
void this.flush();
|
|
33
|
+
}
|
|
34
|
+
private syncDirectory(): void {
|
|
35
|
+
const descriptor = openSync(this.directory, 'r');
|
|
36
|
+
try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
|
|
37
|
+
}
|
|
38
|
+
async flush(): Promise<void> {
|
|
39
|
+
if (this.active) return;
|
|
40
|
+
this.active = true;
|
|
41
|
+
try {
|
|
42
|
+
const files = readdirSync(this.directory).filter(file => /^[a-f0-9]{64}\.json$/.test(file)).map(file => ({ file, created: statSync(join(this.directory, file)).mtimeMs })).sort((a, b) => a.created - b.created);
|
|
43
|
+
for (const { file } of files.slice(0, 100)) {
|
|
44
|
+
const location = join(this.directory, file);
|
|
45
|
+
const payload=readFileSync(location,'utf8');
|
|
46
|
+
const input=JSON.parse(payload) as ChannelInput;
|
|
47
|
+
if(mediaGroupKey(input)&&Date.now()-statSync(location).mtimeMs<this.mediaGroupWaitMs)continue;
|
|
48
|
+
const response = await this.request(this.callback, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, signal: AbortSignal.timeout(10000) });
|
|
49
|
+
if (!response.ok) break;
|
|
50
|
+
if(readFileSync(location,'utf8')===payload){unlinkSync(location); this.syncDirectory();}
|
|
51
|
+
}
|
|
52
|
+
} catch { /* persisted message is retried on next tick or process restart */ }
|
|
53
|
+
finally { this.active = false; }
|
|
54
|
+
}
|
|
55
|
+
close(): void { clearInterval(this.timer); }
|
|
56
|
+
}
|
|
@@ -28,7 +28,15 @@ export class ShareFileModule implements ToolModule {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
getTools(): McpToolDefinition[] {
|
|
31
|
-
return shareFileToolDefs;
|
|
31
|
+
if (process.env.GATEWAY_ORCHESTRATION_ROLE !== 'worker') return shareFileToolDefs;
|
|
32
|
+
return shareFileToolDefs.map(tool => ({
|
|
33
|
+
...tool,
|
|
34
|
+
description: tool.description + ' In a task, use the original absolute file path in the active task workspace, a supplied input attachment or this sessionโs media directory. The gateway stages it automatically; do not copy it to the agent-wide media root. Describe this as creating a link (Thai: เธชเธฃเนเธฒเธเธฅเธดเธเธเน).',
|
|
35
|
+
inputSchema: { ...shareFileInputSchema, properties: { ...shareFileInputSchema.properties,
|
|
36
|
+
path: { type: 'string', description: 'Original absolute file path in the active task workspace or session media, or artifact:<id>.' },
|
|
37
|
+
paths: { type: 'array', items: { type: 'string' }, description: 'Up to 5 authorized absolute file paths or artifact refs.' },
|
|
38
|
+
} },
|
|
39
|
+
}));
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
async handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult> {
|
|
@@ -71,6 +71,7 @@ export class ShareClientError extends Error {
|
|
|
71
71
|
* is absent the private mint endpoint is not mounted and calls fail closed.
|
|
72
72
|
*/
|
|
73
73
|
export function shareBridgeEnabled(): boolean {
|
|
74
|
+
if (process.env.GATEWAY_ORCHESTRATION_ROLE === 'worker' && process.env.GATEWAY_ORCHESTRATION_MEDIA === 'true' && process.env.GATEWAY_ORCHESTRATION_TICKET_FILE) return true;
|
|
74
75
|
return (
|
|
75
76
|
!!process.env.GATEWAY_API_URL &&
|
|
76
77
|
!!process.env.GATEWAY_API_KEY &&
|
|
@@ -88,6 +89,24 @@ async function callGateway(
|
|
|
88
89
|
pathname: string,
|
|
89
90
|
body?: Record<string, unknown>,
|
|
90
91
|
): Promise<{ status: number; json: Record<string, unknown> }> {
|
|
92
|
+
if (process.env.GATEWAY_ORCHESTRATION_ROLE === 'worker' && process.env.GATEWAY_ORCHESTRATION_TICKET_FILE) {
|
|
93
|
+
const { readFile } = await import('node:fs/promises');
|
|
94
|
+
const ticket = JSON.parse(await readFile(process.env.GATEWAY_ORCHESTRATION_TICKET_FILE, 'utf8'));
|
|
95
|
+
const response = await fetch(ticket.url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ticket.token}` },
|
|
96
|
+
body: JSON.stringify({ tool: 'task_share_call', action_id: crypto.randomUUID(), args: { method, pathname, body } }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const failure = await response.json().catch(() => ({})) as Record<string, unknown> | null;
|
|
99
|
+
// The bridge uses {error: CODE}; retain that cause instead of treating
|
|
100
|
+
// path validation, stale attempts and unavailable configuration alike.
|
|
101
|
+
const code = typeof failure?.error === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(failure.error)
|
|
102
|
+
? failure.error : 'share_scope_denied';
|
|
103
|
+
const error = code === 'ARTIFACT_PATH_DENIED'
|
|
104
|
+
? 'File is outside this task scope. Use the original fileโs absolute path in the active task workspace, a supplied input attachment, or this sessionโs media directory. Do not copy it to the agent-wide media root. The gateway stages authorized files automatically.'
|
|
105
|
+
: code === 'share_scope_denied' ? 'Scoped share request denied' : code;
|
|
106
|
+
return { status: response.status, json: { error, code } };
|
|
107
|
+
}
|
|
108
|
+
return await response.json() as { status: number; json: Record<string, unknown> };
|
|
109
|
+
}
|
|
91
110
|
const res = await fetch(`${apiBase()}${pathname}`, {
|
|
92
111
|
method,
|
|
93
112
|
headers: {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// MCP sources are published, src/ is not. Share the compiled schema so an
|
|
2
|
+
// installed package has the same inventory as a development checkout.
|
|
3
|
+
import { WORKFLOW_SCHEMA } from '../../../dist/orchestration/workflow.js';
|
|
4
|
+
import type { McpToolDefinition, McpToolResult } from '../../types';
|
|
5
|
+
|
|
6
|
+
const schema = (properties: Record<string, unknown>, required: string[]) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
7
|
+
const text = { type: 'string' };
|
|
8
|
+
export const AGENT_TASK_TOOLS: McpToolDefinition[] = [
|
|
9
|
+
{ name: 'capabilities_list', description: 'Discover all capabilities this agent can offer: installed/shared/native skills, gateway MCP tools and enabled connector/plugin MCP tools, including tools executed only by workers. Read metadata only; never executes a tool or grants permissions. Use query to find relevant tools before assigning work. Follow next_offset until null when the user asks for the complete list, passing catalog_version from the first page. On CAPABILITY_CATALOG_CHANGED restart from offset 0. Report unavailable discovery honestly; do not confuse your own restricted callable tools with the capabilities of the agent as a whole.', inputSchema: schema({query:text,catalog_version:text,offset:{type:'integer',minimum:0}},[]) },
|
|
10
|
+
{ name: 'conversation_intake', description: 'Prepare executable work, incomplete materials, or an amendment to an existing task. Do not call this for greetings, introductions, casual conversation or questions you can answer directly; answer those normally once. When semantic intake is enabled, acknowledge before work mutations. Incomplete material is inspected now; the gateway asks the supplied clarification after the configured silence interval, not on every item.',
|
|
11
|
+
inputSchema: schema({mode:{type:'string',enum:['ready','wait','update']},acknowledgement:{type:'string',description:'A brief contextual message addressed directly to the user about the work you will do. Immediately displayed and spoken aloud. Never include private classification, reasoning, or instructions to yourself.'},preparation:text,clarification:text,task_id:text},['mode']) },
|
|
12
|
+
{ name: 'task_spawn', description: 'Durably queue a task for an independent worker. Returns after commit without waiting for worker startup or completion.',
|
|
13
|
+
inputSchema: schema({ title: text, instructions: text, target_profile: text, spoken_acknowledgement: { type: 'string', description: 'Required for live voice: a short natural response in the user language explaining the specific work you are about to do. Describe the specific user request as your own task, in the voice of your existing persona; do not announce handing it to a worker or use a generic acknowledgement. Speak as the person doing the work; avoid Thai delegation phrases such as เธชเธฑเนเธเนเธเนเธเนเธฅเนเธง, เธชเธฑเนเธเนเธเนเธฅเนเธง or เธชเนเธเนเธซเนเธเธฃเธงเธเนเธฅเนเธง. Describe queued work as accepted/to do next, and only say it is running when that state is confirmed. Do not claim completion. The gateway manages playback and channel text-before-voice ordering.' }, skill_name: { type: 'string', description: 'Exact installed gateway or discovered Claude Code runtime skill name for skill-worker. Native skills are invoked directly by the CLI; shared/workspace skills are supplied with their resources. Never supply a file path.' }, skill_args: { type: 'string', description: 'Arguments for the installed skill; required with skill_name.' }, continue_task_id: { type: 'string', description: 'Prior task ID in this conversation for related follow-up work. Reuses its worker session when compatible; waits for success by default. Queue every authorized dependent step now, using each returned task ID for the next step. Omit for independent work.' }, continuation_policy: { type: 'string', enum: ['after_success', 'after_terminal'], description: 'With continue_task_id: after_success (default) starts only after the predecessor succeeds; otherwise fails without executing. Use after_terminal only for explicitly authorized recovery/inspection that must run after failure or cancellation.' }, context_refs: { type: 'array', items: text, description: 'Only orchestration input IDs, authorized attachment refs or task artifact IDs. Put project filenames in instructions instead.' } }, ['title', 'instructions', 'target_profile']) },
|
|
14
|
+
{ name: 'task_status', description: 'Read the task index in this conversation without waiting. Pass task_id to retrieve the complete stored result and evidence before discussing an earlier task. An index entry is not a partial worker report.', inputSchema: schema({ task_id: text }, []) },
|
|
15
|
+
{ name: 'task_cancel', description: 'Request cancellation, including needs_reconciliation cleanup. cancel_requested is not proof of termination. Existing files are retained. Set replaced_by_task_id only for a known replacement task, never infer from similar titles.', inputSchema: schema({ task_id: text, replaced_by_task_id: text }, ['task_id']) },
|
|
16
|
+
{ name: 'task_update', description: 'Accept a revised task instruction. applied_revision changes only when the worker accepts it.', inputSchema: schema({ task_id: text, expected_revision: { type: 'integer', minimum: 1 }, instruction: text, mode: { type: 'string', enum: ['when_ready', 'interrupt_and_resume'] } }, ['task_id', 'expected_revision', 'instruction', 'mode']) },
|
|
17
|
+
{ name: 'task_answer', description: 'Answer the matching pending question on a task.', inputSchema: schema({ task_id: text, question_id: text, answer: text }, ['task_id', 'question_id', 'answer']) },
|
|
18
|
+
];
|
|
19
|
+
export const WORKER_REPORT_TOOLS: McpToolDefinition[] = [
|
|
20
|
+
{ name: 'task_memory_append', description: 'Append an explicitly requested memory note for a channel task. API tasks cannot write memory. Files are restricted to MEMORY.md, USER.md, or memory/<name>.md. Retains existing content and deduplicates retries.', inputSchema: schema({ note: text, path: text }, ['note']) },
|
|
21
|
+
{ name: 'task_stage_file', description: 'Stage a finished image or document for the user. The orchestration attaches it to the next completed agent response after this task succeeds, using the original destination. This tool does not send to a channel. Call once per output file. For an image returned by an MCP tool (such as a remote browser screenshot), omit path to use the latest captured image in this attempt, or pass source_tool_call_id to select its tool call. The gateway saves the actual image bytes; never invent a screenshot filename. For an existing local file supply path.', inputSchema: schema({ path: text, source_tool_call_id: text, caption: text }, []) },
|
|
22
|
+
{ name: 'task_report_progress', description: 'Report factual progress internally. At phase changes include a checkpoint with phase, evidenceVersion, checks, findings and nextAction. This does not send a user message.', inputSchema: schema({ text, checkpoint: WORKFLOW_SCHEMA }, ['text']) },
|
|
23
|
+
{ name: 'task_request_input', description: 'Ask the user for information required by your task, then end this turn immediately.', inputSchema: schema({ question: text }, ['question']) },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export async function callTaskBridge(tool: string, args: Record<string, unknown>, requestId: string, signal: AbortSignal): Promise<McpToolResult> {
|
|
27
|
+
// Rotated by the gateway at each decision/attempt boundary. A loaded token is
|
|
28
|
+
// immutable for this request; late requests cannot borrow the next decision.
|
|
29
|
+
const { readFile } = await import('node:fs/promises');
|
|
30
|
+
const scope = JSON.parse(await readFile(process.env.GATEWAY_ORCHESTRATION_TICKET_FILE!, 'utf8')) as { url: string; token: string };
|
|
31
|
+
const response = await fetch(scope.url, { method: 'POST', signal, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${scope.token}` },
|
|
32
|
+
body: JSON.stringify({ tool, args, action_id: requestId }) });
|
|
33
|
+
const body = await response.text();
|
|
34
|
+
return { content: [{ type: 'text', text: body }], ...(response.ok ? {} : { isError: true }) };
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export type ChannelInput = {content:string;meta:Record<string,string>};
|
|
3
|
+
export function mediaGroupKey(input: ChannelInput): string | undefined {
|
|
4
|
+
const m=input?.meta;
|
|
5
|
+
return m?.media_group_id ? createHash('sha256').update(JSON.stringify([m.chat_id,m.user_id,m.message_thread_id??'',m.media_group_id])).digest('hex') : undefined;
|
|
6
|
+
}
|
|
7
|
+
/** Telegram albums arrive as separate updates. Keep the native group intact. */
|
|
8
|
+
export function mergeMediaGroup(prior:ChannelInput|undefined,input:ChannelInput):ChannelInput {
|
|
9
|
+
const ids:string[]=prior?JSON.parse(prior.meta.message_ids_json):[];
|
|
10
|
+
if(ids.includes(input.meta.message_id))return prior!;
|
|
11
|
+
if(ids.length>=10)throw Error('Telegram media group exceeds 10 items');
|
|
12
|
+
const parts:Array<{id:string;caption:string;ref?:string;path?:string;name?:string}> = prior?JSON.parse(prior.meta.media_group_parts_json):[];
|
|
13
|
+
parts.push({id:input.meta.message_id,caption:input.meta.media_caption??'',ref:input.meta.attachment_file_id,path:input.meta.image_path,name:input.meta.attachment_name});
|
|
14
|
+
parts.sort((a,b)=>Number(a.id)-Number(b.id));
|
|
15
|
+
const meta:Record<string,string>={...(prior?.meta??input.meta),media_group_id:input.meta.media_group_id,message_id:parts[0]!.id,
|
|
16
|
+
message_ids_json:JSON.stringify(parts.map(p=>p.id)),media_group_parts_json:JSON.stringify(parts),
|
|
17
|
+
attachments_json:JSON.stringify(parts.map(({ref,path,name})=>({ref,path,name})))};
|
|
18
|
+
// Album files must not turn a mixed album into a single-file voice-note turn.
|
|
19
|
+
delete meta.attachment_file_id;delete meta.image_path;delete meta.attachment_kind;
|
|
20
|
+
return {content:parts.map(p=>p.caption).filter(Boolean).join('\n\n')||'[Media album attached]',meta};
|
|
21
|
+
}
|
|
@@ -25,6 +25,13 @@ import { z } from 'zod'
|
|
|
25
25
|
import { Bot, GrammyError, InlineKeyboard, InputFile, type Context } from 'grammy'
|
|
26
26
|
import type { ReactionTypeEmoji } from 'grammy/types'
|
|
27
27
|
import { randomBytes } from 'crypto'
|
|
28
|
+
import { confirmSelection, voiceModeConfirmation } from './selection-confirmation'
|
|
29
|
+
import { LiveTaskBrowser, type TaskBrowserMessage } from './task-browser'
|
|
30
|
+
import { ReceiverSpool } from '../receiver-spool'
|
|
31
|
+
const orchestrationSpool = process.env.GATEWAY_ORCHESTRATION_INGRESS_DIR && process.env.CLAUDE_CHANNEL_CALLBACK
|
|
32
|
+
? new ReceiverSpool(process.env.GATEWAY_ORCHESTRATION_INGRESS_DIR, process.env.CLAUDE_CHANNEL_CALLBACK, fetch, Math.max(250,Math.min(10000,Number(process.env.GATEWAY_MEDIA_GROUP_WAIT_MS)||2000))) : undefined
|
|
33
|
+
const ORCHESTRATION_ENABLED = process.env.GATEWAY_ORCHESTRATION_ENABLED === 'true' || Boolean(orchestrationSpool)
|
|
34
|
+
const INTERACTIVE_CLI_ENABLED = process.env.GATEWAY_INTERACTIVE_CLI_ENABLED === 'true' && !ORCHESTRATION_ENABLED
|
|
28
35
|
import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync, existsSync } from 'fs'
|
|
29
36
|
import { homedir } from 'os'
|
|
30
37
|
import { join, extname, sep } from 'path'
|
|
@@ -35,6 +42,7 @@ import { extractRepliedAttachment, safeName } from './reply-attachment'
|
|
|
35
42
|
// so a src/ import crashes this bun-run receiver on installed packages (the bug
|
|
36
43
|
// that silenced every bot on systemd installs). Enforced by
|
|
37
44
|
// tests/unit/mcp-no-src-imports.test.ts.
|
|
45
|
+
import { formatTaskDetail, type TaskDetail } from './task-detail'
|
|
38
46
|
import { formatTurnIncident, type TurnIncident } from '../../../dist/agent/turn-trace.js'
|
|
39
47
|
import { createIncidentStore } from '../../../dist/agent/incident-store.js'
|
|
40
48
|
import type { RecoveryOutcome } from '../../../dist/agent/incident.js'
|
|
@@ -214,6 +222,7 @@ const typingManager = createWorkingStateManager(
|
|
|
214
222
|
)
|
|
215
223
|
}
|
|
216
224
|
},
|
|
225
|
+
ORCHESTRATION_ENABLED ? 'orchestration' : 'legacy',
|
|
217
226
|
)
|
|
218
227
|
|
|
219
228
|
// Stages whose recovery must run in the runner process (live session control):
|
|
@@ -863,17 +872,18 @@ const RECEIVER_MODE = process.env.TELEGRAM_RECEIVER_MODE === 'true'
|
|
|
863
872
|
import { parseMenuFileContent } from './menu-parser'
|
|
864
873
|
|
|
865
874
|
const BOT_COMMANDS = [
|
|
875
|
+
...(ORCHESTRATION_ENABLED ? [{ command: 'tasks', description: 'View pending tasks in this chat' }, { command: 'voice', description: 'Turn automatic voice replies on or off' }, { command: 'voices', description: 'Choose the agent voice' }] : []),
|
|
866
876
|
{ command: 'session', description: 'Show current session info' },
|
|
867
877
|
{ command: 'sessions', description: 'Manage conversation sessions' },
|
|
868
878
|
{ command: 'new', description: 'Create a new session' },
|
|
869
879
|
{ command: 'rename', description: 'Rename current session' },
|
|
870
880
|
{ command: 'clear', description: 'Clear current session history' },
|
|
871
881
|
{ command: 'compact', description: 'Summarize and compress session history' },
|
|
872
|
-
{ command: 'stop', description: 'Interrupt the agent and stop current work' },
|
|
882
|
+
{ command: 'stop', description: ORCHESTRATION_ENABLED ? 'Stop the agent reply and choose a task to cancel' : 'Interrupt the agent and stop current work' },
|
|
873
883
|
{ command: 'restart', description: 'Graceful restart session' },
|
|
874
884
|
{ command: 'model', description: 'Show current AI model' },
|
|
875
885
|
{ command: 'models', description: 'Switch AI model' },
|
|
876
|
-
{ command: 'cli', description: 'Open the live terminal viewer' },
|
|
886
|
+
...(INTERACTIVE_CLI_ENABLED ? [{ command: 'cli', description: 'Open the live terminal viewer' }] : []),
|
|
877
887
|
{ command: 'start', description: 'Welcome and setup guide' },
|
|
878
888
|
{ command: 'status', description: 'Check your pairing status' },
|
|
879
889
|
{ command: 'help', description: 'What this bot can do' },
|
|
@@ -1023,7 +1033,7 @@ async function handleInbound(
|
|
|
1023
1033
|
// Download photo from replied-to message if present
|
|
1024
1034
|
const replyMsg = ctx.message?.reply_to_message
|
|
1025
1035
|
let repliedImagePath: string | undefined
|
|
1026
|
-
if (replyMsg?.photo) {
|
|
1036
|
+
if (replyMsg?.photo && !orchestrationSpool) {
|
|
1027
1037
|
try {
|
|
1028
1038
|
const largest = replyMsg.photo[replyMsg.photo.length - 1]!
|
|
1029
1039
|
const file = await bot.api.getFile(largest.file_id)
|
|
@@ -1054,7 +1064,9 @@ async function handleInbound(
|
|
|
1054
1064
|
content: text,
|
|
1055
1065
|
meta: {
|
|
1056
1066
|
chat_id,
|
|
1067
|
+
...(ctx.message?.media_group_id ? {media_group_id:ctx.message.media_group_id,media_caption:ctx.message.caption??''} : {}),
|
|
1057
1068
|
...(msgId != null ? { message_id: String(msgId) } : {}),
|
|
1069
|
+
...(ctx.message?.message_thread_id != null ? { message_thread_id: String(ctx.message.message_thread_id) } : {}),
|
|
1058
1070
|
user: from.username ?? String(from.id),
|
|
1059
1071
|
user_id: String(from.id),
|
|
1060
1072
|
// Human-readable name for the chat picker / history (first_name is always
|
|
@@ -1072,8 +1084,9 @@ async function handleInbound(
|
|
|
1072
1084
|
...(replyMsg ? {
|
|
1073
1085
|
replied_message_id: String(replyMsg.message_id),
|
|
1074
1086
|
replied_user: replyMsg.from?.username ?? String(replyMsg.from?.id ?? ''),
|
|
1075
|
-
...(replyMsg.text ? { replied_text: replyMsg.text } : {}),
|
|
1087
|
+
...((replyMsg.text || replyMsg.caption) ? { replied_text: replyMsg.text || replyMsg.caption } : {}),
|
|
1076
1088
|
...(repliedImagePath ? { replied_image_path: repliedImagePath } : {}),
|
|
1089
|
+
...(orchestrationSpool && replyMsg.photo ? {replied_attachment_file_id:replyMsg.photo[replyMsg.photo.length-1]!.file_id} : {}),
|
|
1077
1090
|
...(repliedAttachment ? {
|
|
1078
1091
|
replied_attachment_kind: repliedAttachment.kind,
|
|
1079
1092
|
replied_attachment_file_id: repliedAttachment.file_id,
|
|
@@ -1103,15 +1116,16 @@ async function handleInbound(
|
|
|
1103
1116
|
// turns in --print --channels mode).
|
|
1104
1117
|
const callbackUrl = process.env.CLAUDE_CHANNEL_CALLBACK
|
|
1105
1118
|
if (callbackUrl) {
|
|
1106
|
-
|
|
1119
|
+
if (orchestrationSpool) orchestrationSpool.enqueue(channelParams)
|
|
1120
|
+
else fetch(callbackUrl, {
|
|
1107
1121
|
method: 'POST',
|
|
1108
1122
|
headers: { 'Content-Type': 'application/json' },
|
|
1109
1123
|
body: JSON.stringify(channelParams),
|
|
1110
1124
|
}).catch(err => {
|
|
1111
1125
|
process.stderr.write(`telegram channel: callback POST failed: ${err}\n`)
|
|
1112
1126
|
})
|
|
1113
|
-
//
|
|
1114
|
-
if (RECEIVER_MODE) {
|
|
1127
|
+
// Legacy turn watchdog only. Orchestration owns typing through durable task/decision state.
|
|
1128
|
+
if (RECEIVER_MODE && !ORCHESTRATION_ENABLED) {
|
|
1115
1129
|
typingManager.start(chat_id)
|
|
1116
1130
|
}
|
|
1117
1131
|
}
|
|
@@ -1157,12 +1171,13 @@ bot.command('help', async ctx => {
|
|
|
1157
1171
|
`/rename <name> โ rename current session\n` +
|
|
1158
1172
|
`/clear โ clear current session history\n` +
|
|
1159
1173
|
`/compact โ summarise and compress session history\n` +
|
|
1160
|
-
`/stop โ interrupt the running turn\n` +
|
|
1174
|
+
(ORCHESTRATION_ENABLED ? `/stop โ stop the reply and choose a task to cancel\n` : `/stop โ interrupt the running turn\n`) +
|
|
1161
1175
|
`/restart โ graceful restart session\n\n` +
|
|
1162
1176
|
`*Agent*\n` +
|
|
1163
1177
|
`/model โ show current AI model\n` +
|
|
1164
1178
|
`/models โ switch AI model\n` +
|
|
1165
|
-
`/
|
|
1179
|
+
(ORCHESTRATION_ENABLED ? `/tasks โ view pending tasks in this chat\n/voice โ turn automatic voice replies on/off (default off)\n/voices โ choose the agent voice\n` : '') +
|
|
1180
|
+
(INTERACTIVE_CLI_ENABLED ? `/cli โ open the live terminal viewer\n\n` : '\n') +
|
|
1166
1181
|
`*Account*\n` +
|
|
1167
1182
|
`/start โ pairing instructions\n` +
|
|
1168
1183
|
`/status โ check your pairing state`,
|
|
@@ -1295,6 +1310,7 @@ bot.command('models', async ctx => {
|
|
|
1295
1310
|
// this bot's token), so nothing secret rides in the URL. Private chat + allowlist
|
|
1296
1311
|
// only, matching every other command.
|
|
1297
1312
|
bot.command('cli', async ctx => {
|
|
1313
|
+
if (!INTERACTIVE_CLI_ENABLED) { await ctx.reply('Terminal viewer requires interactive mode (headless: false).'); return }
|
|
1298
1314
|
if (ctx.chat?.type !== 'private') return
|
|
1299
1315
|
const access = loadAccess()
|
|
1300
1316
|
if (!access.allowFrom.includes(String(ctx.from!.id))) return
|
|
@@ -1360,6 +1376,144 @@ bot.command('restart', async ctx => {
|
|
|
1360
1376
|
)
|
|
1361
1377
|
})
|
|
1362
1378
|
|
|
1379
|
+
type VoicePicker = {menuId:string;provider:string;page:number;pages:number;gender?:string;groups:string[];selected:string;voices:Array<{name:string;gender?:string;index:number;selected:boolean}>}
|
|
1380
|
+
async function voicePickerRequest(chatId:string,userId:string,payload:Record<string,unknown>={}) {
|
|
1381
|
+
if(!CALLBACK_URL_BASE)throw new Error('not_configured')
|
|
1382
|
+
const response=await fetch(CALLBACK_URL_BASE+'/command',{method:'POST',headers:{'Content-Type':'application/json'},signal:AbortSignal.timeout(20000),body:JSON.stringify({command:'telegram_voices',chat_id:chatId,payload:{...payload,user_id:userId}})})
|
|
1383
|
+
const result=await response.json() as VoicePicker & {success?:boolean;name?:string}
|
|
1384
|
+
if(!response.ok||!result.success)throw new Error('unavailable')
|
|
1385
|
+
return result
|
|
1386
|
+
}
|
|
1387
|
+
function voicePickerMenu(menu:VoicePicker) {
|
|
1388
|
+
const keyboard=new InlineKeyboard()
|
|
1389
|
+
const labels:Record<string,string>={male:'๐จ Male',female:'๐ฉ Female',neutral:'Neutral',unspecified:'Unspecified'}
|
|
1390
|
+
if(!menu.gender){
|
|
1391
|
+
for(const group of menu.groups)keyboard.text(labels[group],`voicegroup:${menu.menuId}:${group}`).row()
|
|
1392
|
+
}else{
|
|
1393
|
+
for(const v of menu.voices)keyboard.text(`${v.selected?'โ
':''}${v.name.slice(0,60)}`,`voicepick:${menu.menuId}:${v.index}`).row()
|
|
1394
|
+
if(menu.page>0)keyboard.text('Previous',`voicepage:${menu.menuId}:${menu.gender}:${menu.page-1}`)
|
|
1395
|
+
if(menu.page+1<menu.pages)keyboard.text('Next',`voicepage:${menu.menuId}:${menu.gender}:${menu.page+1}`)
|
|
1396
|
+
keyboard.row().text('Back',`voicegroup:${menu.menuId}:back`).row()
|
|
1397
|
+
}
|
|
1398
|
+
keyboard.text('Dismiss',`voicedismiss:${menu.menuId}`)
|
|
1399
|
+
return {text:`Agent voice ยท ${menu.provider}\nSelected: ${menu.selected}\n${menu.gender?`${labels[menu.gender]} โ choose a voice (${menu.page+1}/${menu.pages}):`:'Choose a voice category:'}`,reply_markup:keyboard}
|
|
1400
|
+
}
|
|
1401
|
+
bot.command('voices',async ctx=>{
|
|
1402
|
+
if(ctx.chat?.type!=='private'||!isCallbackAuthorized(ctx))return
|
|
1403
|
+
if(!ORCHESTRATION_ENABLED){await ctx.reply('Voice controls require orchestration mode.');return}
|
|
1404
|
+
try{const menu=voicePickerMenu(await voicePickerRequest(String(ctx.chat.id),String(ctx.from!.id)));await ctx.reply(menu.text,{reply_markup:menu.reply_markup})}
|
|
1405
|
+
catch{await ctx.reply('Voice list unavailable. Check the TTS provider configuration and try /voices again.')}
|
|
1406
|
+
})
|
|
1407
|
+
|
|
1408
|
+
type TaskView = TaskDetail & {taskId:string;title:string;state:string;progress?:string;question?:string;updatedAt:number;startedAt?:number;finishedAt?:number;canStop:boolean}
|
|
1409
|
+
type TaskBrowser = {sessionId:string;task?:TaskView;tasks?:TaskView[];page?:number;pages?:number;total?:number}
|
|
1410
|
+
async function taskBrowserRequest(chatId:string,userId:string,payload:Record<string,unknown>={}) {
|
|
1411
|
+
if(!CALLBACK_URL_BASE)throw Error('not_configured')
|
|
1412
|
+
const response=await fetch(CALLBACK_URL_BASE+'/command',{method:'POST',headers:{'Content-Type':'application/json'},signal:AbortSignal.timeout(10000),body:JSON.stringify({command:'telegram_tasks',chat_id:chatId,payload:{...payload,user_id:userId}})})
|
|
1413
|
+
const result=await response.json() as TaskBrowser & {success?:boolean}
|
|
1414
|
+
if(!response.ok||!result.success)throw Error((result as {error?:string}).error==='TASK_SESSION_CHANGED'?'TASK_SESSION_CHANGED':'unavailable')
|
|
1415
|
+
if(typeof result.sessionId!=='string'||!result.sessionId)throw Error('unavailable')
|
|
1416
|
+
return result
|
|
1417
|
+
}
|
|
1418
|
+
function taskBrowserMenu(result:TaskBrowser) {
|
|
1419
|
+
const keyboard=new InlineKeyboard()
|
|
1420
|
+
const labels:Record<string,string>={queued:'โณ Queued',starting:'โณ Starting',running:'๐ฅ Running',waiting_input:'๐ฌ Waiting for your input',interrupting:'โธ Interrupting',cancel_requested:'โน Stopping',recovering:'๐ Recovering',needs_reconciliation:'โ ๏ธ Needs reconciliation',completed:'โ
Completed',failed:'โ Failed',cancelled:'โน Cancelled'}
|
|
1421
|
+
let text:string
|
|
1422
|
+
if(result.task){
|
|
1423
|
+
const task=result.task
|
|
1424
|
+
text=formatTaskDetail(task,labels[task.state]??task.state)
|
|
1425
|
+
if(task.canStop)keyboard.text(task.state==='needs_reconciliation'?'๐ Retry cleanup':'๐ด Stop task',`taskcancel:${task.taskId}`).row()
|
|
1426
|
+
keyboard.text('Back','tasklist:0').row()
|
|
1427
|
+
}else{
|
|
1428
|
+
text=result.total?`Tasks (${result.total}) ยท Page ${(result.page??0)+1}/${result.pages}\nChoose a task for details.`:'No pending tasks in this chat.'
|
|
1429
|
+
for(const [i,task] of (result.tasks??[]).entries())keyboard.text(`${(result.page??0)*10+i+1}. ${task.title.replace(/\s+/g,' ').slice(0,45)} ยท ${labels[task.state]??task.state}`,`taskview:${task.taskId}`).row()
|
|
1430
|
+
if((result.page??0)>0)keyboard.text('Previous',`tasklist:${result.page!-1}`)
|
|
1431
|
+
if((result.page??0)+1<(result.pages??1))keyboard.text('Next',`tasklist:${result.page!+1}`)
|
|
1432
|
+
keyboard.row()
|
|
1433
|
+
}
|
|
1434
|
+
keyboard.text('Dismiss','taskdismiss')
|
|
1435
|
+
return {text,reply_markup:keyboard}
|
|
1436
|
+
}
|
|
1437
|
+
const TASK_BROWSER_FILE = join(STATE_DIR, 'task-browser.json')
|
|
1438
|
+
let restoredTaskBrowsers: TaskBrowserMessage[] = []
|
|
1439
|
+
try { const saved = JSON.parse(readFileSync(TASK_BROWSER_FILE, 'utf8')); if (Array.isArray(saved)) restoredTaskBrowsers = saved } catch {}
|
|
1440
|
+
const liveTaskBrowser = new LiveTaskBrowser({
|
|
1441
|
+
read: taskBrowserRequest,
|
|
1442
|
+
render: result => taskBrowserMenu(result as TaskBrowser),
|
|
1443
|
+
send: async (chat, menu) => (await bot.api.sendMessage(chat, menu.text, {reply_markup: menu.reply_markup as InlineKeyboard})).message_id,
|
|
1444
|
+
edit: (chat, id, menu) => bot.api.editMessageText(chat, id, menu.text, {reply_markup: menu.reply_markup as InlineKeyboard}),
|
|
1445
|
+
remove: (chat, id) => bot.api.deleteMessage(chat, id),
|
|
1446
|
+
close: (chat, id) => bot.api.editMessageText(chat, id, 'Task view closed.', {reply_markup: {inline_keyboard: []}}),
|
|
1447
|
+
allowed: (chat, user) => ORCHESTRATION_ENABLED && chat === user && loadAccess().allowFrom.includes(user),
|
|
1448
|
+
persist: entries => {
|
|
1449
|
+
mkdirSync(STATE_DIR, {recursive:true,mode:0o700})
|
|
1450
|
+
const temp = TASK_BROWSER_FILE + '.tmp'
|
|
1451
|
+
writeFileSync(temp,JSON.stringify(entries),{mode:0o600});renameSync(temp,TASK_BROWSER_FILE)
|
|
1452
|
+
},
|
|
1453
|
+
}, restoredTaskBrowsers)
|
|
1454
|
+
if (ORCHESTRATION_ENABLED) setInterval(() => { void liveTaskBrowser.tick().catch(() => {}) }, 3000).unref()
|
|
1455
|
+
bot.command('tasks',async ctx=>{
|
|
1456
|
+
if(ctx.chat?.type!=='private'||!isCallbackAuthorized(ctx))return
|
|
1457
|
+
if(!ORCHESTRATION_ENABLED){await ctx.reply('Task controls require orchestration mode.');return}
|
|
1458
|
+
try{await liveTaskBrowser.open(String(ctx.chat.id),String(ctx.from!.id))}
|
|
1459
|
+
catch{await ctx.reply('Could not load tasks. Please try /tasks again.')}
|
|
1460
|
+
})
|
|
1461
|
+
|
|
1462
|
+
async function taskStopRequest(chatId: string, userId: string, menuId?: string, index?: number) {
|
|
1463
|
+
if (!CALLBACK_URL_BASE) throw new Error('not_configured')
|
|
1464
|
+
const response=await fetch(CALLBACK_URL_BASE+'/command', {
|
|
1465
|
+
method:'POST',headers:{'Content-Type':'application/json'},signal:AbortSignal.timeout(10000),
|
|
1466
|
+
body:JSON.stringify({command:'task_stop',chat_id:chatId,payload:{user_id:userId,menu_id:menuId,index}}),
|
|
1467
|
+
})
|
|
1468
|
+
const result=await response.json() as {success?:boolean;legacy?:boolean;text?:string;menuId?:string;tasks?:Array<{title:string;state:string}>}
|
|
1469
|
+
if (!response.ok || !result.success) throw new Error('unavailable')
|
|
1470
|
+
return result
|
|
1471
|
+
}
|
|
1472
|
+
bot.command('stop',async (ctx,next)=>{
|
|
1473
|
+
if (!ORCHESTRATION_ENABLED || ctx.chat?.type!=='private') return next()
|
|
1474
|
+
if (!isCallbackAuthorized(ctx)) return
|
|
1475
|
+
try {
|
|
1476
|
+
const result=await taskStopRequest(String(ctx.chat.id),String(ctx.from!.id))
|
|
1477
|
+
if (result.legacy) return
|
|
1478
|
+
const keyboard=new InlineKeyboard()
|
|
1479
|
+
for (const [i,task] of (result.tasks??[]).entries()) keyboard.text(`${i+1}. ${task.title.slice(0,65)} (${task.state})`,`taskstop:${result.menuId}:${i+1}`).row()
|
|
1480
|
+
if (result.tasks?.length) keyboard.text('Dismiss',`taskstop:${result.menuId}:0`)
|
|
1481
|
+
await ctx.reply(result.text??'Nothing to stop.',result.tasks?.length?{reply_markup:keyboard}:{})
|
|
1482
|
+
} catch { await ctx.reply('Could not load tasks. Please try /stop again.') }
|
|
1483
|
+
})
|
|
1484
|
+
|
|
1485
|
+
// Voice settings are handled directly; no model call or TTS for this menu.
|
|
1486
|
+
type VoiceReplyMode = 'on' | 'auto' | 'off'
|
|
1487
|
+
async function telegramVoiceSetting(chatId: string, mode?: VoiceReplyMode): Promise<VoiceReplyMode> {
|
|
1488
|
+
if (!CALLBACK_URL_BASE) throw new Error('not_configured')
|
|
1489
|
+
const response = await fetch(CALLBACK_URL_BASE + '/command', {
|
|
1490
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1491
|
+
body: JSON.stringify({ command: 'telegram_voice', chat_id: chatId, payload: { mode } }),
|
|
1492
|
+
signal: AbortSignal.timeout(10000),
|
|
1493
|
+
})
|
|
1494
|
+
const result = await response.json() as { success?: boolean; enabled?: boolean; mode?: VoiceReplyMode }
|
|
1495
|
+
if (!response.ok || !result.success || typeof result.enabled !== 'boolean') throw new Error('unavailable')
|
|
1496
|
+
return result.mode ?? (result.enabled ? 'on' : 'off')
|
|
1497
|
+
}
|
|
1498
|
+
function voiceMenu(mode: VoiceReplyMode) {
|
|
1499
|
+
const labels = {on:'๐ Always', auto:'๐๏ธ Only reply voice message', off:'๐ Off'}
|
|
1500
|
+
const keyboard = new InlineKeyboard()
|
|
1501
|
+
for (const value of ['on','auto','off'] as const) keyboard.text(`${mode === value ? 'โ
' : ''}${labels[value]}`, `voice:${value}`).row()
|
|
1502
|
+
return {text: `Voice replies: ${labels[mode]}\nAuto replies with audio to voice messages and their task results.`, reply_markup: keyboard.text('Dismiss','voice:dismiss')}
|
|
1503
|
+
}
|
|
1504
|
+
bot.command('voice', async ctx => {
|
|
1505
|
+
if (!ORCHESTRATION_ENABLED) { await ctx.reply('Voice controls require orchestration mode.'); return }
|
|
1506
|
+
if (ctx.chat?.type !== 'private' || !isCallbackAuthorized(ctx)) return
|
|
1507
|
+
const choice = ctx.match.trim().toLowerCase()
|
|
1508
|
+
if (choice && choice !== 'on' && choice !== 'off' && choice !== 'auto') {
|
|
1509
|
+
await ctx.reply('Use /voice to open the menu, or /voice on, /voice auto and /voice off.'); return
|
|
1510
|
+
}
|
|
1511
|
+
try {
|
|
1512
|
+
const menu = voiceMenu(await telegramVoiceSetting(String(ctx.chat.id), choice ? choice as VoiceReplyMode : undefined))
|
|
1513
|
+
await ctx.reply(menu.text, choice ? {} : { reply_markup: menu.reply_markup })
|
|
1514
|
+
} catch { await ctx.reply('Voice settings are unavailable. Check the agent TTS configuration.') }
|
|
1515
|
+
})
|
|
1516
|
+
|
|
1363
1517
|
// /session โ show current session info (direct command, no typing manager)
|
|
1364
1518
|
bot.command('session', async ctx => {
|
|
1365
1519
|
if (ctx.chat?.type !== 'private') return
|
|
@@ -1481,6 +1635,69 @@ function validModelRows(value: unknown): Array<{ id: string; label: string }> {
|
|
|
1481
1635
|
bot.on('callback_query:data', async ctx => {
|
|
1482
1636
|
const data = ctx.callbackQuery.data
|
|
1483
1637
|
|
|
1638
|
+
const voicePicker=/^(voicepick|voicepage|voicegroup|voicedismiss):([a-f0-9-]{36})(?::(male|female|neutral|unspecified|back))?(?::(\d+))?$/.exec(data)
|
|
1639
|
+
if(voicePicker){
|
|
1640
|
+
if(!ORCHESTRATION_ENABLED||ctx.callbackQuery.message?.chat.type!=='private'||!isCallbackAuthorized(ctx)){await ctx.answerCallbackQuery({text:'Not available or not authorized.'}).catch(()=>{});return}
|
|
1641
|
+
try{
|
|
1642
|
+
const action=voicePicker[1],id=voicePicker[2],n=Number(voicePicker[4]),chatId=String(ctx.callbackQuery.message.chat.id),userId=String(ctx.from.id)
|
|
1643
|
+
if(action==='voicepage'||action==='voicegroup'){
|
|
1644
|
+
const menu=voicePickerMenu(await voicePickerRequest(chatId,userId,{menu_id:id,page:action==='voicepage'?n:0,gender:voicePicker[3]==='back'?undefined:voicePicker[3]}))
|
|
1645
|
+
await ctx.editMessageText(menu.text,{reply_markup:menu.reply_markup}).catch(()=>{});await ctx.answerCallbackQuery().catch(()=>{})
|
|
1646
|
+
}else{
|
|
1647
|
+
const result=await voicePickerRequest(chatId,userId,{action:action==='voicepick'?'choose':'dismiss',menu_id:id,index:n})
|
|
1648
|
+
await ctx.answerCallbackQuery({text:action==='voicepick'?`Selected: ${result.name}`.slice(0,180):'Dismissed'}).catch(()=>{})
|
|
1649
|
+
if(action==='voicepick') await confirmSelection(ctx,`โ
Agent voice: ${result.name} ยท ${result.provider}`)
|
|
1650
|
+
else await ctx.deleteMessage().catch(()=>{})
|
|
1651
|
+
}
|
|
1652
|
+
}catch{await ctx.answerCallbackQuery({text:'Voice list changed or unavailable. Use /voices again.'}).catch(()=>{})}
|
|
1653
|
+
return
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const taskBrowser=/^(taskview|taskcancel):([a-f0-9-]{36})$|^(tasklist):(\d+)$|^(taskdismiss)$/.exec(data)
|
|
1657
|
+
if(taskBrowser){
|
|
1658
|
+
if(!ORCHESTRATION_ENABLED||ctx.callbackQuery.message?.chat.type!=='private'||!isCallbackAuthorized(ctx)){await ctx.answerCallbackQuery({text:'Not available or not authorized.'}).catch(()=>{});return}
|
|
1659
|
+
try{
|
|
1660
|
+
await ctx.answerCallbackQuery().catch(()=>{})
|
|
1661
|
+
const payload=data==='taskdismiss'?{action:'dismiss'}:taskBrowser[1]?{action:taskBrowser[1]==='taskcancel'?'cancel':'detail',task_id:taskBrowser[2]}:{page:Number(taskBrowser[4])}
|
|
1662
|
+
await liveTaskBrowser.navigate(String(ctx.callbackQuery.message.chat.id),String(ctx.from.id),ctx.callbackQuery.message.message_id,payload)
|
|
1663
|
+
}catch{await ctx.answerCallbackQuery({text:'Task unavailable. Use /tasks to refresh.'}).catch(()=>{})}
|
|
1664
|
+
return
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const taskStop=/^taskstop:([a-f0-9-]{36}):(\d+)$/.exec(data)
|
|
1668
|
+
if (taskStop) {
|
|
1669
|
+
if (!ORCHESTRATION_ENABLED) { await ctx.answerCallbackQuery({text:'Task controls require orchestration mode.'}).catch(()=>{}); return }
|
|
1670
|
+
if (ctx.callbackQuery.message?.chat.type!=='private' || !isCallbackAuthorized(ctx)) {
|
|
1671
|
+
await ctx.answerCallbackQuery({text:'Not authorized.'}).catch(()=>{}); return
|
|
1672
|
+
}
|
|
1673
|
+
try {
|
|
1674
|
+
const result=await taskStopRequest(String(ctx.callbackQuery.message.chat.id),String(ctx.from.id),taskStop[1],Number(taskStop[2]))
|
|
1675
|
+
await ctx.answerCallbackQuery({text:Number(taskStop[2])===0?'Dismissed':'Stop requested.'}).catch(()=>{})
|
|
1676
|
+
await ctx.deleteMessage().catch(()=>{})
|
|
1677
|
+
if (Number(taskStop[2])!==0) await ctx.reply(result.text??'Stop requested.')
|
|
1678
|
+
} catch { await ctx.answerCallbackQuery({text:'Selection unavailable. Use /stop to refresh.'}).catch(()=>{}) }
|
|
1679
|
+
return
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
if (data === 'voice:on' || data === 'voice:auto' || data === 'voice:off' || data === 'voice:dismiss') {
|
|
1683
|
+
if (!ORCHESTRATION_ENABLED) { await ctx.answerCallbackQuery({text:'Voice controls require orchestration mode.'}).catch(()=>{}); return }
|
|
1684
|
+
if (ctx.callbackQuery.message?.chat.type !== 'private' || !isCallbackAuthorized(ctx)) {
|
|
1685
|
+
await ctx.answerCallbackQuery({ text: 'Not authorized.' }).catch(() => {}); return
|
|
1686
|
+
}
|
|
1687
|
+
if (data === 'voice:dismiss') {
|
|
1688
|
+
await ctx.answerCallbackQuery({ text: 'Dismissed' }).catch(() => {})
|
|
1689
|
+
await ctx.deleteMessage().catch(() => {})
|
|
1690
|
+
return
|
|
1691
|
+
}
|
|
1692
|
+
try {
|
|
1693
|
+
await telegramVoiceSetting(String(ctx.callbackQuery.message.chat.id), data.slice(6) as VoiceReplyMode)
|
|
1694
|
+
const confirmation=voiceModeConfirmation(data.slice(6))
|
|
1695
|
+
await ctx.answerCallbackQuery({text:confirmation}).catch(()=>{})
|
|
1696
|
+
await confirmSelection(ctx,confirmation)
|
|
1697
|
+
} catch { await ctx.answerCallbackQuery({ text: 'Could not update voice settings. Please try again.' }).catch(() => {}) }
|
|
1698
|
+
return
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1484
1701
|
// Handle interactive-menu choice: choice:<N>. A tap is routed back exactly like
|
|
1485
1702
|
// the user typing "N" โ same content + meta as the text path โ so the session's
|
|
1486
1703
|
// pending-menu handler injects the selection into the PTY. Security mirrors the
|
|
@@ -1514,7 +1731,7 @@ bot.on('callback_query:data', async ctx => {
|
|
|
1514
1731
|
}).catch(err => {
|
|
1515
1732
|
process.stderr.write(`telegram channel: choice callback POST failed: ${err}\n`)
|
|
1516
1733
|
})
|
|
1517
|
-
if (RECEIVER_MODE) typingManager.start(chat_id)
|
|
1734
|
+
if (RECEIVER_MODE && !ORCHESTRATION_ENABLED) typingManager.start(chat_id)
|
|
1518
1735
|
}
|
|
1519
1736
|
return
|
|
1520
1737
|
}
|
|
@@ -1732,7 +1949,7 @@ bot.on('callback_query:data', async ctx => {
|
|
|
1732
1949
|
await ctx.answerCallbackQuery({ text: 'Compacting...' }).catch(() => {})
|
|
1733
1950
|
await ctx.deleteMessage().catch(() => {})
|
|
1734
1951
|
await ctx.reply('๐ง Session compacting, please wait...\nThis may take a moment.').catch(() => {})
|
|
1735
|
-
if (RECEIVER_MODE) {
|
|
1952
|
+
if (RECEIVER_MODE && !ORCHESTRATION_ENABLED) {
|
|
1736
1953
|
typingManager.start(chatId)
|
|
1737
1954
|
}
|
|
1738
1955
|
await fetch(CALLBACK_URL_BASE + '/command', {
|
|
@@ -1978,6 +2195,10 @@ bot.on('message:text', async ctx => {
|
|
|
1978
2195
|
|
|
1979
2196
|
bot.on('message:photo', async ctx => {
|
|
1980
2197
|
const caption = ctx.message.caption ?? '(photo)'
|
|
2198
|
+
if(orchestrationSpool){
|
|
2199
|
+
const photo=ctx.message.photo[ctx.message.photo.length-1]!;
|
|
2200
|
+
await handleInbound(ctx,caption,undefined,{kind:'photo',file_id:photo.file_id,size:photo.file_size,mime:'image/jpeg'});return;
|
|
2201
|
+
}
|
|
1981
2202
|
// Defer download until after the gate approves โ any user can send photos,
|
|
1982
2203
|
// and we don't want to burn API quota or fill the inbox for dropped messages.
|
|
1983
2204
|
await handleInbound(ctx, caption, async () => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Keep a durable confirmation in chat, matching the /models menu behavior. */
|
|
2
|
+
export async function confirmSelection(ctx: {
|
|
3
|
+
editMessageText(text: string, options: {reply_markup: {inline_keyboard: never[]}}): Promise<unknown>;
|
|
4
|
+
reply(text: string): Promise<unknown>;
|
|
5
|
+
deleteMessage(): Promise<unknown>;
|
|
6
|
+
}, text: string): Promise<void> {
|
|
7
|
+
try { await ctx.editMessageText(text, {reply_markup: {inline_keyboard: []}}); }
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (/message is not modified/i.test(String((error as any)?.description ?? (error as any)?.message))) return;
|
|
10
|
+
// If Telegram cannot edit an old menu, retain the confirmation as a new
|
|
11
|
+
// message before removing the old controls. Never lose both.
|
|
12
|
+
await ctx.reply(text);
|
|
13
|
+
await ctx.deleteMessage().catch(() => {});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function voiceModeConfirmation(mode: string): string {
|
|
17
|
+
const labels: Record<string,string> = {on:'Always',auto:'Only reply voice message',off:'Off'};
|
|
18
|
+
if (!(mode in labels)) throw Error('INVALID_VOICE_MODE');
|
|
19
|
+
return `โ
Voice replies: ${labels[mode]}`;
|
|
20
|
+
}
|