@0xmaxma/claude-gateway 1.8.13 → 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 -1605
- package/config.template.json +7 -1
- package/dist/agent/runner.d.ts +394 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +867 -38
- 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 +372 -11
- 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/api/wechat-access.d.ts +45 -0
- package/dist/api/wechat-access.d.ts.map +1 -0
- package/dist/api/wechat-access.js +48 -0
- package/dist/api/wechat-access.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 +12 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/history/types.js +10 -1
- package/dist/history/types.js.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 +144 -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 +54 -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 +207 -0
- package/dist/wechat/ilink-client.d.ts.map +1 -0
- package/dist/wechat/ilink-client.js +660 -0
- package/dist/wechat/ilink-client.js.map +1 -0
- package/dist/wechat/manager.d.ts +93 -0
- package/dist/wechat/manager.d.ts.map +1 -0
- package/dist/wechat/manager.js +409 -0
- package/dist/wechat/manager.js.map +1 -0
- 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 -35
- 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/tools/wechat/client.ts +49 -0
- package/mcp/tools/wechat/module.ts +88 -0
- 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
package/dist/agent/runner.js
CHANGED
|
@@ -35,6 +35,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.AgentRunner = exports.CHANNEL_COALESCE_WINDOW_MS = exports.WHATSAPP_CLOUD_WINDOW_NOTE = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
37
37
|
exports.toRelMediaFiles = toRelMediaFiles;
|
|
38
|
+
const channel_input_media_1 = require("../orchestration/channel-input-media");
|
|
39
|
+
const chunks_1 = require("../telegram/chunks");
|
|
40
|
+
const file_delivery_1 = require("../orchestration/file-delivery");
|
|
41
|
+
const voice_reply_policy_1 = require("../orchestration/voice-reply-policy");
|
|
42
|
+
const gateway_config_1 = require("../orchestration/gateway-config");
|
|
43
|
+
const delivery_1 = require("../orchestration/delivery");
|
|
44
|
+
const control_delivery_1 = require("../orchestration/control-delivery");
|
|
45
|
+
const stop_controls_1 = require("../orchestration/stop-controls");
|
|
38
46
|
const crypto_1 = require("crypto");
|
|
39
47
|
const events_1 = require("events");
|
|
40
48
|
const fs = __importStar(require("fs"));
|
|
@@ -54,6 +62,10 @@ const slack_client_1 = require("../api/slack-client");
|
|
|
54
62
|
const whatsapp_cloud_client_1 = require("../api/whatsapp-cloud-client");
|
|
55
63
|
const manager_1 = require("../whatsapp/manager");
|
|
56
64
|
const whatsapp_accounts_1 = require("../config/whatsapp-accounts");
|
|
65
|
+
const manager_2 = require("../wechat/manager");
|
|
66
|
+
const ilink_client_1 = require("../wechat/ilink-client");
|
|
67
|
+
const wechat_access_1 = require("../api/wechat-access");
|
|
68
|
+
const pending_senders_1 = require("../api/pending-senders");
|
|
57
69
|
const markdown_1 = require("../telegram/markdown");
|
|
58
70
|
const skills_1 = require("../skills");
|
|
59
71
|
const builtin_commands_1 = require("./builtin-commands");
|
|
@@ -71,6 +83,7 @@ const screen_1 = require("../shell/screen");
|
|
|
71
83
|
const db_1 = require("../history/db");
|
|
72
84
|
const media_store_1 = require("../history/media-store");
|
|
73
85
|
const cleanup_1 = require("../history/cleanup");
|
|
86
|
+
const types_1 = require("../history/types");
|
|
74
87
|
const knowledge_1 = require("./knowledge");
|
|
75
88
|
const resolve_1 = require("../connectors/resolve");
|
|
76
89
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
|
|
@@ -221,6 +234,229 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
221
234
|
channelFor(chatId) {
|
|
222
235
|
return this.channelSourceMap.get(chatId) ?? 'telegram';
|
|
223
236
|
}
|
|
237
|
+
requiresDurableChannelIngress(source) {
|
|
238
|
+
return Boolean((this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(source)) ||
|
|
239
|
+
this.orchestration?.ownsChannelIngress(source));
|
|
240
|
+
}
|
|
241
|
+
orchestrationForApi(sessionId) {
|
|
242
|
+
return Boolean((this.orchestration?.ownsSession(sessionId) && !this.orchestration.canReturnToLegacy()) ||
|
|
243
|
+
(this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes('api')));
|
|
244
|
+
}
|
|
245
|
+
async sendLinkedOrchestrationChannel(...[binding, text, _id, file]) {
|
|
246
|
+
const channel = String(binding.channel), chat = String(binding.chat_id);
|
|
247
|
+
if (channel === 'wechat' && file)
|
|
248
|
+
return { state: 'failed', code: 'CHANNEL_ATTACHMENTS_UNSUPPORTED' };
|
|
249
|
+
let path;
|
|
250
|
+
if (file) {
|
|
251
|
+
try {
|
|
252
|
+
path = (0, file_delivery_1.resolveChannelFile)(this.agentConfig, file).path;
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return { state: 'failed', code: 'ATTACHMENT_UNAVAILABLE' };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
if (channel === 'whatsapp') {
|
|
260
|
+
// Persist the receiving number in the binding; an in-memory last-chat map
|
|
261
|
+
// can change while a worker is running or disappear after a restart.
|
|
262
|
+
const thread = String(binding.thread_key ?? '');
|
|
263
|
+
const account = thread.startsWith('whatsapp-account:') ? thread.slice('whatsapp-account:'.length) : undefined;
|
|
264
|
+
await this.sendWhatsAppMessage(chat, file?.caption || text, path, account, { asDocument: !!file && file.kind !== 'image' });
|
|
265
|
+
}
|
|
266
|
+
else if (channel === 'wechat')
|
|
267
|
+
await this.sendWeChatMessage(chat, text);
|
|
268
|
+
else
|
|
269
|
+
return { state: 'failed', code: 'DELIVERY_NOT_CONFIGURED' };
|
|
270
|
+
return { state: 'delivered' };
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return { state: 'unknown', code: 'PROVIDER_RECEIPT_UNKNOWN' };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async sendOrchestrationControl(channel, chatId, menu, meta) {
|
|
277
|
+
if (!['whatsapp', 'whatsapp_cloud', 'wechat'].includes(channel))
|
|
278
|
+
return (0, control_delivery_1.sendControlMenu)(this.agentConfig, channel, chatId, menu, meta);
|
|
279
|
+
const text = [menu.text, ...menu.buttons.map(button => `${button.label}: /orch ${button.data.replace(/^orch:/, '')}`)].join('\n');
|
|
280
|
+
const send = (0, delivery_1.channelSender)(() => this.agentConfig, fetch, () => false, (...args) => this.sendLinkedOrchestrationChannel(...args));
|
|
281
|
+
for (const part of (0, chunks_1.chunkText)(text, 1900)) {
|
|
282
|
+
const result = await send({ channel, chat_id: chatId, thread_key: channel === 'whatsapp' && meta.account_id ? `whatsapp-account:${meta.account_id}` : '' }, part, (0, crypto_1.randomUUID)());
|
|
283
|
+
if (result.state !== 'delivered')
|
|
284
|
+
throw new Error(result.code);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async getOrchestration() {
|
|
288
|
+
if (this.orchestration)
|
|
289
|
+
return this.orchestration;
|
|
290
|
+
if (!this.orchestrationStarting) {
|
|
291
|
+
this.orchestrationStarting = Promise.resolve().then(() => __importStar(require('../orchestration/runtime'))).then(({ AgentOrchestrationRuntime }) => AgentOrchestrationRuntime.open(this.agentConfig, this.gatewayConfig, this.agentDir, this.sessionStore, this.historyDb, {
|
|
292
|
+
sendLinkedChannel: (...args) => this.sendLinkedOrchestrationChannel(...args),
|
|
293
|
+
skills: () => this.skillRegistry,
|
|
294
|
+
refreshSkills: async () => {
|
|
295
|
+
try {
|
|
296
|
+
const { discoverCliSkills } = await Promise.resolve().then(() => __importStar(require('../orchestration/cli-skills')));
|
|
297
|
+
this.skillRegistry.cliSkills = await discoverCliSkills(this.agentConfig, undefined, this.gatewayConfig);
|
|
298
|
+
delete this.skillRegistry.cliDiscoveryError;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
this.skillRegistry.cliSkills = [];
|
|
302
|
+
this.skillRegistry.cliDiscoveryError = 'CLI_SKILL_DISCOVERY_UNAVAILABLE';
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
onManagedTurn: (sessionId, text, metrics, skills) => {
|
|
306
|
+
const key = `orchestration:${sessionId}`;
|
|
307
|
+
this.skillLearning?.onTurnStart(key, sessionId, text, skills, metrics.startedAt);
|
|
308
|
+
for (const id of metrics.toolIds)
|
|
309
|
+
this.skillLearning?.onToolUse(key, id);
|
|
310
|
+
this.skillLearning?.onTokenUsage(key, metrics.inputTokens, metrics.totalTokens);
|
|
311
|
+
this.skillLearning?.onTurnEnd(key, sessionId);
|
|
312
|
+
},
|
|
313
|
+
createAgentSession: async (sessionId, profile, model, scope) => {
|
|
314
|
+
const key = scope && scope.source !== 'api' ? scope.chatId : sessionId;
|
|
315
|
+
const old = this.sessions.get(key);
|
|
316
|
+
if (old) {
|
|
317
|
+
if (!old.isIdle(0))
|
|
318
|
+
throw Object.assign(new Error('Agent session is busy'), { code: 'CONFLICT' });
|
|
319
|
+
await old.stop();
|
|
320
|
+
this.sessions.delete(key);
|
|
321
|
+
}
|
|
322
|
+
if (this.sessions.size >= this.maxConcurrent)
|
|
323
|
+
throw new Error('Session pool full');
|
|
324
|
+
if (this.agentConfig.type !== 'app-agent')
|
|
325
|
+
(0, knowledge_1.spawnArchiveReindex)(this.agentConfig.workspace, this.agentConfig.knowledge, this.gatewayConfig.gateway.knowledge);
|
|
326
|
+
const agentSession = new process_1.SessionProcess(sessionId, scope?.source ?? 'api', this.agentConfig, this.gatewayConfig, this.sessionStore, scope?.chatId, profile);
|
|
327
|
+
agentSession.historyLimit = (0, process_1.resolveMaxHistoryMessages)(this.agentConfig.history?.maxHistoryMessages, this.gatewayConfig.gateway.history?.maxHistoryMessages);
|
|
328
|
+
if (model)
|
|
329
|
+
agentSession.modelOverride = model;
|
|
330
|
+
this.sessions.set(key, agentSession);
|
|
331
|
+
return agentSession;
|
|
332
|
+
},
|
|
333
|
+
releaseAgentSession: async (sessionId, agentSession) => {
|
|
334
|
+
await agentSession.stop();
|
|
335
|
+
for (const [key, value] of this.sessions)
|
|
336
|
+
if (value === agentSession)
|
|
337
|
+
this.sessions.delete(key);
|
|
338
|
+
},
|
|
339
|
+
})).then(orchestration => { this.orchestration = orchestration; return orchestration; }).catch(error => { this.orchestrationStarting = undefined; throw error; });
|
|
340
|
+
}
|
|
341
|
+
return this.orchestrationStarting;
|
|
342
|
+
}
|
|
343
|
+
async orchestrationActivity(sessionId, principalId, after) {
|
|
344
|
+
return (await this.getOrchestration()).activity(sessionId, principalId, after);
|
|
345
|
+
}
|
|
346
|
+
async subscribeResponseText(sessionId, principalId, receive) {
|
|
347
|
+
return (await this.getOrchestration()).subscribeText(sessionId, principalId, receive);
|
|
348
|
+
}
|
|
349
|
+
async voiceReplay(sessionId, principalId, responseId) {
|
|
350
|
+
return (await this.getOrchestration()).voiceAudio(sessionId, principalId, responseId);
|
|
351
|
+
}
|
|
352
|
+
saveVoiceReplay(sessionId, principalId, responseId, audio) {
|
|
353
|
+
this.orchestration?.saveVoiceAudio(sessionId, principalId, responseId, audio);
|
|
354
|
+
}
|
|
355
|
+
async authorizeVoiceSession(sessionId, principalId) {
|
|
356
|
+
(await this.getOrchestration()).authorizeSession(sessionId, principalId);
|
|
357
|
+
}
|
|
358
|
+
async subscribeVoiceResults(sessionId, principalId, receive, gender) {
|
|
359
|
+
return (await this.getOrchestration()).subscribeVoiceResults(sessionId, principalId, receive, gender);
|
|
360
|
+
}
|
|
361
|
+
recordVoicePlayback(responseId, principalId, progress, state) {
|
|
362
|
+
this.orchestration?.recordPlayback(responseId, principalId, progress, state);
|
|
363
|
+
}
|
|
364
|
+
async submitVoiceUtterance(sessionId, chatId, principalId, text, utteranceId, allowTools, model) {
|
|
365
|
+
if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.voice?.enabled)
|
|
366
|
+
throw new Error('Voice is disabled');
|
|
367
|
+
if (!(await this.apiSessionExists(chatId, sessionId)))
|
|
368
|
+
throw new Error('Session not found');
|
|
369
|
+
const orchestration = await this.getOrchestration();
|
|
370
|
+
const accepted = orchestration.submitInput({ scope: { agentId: this.agentConfig.id, agentSessionId: sessionId, source: 'api', accountId: principalId,
|
|
371
|
+
chatId, threadKey: '', principalId }, text, model, modality: 'live_voice', ingressKey: `utterance:${utteranceId}` }, { execute: allowTools, writeMemory: false });
|
|
372
|
+
// The browser refreshes history on utterance.accepted, before inference finishes.
|
|
373
|
+
await orchestration.flushHistory();
|
|
374
|
+
return { ...accepted, responseId: () => orchestration.responseIdForInput(accepted.inputId) };
|
|
375
|
+
}
|
|
376
|
+
async listApiTasks(sessionId, principalId, page = 0, pageSize = 10, includeFinished = false) {
|
|
377
|
+
if (!this.agentConfig.orchestration?.enabled)
|
|
378
|
+
throw new Error('ORCHESTRATION_DISABLED');
|
|
379
|
+
const runtime = await this.getOrchestration();
|
|
380
|
+
runtime.authorizeSession(sessionId, principalId);
|
|
381
|
+
return runtime.taskControls.list(sessionId, principalId, page, pageSize, includeFinished);
|
|
382
|
+
}
|
|
383
|
+
async showApiTask(sessionId, principalId, taskId) {
|
|
384
|
+
if (!this.agentConfig.orchestration?.enabled)
|
|
385
|
+
throw new Error('ORCHESTRATION_DISABLED');
|
|
386
|
+
const runtime = await this.getOrchestration();
|
|
387
|
+
const detail = runtime.taskControls.detail(sessionId, principalId, taskId);
|
|
388
|
+
const task = runtime.store.task(taskId);
|
|
389
|
+
// Full retained result, with worker-only skill context removed by status().
|
|
390
|
+
return { ...runtime.tasks.status(task.conversationId, principalId, taskId)[0], ...detail };
|
|
391
|
+
}
|
|
392
|
+
async cancelApiTask(sessionId, principalId, taskId) {
|
|
393
|
+
if (!this.agentConfig.orchestration?.enabled)
|
|
394
|
+
throw new Error('ORCHESTRATION_DISABLED');
|
|
395
|
+
const runtime = await this.getOrchestration();
|
|
396
|
+
const task = runtime.taskControls.cancel(sessionId, principalId, taskId);
|
|
397
|
+
return task;
|
|
398
|
+
}
|
|
399
|
+
stopVoiceResponse(sessionId) { this.orchestration?.stopResponse(sessionId); }
|
|
400
|
+
async sendOrchestratedApi(sessionId, chatId, message, opts, onText, onTool) {
|
|
401
|
+
const semantic = this.agentConfig.orchestration?.conversation?.semanticIntake === true;
|
|
402
|
+
if (!semantic && this.pendingApiSessions.has(sessionId))
|
|
403
|
+
throw Object.assign(new Error('Session already has a pending request'), { code: 'CONFLICT' });
|
|
404
|
+
if (!opts.principalId)
|
|
405
|
+
throw new Error('Authenticated principal required for conversation orchestration');
|
|
406
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
407
|
+
if (!semantic)
|
|
408
|
+
this.pendingApiSessions.add(sessionId); // reserve before the first await
|
|
409
|
+
try {
|
|
410
|
+
const orchestration = await this.getOrchestration();
|
|
411
|
+
await this.sessionStore.ensureApiSession(this.agentConfig.id, chatId, sessionId);
|
|
412
|
+
this.apiChatIds.set(sessionId, chatId);
|
|
413
|
+
const media = opts.mediaFiles?.length ? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger) : [];
|
|
414
|
+
const index = await this.sessionStore.loadIndex(this.agentConfig.id, chatId, 'api');
|
|
415
|
+
const saved = index?.sessions.find(session => session.id === sessionId)?.imageConfig;
|
|
416
|
+
const selected = { ...(saved ? AgentRunner.durableImageConfig(saved) : {}), ...opts.imageParams };
|
|
417
|
+
const imageParams = AgentRunner.remapImageParamsRefs(Object.keys(selected).length ? selected : undefined, opts.mediaFiles, media);
|
|
418
|
+
const videoParams = AgentRunner.remapVideoParamsRefs(opts.videoParams, opts.mediaFiles, media);
|
|
419
|
+
if (videoParams)
|
|
420
|
+
await this.sessionStore.updateSessionMeta(this.agentConfig.id, chatId, sessionId, { videoConfig: videoParams }, 'api');
|
|
421
|
+
const imageConfig = opts.imageParams && imageParams ? AgentRunner.durableImageConfig(imageParams) : undefined;
|
|
422
|
+
if (imageConfig)
|
|
423
|
+
await this.sessionStore.updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig }, 'api');
|
|
424
|
+
const requestId = opts.requestId ?? (0, crypto_1.randomUUID)();
|
|
425
|
+
const scopedInput = { scope: { agentId: this.agentConfig.id, agentSessionId: sessionId, source: 'api', accountId: opts.principalId,
|
|
426
|
+
chatId, threadKey: '', principalId: opts.principalId }, text: message || '[Attachment inspection requested]', attachmentIds: media,
|
|
427
|
+
requestId, storeUserMessage: !opts.skipUserMessage,
|
|
428
|
+
metadata: imageParams || videoParams ? { promptContext: (imageParams ? AgentRunner.buildImageParamsNote(imageParams) : '') + (videoParams ? AgentRunner.buildVideoParamsNote(videoParams) : ''), imageRefs: imageParams?.image_refs } : undefined };
|
|
429
|
+
let text;
|
|
430
|
+
if (semantic) {
|
|
431
|
+
const accepted = orchestration.submitInput({ ...scopedInput, model: opts.model }, { execute: opts.allowTools ?? false, writeMemory: false }, onTool);
|
|
432
|
+
const lengths = new Map();
|
|
433
|
+
const unsubscribe = orchestration.subscribeText(sessionId, opts.principalId, event => {
|
|
434
|
+
if (!orchestration.responseBelongsToInput(accepted.inputId, event.responseId) || !event.text)
|
|
435
|
+
return;
|
|
436
|
+
const before = lengths.get(event.responseId) ?? 0;
|
|
437
|
+
if (event.text.length > before)
|
|
438
|
+
onText?.(event.text.slice(before));
|
|
439
|
+
lengths.set(event.responseId, event.text.length);
|
|
440
|
+
});
|
|
441
|
+
try {
|
|
442
|
+
text = await accepted.response;
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
unsubscribe();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else
|
|
449
|
+
text = await orchestration.send(scopedInput, { execute: opts.allowTools ?? false, writeMemory: false }, { timeoutMs: opts.timeoutMs, model: opts.model, onText, onTool });
|
|
450
|
+
if (opts.waitForTasks)
|
|
451
|
+
text = await orchestration.waitForTaskReport(sessionId, opts.principalId, requestId, text, deadline);
|
|
452
|
+
this.addApiAttachments(sessionId, orchestration.responseFiles(sessionId, requestId).map(ref => media_store_1.MediaStore.resolvePath(this.agentsBaseDir, this.agentConfig.id, ref)));
|
|
453
|
+
return { text, attachments: this.popApiAttachments(sessionId) };
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
if (!semantic)
|
|
457
|
+
this.pendingApiSessions.delete(sessionId);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
224
460
|
constructor(agentConfig, gatewayConfig, logger) {
|
|
225
461
|
super();
|
|
226
462
|
this.stopping = false;
|
|
@@ -241,6 +477,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
241
477
|
this.slackThreadTs = new Map();
|
|
242
478
|
this.receiver = null;
|
|
243
479
|
this.discordReceiver = null;
|
|
480
|
+
this.discordCommandConfig = '';
|
|
244
481
|
// LINE slow-LLM postback button manager (null when LINE disabled or threshold=0).
|
|
245
482
|
this.lineReply = null;
|
|
246
483
|
// Slack has no reply-token TTL to work around (see the plan's "no reply-manager
|
|
@@ -271,6 +508,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
271
508
|
// that omits account_id) answer on the SAME number the user wrote to
|
|
272
509
|
// instead of guessing 'default' and replying from the wrong line.
|
|
273
510
|
this.whatsappAccountForChat = new Map();
|
|
511
|
+
// WeChat: unlike every credential-input channel above, constructed
|
|
512
|
+
// unconditionally (there's no config field to gate on — the link flow is
|
|
513
|
+
// QR device-pairing, not a typed-in token, same reasoning WhatsApp/Baileys
|
|
514
|
+
// uses). Does nothing until startWeChatLinking() is called or a
|
|
515
|
+
// previously-linked session is resumed at start().
|
|
516
|
+
this.wechat = null;
|
|
274
517
|
this.idleCleanerTimer = null;
|
|
275
518
|
// Tracks session IDs with an in-flight API request (prevents concurrent turns)
|
|
276
519
|
this.pendingApiSessions = new Set();
|
|
@@ -346,6 +589,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
346
589
|
this.apiChatIds = new Map();
|
|
347
590
|
// Skill registry for detecting /skill-name commands in user messages
|
|
348
591
|
this.skillRegistry = { skills: new Map() };
|
|
592
|
+
this.skillNotificationOrigins = new Map();
|
|
349
593
|
// Cancel function for the daily history cleanup timer
|
|
350
594
|
this.cancelCleanup = null;
|
|
351
595
|
/**
|
|
@@ -388,7 +632,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
388
632
|
}
|
|
389
633
|
});
|
|
390
634
|
};
|
|
391
|
-
this.
|
|
635
|
+
this.telegramCommandConfig = '';
|
|
636
|
+
this.agentConfig = (0, gateway_config_1.applyGatewayOrchestration)(agentConfig, gatewayConfig);
|
|
392
637
|
this.gatewayConfig = gatewayConfig;
|
|
393
638
|
this.logger = logger ?? (0, logger_1.createLogger)(agentConfig.id, gatewayConfig.gateway.logDir);
|
|
394
639
|
// Resolve agentsBaseDir: workspace is at <agentsBaseDir>/<agentId>/workspace
|
|
@@ -417,6 +662,25 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
417
662
|
return this.skillRegistry;
|
|
418
663
|
}
|
|
419
664
|
/** Wire the skill-learning manager (planning-62). Optional; unset = feature off. */
|
|
665
|
+
async notifySkillLearning(sessionId, text) {
|
|
666
|
+
if (this.orchestration?.ownsSession(sessionId)) {
|
|
667
|
+
const conversation = this.orchestration.store.get('SELECT source,chat_id FROM conversations WHERE agent_session_id=?', sessionId);
|
|
668
|
+
const source = conversation?.source;
|
|
669
|
+
const index = source && source !== 'api' ? await this.sessionStore.loadIndex(this.agentConfig.id, String(conversation.chat_id), source) : undefined;
|
|
670
|
+
await this.orchestration.notifySession(sessionId, text, source === 'api' || index?.activeSessionId === sessionId);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const origin = this.skillNotificationOrigins.get(sessionId);
|
|
674
|
+
if (!origin)
|
|
675
|
+
return; // Diary only when the original destination is unavailable.
|
|
676
|
+
this.historyDb.insertMessage({ chatId: `${origin.source}-${origin.chatId}`, sessionId, source: origin.source, role: 'assistant', content: text, ts: Date.now() });
|
|
677
|
+
const index = await this.sessionStore.loadIndex(this.agentConfig.id, origin.chatId, origin.source);
|
|
678
|
+
if (index?.activeSessionId !== sessionId)
|
|
679
|
+
return;
|
|
680
|
+
const result = await (0, delivery_1.channelSender)(() => this.agentConfig)({ channel: origin.source, chat_id: origin.chatId, thread_key: origin.thread }, text, (0, crypto_1.randomUUID)());
|
|
681
|
+
if (result.state !== 'delivered')
|
|
682
|
+
this.logger.warn('Skill notification delivery failed', { sessionId, code: result.code });
|
|
683
|
+
}
|
|
420
684
|
setSkillLearning(manager) {
|
|
421
685
|
this.skillLearning = manager;
|
|
422
686
|
}
|
|
@@ -442,7 +706,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
442
706
|
req.on('data', (chunk) => {
|
|
443
707
|
raw += chunk;
|
|
444
708
|
});
|
|
445
|
-
req.on('end', () => {
|
|
709
|
+
req.on('end', async () => {
|
|
446
710
|
if (url.pathname === '/command') {
|
|
447
711
|
this.handleCommandRequest(raw, res);
|
|
448
712
|
return;
|
|
@@ -460,26 +724,79 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
460
724
|
// receiver (Bun) can try to reuse the stale socket → "socket connection
|
|
461
725
|
// was closed unexpectedly". Closing per-request avoids the race entirely.
|
|
462
726
|
res.setHeader('Connection', 'close');
|
|
463
|
-
res.writeHead(200);
|
|
464
|
-
res.end('ok');
|
|
465
727
|
try {
|
|
466
728
|
const params = JSON.parse(raw);
|
|
467
729
|
const meta = params.meta ?? {};
|
|
468
730
|
const chatId = meta['chat_id'] ?? '';
|
|
469
731
|
const content = params.content ?? '';
|
|
470
|
-
// Set channel early so all handlers (including session commands) have it
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
: meta['source'] === 'whatsapp'
|
|
478
|
-
? 'whatsapp'
|
|
479
|
-
: meta['source'] === 'whatsapp_cloud'
|
|
480
|
-
? 'whatsapp_cloud'
|
|
481
|
-
: 'telegram');
|
|
732
|
+
// Set channel early so all handlers (including session commands) have it.
|
|
733
|
+
// Derived from the canonical CHAT_CHANNELS list (see history/types.ts's
|
|
734
|
+
// doc comment) — a hand-copied ternary here is exactly what silently
|
|
735
|
+
// turned every WeChat (and, historically, Slack) message into
|
|
736
|
+
// 'telegram' before, since a new channel added elsewhere is easy to
|
|
737
|
+
// forget adding to a chain of `=== 'x' ? 'x' : ...` checks.
|
|
738
|
+
const channelSource = ((0, types_1.isChatChannel)(meta['source']) ? meta['source'] : 'telegram');
|
|
482
739
|
this.channelSourceMap.set(chatId, channelSource);
|
|
740
|
+
const channelOrchestration = this.orchestration?.ownsChannel(channelSource, chatId) || (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource));
|
|
741
|
+
if (channelSource !== 'telegram' && this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource) && /^\/sessions?(?:\s|$)/.test(content.trim())) {
|
|
742
|
+
const index = await this.sessionStore.listSessions(this.agentConfig.id, chatId, channelSource);
|
|
743
|
+
const current = index.sessions.find(session => session.id === index.activeSessionId);
|
|
744
|
+
const text = content.trim() === '/sessions'
|
|
745
|
+
? `Sessions\n${index.sessions.slice(0, 15).map(session => `${session.id === index.activeSessionId ? '✅ ' : ''}${session.name}\n${session.id}`).join('\n\n')}`
|
|
746
|
+
: `Current session: ${current?.name ?? '(unnamed)'}\n${index.activeSessionId}\nMode: Orchestration\nModel: ${this.agentConfig.claude.model}\nMessages: ${current?.messageCount ?? 0}\n\nCommands: /session /sessions /voice /voices /tasks /stop`;
|
|
747
|
+
await this.sendOrchestrationControl(channelSource, chatId, { text, buttons: [] }, meta);
|
|
748
|
+
res.writeHead(200);
|
|
749
|
+
res.end('ok');
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (channelSource !== 'telegram' && (/^\/(voice|voices|tasks|stop|orch)(?:\s|$)/.test(content.trim()) || (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource) && content.trim() === '/help'))) {
|
|
753
|
+
const enabled = !!(this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource));
|
|
754
|
+
if (enabled) {
|
|
755
|
+
const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, channelSource);
|
|
756
|
+
const runtime = await this.getOrchestration();
|
|
757
|
+
const scope = { channel: channelSource, chatId, thread: meta.thread_ts ?? meta.message_thread_id ?? '', sessionId, principalId: `${channelSource}:${meta.user_id ?? meta.user ?? chatId}` };
|
|
758
|
+
let menu;
|
|
759
|
+
try {
|
|
760
|
+
menu = await runtime.channelControls.handle(scope, content.trim());
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
menu = { text: 'Control unavailable or expired. Please open the command again.', buttons: [] };
|
|
764
|
+
}
|
|
765
|
+
await this.sendOrchestrationControl(channelSource, chatId, menu, meta);
|
|
766
|
+
res.writeHead(200);
|
|
767
|
+
res.end('ok');
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
// Keep the legacy /stop path, but never turn disabled controls into model prompts.
|
|
771
|
+
if (!/^\/stop(?:\s|$)/.test(content.trim())) {
|
|
772
|
+
await this.sendOrchestrationControl(channelSource, chatId, { text: 'These controls require orchestration mode.', buttons: [] }, meta);
|
|
773
|
+
res.writeHead(200);
|
|
774
|
+
res.end('ok');
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (channelOrchestration && !(0, builtin_commands_1.isBuiltinCommand)(content.trim(), channelSource)) {
|
|
779
|
+
const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, channelSource);
|
|
780
|
+
const orchestration = await this.getOrchestration();
|
|
781
|
+
const { media, quoted, details } = await (0, channel_input_media_1.channelInputMedia)(this.agentConfig, this.agentsBaseDir, channelSource, chatId, meta, AgentRunner.discardEphemeralStaging);
|
|
782
|
+
const senderId = meta.user_id ?? meta.user ?? chatId;
|
|
783
|
+
const accepted = orchestration.submitInput({ scope: { agentId: this.agentConfig.id, agentSessionId: sessionId, source: channelSource,
|
|
784
|
+
accountId: this.agentConfig.id, chatId, threadKey: channelSource === 'whatsapp' && meta.account_id ? `whatsapp-account:${meta.account_id}` : meta.thread_ts ?? meta.message_thread_id ?? '', principalId: `${channelSource}:${senderId}` },
|
|
785
|
+
text: content || '[Attachment inspection requested]', attachmentIds: media, trustedChannelMember: true,
|
|
786
|
+
modality: ['voice', 'audio'].includes(meta.media_type ?? meta.attachment_kind) ? 'voice_note' : 'text',
|
|
787
|
+
ingressKey: meta.message_id, metadata: { senderId, senderName: meta.sender_name, platformMessageId: meta.message_id, platformMessageIds: meta.message_ids_json ? JSON.parse(meta.message_ids_json) : undefined, mediaGroupId: meta.media_group_id,
|
|
788
|
+
attachmentName: meta.attachment_name, mediaType: meta.media_type ?? meta.attachment_kind, repliedText: meta.replied_text, repliedMessageId: meta.replied_message_id, repliedSender: meta.replied_user, repliedAttachmentIds: quoted, attachmentDetails: details, attachmentError: meta.attachment_error } }, { execute: this.agentConfig.allow_tools !== false, writeMemory: this.agentConfig.allow_tools !== false });
|
|
789
|
+
// The durable receipt precedes receiver acknowledgment and inference.
|
|
790
|
+
res.writeHead(200);
|
|
791
|
+
res.end('ok');
|
|
792
|
+
void accepted.response.catch(error => {
|
|
793
|
+
// The durable decision/outbox owns failure delivery and activity, including autonomous turns.
|
|
794
|
+
this.logger.error('Conversation channel turn failed', { chatId, code: error.code ?? 'ORCHESTRATION_ERROR', ...(error.timeout ? { timeout: error.timeout } : {}) });
|
|
795
|
+
});
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
res.writeHead(200);
|
|
799
|
+
res.end('ok');
|
|
483
800
|
// Slack: remember the current message's thread context so the
|
|
484
801
|
// auto-forward fallback (writeAutoForward) can stay in-thread. A
|
|
485
802
|
// top-level message clears it so a later fallback isn't misrouted
|
|
@@ -548,6 +865,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
548
865
|
return;
|
|
549
866
|
}
|
|
550
867
|
catch (err) {
|
|
868
|
+
if (!res.headersSent) {
|
|
869
|
+
res.writeHead(503);
|
|
870
|
+
res.end('Channel input was not accepted');
|
|
871
|
+
}
|
|
551
872
|
this.logger.warn('Failed to parse channel callback body', {
|
|
552
873
|
error: err.message,
|
|
553
874
|
});
|
|
@@ -591,6 +912,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
591
912
|
return;
|
|
592
913
|
}
|
|
593
914
|
const session = this.sessions.get(chatId);
|
|
915
|
+
if (session?.runtimeProfile || this.requiresDurableChannelIngress('telegram')) {
|
|
916
|
+
respond({ ok: false, error: 'Recovery is owned by orchestration; legacy turn replay is disabled.' });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
594
919
|
const autoRecover = this.gatewayConfig.gateway.selfHealing?.autoRecover === true;
|
|
595
920
|
const req = {
|
|
596
921
|
incidentId: body.incidentId ?? '',
|
|
@@ -758,6 +1083,119 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
758
1083
|
return;
|
|
759
1084
|
}
|
|
760
1085
|
const command = body.command;
|
|
1086
|
+
if (command === 'telegram_voices') {
|
|
1087
|
+
const chatId = body.chat_id ?? '', payload = body.payload ?? {};
|
|
1088
|
+
if (!/^\d+$/.test(chatId) || payload.user_id !== chatId) {
|
|
1089
|
+
respond({ success: false }, 400);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.orchestration.channels?.includes('telegram')) {
|
|
1093
|
+
respond({ success: false, error: 'Voice controls unavailable.' });
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
const voices = (await this.getOrchestration()).telegramVoices;
|
|
1098
|
+
if (payload.action === 'dismiss' && typeof payload.menu_id === 'string') {
|
|
1099
|
+
voices.dismiss(chatId, payload.menu_id);
|
|
1100
|
+
respond({ success: true });
|
|
1101
|
+
}
|
|
1102
|
+
else if (payload.action === 'choose' && typeof payload.menu_id === 'string' && typeof payload.index === 'number')
|
|
1103
|
+
respond({ success: true, ...await voices.choose(chatId, payload.menu_id, payload.index) });
|
|
1104
|
+
else
|
|
1105
|
+
respond({ success: true, ...await voices.menu(chatId, typeof payload.page === 'number' ? payload.page : 0, typeof payload.menu_id === 'string' ? payload.menu_id : undefined, typeof payload.gender === 'string' ? payload.gender : undefined) });
|
|
1106
|
+
}
|
|
1107
|
+
catch {
|
|
1108
|
+
respond({ success: false, error: 'Voice list unavailable or provider changed. Use /voices to refresh.' });
|
|
1109
|
+
}
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (command === 'telegram_tasks') {
|
|
1113
|
+
const chatId = body.chat_id ?? '', payload = body.payload ?? {}, principalId = `telegram:${payload.user_id ?? ''}`;
|
|
1114
|
+
if (!/^\d+$/.test(chatId) || payload.user_id !== chatId) {
|
|
1115
|
+
respond({ success: false }, 400);
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.orchestration.channels?.includes('telegram')) {
|
|
1119
|
+
respond({ success: false, error: 'Task controls require orchestration mode.' });
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
try {
|
|
1123
|
+
const runtime = await this.getOrchestration();
|
|
1124
|
+
const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, 'telegram');
|
|
1125
|
+
if (payload.session_id !== undefined && payload.session_id !== sessionId) {
|
|
1126
|
+
respond({ success: false, error: 'TASK_SESSION_CHANGED' });
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (payload.action === 'detail' || payload.action === 'cancel') {
|
|
1130
|
+
if (typeof payload.task_id !== 'string')
|
|
1131
|
+
throw Error('INVALID_TASK');
|
|
1132
|
+
const task = payload.action === 'cancel' ? runtime.taskControls.cancel(sessionId, principalId, payload.task_id) : runtime.taskControls.detail(sessionId, principalId, payload.task_id);
|
|
1133
|
+
respond({ success: true, sessionId, task });
|
|
1134
|
+
}
|
|
1135
|
+
else
|
|
1136
|
+
respond({ success: true, sessionId, ...runtime.taskControls.list(sessionId, principalId, typeof payload.page === 'number' ? payload.page : 0) });
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
respond({ success: false, error: 'Task unavailable. Use /tasks to refresh.' });
|
|
1140
|
+
}
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (command === 'task_stop') {
|
|
1144
|
+
const chatId = body.chat_id ?? '', principalId = `telegram:${body.payload?.user_id ?? ''}`;
|
|
1145
|
+
if (!/^\d+$/.test(chatId) || body.payload?.user_id !== chatId) {
|
|
1146
|
+
respond({ success: false, error: 'invalid_request' }, 400);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
if (!(this.agentConfig.orchestration?.enabled && this.agentConfig.orchestration.channels?.includes('telegram'))) {
|
|
1151
|
+
if (body.payload?.menu_id) {
|
|
1152
|
+
respond({ success: false, error: 'Orchestration is disabled.' });
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
await this.handleCommandStop(chatId);
|
|
1156
|
+
respond({ success: true, legacy: true });
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const runtime = await this.getOrchestration();
|
|
1160
|
+
const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, 'telegram');
|
|
1161
|
+
if (typeof body.payload?.menu_id === 'string' && typeof body.payload?.index === 'number') {
|
|
1162
|
+
respond({ success: true, ...runtime.stopControls.choose(sessionId, principalId, body.payload.menu_id, body.payload.index) });
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
const menu = runtime.stopControls.open(sessionId, principalId);
|
|
1166
|
+
respond({ success: true, ...menu, text: (0, stop_controls_1.stopMenuText)(menu, true) });
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
catch {
|
|
1170
|
+
respond({ success: false, error: 'Selection unavailable. Use /stop to refresh the task list.' });
|
|
1171
|
+
}
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (command === 'telegram_voice') {
|
|
1175
|
+
const chatId = body.chat_id ?? '';
|
|
1176
|
+
const value = body.payload?.enabled;
|
|
1177
|
+
const mode = body.payload?.mode;
|
|
1178
|
+
if (!/^\d+$/.test(chatId) || (value !== undefined && typeof value !== 'boolean') || (mode !== undefined && !voice_reply_policy_1.VOICE_REPLY_MODES.includes(mode))) {
|
|
1179
|
+
respond({ success: false, error: 'invalid_request' }, 400);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.orchestration.channels?.includes('telegram') || !this.agentConfig.voice?.enabled || this.agentConfig.voice?.notes?.replyWithVoice === false) {
|
|
1183
|
+
respond({ success: false, error: 'voice_not_configured' });
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
try {
|
|
1187
|
+
const runtime = await this.getOrchestration();
|
|
1188
|
+
if (mode !== undefined)
|
|
1189
|
+
runtime.store.setTelegramVoiceMode(chatId, mode);
|
|
1190
|
+
else if (typeof value === 'boolean')
|
|
1191
|
+
runtime.store.setTelegramVoice(chatId, value);
|
|
1192
|
+
respond({ success: true, enabled: runtime.store.telegramVoice(chatId), mode: runtime.store.telegramVoiceMode(chatId) });
|
|
1193
|
+
}
|
|
1194
|
+
catch {
|
|
1195
|
+
respond({ success: false, error: 'voice_settings_unavailable' }, 500);
|
|
1196
|
+
}
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
761
1199
|
if (command === 'get_model') {
|
|
762
1200
|
respond({ model: this.agentConfig.claude.model });
|
|
763
1201
|
return;
|
|
@@ -790,6 +1228,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
790
1228
|
// its initData. The browser-facing routes live on the gateway; here we only
|
|
791
1229
|
// mint the pairing and hand back the phone-openable link.
|
|
792
1230
|
if (command === 'cli_pair') {
|
|
1231
|
+
if (this.gatewayConfig.gateway.headless !== false || this.agentConfig.type === 'app-agent' || this.agentConfig.orchestration?.enabled) {
|
|
1232
|
+
respond({ success: false, error: 'interactive_mode_required' });
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
793
1235
|
const payload = body.payload ?? {};
|
|
794
1236
|
const channel = payload['channel'];
|
|
795
1237
|
const userId = typeof payload['user_id'] === 'string' ? payload['user_id'] : '';
|
|
@@ -1074,7 +1516,21 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1074
1516
|
for (const entry of entries) {
|
|
1075
1517
|
const meta = entry.meta ?? {};
|
|
1076
1518
|
const content = entry.content ?? '';
|
|
1077
|
-
|
|
1519
|
+
// `image_path` is used for ANY attachment, not just photos (see
|
|
1520
|
+
// handleWeChatInboundMessage's doc comment) — a non-image
|
|
1521
|
+
// extension (e.g. WeChat's PDF/file support) must not show as
|
|
1522
|
+
// "(photo)" in the transcript.
|
|
1523
|
+
const isImagePath = /\.(jpe?g|png|gif|webp|heic|bmp)$/i.test(meta['image_path'] ?? '');
|
|
1524
|
+
const attachmentPlaceholder = meta['attachment_file_id']
|
|
1525
|
+
? '(photo)'
|
|
1526
|
+
: meta['image_path']
|
|
1527
|
+
? (isImagePath ? '(photo)' : '(file)')
|
|
1528
|
+
: meta['document_path']
|
|
1529
|
+
? '(document)'
|
|
1530
|
+
: meta['sticker_path']
|
|
1531
|
+
? '(sticker)'
|
|
1532
|
+
: '';
|
|
1533
|
+
const userContent = content || attachmentPlaceholder;
|
|
1078
1534
|
const userTs = Date.now();
|
|
1079
1535
|
await this.sessionStore.appendTelegramMessage(this.agentConfig.id, chatId, sessionId, {
|
|
1080
1536
|
role: 'user',
|
|
@@ -1220,6 +1676,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1220
1676
|
session.setProcessing(true);
|
|
1221
1677
|
const turnText = blocks.join('\n');
|
|
1222
1678
|
// Skill-learning: mark the start of a user turn (cheap, best-effort).
|
|
1679
|
+
const originMeta = entries[0]?.meta ?? {};
|
|
1680
|
+
this.skillNotificationOrigins.set(sessionId, { source: channelSource, chatId, thread: originMeta.thread_ts ?? originMeta.message_thread_id ?? '' });
|
|
1223
1681
|
this.skillLearning?.onTurnStart(chatId, sessionId, entries[0]?.content ?? '', invokedSkills);
|
|
1224
1682
|
session.sendMessage(turnText);
|
|
1225
1683
|
// Remember this turn for the C1 guarded resend (Phase 3b): reset the
|
|
@@ -1717,6 +2175,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1717
2175
|
let lastAssistantTextThisTurn = '';
|
|
1718
2176
|
let typingDoneTimer = null;
|
|
1719
2177
|
const TYPING_DONE_DELAY_MS = 3000;
|
|
2178
|
+
// Same hand-copied-ternary gap CHAT_CHANNELS' doc comment warns about —
|
|
2179
|
+
// 'wechat' was missing here too (confirmed live 2026-09-11), so this
|
|
2180
|
+
// turn-completion tracking never recognized a wechat_reply tool call.
|
|
1720
2181
|
const replyToolName = source === 'discord'
|
|
1721
2182
|
? 'mcp__gateway__discord_reply'
|
|
1722
2183
|
: source === 'line'
|
|
@@ -1727,7 +2188,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1727
2188
|
? 'mcp__gateway__whatsapp_reply'
|
|
1728
2189
|
: source === 'whatsapp_cloud'
|
|
1729
2190
|
? 'mcp__gateway__whatsapp_cloud_reply'
|
|
1730
|
-
: '
|
|
2191
|
+
: source === 'wechat'
|
|
2192
|
+
? 'mcp__gateway__wechat_reply'
|
|
2193
|
+
: 'mcp__gateway__telegram_reply';
|
|
1731
2194
|
proc.on('output', (line) => {
|
|
1732
2195
|
try {
|
|
1733
2196
|
const obj = JSON.parse(line);
|
|
@@ -2090,7 +2553,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2090
2553
|
// event. Start the typing-done timer here so the indicator stays alive
|
|
2091
2554
|
// through multi-turn tool-call sequences and only stops when all work is done.
|
|
2092
2555
|
if (obj['type'] === 'session_idle' && proc.backend === 'pty-shell') {
|
|
2093
|
-
//
|
|
2556
|
+
// An agent can become idle immediately after dispatching background
|
|
2094
2557
|
// Agent/Workflow work. Keep the Telegram typing signal and processing
|
|
2095
2558
|
// sentinel alive until the task notification starts its follow-up turn.
|
|
2096
2559
|
const waitingForBackgroundWork = proc.retainBackgroundWorkingState();
|
|
@@ -2146,7 +2609,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2146
2609
|
}, this.channelFor(mapKey)).catch(() => { });
|
|
2147
2610
|
}
|
|
2148
2611
|
proc.on('backgroundWorkExpired', () => {
|
|
2149
|
-
// The child task never notified its
|
|
2612
|
+
// The child task never notified its agent within the bounded grace
|
|
2150
2613
|
// window; release the Telegram typing signal instead of leaving it live.
|
|
2151
2614
|
this.writeTypingDone(mapKey);
|
|
2152
2615
|
});
|
|
@@ -2211,6 +2674,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2211
2674
|
});
|
|
2212
2675
|
return proc;
|
|
2213
2676
|
}
|
|
2677
|
+
apiTaskStopReply(sessionId, principalId, text) {
|
|
2678
|
+
return this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes('api') ? this.orchestration?.stopControls.replyCommand(sessionId, principalId, text) : undefined;
|
|
2679
|
+
}
|
|
2214
2680
|
static isApiBuiltinCommand(content) {
|
|
2215
2681
|
return (0, builtin_commands_1.isBuiltinCommand)(content, 'api');
|
|
2216
2682
|
}
|
|
@@ -2410,6 +2876,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2410
2876
|
* "No turn in progress." that fired whenever interrupt() no-op'd mid-spawn.
|
|
2411
2877
|
*/
|
|
2412
2878
|
async handleCommandStop(chatId) {
|
|
2879
|
+
if (this.orchestration && this.agentConfig.orchestration?.enabled) {
|
|
2880
|
+
const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, this.channelFor(chatId));
|
|
2881
|
+
if (this.orchestration.stopResponse(sessionId)) {
|
|
2882
|
+
this.writeAutoForward(chatId, 'Response stopped. Tasks remain active.');
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2413
2886
|
// Cancel queued + buffered work first so nothing flushes in behind the interrupt.
|
|
2414
2887
|
const buffered = this.channelCoalesce.get(chatId);
|
|
2415
2888
|
if (buffered?.timer)
|
|
@@ -2791,7 +3264,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2791
3264
|
const { immediate, deferred } = await this.restartOrDefer({
|
|
2792
3265
|
skipBusy: false,
|
|
2793
3266
|
deferIdle: true,
|
|
2794
|
-
|
|
3267
|
+
// Managed turns use a fresh subprocess/config on their next decision or task.
|
|
3268
|
+
// Never send them through legacy restart/replay while effects may be in flight.
|
|
3269
|
+
filter: (proc) => !proc.runtimeProfile && proc.connectorConfigChanged(connectorId, target),
|
|
2795
3270
|
});
|
|
2796
3271
|
return { restarted: immediate + deferred > 0 };
|
|
2797
3272
|
}
|
|
@@ -2935,6 +3410,21 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2935
3410
|
}
|
|
2936
3411
|
return;
|
|
2937
3412
|
}
|
|
3413
|
+
// WeChat has no .forward consumer either — same gap that used to silently
|
|
3414
|
+
// drop every command reply / no-tool-call fallback for Slack (confirmed
|
|
3415
|
+
// live 2026-09-11: the agent's plain-text reply showed up in the web
|
|
3416
|
+
// session view but never reached the user's phone, because it landed in
|
|
3417
|
+
// a chatId.forward file nothing reads for this channel). Deliver via the
|
|
3418
|
+
// manager directly, mirroring Slack's branch above.
|
|
3419
|
+
if (this.channelFor(chatId) === 'wechat') {
|
|
3420
|
+
void this.wechat?.sendMessage(chatId, text).catch((err) => {
|
|
3421
|
+
this.logger.warn('WeChat auto-forward failed', {
|
|
3422
|
+
chatId,
|
|
3423
|
+
error: err instanceof Error ? err.message : String(err),
|
|
3424
|
+
});
|
|
3425
|
+
});
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
2938
3428
|
const typingDir = this.getTypingDir(chatId);
|
|
2939
3429
|
const forwardPath = path.join(typingDir, `${chatId}.forward`);
|
|
2940
3430
|
try {
|
|
@@ -3003,7 +3493,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3003
3493
|
startIdleCleaner() {
|
|
3004
3494
|
this.idleCleanerTimer = setInterval(async () => {
|
|
3005
3495
|
for (const [id, proc] of this.sessions) {
|
|
3006
|
-
//
|
|
3496
|
+
// An agent that just dispatched Agent/Workflow/Monitor work is CLI-idle
|
|
3007
3497
|
// but that dispatch may still need this session to receive its
|
|
3008
3498
|
// eventual task notification.
|
|
3009
3499
|
if (proc.hasLikelyOutstandingBackgroundWork())
|
|
@@ -3018,16 +3508,20 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3018
3508
|
}, 5 * 60 * 1000);
|
|
3019
3509
|
}
|
|
3020
3510
|
async start() {
|
|
3511
|
+
if (this.agentConfig.orchestration?.enabled || fs.existsSync(path.join(this.agentDir, 'orchestration.db')))
|
|
3512
|
+
await this.getOrchestration();
|
|
3021
3513
|
this.stopping = false;
|
|
3022
3514
|
await this.sweepStaleSessionDirs();
|
|
3023
3515
|
await this.startCallbackServer();
|
|
3024
3516
|
if (this.agentConfig.telegram?.botToken) {
|
|
3025
|
-
this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
|
|
3517
|
+
this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
|
|
3026
3518
|
this.receiver.start();
|
|
3519
|
+
this.telegramCommandConfig = this.telegramCommandsKey();
|
|
3027
3520
|
}
|
|
3028
3521
|
if (this.agentConfig.discord?.botToken) {
|
|
3029
|
-
this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
|
|
3522
|
+
this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
|
|
3030
3523
|
this.discordReceiver.start();
|
|
3524
|
+
this.discordCommandConfig = this.telegramCommandsKey();
|
|
3031
3525
|
}
|
|
3032
3526
|
// LINE slow-LLM postback button: gateway-side token lifecycle + cache.
|
|
3033
3527
|
// Enabled for any LINE agent unless its threshold is 0 (then the MCP
|
|
@@ -3043,6 +3537,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3043
3537
|
// no-ops here and stays 'unlinked' until the user starts a QR/pairing
|
|
3044
3538
|
// flow via the API.
|
|
3045
3539
|
this.syncWhatsAppAccounts();
|
|
3540
|
+
this.ensureWeChatManager();
|
|
3541
|
+
await this.wechat?.resumeIfLinked();
|
|
3046
3542
|
this.startIdleCleaner();
|
|
3047
3543
|
this._startCleanupScheduler();
|
|
3048
3544
|
this.logger.info('AgentRunner started', { agentId: this.agentConfig.id });
|
|
@@ -3086,8 +3582,34 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3086
3582
|
}
|
|
3087
3583
|
}
|
|
3088
3584
|
}
|
|
3585
|
+
telegramCommandsKey() {
|
|
3586
|
+
return JSON.stringify([this.gatewayConfig.gateway.headless !== false, this.agentConfig.type,
|
|
3587
|
+
Boolean(this.agentConfig.orchestration?.enabled), this.agentConfig.orchestration?.channels ?? ['api']]);
|
|
3588
|
+
}
|
|
3589
|
+
/** Reload only the receiver when command visibility changes; Agent/Worker work continues. */
|
|
3590
|
+
refreshTelegramCommands() {
|
|
3591
|
+
const discord = this.discordReceiver, discordKey = this.telegramCommandsKey();
|
|
3592
|
+
if (discord && discordKey !== this.discordCommandConfig) {
|
|
3593
|
+
this.discordCommandConfig = discordKey;
|
|
3594
|
+
void discord.stop().then(() => { if (this.discordReceiver !== discord || this.stopping)
|
|
3595
|
+
return; this.discordReceiver = null; this.startDiscordReceiver(); });
|
|
3596
|
+
}
|
|
3597
|
+
const previous = this.receiver, key = this.telegramCommandsKey();
|
|
3598
|
+
if (!previous || key === this.telegramCommandConfig)
|
|
3599
|
+
return;
|
|
3600
|
+
this.telegramCommandConfig = key;
|
|
3601
|
+
void previous.stop().then(() => {
|
|
3602
|
+
if (this.receiver !== previous || this.stopping)
|
|
3603
|
+
return;
|
|
3604
|
+
this.receiver = null;
|
|
3605
|
+
this.startTelegramReceiver();
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3089
3608
|
updateAgentConfig(newConfig) {
|
|
3609
|
+
newConfig = (0, gateway_config_1.applyGatewayOrchestration)(newConfig, this.gatewayConfig);
|
|
3610
|
+
this.orchestration?.updateAgentConfig(newConfig);
|
|
3090
3611
|
this.agentConfig = newConfig;
|
|
3612
|
+
this.refreshTelegramCommands();
|
|
3091
3613
|
// Restart LineReplyManager if the LINE config changed so the live instance
|
|
3092
3614
|
// picks up a new access token, threshold, or labels without a full restart.
|
|
3093
3615
|
this.stopLineReply();
|
|
@@ -3104,6 +3626,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3104
3626
|
// (access-control fields) into every already-running manager, and
|
|
3105
3627
|
// add/remove managers only if the set of configured accounts changed.
|
|
3106
3628
|
this.syncWhatsAppAccounts();
|
|
3629
|
+
// WeChat has no credential to react to (QR-linked, not config-driven) —
|
|
3630
|
+
// just hand the manager the fresh config so its access-control reads
|
|
3631
|
+
// (dmPolicy/dmAllowlist/pairing) see the latest values.
|
|
3632
|
+
this.wechat?.updateAgentConfig(newConfig);
|
|
3107
3633
|
}
|
|
3108
3634
|
/**
|
|
3109
3635
|
* Reconcile `this.whatsappAccounts` with `agentConfig.whatsapp.accounts`.
|
|
@@ -3220,6 +3746,152 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3220
3746
|
opts) {
|
|
3221
3747
|
await this.whatsAppManagerFor(jid, accountId).sendMessage(jid, text, imagePath, opts);
|
|
3222
3748
|
}
|
|
3749
|
+
/** Lazily construct this agent's WeChatManager — safe to call repeatedly. */
|
|
3750
|
+
ensureWeChatManager() {
|
|
3751
|
+
if (!this.wechat) {
|
|
3752
|
+
// WECHAT_ILINK_FAKE is local-testing-only (see ilink-client.ts's
|
|
3753
|
+
// createFakeILinkClient doc comment) — never set in a real deployment.
|
|
3754
|
+
// The real client needs no account/credential of its own (confirmed
|
|
3755
|
+
// against Tencent's own protocol doc — see ilink-client.ts's module
|
|
3756
|
+
// comment), this flag exists purely to skip a real QR scan while
|
|
3757
|
+
// iterating on the UI.
|
|
3758
|
+
const client = (0, ilink_client_1.isWeChatILinkFakeEnabled)()
|
|
3759
|
+
? (0, ilink_client_1.createFakeILinkClient)()
|
|
3760
|
+
: (0, ilink_client_1.createILinkClient)(process.env.ILINK_BASE_URL ?? '', this.agentConfig.wechat?.botAgent);
|
|
3761
|
+
this.wechat = new manager_2.WeChatManager(this.agentConfig, this.gatewayConfig.gateway.logDir, client, (update) => this.handleWeChatInboundMessage(update));
|
|
3762
|
+
}
|
|
3763
|
+
return this.wechat;
|
|
3764
|
+
}
|
|
3765
|
+
getWeChatStatus() {
|
|
3766
|
+
return this.ensureWeChatManager().getStatus();
|
|
3767
|
+
}
|
|
3768
|
+
async startWeChatLinking() {
|
|
3769
|
+
await this.ensureWeChatManager().startLinking();
|
|
3770
|
+
}
|
|
3771
|
+
async unlinkWeChat() {
|
|
3772
|
+
await this.ensureWeChatManager().unlink();
|
|
3773
|
+
}
|
|
3774
|
+
async sendWeChatMessage(toId, text) {
|
|
3775
|
+
await this.ensureWeChatManager().sendMessage(toId, text);
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Gate + forward one inbound WeChat message. Mirrors the LINE webhook
|
|
3779
|
+
* router's own gate-then-forward-to-/channel shape exactly (see
|
|
3780
|
+
* src/api/line-webhook-router.ts's `recordDeniedSender` call and its
|
|
3781
|
+
* "forward to the agent's existing /channel intake" comment) — WeChat has
|
|
3782
|
+
* no webhook of its own, so this is the equivalent point for a message
|
|
3783
|
+
* arriving off WeChatManager's long-poll loop instead of an Express route.
|
|
3784
|
+
*/
|
|
3785
|
+
handleWeChatInboundMessage(update) {
|
|
3786
|
+
const cfg = this.agentConfig.wechat;
|
|
3787
|
+
const allowed = (0, wechat_access_1.isWeChatConversationAllowed)(cfg, { fromId: update.fromId });
|
|
3788
|
+
if (!allowed) {
|
|
3789
|
+
const pairingOn = cfg?.pairing !== false; // absent ⇒ on, mirrors line.pairing/slack.pairing
|
|
3790
|
+
const existing = (0, pending_senders_1.getPendingSender)('wechat', this.agentConfig.id, update.fromId);
|
|
3791
|
+
const code = pairingOn ? (existing?.code ?? (0, pending_senders_1.generatePairingCode)()) : undefined;
|
|
3792
|
+
const isNew = (0, pending_senders_1.recordDeniedSender)('wechat', this.agentConfig.id, update.fromId, update.displayName, Date.now(), code);
|
|
3793
|
+
if (isNew && pairingOn && code) {
|
|
3794
|
+
this.wechat
|
|
3795
|
+
?.sendMessage(update.fromId, `This account isn't approved to message this agent yet. Give the admin this code: ${code}`)
|
|
3796
|
+
.catch((err) => this.logger.warn('WeChat pairing-code reply failed', { error: err.message }));
|
|
3797
|
+
}
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
// Self-heal a stale pending-knock record for a sender who's now allowed.
|
|
3801
|
+
// Approving a sender already clears their pending entry (router.ts's
|
|
3802
|
+
// wechat_dm_allowlist PATCH handler), but confirmed live 2026-09-11: a
|
|
3803
|
+
// sender can still show as both allowed AND pending in the UI, most
|
|
3804
|
+
// likely a race between that approval and a message processed in the
|
|
3805
|
+
// same window. Clearing it here too means any such staleness can't
|
|
3806
|
+
// outlive this sender's very next allowed message.
|
|
3807
|
+
(0, pending_senders_1.clearPendingSender)('wechat', this.agentConfig.id, update.fromId);
|
|
3808
|
+
const meta = {
|
|
3809
|
+
source: 'wechat',
|
|
3810
|
+
chat_id: update.fromId,
|
|
3811
|
+
user_id: update.fromId,
|
|
3812
|
+
user: update.displayName || update.fromId,
|
|
3813
|
+
message_id: update.id,
|
|
3814
|
+
ts: new Date(update.timestamp ?? Date.now()).toISOString(),
|
|
3815
|
+
};
|
|
3816
|
+
void (async () => {
|
|
3817
|
+
// Stage an inbound image or file to a temp path the same way LINE's
|
|
3818
|
+
// webhook router does (src/api/line-webhook-router.ts's
|
|
3819
|
+
// downloadLineImage/drainToFile pattern) — AgentRunner.injectTurn
|
|
3820
|
+
// already knows how to take meta.image_path from here (the field name
|
|
3821
|
+
// is historical; the mechanism itself is content-agnostic — it just
|
|
3822
|
+
// copies whatever's at that path into MediaStore), rewrite the path
|
|
3823
|
+
// for the agent's sandbox, and clean up the ephemeral original. No
|
|
3824
|
+
// format sniffing for images: always staged as .jpg, matching
|
|
3825
|
+
// Hermes-agent's own assumption for this same protocol. Files keep
|
|
3826
|
+
// their real name/extension (sniffing isn't needed — it's given).
|
|
3827
|
+
// update.id (Tencent's message_id) arrives as a JSON number at runtime
|
|
3828
|
+
// despite ILinkUpdate declaring it a string (confirmed live
|
|
3829
|
+
// 2026-09-11: `.replace is not a function` on a real image message) —
|
|
3830
|
+
// String() it before sanitizing.
|
|
3831
|
+
const safeId = String(update.id).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
3832
|
+
if (update.image) {
|
|
3833
|
+
try {
|
|
3834
|
+
const bytes = await (0, ilink_client_1.downloadWeixinImage)(update.image, { maxBytes: media_store_1.MediaStore.maxUploadBytes });
|
|
3835
|
+
const tempPath = path.join(os.tmpdir(), `wechat-img-${safeId}-${Date.now()}.jpg`);
|
|
3836
|
+
await fsPromises.writeFile(tempPath, bytes);
|
|
3837
|
+
meta.image_path = tempPath;
|
|
3838
|
+
meta.media_ephemeral = '1';
|
|
3839
|
+
}
|
|
3840
|
+
catch (err) {
|
|
3841
|
+
this.logger.warn('WeChat image download failed, forwarding text only', {
|
|
3842
|
+
error: err.message,
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
else if (update.file) {
|
|
3847
|
+
try {
|
|
3848
|
+
const bytes = await (0, ilink_client_1.downloadWeixinImage)(update.file, { maxBytes: media_store_1.MediaStore.maxUploadBytes });
|
|
3849
|
+
// Preserve the sender's real filename/extension (unlike images,
|
|
3850
|
+
// which are always staged as .jpg) — the agent's Read tool needs a
|
|
3851
|
+
// correct extension to make sense of a PDF/docx/etc.
|
|
3852
|
+
const safeName = update.file.fileName.replace(/[^a-zA-Z0-9_.-]/g, '_') || 'file';
|
|
3853
|
+
const tempPath = path.join(os.tmpdir(), `wechat-file-${safeId}-${Date.now()}-${safeName}`);
|
|
3854
|
+
await fsPromises.writeFile(tempPath, bytes);
|
|
3855
|
+
meta.image_path = tempPath;
|
|
3856
|
+
meta.media_ephemeral = '1';
|
|
3857
|
+
}
|
|
3858
|
+
catch (err) {
|
|
3859
|
+
this.logger.warn('WeChat file download failed, forwarding text only', {
|
|
3860
|
+
error: err.message,
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
if (update.reply) {
|
|
3865
|
+
if (update.reply.messageId)
|
|
3866
|
+
meta.replied_message_id = update.reply.messageId;
|
|
3867
|
+
if (update.reply.text)
|
|
3868
|
+
meta.replied_text = update.reply.text;
|
|
3869
|
+
const ref = update.reply.image ?? update.reply.file;
|
|
3870
|
+
if (ref)
|
|
3871
|
+
try {
|
|
3872
|
+
const bytes = await (0, ilink_client_1.downloadWeixinImage)(ref, { maxBytes: media_store_1.MediaStore.maxUploadBytes });
|
|
3873
|
+
const name = update.reply.file?.fileName.replace(/[^a-zA-Z0-9_.-]/g, '_') ?? 'image.jpg';
|
|
3874
|
+
const dest = path.join(os.tmpdir(), `wechat-reply-${safeId}-${Date.now()}-${name}`);
|
|
3875
|
+
await fsPromises.writeFile(dest, bytes, { mode: 0o600 });
|
|
3876
|
+
meta.replied_image_path = dest;
|
|
3877
|
+
meta.media_ephemeral = '1';
|
|
3878
|
+
}
|
|
3879
|
+
catch {
|
|
3880
|
+
meta.attachment_error = 'The quoted attachment could not be downloaded. Do not claim to have read it.';
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
try {
|
|
3884
|
+
await fetch(`http://127.0.0.1:${this.callbackPort}/channel`, {
|
|
3885
|
+
method: 'POST',
|
|
3886
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3887
|
+
body: JSON.stringify({ content: update.text ?? '', meta }),
|
|
3888
|
+
});
|
|
3889
|
+
}
|
|
3890
|
+
catch (err) {
|
|
3891
|
+
this.logger.error('WeChat: failed to forward to callback', { error: err.message });
|
|
3892
|
+
}
|
|
3893
|
+
})();
|
|
3894
|
+
}
|
|
3223
3895
|
startSlackOutbound() {
|
|
3224
3896
|
if (!this.agentConfig.slack?.botToken || !this.agentConfig.slack?.signingSecret)
|
|
3225
3897
|
return;
|
|
@@ -3285,8 +3957,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3285
3957
|
return;
|
|
3286
3958
|
if (!this.agentConfig.telegram?.botToken)
|
|
3287
3959
|
return;
|
|
3288
|
-
this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
|
|
3960
|
+
this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
|
|
3289
3961
|
this.receiver.start();
|
|
3962
|
+
this.telegramCommandConfig = this.telegramCommandsKey();
|
|
3290
3963
|
this.logger.info('TelegramReceiver hot-started', { agentId: this.agentConfig.id });
|
|
3291
3964
|
}
|
|
3292
3965
|
stopTelegramReceiver() {
|
|
@@ -3304,8 +3977,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3304
3977
|
return;
|
|
3305
3978
|
if (!this.agentConfig.discord?.botToken)
|
|
3306
3979
|
return;
|
|
3307
|
-
this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
|
|
3980
|
+
this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
|
|
3308
3981
|
this.discordReceiver.start();
|
|
3982
|
+
this.discordCommandConfig = this.telegramCommandsKey();
|
|
3309
3983
|
this.logger.info('DiscordReceiver hot-started', { agentId: this.agentConfig.id });
|
|
3310
3984
|
}
|
|
3311
3985
|
stopDiscordReceiver() {
|
|
@@ -3320,6 +3994,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3320
3994
|
}
|
|
3321
3995
|
async stop() {
|
|
3322
3996
|
this.stopping = true;
|
|
3997
|
+
if (this.orchestrationStarting) {
|
|
3998
|
+
const orchestration = await this.orchestrationStarting.catch(() => undefined);
|
|
3999
|
+
await orchestration?.close();
|
|
4000
|
+
this.orchestration = undefined;
|
|
4001
|
+
this.orchestrationStarting = undefined;
|
|
4002
|
+
}
|
|
3323
4003
|
if (this.idleCleanerTimer !== null) {
|
|
3324
4004
|
clearInterval(this.idleCleanerTimer);
|
|
3325
4005
|
this.idleCleanerTimer = null;
|
|
@@ -3402,6 +4082,17 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3402
4082
|
isRunning() {
|
|
3403
4083
|
return this.receiver?.isRunning() ?? false;
|
|
3404
4084
|
}
|
|
4085
|
+
getOrchestrationSummary() {
|
|
4086
|
+
if (!this.agentConfig.orchestration?.enabled && !this.orchestration)
|
|
4087
|
+
return undefined;
|
|
4088
|
+
const summary = this.orchestration?.dashboardSummary() ?? { enabled: true, backend: 'headless', workspaceMode: this.agentConfig.type === 'app-agent' ? 'container' : this.agentConfig.orchestration?.tasks?.workspaceMode ?? 'host', activeAgentSessions: [], tasks: [], sessions: [] };
|
|
4089
|
+
const processes = [...this.sessions.values()].filter(p => p.runtimeProfile?.role === 'agent' && p.isRunning());
|
|
4090
|
+
return { ...summary, sessions: summary.sessions.map(s => {
|
|
4091
|
+
const p = processes.find(p => p.sessionId === s.sessionId);
|
|
4092
|
+
return { ...s, model: p?.model ?? s.model, tokens: p?.totalTokens ?? 0, spawnedAt: p?.spawnedAt ?? 0,
|
|
4093
|
+
uptimeSec: p ? Math.floor((Date.now() - p.spawnedAt) / 1000) : 0 };
|
|
4094
|
+
}), agentProcesses: processes.map(p => ({ sessionId: p.sessionId, hostProcessId: p.processId, container: this.agentConfig.container })) };
|
|
4095
|
+
}
|
|
3405
4096
|
getSessionsSummary() {
|
|
3406
4097
|
const now = Date.now();
|
|
3407
4098
|
// A single logical session can appear multiple times in the ring buffer: a
|
|
@@ -3439,6 +4130,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3439
4130
|
* - Appends user message and assistant reply to SessionStore for history persistence.
|
|
3440
4131
|
*/
|
|
3441
4132
|
async sendApiMessage(sessionId, chatId, message, opts) {
|
|
4133
|
+
if (this.orchestrationForApi(sessionId))
|
|
4134
|
+
return this.sendOrchestratedApi(sessionId, chatId, message, opts);
|
|
3442
4135
|
if (this.pendingApiSessions.has(sessionId)) {
|
|
3443
4136
|
const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
|
|
3444
4137
|
throw err;
|
|
@@ -3721,6 +4414,18 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3721
4414
|
* expires (#421).
|
|
3722
4415
|
*/
|
|
3723
4416
|
async sendApiMessageStream(sessionId, chatId, message, callbacks, opts) {
|
|
4417
|
+
if (this.orchestrationForApi(sessionId)) {
|
|
4418
|
+
if (this.pendingApiSessions.has(sessionId))
|
|
4419
|
+
throw Object.assign(new Error('Session already has a pending request'), { code: 'CONFLICT' });
|
|
4420
|
+
const turn = this.turnStreams.start((0, turn_stream_1.turnStreamKey)('api', sessionId), opts.requestId ?? (0, crypto_1.randomUUID)());
|
|
4421
|
+
const sink = (0, turn_stream_1.callbackSink)(callbacks);
|
|
4422
|
+
turn.attach(sink, 0);
|
|
4423
|
+
void this.sendOrchestratedApi(sessionId, chatId, message, { ...opts, requestId: turn.requestId }, text => turn.emit({ type: 'text_delta', text }), event => { if (event.type === 'tool_use')
|
|
4424
|
+
turn.emit({ ...event, type: 'tool_use' }); })
|
|
4425
|
+
.then(result => this.turnStreams.complete(turn, (0, turn_stream_1.resultEvent)(result.text, result.attachments)))
|
|
4426
|
+
.catch(error => this.turnStreams.complete(turn, (0, turn_stream_1.errorEvent)(error), error));
|
|
4427
|
+
return () => turn.detach(sink);
|
|
4428
|
+
}
|
|
3724
4429
|
if (this.pendingApiSessions.has(sessionId)) {
|
|
3725
4430
|
const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
|
|
3726
4431
|
throw err;
|
|
@@ -4192,6 +4897,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
4192
4897
|
if (!AgentRunner.isApiBuiltinCommand(cmd)) {
|
|
4193
4898
|
throw new Error(`Unknown command: ${cmd}`);
|
|
4194
4899
|
}
|
|
4900
|
+
if (cmd === '/stop' && this.agentConfig.orchestration?.enabled && opts?.principalId)
|
|
4901
|
+
this.orchestration?.authorizeSession(sessionId, opts.principalId);
|
|
4195
4902
|
// Register session in the api-{chatId} index on first use (same as sendApiMessageStream)
|
|
4196
4903
|
await this.sessionStore.ensureApiSession(agentId, storeChatId, sessionId).catch((err) => {
|
|
4197
4904
|
this.logger.warn('Failed to register API session in index for command', { agentId, chatId, sessionId, error: err.message });
|
|
@@ -4214,7 +4921,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
4214
4921
|
// Persist the full user command before executing so it appears in history.
|
|
4215
4922
|
// Skip for /clear — clearSession() below wipes the table anyway; only the response survives.
|
|
4216
4923
|
if (cmd !== '/clear') {
|
|
4217
|
-
persist('user', command);
|
|
4924
|
+
persist('user', opts?.displayCommand ?? command);
|
|
4218
4925
|
}
|
|
4219
4926
|
let result;
|
|
4220
4927
|
let responseText;
|
|
@@ -4229,17 +4936,41 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
4229
4936
|
: `Current model: ${model}`;
|
|
4230
4937
|
}
|
|
4231
4938
|
else if (cmd === '/stop') {
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4939
|
+
if (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes('api') && this.orchestration && opts?.principalId) {
|
|
4940
|
+
const selection = /^\/stop\s+([a-f0-9-]{36})\s+(\d+)$/.exec(command.trim());
|
|
4941
|
+
if (selection) {
|
|
4942
|
+
try {
|
|
4943
|
+
const selected = this.orchestration.stopControls.choose(sessionId, opts.principalId, selection[1], Number(selection[2]));
|
|
4944
|
+
result = { ...selected };
|
|
4945
|
+
responseText = selected.text;
|
|
4946
|
+
}
|
|
4947
|
+
catch {
|
|
4948
|
+
result = { error: 'STOP_SELECTION_UNAVAILABLE' };
|
|
4949
|
+
responseText = 'Selection unavailable. Use /stop to refresh the task list.';
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
else {
|
|
4953
|
+
const menu = this.orchestration.stopControls.open(sessionId, opts.principalId);
|
|
4954
|
+
responseText = (0, stop_controls_1.stopMenuText)(menu);
|
|
4955
|
+
result = { ...menu, responseText };
|
|
4956
|
+
}
|
|
4957
|
+
forcePersist = true;
|
|
4958
|
+
}
|
|
4959
|
+
else {
|
|
4960
|
+
const session = this.sessions.get(sessionId);
|
|
4961
|
+
const stopped = this.agentConfig.orchestration?.enabled && this.orchestration?.isBusy(sessionId)
|
|
4962
|
+
? this.orchestration.stopResponse(sessionId)
|
|
4963
|
+
: session ? session.interrupt() : false;
|
|
4964
|
+
result = { stopped };
|
|
4965
|
+
responseText = stopped
|
|
4966
|
+
? 'Session was interrupted before I could respond.'
|
|
4967
|
+
: 'No active session to stop.';
|
|
4968
|
+
// A turn was actually cut off — always leave a visible note in history so the
|
|
4969
|
+
// web Stop button (skipPersist:true, to suppress the "/stop" command echo) and
|
|
4970
|
+
// a typed /stop command show the same outcome instead of the button leaving a
|
|
4971
|
+
// silently-dangling user turn.
|
|
4972
|
+
forcePersist = stopped;
|
|
4973
|
+
}
|
|
4243
4974
|
}
|
|
4244
4975
|
else if (cmd === '/restart') {
|
|
4245
4976
|
this.restartProcess(sessionId).catch(() => { });
|
|
@@ -4519,6 +5250,100 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
4519
5250
|
const mediaPaths = this.historyDb.clearSession(`api-${chatId}`, sessionId);
|
|
4520
5251
|
media_store_1.MediaStore.deleteMediaFiles(this.agentsBaseDir, this.agentConfig.id, mediaPaths);
|
|
4521
5252
|
}
|
|
5253
|
+
/** The route has already authorized this API principal for the Agent. Preserve
|
|
5254
|
+
* the original channel binding, but never impersonate its human sender. */
|
|
5255
|
+
async sendOrchestratedChannel(rawChatId, channel, sessionId, message, senderName, callbacks, opts) {
|
|
5256
|
+
if (!opts.principalId)
|
|
5257
|
+
throw new Error('Authenticated principal required for conversation orchestration');
|
|
5258
|
+
const runtime = await this.getOrchestration();
|
|
5259
|
+
const rows = runtime.store.all('SELECT * FROM conversations WHERE agent_session_id=?', sessionId);
|
|
5260
|
+
if (rows.length > 1 || rows.some(row => row.source !== channel || row.chat_id !== rawChatId))
|
|
5261
|
+
throw new Error('Ambiguous or mismatched channel session');
|
|
5262
|
+
if (!rows.length) {
|
|
5263
|
+
const index = await this.sessionStore.loadIndex(this.agentConfig.id, rawChatId, channel);
|
|
5264
|
+
if (!index?.sessions.some(session => session.id === sessionId))
|
|
5265
|
+
throw new Error('Session not found');
|
|
5266
|
+
}
|
|
5267
|
+
const requestId = opts.requestId ?? (0, crypto_1.randomUUID)();
|
|
5268
|
+
const accepted = runtime.submitInput({ scope: { agentId: this.agentConfig.id, agentSessionId: sessionId,
|
|
5269
|
+
source: channel, accountId: String(rows[0]?.account_id ?? this.agentConfig.id), chatId: rawChatId,
|
|
5270
|
+
threadKey: String(rows[0]?.thread_key ?? ''), principalId: opts.principalId },
|
|
5271
|
+
text: message, requestId, trustedChannelMember: true, metadata: { senderName, senderId: opts.principalId } }, { execute: opts.allowTools ?? false, writeMemory: false });
|
|
5272
|
+
const turn = this.turnStreams.start((0, turn_stream_1.turnStreamKey)(channel, sessionId), requestId);
|
|
5273
|
+
const sink = (0, turn_stream_1.callbackSink)(callbacks);
|
|
5274
|
+
turn.attach(sink, 0);
|
|
5275
|
+
const displayed = new Map();
|
|
5276
|
+
const unsubscribe = runtime.subscribeText(sessionId, opts.principalId, event => {
|
|
5277
|
+
if (!runtime.responseBelongsToInput(accepted.inputId, event.responseId))
|
|
5278
|
+
return;
|
|
5279
|
+
const previous = displayed.get(event.responseId) ?? '';
|
|
5280
|
+
if (event.text.startsWith(previous)) {
|
|
5281
|
+
const delta = event.text.slice(previous.length);
|
|
5282
|
+
displayed.set(event.responseId, event.text);
|
|
5283
|
+
if (delta)
|
|
5284
|
+
turn.emit({ type: 'text_delta', text: delta });
|
|
5285
|
+
}
|
|
5286
|
+
});
|
|
5287
|
+
const input = runtime.store.get('SELECT conversation_id FROM conversation_inputs WHERE id=?', accepted.inputId);
|
|
5288
|
+
const conversation = runtime.store.assertMember(String(input.conversation_id), opts.principalId);
|
|
5289
|
+
let cursor = { streamId: String(conversation.stream_id), seq: Number(conversation.last_event_seq) };
|
|
5290
|
+
const events = runtime.events.subscribe(String(conversation.id), opts.principalId, cursor);
|
|
5291
|
+
let closed = false;
|
|
5292
|
+
let timer;
|
|
5293
|
+
const close = () => { closed = true; clearTimeout(timer); unsubscribe(); events.close(); };
|
|
5294
|
+
const forward = (page) => {
|
|
5295
|
+
if (page.snapshotRequired)
|
|
5296
|
+
throw new Error('Tool activity history expired; reload session activity');
|
|
5297
|
+
for (const event of page.events) {
|
|
5298
|
+
if (event.seq <= cursor.seq)
|
|
5299
|
+
continue;
|
|
5300
|
+
const payload = event.payload;
|
|
5301
|
+
if (event.type === 'tool.activity' && payload.type === 'tool_use')
|
|
5302
|
+
turn.emit({ ...payload, type: 'tool_use' });
|
|
5303
|
+
cursor = { streamId: page.cursor.streamId, seq: event.seq };
|
|
5304
|
+
}
|
|
5305
|
+
};
|
|
5306
|
+
const drain = () => {
|
|
5307
|
+
while (true) {
|
|
5308
|
+
const page = runtime.events.read(String(conversation.id), opts.principalId, cursor);
|
|
5309
|
+
forward(page);
|
|
5310
|
+
if (!page.snapshotRequired && !page.events.length)
|
|
5311
|
+
break;
|
|
5312
|
+
}
|
|
5313
|
+
};
|
|
5314
|
+
const fail = (error) => {
|
|
5315
|
+
if (closed)
|
|
5316
|
+
return;
|
|
5317
|
+
try {
|
|
5318
|
+
drain();
|
|
5319
|
+
}
|
|
5320
|
+
catch { /* Preserve the original stream/provider failure. */ }
|
|
5321
|
+
this.turnStreams.completeAndRelease(turn, (0, turn_stream_1.errorEvent)(error), error);
|
|
5322
|
+
close();
|
|
5323
|
+
};
|
|
5324
|
+
void (async () => {
|
|
5325
|
+
for await (const page of events.pages) {
|
|
5326
|
+
if (closed)
|
|
5327
|
+
break;
|
|
5328
|
+
forward(page);
|
|
5329
|
+
}
|
|
5330
|
+
})().catch(fail);
|
|
5331
|
+
timer = setTimeout(() => fail(Object.assign(new Error('Agent response timeout'), { code: 'TIMEOUT_SOFT' })), opts.timeoutMs);
|
|
5332
|
+
void accepted.response.then(text => {
|
|
5333
|
+
if (closed)
|
|
5334
|
+
return;
|
|
5335
|
+
try {
|
|
5336
|
+
// Drain before terminal success, even when completion precedes the next poll.
|
|
5337
|
+
drain();
|
|
5338
|
+
this.turnStreams.completeAndRelease(turn, (0, turn_stream_1.resultEvent)(text, []));
|
|
5339
|
+
close();
|
|
5340
|
+
}
|
|
5341
|
+
catch (error) {
|
|
5342
|
+
fail(error instanceof Error ? error : new Error('Tool activity stream failed'));
|
|
5343
|
+
}
|
|
5344
|
+
}, fail);
|
|
5345
|
+
return () => { turn.detach(sink); }; // Durable work and original channel delivery survive disconnect.
|
|
5346
|
+
}
|
|
4522
5347
|
/**
|
|
4523
5348
|
* Send a message into an existing channel session (cross-channel continuation from UI).
|
|
4524
5349
|
* The session process receives full history context from the session JSON (Layer 1).
|
|
@@ -4528,6 +5353,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
4528
5353
|
* parser — and writing here as well produced a second row per turn. See done().
|
|
4529
5354
|
*/
|
|
4530
5355
|
async sendMessageToSession(rawChatId, channel, sessionId, message, senderName, callbacks, opts) {
|
|
5356
|
+
const managed = (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channel)) ||
|
|
5357
|
+
(this.orchestration?.ownsSession(sessionId) && !this.orchestration.canReturnToLegacy());
|
|
5358
|
+
if (managed)
|
|
5359
|
+
return this.sendOrchestratedChannel(rawChatId, channel, sessionId, message, senderName, callbacks, opts);
|
|
4531
5360
|
// Ensure the session process uses the correct channel source
|
|
4532
5361
|
this.channelSourceMap.set(rawChatId, channel);
|
|
4533
5362
|
// Channel sessions use agent-level model (not per-session)
|