@0xmaxma/claude-gateway 1.8.14 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1613
- package/config.template.json +7 -1
- package/dist/agent/runner.d.ts +377 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +680 -24
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/skill-learning/index.d.ts +6 -4
- package/dist/agent/skill-learning/index.d.ts.map +1 -1
- package/dist/agent/skill-learning/index.js +3 -3
- package/dist/agent/skill-learning/index.js.map +1 -1
- package/dist/agent/skill-learning/notifier.d.ts +3 -4
- package/dist/agent/skill-learning/notifier.d.ts.map +1 -1
- package/dist/agent/skill-learning/notifier.js +15 -13
- package/dist/agent/skill-learning/notifier.js.map +1 -1
- package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
- package/dist/agent/skill-learning/reviewer.js +3 -1
- package/dist/agent/skill-learning/reviewer.js.map +1 -1
- package/dist/api/connectors-router.d.ts.map +1 -1
- package/dist/api/connectors-router.js +110 -4
- package/dist/api/connectors-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts +1 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +13 -1
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-webhook-router.d.ts +2 -2
- package/dist/api/line-webhook-router.d.ts.map +1 -1
- package/dist/api/line-webhook-router.js +264 -230
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +135 -10
- package/dist/api/router.js.map +1 -1
- package/dist/api/share-router.d.ts.map +1 -1
- package/dist/api/share-router.js +29 -10
- package/dist/api/share-router.js.map +1 -1
- package/dist/api/slack-client.d.ts +2 -0
- package/dist/api/slack-client.d.ts.map +1 -1
- package/dist/api/slack-client.js +6 -2
- package/dist/api/slack-client.js.map +1 -1
- package/dist/api/slack-webhook-router.d.ts +3 -0
- package/dist/api/slack-webhook-router.d.ts.map +1 -1
- package/dist/api/slack-webhook-router.js +226 -131
- package/dist/api/slack-webhook-router.js.map +1 -1
- package/dist/api/voice-router.d.ts +21 -0
- package/dist/api/voice-router.d.ts.map +1 -0
- package/dist/api/voice-router.js +372 -0
- package/dist/api/voice-router.js.map +1 -0
- package/dist/api/voice-settings-router.d.ts +5 -0
- package/dist/api/voice-settings-router.d.ts.map +1 -0
- package/dist/api/voice-settings-router.js +318 -0
- package/dist/api/voice-settings-router.js.map +1 -0
- package/dist/apps/agent-container-migration.d.ts +11 -0
- package/dist/apps/agent-container-migration.d.ts.map +1 -0
- package/dist/apps/agent-container-migration.js +85 -0
- package/dist/apps/agent-container-migration.js.map +1 -0
- package/dist/apps/installer.d.ts +1 -1
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +2 -1
- package/dist/apps/installer.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +5 -0
- package/dist/cli/commands/tasks.d.ts.map +1 -0
- package/dist/cli/commands/tasks.js +92 -0
- package/dist/cli/commands/tasks.js.map +1 -0
- package/dist/cli/http-client.d.ts +2 -0
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +8 -3
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/config-write-lock.d.ts +2 -0
- package/dist/config/config-write-lock.d.ts.map +1 -1
- package/dist/config/config-write-lock.js +5 -0
- package/dist/config/config-write-lock.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +87 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/config/migrator.d.ts.map +1 -1
- package/dist/config/migrator.js +12 -1
- package/dist/config/migrator.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +6 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/connectors/resolve.d.ts.map +1 -1
- package/dist/connectors/resolve.js +3 -1
- package/dist/connectors/resolve.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/cron/manager.d.ts.map +1 -1
- package/dist/cron/manager.js +21 -2
- package/dist/cron/manager.js.map +1 -1
- package/dist/discord/receiver.d.ts +2 -1
- package/dist/discord/receiver.d.ts.map +1 -1
- package/dist/discord/receiver.js +5 -1
- package/dist/discord/receiver.js.map +1 -1
- package/dist/history/db.d.ts +3 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +21 -1
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +2 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/index.js +48 -9
- package/dist/index.js.map +1 -1
- package/dist/orchestration/bounded-queue.d.ts +15 -0
- package/dist/orchestration/bounded-queue.d.ts.map +1 -0
- package/dist/orchestration/bounded-queue.js +58 -0
- package/dist/orchestration/bounded-queue.js.map +1 -0
- package/dist/orchestration/bridge.d.ts +55 -0
- package/dist/orchestration/bridge.d.ts.map +1 -0
- package/dist/orchestration/bridge.js +234 -0
- package/dist/orchestration/bridge.js.map +1 -0
- package/dist/orchestration/browser-routing.d.ts +7 -0
- package/dist/orchestration/browser-routing.d.ts.map +1 -0
- package/dist/orchestration/browser-routing.js +22 -0
- package/dist/orchestration/browser-routing.js.map +1 -0
- package/dist/orchestration/capabilities.d.ts +49 -0
- package/dist/orchestration/capabilities.d.ts.map +1 -0
- package/dist/orchestration/capabilities.js +417 -0
- package/dist/orchestration/capabilities.js.map +1 -0
- package/dist/orchestration/capacity.d.ts +14 -0
- package/dist/orchestration/capacity.d.ts.map +1 -0
- package/dist/orchestration/capacity.js +52 -0
- package/dist/orchestration/capacity.js.map +1 -0
- package/dist/orchestration/channel-activity.d.ts +21 -0
- package/dist/orchestration/channel-activity.d.ts.map +1 -0
- package/dist/orchestration/channel-activity.js +72 -0
- package/dist/orchestration/channel-activity.js.map +1 -0
- package/dist/orchestration/channel-controls.d.ts +32 -0
- package/dist/orchestration/channel-controls.d.ts.map +1 -0
- package/dist/orchestration/channel-controls.js +105 -0
- package/dist/orchestration/channel-controls.js.map +1 -0
- package/dist/orchestration/channel-input-media.d.ts +11 -0
- package/dist/orchestration/channel-input-media.d.ts.map +1 -0
- package/dist/orchestration/channel-input-media.js +54 -0
- package/dist/orchestration/channel-input-media.js.map +1 -0
- package/dist/orchestration/channel-media.d.ts +4 -0
- package/dist/orchestration/channel-media.d.ts.map +1 -0
- package/dist/orchestration/channel-media.js +70 -0
- package/dist/orchestration/channel-media.js.map +1 -0
- package/dist/orchestration/channel-speech.d.ts +7 -0
- package/dist/orchestration/channel-speech.d.ts.map +1 -0
- package/dist/orchestration/channel-speech.js +65 -0
- package/dist/orchestration/channel-speech.js.map +1 -0
- package/dist/orchestration/cli-skills.d.ts +12 -0
- package/dist/orchestration/cli-skills.d.ts.map +1 -0
- package/dist/orchestration/cli-skills.js +132 -0
- package/dist/orchestration/cli-skills.js.map +1 -0
- package/dist/orchestration/config.d.ts +256 -0
- package/dist/orchestration/config.d.ts.map +1 -0
- package/dist/orchestration/config.js +110 -0
- package/dist/orchestration/config.js.map +1 -0
- package/dist/orchestration/container.d.ts +21 -0
- package/dist/orchestration/container.d.ts.map +1 -0
- package/dist/orchestration/container.js +195 -0
- package/dist/orchestration/container.js.map +1 -0
- package/dist/orchestration/control-delivery.d.ts +5 -0
- package/dist/orchestration/control-delivery.d.ts.map +1 -0
- package/dist/orchestration/control-delivery.js +50 -0
- package/dist/orchestration/control-delivery.js.map +1 -0
- package/dist/orchestration/conversation-intake.d.ts +32 -0
- package/dist/orchestration/conversation-intake.d.ts.map +1 -0
- package/dist/orchestration/conversation-intake.js +92 -0
- package/dist/orchestration/conversation-intake.js.map +1 -0
- package/dist/orchestration/decisions.d.ts +27 -0
- package/dist/orchestration/decisions.d.ts.map +1 -0
- package/dist/orchestration/decisions.js +148 -0
- package/dist/orchestration/decisions.js.map +1 -0
- package/dist/orchestration/delivery.d.ts +30 -0
- package/dist/orchestration/delivery.d.ts.map +1 -0
- package/dist/orchestration/delivery.js +219 -0
- package/dist/orchestration/delivery.js.map +1 -0
- package/dist/orchestration/display-stream.d.ts +4 -0
- package/dist/orchestration/display-stream.d.ts.map +1 -0
- package/dist/orchestration/display-stream.js +77 -0
- package/dist/orchestration/display-stream.js.map +1 -0
- package/dist/orchestration/events.d.ts +30 -0
- package/dist/orchestration/events.d.ts.map +1 -0
- package/dist/orchestration/events.js +65 -0
- package/dist/orchestration/events.js.map +1 -0
- package/dist/orchestration/execution-observation.d.ts +38 -0
- package/dist/orchestration/execution-observation.d.ts.map +1 -0
- package/dist/orchestration/execution-observation.js +25 -0
- package/dist/orchestration/execution-observation.js.map +1 -0
- package/dist/orchestration/file-delivery.d.ts +16 -0
- package/dist/orchestration/file-delivery.d.ts.map +1 -0
- package/dist/orchestration/file-delivery.js +139 -0
- package/dist/orchestration/file-delivery.js.map +1 -0
- package/dist/orchestration/gateway-config.d.ts +17 -0
- package/dist/orchestration/gateway-config.d.ts.map +1 -0
- package/dist/orchestration/gateway-config.js +128 -0
- package/dist/orchestration/gateway-config.js.map +1 -0
- package/dist/orchestration/history.d.ts +13 -0
- package/dist/orchestration/history.d.ts.map +1 -0
- package/dist/orchestration/history.js +48 -0
- package/dist/orchestration/history.js.map +1 -0
- package/dist/orchestration/identity.d.ts +6 -0
- package/dist/orchestration/identity.d.ts.map +1 -0
- package/dist/orchestration/identity.js +13 -0
- package/dist/orchestration/identity.js.map +1 -0
- package/dist/orchestration/inference-errors.d.ts +3 -0
- package/dist/orchestration/inference-errors.d.ts.map +1 -0
- package/dist/orchestration/inference-errors.js +16 -0
- package/dist/orchestration/inference-errors.js.map +1 -0
- package/dist/orchestration/input-images.d.ts +12 -0
- package/dist/orchestration/input-images.d.ts.map +1 -0
- package/dist/orchestration/input-images.js +66 -0
- package/dist/orchestration/input-images.js.map +1 -0
- package/dist/orchestration/instance-lock.d.ts +5 -0
- package/dist/orchestration/instance-lock.d.ts.map +1 -0
- package/dist/orchestration/instance-lock.js +33 -0
- package/dist/orchestration/instance-lock.js.map +1 -0
- package/dist/orchestration/line-loading.d.ts +21 -0
- package/dist/orchestration/line-loading.d.ts.map +1 -0
- package/dist/orchestration/line-loading.js +89 -0
- package/dist/orchestration/line-loading.js.map +1 -0
- package/dist/orchestration/media.d.ts +3 -0
- package/dist/orchestration/media.d.ts.map +1 -0
- package/dist/orchestration/media.js +46 -0
- package/dist/orchestration/media.js.map +1 -0
- package/dist/orchestration/migrations/schema.d.ts +3 -0
- package/dist/orchestration/migrations/schema.d.ts.map +1 -0
- package/dist/orchestration/migrations/schema.js +80 -0
- package/dist/orchestration/migrations/schema.js.map +1 -0
- package/dist/orchestration/notification-mailbox.d.ts +5 -0
- package/dist/orchestration/notification-mailbox.d.ts.map +1 -0
- package/dist/orchestration/notification-mailbox.js +29 -0
- package/dist/orchestration/notification-mailbox.js.map +1 -0
- package/dist/orchestration/process-activity.d.ts +23 -0
- package/dist/orchestration/process-activity.d.ts.map +1 -0
- package/dist/orchestration/process-activity.js +95 -0
- package/dist/orchestration/process-activity.js.map +1 -0
- package/dist/orchestration/process-supervisor.d.ts +15 -0
- package/dist/orchestration/process-supervisor.d.ts.map +1 -0
- package/dist/orchestration/process-supervisor.js +92 -0
- package/dist/orchestration/process-supervisor.js.map +1 -0
- package/dist/orchestration/process-turn.d.ts +35 -0
- package/dist/orchestration/process-turn.d.ts.map +1 -0
- package/dist/orchestration/process-turn.js +246 -0
- package/dist/orchestration/process-turn.js.map +1 -0
- package/dist/orchestration/progress-review.d.ts +28 -0
- package/dist/orchestration/progress-review.d.ts.map +1 -0
- package/dist/orchestration/progress-review.js +48 -0
- package/dist/orchestration/progress-review.js.map +1 -0
- package/dist/orchestration/recovery.d.ts +8 -0
- package/dist/orchestration/recovery.d.ts.map +1 -0
- package/dist/orchestration/recovery.js +35 -0
- package/dist/orchestration/recovery.js.map +1 -0
- package/dist/orchestration/reply-context.d.ts +7 -0
- package/dist/orchestration/reply-context.d.ts.map +1 -0
- package/dist/orchestration/reply-context.js +54 -0
- package/dist/orchestration/reply-context.js.map +1 -0
- package/dist/orchestration/runtime.d.ts +232 -0
- package/dist/orchestration/runtime.d.ts.map +1 -0
- package/dist/orchestration/runtime.js +962 -0
- package/dist/orchestration/runtime.js.map +1 -0
- package/dist/orchestration/skills.d.ts +17 -0
- package/dist/orchestration/skills.d.ts.map +1 -0
- package/dist/orchestration/skills.js +32 -0
- package/dist/orchestration/skills.js.map +1 -0
- package/dist/orchestration/source-policy.d.ts +6 -0
- package/dist/orchestration/source-policy.d.ts.map +1 -0
- package/dist/orchestration/source-policy.js +16 -0
- package/dist/orchestration/source-policy.js.map +1 -0
- package/dist/orchestration/speech.d.ts +24 -0
- package/dist/orchestration/speech.d.ts.map +1 -0
- package/dist/orchestration/speech.js +61 -0
- package/dist/orchestration/speech.js.map +1 -0
- package/dist/orchestration/stop-controls.d.ts +30 -0
- package/dist/orchestration/stop-controls.d.ts.map +1 -0
- package/dist/orchestration/stop-controls.js +79 -0
- package/dist/orchestration/stop-controls.js.map +1 -0
- package/dist/orchestration/store.d.ts +86 -0
- package/dist/orchestration/store.d.ts.map +1 -0
- package/dist/orchestration/store.js +275 -0
- package/dist/orchestration/store.js.map +1 -0
- package/dist/orchestration/task-controls.d.ts +130 -0
- package/dist/orchestration/task-controls.d.ts.map +1 -0
- package/dist/orchestration/task-controls.js +55 -0
- package/dist/orchestration/task-controls.js.map +1 -0
- package/dist/orchestration/task-files.d.ts +29 -0
- package/dist/orchestration/task-files.d.ts.map +1 -0
- package/dist/orchestration/task-files.js +200 -0
- package/dist/orchestration/task-files.js.map +1 -0
- package/dist/orchestration/task-report.d.ts +7 -0
- package/dist/orchestration/task-report.d.ts.map +1 -0
- package/dist/orchestration/task-report.js +33 -0
- package/dist/orchestration/task-report.js.map +1 -0
- package/dist/orchestration/tasks/checkpoint-hook.d.ts +19 -0
- package/dist/orchestration/tasks/checkpoint-hook.d.ts.map +1 -0
- package/dist/orchestration/tasks/checkpoint-hook.js +49 -0
- package/dist/orchestration/tasks/checkpoint-hook.js.map +1 -0
- package/dist/orchestration/tasks/cleanup.d.ts +18 -0
- package/dist/orchestration/tasks/cleanup.d.ts.map +1 -0
- package/dist/orchestration/tasks/cleanup.js +113 -0
- package/dist/orchestration/tasks/cleanup.js.map +1 -0
- package/dist/orchestration/tasks/driver.d.ts +23 -0
- package/dist/orchestration/tasks/driver.d.ts.map +1 -0
- package/dist/orchestration/tasks/driver.js +209 -0
- package/dist/orchestration/tasks/driver.js.map +1 -0
- package/dist/orchestration/tasks/failure.d.ts +5 -0
- package/dist/orchestration/tasks/failure.d.ts.map +1 -0
- package/dist/orchestration/tasks/failure.js +17 -0
- package/dist/orchestration/tasks/failure.js.map +1 -0
- package/dist/orchestration/tasks/pool.d.ts +18 -0
- package/dist/orchestration/tasks/pool.d.ts.map +1 -0
- package/dist/orchestration/tasks/pool.js +73 -0
- package/dist/orchestration/tasks/pool.js.map +1 -0
- package/dist/orchestration/tasks/scheduler.d.ts +38 -0
- package/dist/orchestration/tasks/scheduler.d.ts.map +1 -0
- package/dist/orchestration/tasks/scheduler.js +160 -0
- package/dist/orchestration/tasks/scheduler.js.map +1 -0
- package/dist/orchestration/tasks/service.d.ts +109 -0
- package/dist/orchestration/tasks/service.d.ts.map +1 -0
- package/dist/orchestration/tasks/service.js +697 -0
- package/dist/orchestration/tasks/service.js.map +1 -0
- package/dist/orchestration/tasks/shared-workspace.d.ts +25 -0
- package/dist/orchestration/tasks/shared-workspace.d.ts.map +1 -0
- package/dist/orchestration/tasks/shared-workspace.js +162 -0
- package/dist/orchestration/tasks/shared-workspace.js.map +1 -0
- package/dist/orchestration/tasks/task-directive.d.ts +6 -0
- package/dist/orchestration/tasks/task-directive.d.ts.map +1 -0
- package/dist/orchestration/tasks/task-directive.js +41 -0
- package/dist/orchestration/tasks/task-directive.js.map +1 -0
- package/dist/orchestration/tasks/timing.d.ts +15 -0
- package/dist/orchestration/tasks/timing.d.ts.map +1 -0
- package/dist/orchestration/tasks/timing.js +48 -0
- package/dist/orchestration/tasks/timing.js.map +1 -0
- package/dist/orchestration/tasks/tool-repetition.d.ts +3 -0
- package/dist/orchestration/tasks/tool-repetition.d.ts.map +1 -0
- package/dist/orchestration/tasks/tool-repetition.js +30 -0
- package/dist/orchestration/tasks/tool-repetition.js.map +1 -0
- package/dist/orchestration/tasks/workspace.d.ts +24 -0
- package/dist/orchestration/tasks/workspace.d.ts.map +1 -0
- package/dist/orchestration/tasks/workspace.js +147 -0
- package/dist/orchestration/tasks/workspace.js.map +1 -0
- package/dist/orchestration/telegram-speech.d.ts +17 -0
- package/dist/orchestration/telegram-speech.d.ts.map +1 -0
- package/dist/orchestration/telegram-speech.js +55 -0
- package/dist/orchestration/telegram-speech.js.map +1 -0
- package/dist/orchestration/telegram-tool-status.d.ts +23 -0
- package/dist/orchestration/telegram-tool-status.d.ts.map +1 -0
- package/dist/orchestration/telegram-tool-status.js +207 -0
- package/dist/orchestration/telegram-tool-status.js.map +1 -0
- package/dist/orchestration/telegram-voices.d.ts +42 -0
- package/dist/orchestration/telegram-voices.d.ts.map +1 -0
- package/dist/orchestration/telegram-voices.js +70 -0
- package/dist/orchestration/telegram-voices.js.map +1 -0
- package/dist/orchestration/tool-activity.d.ts +10 -0
- package/dist/orchestration/tool-activity.d.ts.map +1 -0
- package/dist/orchestration/tool-activity.js +38 -0
- package/dist/orchestration/tool-activity.js.map +1 -0
- package/dist/orchestration/types.d.ts +177 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +13 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/orchestration/voice-reply-policy.d.ts +8 -0
- package/dist/orchestration/voice-reply-policy.d.ts.map +1 -0
- package/dist/orchestration/voice-reply-policy.js +46 -0
- package/dist/orchestration/voice-reply-policy.js.map +1 -0
- package/dist/orchestration/worker-shares.d.ts +8 -0
- package/dist/orchestration/worker-shares.d.ts.map +1 -0
- package/dist/orchestration/worker-shares.js +59 -0
- package/dist/orchestration/worker-shares.js.map +1 -0
- package/dist/orchestration/workflow.d.ts +121 -0
- package/dist/orchestration/workflow.d.ts.map +1 -0
- package/dist/orchestration/workflow.js +69 -0
- package/dist/orchestration/workflow.js.map +1 -0
- package/dist/session/input-image.d.ts +10 -0
- package/dist/session/input-image.d.ts.map +1 -0
- package/dist/session/input-image.js +3 -0
- package/dist/session/input-image.js.map +1 -0
- package/dist/session/process.d.ts +15 -3
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +143 -35
- package/dist/session/process.js.map +1 -1
- package/dist/session/runtime-profile.d.ts +36 -0
- package/dist/session/runtime-profile.d.ts.map +1 -0
- package/dist/session/runtime-profile.js +63 -0
- package/dist/session/runtime-profile.js.map +1 -0
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +23 -1
- package/dist/session/store.js.map +1 -1
- package/dist/share/share-store.d.ts +3 -1
- package/dist/share/share-store.d.ts.map +1 -1
- package/dist/share/share-store.js +13 -2
- package/dist/share/share-store.js.map +1 -1
- package/dist/shared/line-request-order.d.ts +2 -0
- package/dist/shared/line-request-order.d.ts.map +1 -0
- package/dist/shared/line-request-order.js +16 -0
- package/dist/shared/line-request-order.js.map +1 -0
- package/dist/shared/task-elapsed.d.ts +3 -0
- package/dist/shared/task-elapsed.d.ts.map +1 -0
- package/dist/shared/task-elapsed.js +12 -0
- package/dist/shared/task-elapsed.js.map +1 -0
- package/dist/shared/task-updated-time.d.ts +3 -0
- package/dist/shared/task-updated-time.d.ts.map +1 -0
- package/dist/shared/task-updated-time.js +15 -0
- package/dist/shared/task-updated-time.js.map +1 -0
- package/dist/skills/loader.d.ts +2 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/telegram/chunks.d.ts +19 -0
- package/dist/telegram/chunks.d.ts.map +1 -0
- package/dist/telegram/chunks.js +129 -0
- package/dist/telegram/chunks.js.map +1 -0
- package/dist/telegram/markdown.d.ts.map +1 -1
- package/dist/telegram/markdown.js +18 -3
- package/dist/telegram/markdown.js.map +1 -1
- package/dist/telegram/receiver.d.ts +2 -1
- package/dist/telegram/receiver.d.ts.map +1 -1
- package/dist/telegram/receiver.js +6 -1
- package/dist/telegram/receiver.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/web-ui.d.ts.map +1 -1
- package/dist/ui/web-ui.js +31 -5
- package/dist/ui/web-ui.js.map +1 -1
- package/dist/voice/diagnostics.d.ts +29 -0
- package/dist/voice/diagnostics.d.ts.map +1 -0
- package/dist/voice/diagnostics.js +37 -0
- package/dist/voice/diagnostics.js.map +1 -0
- package/dist/voice/errors.d.ts +19 -0
- package/dist/voice/errors.d.ts.map +1 -0
- package/dist/voice/errors.js +133 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/line-audio.d.ts +4 -0
- package/dist/voice/line-audio.d.ts.map +1 -0
- package/dist/voice/line-audio.js +27 -0
- package/dist/voice/line-audio.js.map +1 -0
- package/dist/voice/managed-quota.d.ts +5 -0
- package/dist/voice/managed-quota.d.ts.map +1 -0
- package/dist/voice/managed-quota.js +35 -0
- package/dist/voice/managed-quota.js.map +1 -0
- package/dist/voice/mp3.d.ts +3 -0
- package/dist/voice/mp3.d.ts.map +1 -0
- package/dist/voice/mp3.js +30 -0
- package/dist/voice/mp3.js.map +1 -0
- package/dist/voice/notes.d.ts +9 -0
- package/dist/voice/notes.d.ts.map +1 -0
- package/dist/voice/notes.js +68 -0
- package/dist/voice/notes.js.map +1 -0
- package/dist/voice/playback.d.ts +22 -0
- package/dist/voice/playback.d.ts.map +1 -0
- package/dist/voice/playback.js +52 -0
- package/dist/voice/playback.js.map +1 -0
- package/dist/voice/preview.d.ts +6 -0
- package/dist/voice/preview.d.ts.map +1 -0
- package/dist/voice/preview.js +44 -0
- package/dist/voice/preview.js.map +1 -0
- package/dist/voice/protocol.d.ts +10 -0
- package/dist/voice/protocol.d.ts.map +1 -0
- package/dist/voice/protocol.js +30 -0
- package/dist/voice/protocol.js.map +1 -0
- package/dist/voice/providers/cartesia-tts.d.ts +26 -0
- package/dist/voice/providers/cartesia-tts.d.ts.map +1 -0
- package/dist/voice/providers/cartesia-tts.js +95 -0
- package/dist/voice/providers/cartesia-tts.js.map +1 -0
- package/dist/voice/providers/deepgram-stt.d.ts +20 -0
- package/dist/voice/providers/deepgram-stt.d.ts.map +1 -0
- package/dist/voice/providers/deepgram-stt.js +66 -0
- package/dist/voice/providers/deepgram-stt.js.map +1 -0
- package/dist/voice/providers/elevenlabs-stt.d.ts +20 -0
- package/dist/voice/providers/elevenlabs-stt.d.ts.map +1 -0
- package/dist/voice/providers/elevenlabs-stt.js +92 -0
- package/dist/voice/providers/elevenlabs-stt.js.map +1 -0
- package/dist/voice/providers/elevenlabs-tts.d.ts +26 -0
- package/dist/voice/providers/elevenlabs-tts.d.ts.map +1 -0
- package/dist/voice/providers/elevenlabs-tts.js +82 -0
- package/dist/voice/providers/elevenlabs-tts.js.map +1 -0
- package/dist/voice/providers/encoded-audio-tts.d.ts +21 -0
- package/dist/voice/providers/encoded-audio-tts.d.ts.map +1 -0
- package/dist/voice/providers/encoded-audio-tts.js +175 -0
- package/dist/voice/providers/encoded-audio-tts.js.map +1 -0
- package/dist/voice/providers/fake.d.ts +39 -0
- package/dist/voice/providers/fake.d.ts.map +1 -0
- package/dist/voice/providers/fake.js +41 -0
- package/dist/voice/providers/fake.js.map +1 -0
- package/dist/voice/providers/gemini-live-stt.d.ts +21 -0
- package/dist/voice/providers/gemini-live-stt.d.ts.map +1 -0
- package/dist/voice/providers/gemini-live-stt.js +158 -0
- package/dist/voice/providers/gemini-live-stt.js.map +1 -0
- package/dist/voice/providers/gemini.d.ts +60 -0
- package/dist/voice/providers/gemini.d.ts.map +1 -0
- package/dist/voice/providers/gemini.js +124 -0
- package/dist/voice/providers/gemini.js.map +1 -0
- package/dist/voice/providers/model-ref.d.ts +9 -0
- package/dist/voice/providers/model-ref.d.ts.map +1 -0
- package/dist/voice/providers/model-ref.js +31 -0
- package/dist/voice/providers/model-ref.js.map +1 -0
- package/dist/voice/providers/openrouter.d.ts +39 -0
- package/dist/voice/providers/openrouter.d.ts.map +1 -0
- package/dist/voice/providers/openrouter.js +154 -0
- package/dist/voice/providers/openrouter.js.map +1 -0
- package/dist/voice/providers/paxalabs-stt.d.ts +12 -0
- package/dist/voice/providers/paxalabs-stt.d.ts.map +1 -0
- package/dist/voice/providers/paxalabs-stt.js +41 -0
- package/dist/voice/providers/paxalabs-stt.js.map +1 -0
- package/dist/voice/providers/paxalabs-tts.d.ts +23 -0
- package/dist/voice/providers/paxalabs-tts.d.ts.map +1 -0
- package/dist/voice/providers/paxalabs-tts.js +80 -0
- package/dist/voice/providers/paxalabs-tts.js.map +1 -0
- package/dist/voice/providers/recorded-stt.d.ts +34 -0
- package/dist/voice/providers/recorded-stt.d.ts.map +1 -0
- package/dist/voice/providers/recorded-stt.js +59 -0
- package/dist/voice/providers/recorded-stt.js.map +1 -0
- package/dist/voice/providers/registry.d.ts +10 -0
- package/dist/voice/providers/registry.d.ts.map +1 -0
- package/dist/voice/providers/registry.js +77 -0
- package/dist/voice/providers/registry.js.map +1 -0
- package/dist/voice/providers/socket.d.ts +10 -0
- package/dist/voice/providers/socket.d.ts.map +1 -0
- package/dist/voice/providers/socket.js +91 -0
- package/dist/voice/providers/socket.js.map +1 -0
- package/dist/voice/providers/upstream.d.ts +9 -0
- package/dist/voice/providers/upstream.d.ts.map +1 -0
- package/dist/voice/providers/upstream.js +51 -0
- package/dist/voice/providers/upstream.js.map +1 -0
- package/dist/voice/providers/voice-catalog.d.ts +17 -0
- package/dist/voice/providers/voice-catalog.d.ts.map +1 -0
- package/dist/voice/providers/voice-catalog.js +167 -0
- package/dist/voice/providers/voice-catalog.js.map +1 -0
- package/dist/voice/queue.d.ts +2 -0
- package/dist/voice/queue.d.ts.map +1 -0
- package/dist/voice/queue.js +6 -0
- package/dist/voice/queue.js.map +1 -0
- package/dist/voice/session.d.ts +86 -0
- package/dist/voice/session.d.ts.map +1 -0
- package/dist/voice/session.js +464 -0
- package/dist/voice/session.js.map +1 -0
- package/dist/voice/turn-manager.d.ts +27 -0
- package/dist/voice/turn-manager.d.ts.map +1 -0
- package/dist/voice/turn-manager.js +75 -0
- package/dist/voice/turn-manager.js.map +1 -0
- package/dist/voice/types.d.ts +78 -0
- package/dist/voice/types.d.ts.map +1 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/wav.d.ts +2 -0
- package/dist/voice/wav.d.ts.map +1 -0
- package/dist/voice/wav.js +21 -0
- package/dist/voice/wav.js.map +1 -0
- package/dist/wechat/ilink-client.d.ts +13 -0
- package/dist/wechat/ilink-client.d.ts.map +1 -1
- package/dist/wechat/ilink-client.js +19 -0
- package/dist/wechat/ilink-client.js.map +1 -1
- package/dist/whatsapp/manager.d.ts.map +1 -1
- package/dist/whatsapp/manager.js +51 -1
- package/dist/whatsapp/manager.js.map +1 -1
- package/mcp/capability-catalog.ts +206 -0
- package/mcp/modules.ts +56 -0
- package/mcp/server.ts +18 -37
- package/mcp/tools/browser/module.ts +1 -1
- package/mcp/tools/browser/skills/open-browser/SKILL.md +5 -3
- package/mcp/tools/discord/commands.ts +17 -1
- package/mcp/tools/discord/inbound.ts +11 -0
- package/mcp/tools/discord/module.ts +42 -2
- package/mcp/tools/discord/receiver-server.ts +10 -0
- package/mcp/tools/discord/types.ts +4 -1
- package/mcp/tools/image/module.ts +3 -1
- package/mcp/tools/receiver-spool.test.ts +28 -0
- package/mcp/tools/receiver-spool.ts +56 -0
- package/mcp/tools/share-file/module.ts +9 -1
- package/mcp/tools/shared/share-client.ts +19 -0
- package/mcp/tools/tasks/module.ts +35 -0
- package/mcp/tools/telegram/media-group.ts +21 -0
- package/mcp/tools/telegram/receiver-server.ts +232 -11
- package/mcp/tools/telegram/selection-confirmation.ts +20 -0
- package/mcp/tools/telegram/task-browser.ts +117 -0
- package/mcp/tools/telegram/task-detail.ts +36 -0
- package/mcp/tools/telegram/task-elapsed.ts +1 -0
- package/mcp/tools/telegram/typing.ts +6 -116
- package/mcp/tools/video/module.ts +1 -1
- package/mcp/types.ts +5 -0
- package/package.json +2 -1
- package/resource/claude_gateway.jpeg +0 -0
- package/scripts/orchestration/bench-store.cjs +30 -0
- package/scripts/orchestration/inspect.cjs +16 -0
- package/scripts/orchestration/probe-profile.cjs +67 -0
- package/scripts/orchestration/reconcile.cjs +21 -0
- package/scripts/orchestration/smoke-elevenlabs.cjs +90 -0
- package/scripts/orchestration/smoke-media.cjs +70 -0
- package/scripts/orchestration/smoke-text.cjs +101 -0
- package/scripts/orchestration/smoke-vision.cjs +49 -0
package/README.md
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
|
|
2
1
|
# Claude Gateway
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
**An orchestrator, voice, and multi-channel platform for Claude Code.**
|
|
4
|
+
|
|
5
|
+
Claude Gateway keeps conversations responsive while Claude Code workers execute tasks. Talk or type through your connected channels, follow progress, and carry your agents' memory and skills across sessions.
|
|
5
6
|
|
|
6
7
|
<p align="center">
|
|
7
|
-
<img src="resource/claude_gateway.
|
|
8
|
+
<img src="resource/claude_gateway.jpeg" alt="Claude Gateway" width="680" />
|
|
8
9
|
</p>
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
[Documentation](https://0xmaxma.github.io/claude-gateway/) · [Quickstart](https://0xmaxma.github.io/claude-gateway/guide/quickstart.html) · [API reference](https://0xmaxma.github.io/claude-gateway/api/)
|
|
11
12
|
|
|
12
13
|
## Features
|
|
13
14
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
-
|
|
15
|
+
- 🪄 **Agent Orchestration Engine (enabled by default)** — responsive conversations while workers execute durable tasks. Existing configurations without the switch are upgraded automatically; explicit `false` remains an opt-out. See [orchestration settings](https://0xmaxma.github.io/claude-gateway/reference/orchestration-settings.html).
|
|
16
|
+
|
|
17
|
+
- 🔥 **Agent orchestration** — keep conversations responsive while reusable workers execute durable tasks, report progress, accept follow-up instructions, and support cancellation. See [orchestration and tasks](https://0xmaxma.github.io/claude-gateway/guide/orchestration.html).
|
|
18
|
+
- 🎙️ **Voice conversations** — speech recognition and spoken replies with per-agent models and voices, direct or upstream providers, live or recorded speech input, and audio replay. See [voice setup](https://0xmaxma.github.io/claude-gateway/guide/voice.html).
|
|
19
|
+
- 🧠 **Skill self-improvement** — agents learn reusable skills from their own work: after a substantive turn a background reviewer creates or updates a skill, hot-reloaded for the next turn. Provenance-guarded (never overwrites human-written skills), capped per day, and audited to `SKILLS_LEARNED.md`. See [`gateway.skillLearning`](https://0xmaxma.github.io/claude-gateway/reference/memory-settings.html#gateway-skilllearning)
|
|
20
|
+
- 📚 **Knowledge base (two-lane memory)** — per-agent SQLite/FTS5 searchable archive exposed through `memory_search` / `memory_get` MCP tools, so agents recall notes that don't fit the always-injected core; chunks carry fail-closed provenance and the index is refreshed off the gateway event loop. See [`gateway.knowledge`](https://0xmaxma.github.io/claude-gateway/reference/memory-settings.html#gateway-knowledge)
|
|
21
|
+
- 🌙 **Nightly dreaming** — background consolidation of long-term memory: a print-only reviewer proposes ops that a safe applier writes to `MEMORY.md` / `USER.md` (backup, bounded-loss, net-negative when over budget). Deterministic compaction, budget-scaled pruning, and staleness GC keep memory near budget without forgetting — archived entries stay searchable. See [`gateway.dreaming`](https://0xmaxma.github.io/claude-gateway/reference/memory-settings.html#gateway-dreaming)
|
|
17
22
|
- 🤖 **Multi-agent** — run multiple bots from a single gateway, each with isolated sessions
|
|
18
23
|
- 🔌 **Multi-channel MCP** — modular tool system per channel (Telegram, Discord, LINE, Slack, WhatsApp, Cron, Skills, extensible to more)
|
|
19
24
|
- 🧩 **Agent skills** — extensible skill system via SKILL.md files; agents can create, delete, and install skills from URLs at runtime with hot-reload
|
|
@@ -32,1625 +37,35 @@ A self-hosted multi-agent gateway for Claude Code — with agents that improve t
|
|
|
32
37
|
- 🛍️ **App Store** — install, update, and host Docker-compose apps on the gateway; apps get a reverse proxy at `/app/:name/:portName/*`, optional Unix socket bridge for host scripts, and optional AI agent injection
|
|
33
38
|
- ⬆️ **Self-update** — check for newer versions of `claude-gateway` and `claude-code` and trigger an update via a single API call (no SSH or shell access needed), or from the terminal with `claude-gateway update` / `claude-gateway claude update`
|
|
34
39
|
- 💾 **Session persistence** — conversation history saved and restored across restarts
|
|
35
|
-
- 🖥️ **PTY shell (wrap-shell mode)** — optional interactive pseudo-terminal backend (`gateway.headless: false`) for tools that require a real TTY; includes a live browser viewer (xterm.js) and a `/api/v1/sessions/:sessionId/screen` endpoint that returns the visible screen as plain text — agents can poll it to detect hang states, menus, or unexpected output without parsing ANSI escape codes; a `/cli` chat command (Telegram/Discord/LINE) opens the same viewer for a single agent, agent-scoped and without an admin key; app-agents always stay headless
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Requirements
|
|
40
|
-
|
|
41
|
-
- Node.js 22+
|
|
42
|
-
- [Claude Code CLI](https://claude.ai/code) v2.1.0+ installed and authenticated — `channels mode` is required (`claude --version`)
|
|
43
|
-
- The gateway must be able to find the `claude` executable: either have `claude` on the `PATH` of the process that launches the gateway, or set `CLAUDE_BIN` to its full path. When `CLAUDE_BIN` is unset, the gateway also probes the native-installer locations (`~/.local/bin/claude`, then `~/.local/share/claude/versions/`) and the legacy npm/nvm layout, so a Claude Code installer migration does not break new sessions. If none resolve, set `CLAUDE_BIN` explicitly (e.g. `CLAUDE_BIN=~/.local/bin/claude`).
|
|
44
|
-
- [Bun](https://bun.sh) — runs the MCP server subprocess (`mcp/server.ts`)
|
|
45
|
-
- A bot token per agent — Telegram (from [@BotFather](https://t.me/BotFather)) or Discord (from [Discord Developer Portal](https://discord.com/developers/applications))
|
|
46
|
-
- **PTY backend only** (`claude.headless: false`): native build tools required for `node-pty` — `gcc`, `python3`, and `node-gyp` must be available at `npm install` time (pre-built binaries are included for common platforms; build tools are only needed if a pre-built binary is unavailable for your platform)
|
|
47
|
-
|
|
48
|
-
---
|
|
40
|
+
- 🖥️ **PTY shell (wrap-shell mode)** — optional interactive pseudo-terminal backend (`gateway.headless: false`) for tools that require a real TTY; includes a live browser viewer (xterm.js) and a `/api/v1/sessions/:sessionId/screen` endpoint that returns the visible screen as plain text — agents can poll it to detect hang states, menus, or unexpected output without parsing ANSI escape codes; a `/cli` chat command (Telegram/Discord/LINE) opens the same viewer for a single agent, agent-scoped and without an admin key; app-agents and orchestration always stay headless
|
|
49
41
|
|
|
50
|
-
##
|
|
42
|
+
## Get started
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
**1. Install**
|
|
44
|
+
Install Node.js 22+, Bun, and an authenticated Claude Code CLI with channels support. Orchestration requires Linux. Docker/Compose is needed for apps; some voice formats require `ffmpeg`.
|
|
55
45
|
|
|
56
46
|
```bash
|
|
57
47
|
npm install -g @0xmaxma/claude-gateway
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Requires [Bun](https://bun.sh) — MCP server dependencies are installed automatically via `postinstall`.
|
|
61
|
-
|
|
62
|
-
**2. Configure environment (optional)**
|
|
63
|
-
|
|
64
|
-
The gateway auto-loads `~/.claude-gateway/.env` on startup:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
mkdir -p ~/.claude-gateway
|
|
68
|
-
cat > ~/.claude-gateway/.env << 'EOF'
|
|
69
|
-
# HTTP port (default: 10850)
|
|
70
|
-
# PORT=10850
|
|
71
|
-
|
|
72
|
-
# Bind address (default: 0.0.0.0 — all interfaces)
|
|
73
|
-
# Set to 127.0.0.1 if a host-network reverse proxy (e.g. Traefik) is used
|
|
74
|
-
# GATEWAY_BIND=127.0.0.1
|
|
75
|
-
|
|
76
|
-
# Path to gateway config (default: ~/.claude-gateway/config.json)
|
|
77
|
-
# GATEWAY_CONFIG=~/.claude-gateway/config.json
|
|
78
|
-
EOF
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
All variables are optional. Full list: [`.env.example`](.env.example)
|
|
82
|
-
|
|
83
|
-
**3. Start**
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
48
|
claude-gateway gateway start
|
|
87
49
|
```
|
|
88
50
|
|
|
89
|
-
|
|
90
|
-
`gateway start`, so a stray or mistyped command can never leave a gateway listening.
|
|
91
|
-
|
|
92
|
-
No config file needed — on first run, if `~/.claude-gateway/config.json` doesn't exist yet, the gateway creates it automatically with `"agents": []` and a fresh random admin API key, and prints that key once:
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
[gateway] No config found — created one at ~/.claude-gateway/config.json
|
|
96
|
-
[gateway] Admin API key (save this now — it will not be shown again):
|
|
97
|
-
[gateway] <random-hex-key>
|
|
98
|
-
[gateway] The CLI (claude-gateway agents create, etc.) picks this up automatically from ~/.claude-gateway/config.json.
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Save that key somewhere safe — it isn't shown again (though you can always read it back from `config.json` on disk). See [`config.template.json`](config.template.json) for the full config format (models list, more options) if you want to customize it by hand later.
|
|
102
|
-
|
|
103
|
-
**4. Create an agent**
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
claude-gateway agents create
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Interactive wizard — describe the agent, Claude generates the workspace files, review and accept them, then optionally connect a Telegram or Discord bot. Hot-reloads immediately, no restart needed. The CLI picks up the admin key from `config.json` automatically — no need to pass `--key`. (You can also add an agent entry to `config.json` by hand instead — same template link as above.)
|
|
110
|
-
|
|
111
|
-
**Run as a service (optional)**
|
|
112
|
-
|
|
113
|
-
To keep the gateway running after you log out or the machine reboots, let the CLI install the
|
|
114
|
-
service for you. It shows the exact unit it will write, asks before installing, and verifies
|
|
115
|
-
`/health` afterwards:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
claude-gateway service install # systemd *user* unit — no sudo
|
|
119
|
-
claude-gateway service install --print # just show what it would install
|
|
120
|
-
claude-gateway service status
|
|
121
|
-
claude-gateway service uninstall # asks first — this stops a running gateway
|
|
122
|
-
claude-gateway service start # start the installed service (found even if inactive)
|
|
123
|
-
claude-gateway service restart
|
|
124
|
-
claude-gateway service stop
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Install and uninstall both prompt before acting; pass `--yes` in scripts (without it, a
|
|
128
|
-
non-interactive run is refused rather than left hanging). Always stop the gateway through
|
|
129
|
-
`service uninstall` or `systemctl --user stop claude-gateway.service` — a bare `kill <pid>` bypasses
|
|
130
|
-
systemd's own stop tracking, so `Restart=always` brings it right back regardless of exit code.
|
|
131
|
-
|
|
132
|
-
`install`, `start` and `restart` — the commands meant to leave a running gateway behind — share one
|
|
133
|
-
exit-code contract with three outcomes: `0` fully healthy, `1` the action itself failed (or a
|
|
134
|
-
validation/confirmation gate refused, so nothing was written), `2` the action succeeded but
|
|
135
|
-
`/health` never answered within the poll window. A script that only checks the exit code — not the
|
|
136
|
-
JSON result on stdout — can still tell "didn't happen" apart from "happened, health unconfirmed"
|
|
137
|
-
this way. `start` on an already-running service is no exception: it still probes `/health` and can
|
|
138
|
-
still exit `2`, because "the process manager calls it active" is not "the gateway answers".
|
|
139
|
-
|
|
140
|
-
`install` also refuses (rather than just warning) if a `claude-gateway.service` unit already
|
|
141
|
-
exists and is enabled or active at *system* scope (e.g. one written by provisioning outside this
|
|
142
|
-
CLI) — installing a second, independent unit alongside it would race for the port on the next
|
|
143
|
-
reboot. It prints the exact `sudo systemctl disable --now claude-gateway.service` to resolve it;
|
|
144
|
-
pass `--force` to install anyway.
|
|
145
|
-
|
|
146
|
-
The systemd path writes `~/.config/systemd/user/claude-gateway.service`. Run
|
|
147
|
-
`loginctl enable-linger $USER` once if it must keep running while you're logged out.
|
|
148
|
-
The unit sets `OOMPolicy=continue` so that an OOM-killed child process (e.g. a dev server an
|
|
149
|
-
agent spawned on its own) doesn't take the whole gateway down with it — only `Restart=always`
|
|
150
|
-
restarting the *gateway's own* process is intended. Re-running `claude-gateway service install`
|
|
151
|
-
against an already-active unit whose rendered content changed (a newer CLI version, or different
|
|
152
|
-
flags) automatically restarts it via `systemctl ... restart`, so the update takes effect
|
|
153
|
-
immediately; re-running with unchanged content leaves the running unit alone.
|
|
154
|
-
Prefer [PM2](https://pm2.keymetrics.io)? `claude-gateway service install --manager pm2` registers
|
|
155
|
-
and saves the process instead (run `pm2 startup` separately for boot-time start).
|
|
156
|
-
|
|
157
|
-
**System-scope installs (for automated/infra provisioning)**
|
|
158
|
-
|
|
159
|
-
Pass `--scope system` to install a root-owned unit at `/etc/systemd/system/claude-gateway.service`
|
|
160
|
-
instead — for provisioning that needs the gateway to run under a fixed system account rather than
|
|
161
|
-
whoever happens to run the install interactively. It requires:
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
sudo claude-gateway service install --scope system --run-as gwuser --yes
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
- The caller must already be root — `--scope system` never escalates via `sudo` on its own, and
|
|
168
|
-
refuses immediately if it isn't.
|
|
169
|
-
- `--run-as <user>` is required and becomes the unit's `User=`; `WantedBy=` is
|
|
170
|
-
`multi-user.target` instead of `default.target`, so it starts at boot regardless of any login
|
|
171
|
-
session (the `loginctl enable-linger` hint is skipped — it's meaningless here).
|
|
172
|
-
- `WorkingDirectory=`/`HOME=`/the config path all resolve to `--run-as`'s own home directory
|
|
173
|
-
(looked up via `getent passwd`, not the installing root process's home) — the unit runs as that
|
|
174
|
-
user, so its paths must be theirs. A `~/...` in `--config`/`--env-file` expands against that same
|
|
175
|
-
home. `$GATEWAY_CONFIG` from the installing (root) process's own environment is **not** consulted
|
|
176
|
-
for a system-scope install — only an explicit `--config` is — since it belongs to root's
|
|
177
|
-
environment, not `--run-as`'s. If the user's `~/.claude-gateway` doesn't exist yet, the install
|
|
178
|
-
creates it and `chown`s it to them; if it already exists but is owned by someone else (e.g. a
|
|
179
|
-
prior install used a different `--run-as`), ownership is reassigned to match. An
|
|
180
|
-
already-correctly-owned directory is left untouched. Refuses if `--run-as` doesn't resolve to a
|
|
181
|
-
real user on this host.
|
|
182
|
-
- A system-scope install never refuses itself over the system-scope conflict check described
|
|
183
|
-
above — that check exists to protect a *user*-scope install from colliding with an externally
|
|
184
|
-
provisioned system-scope unit, and a system-scope install *is* that unit.
|
|
185
|
-
- `--after <target1,target2>`, `--env-file <path>`, and `--env KEY=VALUE[,KEY=VALUE...]` further
|
|
186
|
-
customize the generated unit (both scopes): extra `After=` ordering targets, an
|
|
187
|
-
`EnvironmentFile=-<path>` for feeding secrets in without ever writing them into the unit text,
|
|
188
|
-
and additional non-secret `Environment=` lines. `--env` refuses to override `HOME`, `PATH`, or
|
|
189
|
-
`GATEWAY_CONFIG` (the installer's own reserved names) — use `--env-file` for anything sensitive.
|
|
190
|
-
- `service status --scope system` and `service uninstall --scope system` work the same way against
|
|
191
|
-
the system-scope unit (uninstall also requires root).
|
|
192
|
-
|
|
193
|
-
Once installed, drive it through the CLI — it detects whichever manager owns the process:
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
claude-gateway gateway status # manager, URL, health
|
|
197
|
-
claude-gateway gateway restart
|
|
198
|
-
claude-gateway gateway stop
|
|
199
|
-
claude-gateway gateway logs # tail the gateway's own log (works even when it is dead)
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
`gateway restart`/`stop` only drive whatever manager is currently reported *active*. To start an
|
|
203
|
-
installed service that is currently stopped — or to act on a specific `--manager`/`--scope`
|
|
204
|
-
regardless of what else might be running — use `service start`/`stop`/`restart` instead; they
|
|
205
|
-
discover the installed unit from disk the same way `service status`/`uninstall` do.
|
|
206
|
-
|
|
207
|
-
Managing PM2 directly still works too:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
pm2 status # check gateway status
|
|
211
|
-
pm2 logs gateway # tail logs
|
|
212
|
-
pm2 restart gateway # restart
|
|
213
|
-
pm2 stop gateway # stop
|
|
214
|
-
pm2 delete gateway # remove from PM2
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
### For development
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
git clone https://github.com/0xMaxMa/claude-gateway
|
|
223
|
-
cd claude-gateway
|
|
224
|
-
npm install # also runs bun install in mcp/
|
|
225
|
-
npm run build
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Start the gateway
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
npm start
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Config is auto-loaded from `~/.claude-gateway/config.json` — if it doesn't exist yet, `npm start` creates it automatically with `"agents": []` and a fresh admin key (see the Start step in the npm-install path above). Bot tokens are auto-loaded from `~/.claude-gateway/agents/<id>/.env`.
|
|
235
|
-
|
|
236
|
-
### Create an agent
|
|
237
|
-
|
|
238
|
-
The interactive wizard handles everything — workspace files, bot token, and pairing:
|
|
51
|
+
In another terminal:
|
|
239
52
|
|
|
240
53
|
```bash
|
|
241
54
|
claude-gateway agents create
|
|
242
55
|
```
|
|
243
56
|
|
|
244
|
-
|
|
245
|
-
1. Choose an agent id and describe its role — Claude generates workspace files
|
|
246
|
-
2. Review and accept the generated files
|
|
247
|
-
3. Optionally connect a channel: **Telegram** or **Discord** — paste the bot token, wizard verifies it automatically
|
|
248
|
-
4. Agent hot-reloads immediately — send any message to the bot, then approve pairing:
|
|
249
|
-
```bash
|
|
250
|
-
claude-gateway channels approve --agent <id> --channel telegram --code <code>
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
To manage an existing agent — regenerate `AGENTS.md`, or connect/update/disconnect Telegram, Discord, LINE, or Slack — run `claude-gateway agents update`.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Workspace Files
|
|
258
|
-
|
|
259
|
-
Each agent has a workspace directory with markdown files that define its behaviour:
|
|
260
|
-
|
|
261
|
-
| File | Required | Purpose |
|
|
262
|
-
|------|----------|---------|
|
|
263
|
-
| `AGENTS.md` | **Yes** | Core identity, rules, capabilities |
|
|
264
|
-
| `IDENTITY.md` | No | Agent name, emoji, avatar, personality identity |
|
|
265
|
-
| `SOUL.md` | No | Tone, personality, speaking style |
|
|
266
|
-
| `USER.md` | No | User profile and preferences |
|
|
267
|
-
| `MEMORY.md` | No | Long-term memory (auto-appended by the agent) |
|
|
268
|
-
| `HEARTBEAT.md` | No | Scheduled/proactive tasks |
|
|
269
|
-
| `skills/` | No | Directory of SKILL.md files — agent-specific skills |
|
|
270
|
-
|
|
271
|
-
On startup (and on any file change), all files are assembled into `CLAUDE.md` which the Claude subprocess reads as its system prompt. Do not edit `CLAUDE.md` directly.
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## Configuration Reference
|
|
276
|
-
|
|
277
|
-
Config lives at `~/.claude-gateway/config.json` (or set `GATEWAY_CONFIG` env var / `--config` flag).
|
|
278
|
-
|
|
279
|
-
```json
|
|
280
|
-
{
|
|
281
|
-
"configVersion": "1.0.0",
|
|
282
|
-
"gateway": {
|
|
283
|
-
"logDir": "~/.claude-gateway/logs",
|
|
284
|
-
"logs": {
|
|
285
|
-
"level": "info",
|
|
286
|
-
"maxFileBytes": 16777216,
|
|
287
|
-
"maxFiles": 3,
|
|
288
|
-
"retentionDays": 14
|
|
289
|
-
},
|
|
290
|
-
"timezone": "Asia/Bangkok",
|
|
291
|
-
"api": {
|
|
292
|
-
"keys": [
|
|
293
|
-
{
|
|
294
|
-
"key": "${MY_API_KEY}",
|
|
295
|
-
"description": "Internal app",
|
|
296
|
-
"agents": ["alfred"]
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
"key": "${ADMIN_API_KEY}",
|
|
300
|
-
"description": "Admin",
|
|
301
|
-
"agents": "*"
|
|
302
|
-
}
|
|
303
|
-
]
|
|
304
|
-
}
|
|
305
|
-
},
|
|
306
|
-
"agents": [
|
|
307
|
-
{
|
|
308
|
-
"id": "alfred",
|
|
309
|
-
"description": "Personal assistant",
|
|
310
|
-
"workspace": "~/.claude-gateway/agents/alfred/workspace",
|
|
311
|
-
"env": "",
|
|
312
|
-
"session": {
|
|
313
|
-
"idleTimeoutMinutes": 30,
|
|
314
|
-
"maxConcurrent": 20
|
|
315
|
-
},
|
|
316
|
-
"telegram": {
|
|
317
|
-
"botToken": "${ALFRED_BOT_TOKEN}"
|
|
318
|
-
},
|
|
319
|
-
"claude": {
|
|
320
|
-
"model": "claude-sonnet-4-6",
|
|
321
|
-
"extraFlags": []
|
|
322
|
-
},
|
|
323
|
-
"heartbeat": {
|
|
324
|
-
"rateLimitMinutes": 30
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
]
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### `gateway.timezone` (optional)
|
|
332
|
-
|
|
333
|
-
IANA timezone, default `"UTC"`. Shared default for the per-feature scheduling
|
|
334
|
-
timezones below when they are unset or invalid: `gateway.history.cleanupTimezone`,
|
|
335
|
-
`gateway.appBackup.cleanupTimezone`, `gateway.skillLearning.pruneTimezone`,
|
|
336
|
-
`gateway.dreaming.dreamTimezone`, and `gateway.knowledge.reflection.timezone`. A
|
|
337
|
-
valid per-feature field still overrides this shared default for that one feature;
|
|
338
|
-
an invalid per-feature value falls through to `gateway.timezone` rather than being
|
|
339
|
-
treated as set. An invalid `gateway.timezone` itself falls back to `"UTC"` rather
|
|
340
|
-
than crashing that scheduler.
|
|
341
|
-
|
|
342
|
-
### `gateway.publicUrl` (optional)
|
|
343
|
-
|
|
344
|
-
The externally reachable gateway base URL. Set it manually to enable short-lived
|
|
345
|
-
public file shares used by `generate_image` reference edits and `share_file`
|
|
346
|
-
(formerly `share_image`, which still works as a deprecated image-only alias).
|
|
347
|
-
The URL must end in `/gateway`; changing it requires a gateway restart.
|
|
348
|
-
|
|
349
|
-
```json
|
|
350
|
-
{
|
|
351
|
-
"gateway": {
|
|
352
|
-
"publicUrl": "https://vm.example.com/gateway"
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Minted share URLs have the stable form
|
|
358
|
-
`https://vm.example.com/gateway/shared/TOKEN`. When `publicUrl` is set the mint
|
|
359
|
-
response includes this ready-built `url`; when it is unset the response still
|
|
360
|
-
returns the `token` (the share endpoint stays enabled) and callers with their own
|
|
361
|
-
public base — e.g. LINE, which derives its host from the inbound webhook — build
|
|
362
|
-
`<base>/shared/<token>` themselves. HTTP is accepted only for local development
|
|
363
|
-
hosts such as `http://host.docker.internal:10850/gateway`.
|
|
364
|
-
|
|
365
|
-
### `gateway.oauthReturnUrl` (optional)
|
|
366
|
-
|
|
367
|
-
Where to send the browser after a connector OAuth sign-in finishes. The gateway is
|
|
368
|
-
product-agnostic and never hardcodes a downstream app's domain, so this is opt-in.
|
|
369
|
-
|
|
370
|
-
```json
|
|
371
|
-
{
|
|
372
|
-
"gateway": {
|
|
373
|
-
"oauthReturnUrl": "https://app.example.com/settings/connectors"
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
Set, the callback issues a real `302` to it on **every** terminal outcome — success, and
|
|
379
|
-
also a denied, expired or failed sign-in, which carries `?connector_oauth_error=<code>`.
|
|
380
|
-
Unset, the callback renders a plain "Connected — you can close this tab" page instead.
|
|
381
|
-
The value is validated once at startup: anything that isn't a well-formed `http(s)` URL
|
|
382
|
-
is logged and ignored rather than injecting a broken redirect into every future callback.
|
|
383
|
-
The scheme is part of that check — this value becomes the `Location` of a redirect sent
|
|
384
|
-
to the end user's own browser from a public route, so a `javascript:` or `data:` URL
|
|
385
|
-
here would be script running on every sign-in, and is refused like any other malformed
|
|
386
|
-
value.
|
|
387
|
-
|
|
388
|
-
### `gateway.customConnectors` (optional)
|
|
389
|
-
|
|
390
|
-
User-pasted MCP connectors, keyed by a slugified id. Normally written through the API
|
|
391
|
-
(`POST /api/v1/connectors/custom`) rather than by hand.
|
|
392
|
-
|
|
393
|
-
```json
|
|
394
|
-
{
|
|
395
|
-
"gateway": {
|
|
396
|
-
"customConnectors": {
|
|
397
|
-
"firecrawl": {
|
|
398
|
-
"label": "Firecrawl",
|
|
399
|
-
"config": {
|
|
400
|
-
"type": "streamable-http",
|
|
401
|
-
"url": "https://mcp.firecrawl.dev/v2/mcp-oauth",
|
|
402
|
-
"headers": { "Authorization": "Bearer {access_token}" }
|
|
403
|
-
},
|
|
404
|
-
"secretNames": ["access_token"],
|
|
405
|
-
"credentialOwner": "gateway"
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
Each entry is raw `mcpServers`-entry JSON with `{placeholder}` tokens standing in for
|
|
413
|
-
secrets. `credentialOwner` records who holds the credential and keeps it valid — `none`,
|
|
414
|
-
`static` (a pasted value), `gateway` (this gateway ran the OAuth flow and refreshes the
|
|
415
|
-
token itself) or `external` (a control plane pushes tokens in). It is written by the
|
|
416
|
-
route that creates the entry; see [API.md](./API.md#connectors-api). **Only the placeholder names are stored here** — the values live in
|
|
417
|
-
`~/.claude-gateway/mcp-token.env` (mode `0600`), namespaced
|
|
418
|
-
`CUSTOM__<connectorId>__<placeholderName>`, and are substituted in when a session spawns.
|
|
419
|
-
Override that file's path with `GATEWAY_MCP_TOKEN_ENV_PATH`.
|
|
420
|
-
|
|
421
|
-
Custom connectors are **admin-trusted but not code-reviewed** — the config is whatever
|
|
422
|
-
the admin pasted, and it becomes an MCP server in every agent's session. Per-agent
|
|
423
|
-
enablement is opt-out and lives on the agent instead (`PATCH /api/v1/agents/:id` with
|
|
424
|
-
`connectors`); connecting a connector at all is the security gate. See
|
|
425
|
-
[API.md](./API.md#connectors-api) for the full model, the OAuth flow, and the refresh
|
|
426
|
-
behaviour.
|
|
427
|
-
|
|
428
|
-
### `gateway.connectorsDefaultEnabled` (optional)
|
|
429
|
-
|
|
430
|
-
Whether a connected connector is available to an agent that has no explicit entry in its
|
|
431
|
-
own `connectors` map. Defaults to `true` — opt-out: connecting a connector makes it
|
|
432
|
-
available everywhere, and an agent only misses it if explicitly disabled.
|
|
433
|
-
|
|
434
|
-
```json
|
|
435
|
-
{
|
|
436
|
-
"gateway": {
|
|
437
|
-
"connectorsDefaultEnabled": false
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
Set it to `false` on a gateway that hosts agents for **more than one person**. The default
|
|
443
|
-
suits the common single-operator install, but with several owners it hands a credential
|
|
444
|
-
connected by one of them to every agent on the box — including agents whose chat users are
|
|
445
|
-
not that person. With `false`, each agent has to be opted in explicitly (`PATCH
|
|
446
|
-
/api/v1/agents/:id` with `{"connectors": {"<id>": {"enabled": true}}}`).
|
|
447
|
-
|
|
448
|
-
Changing this affects the next session spawn, like any other connector change.
|
|
449
|
-
|
|
450
|
-
### `gateway.logs` (optional)
|
|
451
|
-
|
|
452
|
-
Verbosity, rotation and retention for the files in `logDir`. The whole block is optional —
|
|
453
|
-
omit it and the defaults below apply.
|
|
454
|
-
|
|
455
|
-
| Field | Default | Description |
|
|
456
|
-
|-------|---------|-------------|
|
|
457
|
-
| `level` | `"info"` | Minimum level written, to both the file and stdout. One of `debug`, `info`, `warn`, `error` |
|
|
458
|
-
| `maxFileBytes` | `16777216` (16 MiB) | Rotate `<name>.log` to `<name>.log.1` once an append would carry it past this size |
|
|
459
|
-
| `maxFiles` | `3` | Rotated generations kept per stream; the oldest is deleted. Lowering it collects the generations it orphans at the next rotation. `0` = keep none |
|
|
460
|
-
| `retentionDays` | `14` | Delete logs (live and rotated) older than this, at boot and once a day. `0` = keep forever |
|
|
461
|
-
|
|
462
|
-
`level` is the one that governs disk usage. Session processes log every stream event at `debug`,
|
|
463
|
-
which on a live host measured 19,995 `debug` lines to 5 `info` lines inside a single 217 MB file —
|
|
464
|
-
so `debug` is off by default. Set `"level": "debug"` when you are actually chasing something, and
|
|
465
|
-
expect the directory to grow quickly while it is on. Rotation and retention bound what is *kept*;
|
|
466
|
-
only the level bounds what is *written*.
|
|
467
|
-
|
|
468
|
-
Retention is age-based because each session writes its own `<agent>:session:<uuid>.log` and never
|
|
469
|
-
returns to it — `maxFiles` prunes generations of one stream, so it can never reach them.
|
|
470
|
-
|
|
471
|
-
This block is **hot-reloaded**: edit it in `config.json` and it applies on the next config reload,
|
|
472
|
-
no restart. That matters because turning the level up is something you do while chasing a live
|
|
473
|
-
problem, and a restart would kill the sessions you are trying to observe.
|
|
474
|
-
|
|
475
|
-
### `session`
|
|
476
|
-
|
|
477
|
-
| Field | Default | Description |
|
|
478
|
-
|-------|---------|-------------|
|
|
479
|
-
| `idleTimeoutMinutes` | `30` | Kill idle session subprocess after N minutes of inactivity. Inactivity means no incoming message **and** no subprocess output — a session actively producing output (e.g. a self-paced `/loop`) is not treated as idle |
|
|
480
|
-
| `maxConcurrent` | `20` | Max simultaneous active sessions per agent; oldest idle is evicted when exceeded |
|
|
481
|
-
|
|
482
|
-
### `gateway.history` (optional)
|
|
483
|
-
|
|
484
|
-
Global default retention policy. Can be overridden per-agent with an `history` key inside the agent config.
|
|
485
|
-
|
|
486
|
-
```json
|
|
487
|
-
{
|
|
488
|
-
"gateway": {
|
|
489
|
-
"history": {
|
|
490
|
-
"retentionDays": 90,
|
|
491
|
-
"maxHistoryMessages": 30,
|
|
492
|
-
"cleanupHour": 3,
|
|
493
|
-
"cleanupTimezone": "Asia/Bangkok"
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
| Field | Default | Description |
|
|
500
|
-
|-------|---------|-------------|
|
|
501
|
-
| `retentionDays` | `null` (keep forever) | Delete messages older than N days on each cleanup cycle |
|
|
502
|
-
| `maxHistoryMessages` | `50` | Max history messages re-injected into a session at spawn. Lower it to shrink the context loaded at session start. `0` = inject no history |
|
|
503
|
-
| `cleanupHour` | `3` | Hour of day to run cleanup (24h, in `cleanupTimezone`) |
|
|
504
|
-
| `cleanupTimezone` | `"UTC"` | IANA timezone for the cleanup schedule; falls back to `gateway.timezone` when unset or invalid |
|
|
505
|
-
|
|
506
|
-
Per-agent override example:
|
|
507
|
-
```json
|
|
508
|
-
{
|
|
509
|
-
"agents": [
|
|
510
|
-
{
|
|
511
|
-
"id": "alfred",
|
|
512
|
-
"history": { "retentionDays": 30, "maxHistoryMessages": 30 }
|
|
513
|
-
}
|
|
514
|
-
]
|
|
515
|
-
}
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
### `dmPolicy`
|
|
519
|
-
|
|
520
|
-
Access policy is configured per-channel in the agent's workspace state file, not in `config.json`:
|
|
521
|
-
|
|
522
|
-
| File | Path |
|
|
523
|
-
|------|------|
|
|
524
|
-
| Telegram | `~/.claude-gateway/agents/<id>/workspace/.telegram-state/access.json` |
|
|
525
|
-
| Discord | `~/.claude-gateway/agents/<id>/workspace/.discord-state/access.json` |
|
|
526
|
-
|
|
527
|
-
| Value | Behaviour |
|
|
528
|
-
|-------|-----------|
|
|
529
|
-
| `allowlist` | Only user IDs in `allowFrom` can DM the agent (**default**) |
|
|
530
|
-
| `open` | Anyone can DM the agent |
|
|
531
|
-
| `pairing` | New users DM the bot to receive a pairing code; approve with `claude-gateway channels approve` |
|
|
532
|
-
|
|
533
|
-
### `gateway.headless`
|
|
534
|
-
|
|
535
|
-
Controls the Claude subprocess backend for all non-app agents.
|
|
536
|
-
|
|
537
|
-
| Value | Backend | Description |
|
|
538
|
-
|-------|---------|-------------|
|
|
539
|
-
| `true` *(default)* | Headless (`--print`) | Stateless invocation, lowest overhead |
|
|
540
|
-
| `false` | PTY shell wrapper | Interactive pseudo-terminal — full TUI support |
|
|
541
|
-
|
|
542
|
-
**App-agents always run headless** regardless of this setting.
|
|
543
|
-
|
|
544
|
-
`--dangerously-skip-permissions` is always injected by the gateway automatically — there is no per-agent config field for it.
|
|
545
|
-
|
|
546
|
-
In PTY mode that flag makes Claude Code open a "Bypass Permissions mode" confirmation dialog at startup, which the wrapper accepts on your behalf. How it is accepted depends on the Claude Code build: releases up to **2.1.247** render numbered options (`1. No, exit` / `2. Yes, I accept`) and are accepted with the digit, while **2.1.248 and newer** drop the numbers, so the wrapper walks the caret onto the accept row and only then presses Enter. If a future release changes the dialog beyond what the wrapper recognises, it deliberately sends **no** keystroke and leaves the dialog on screen rather than risk selecting "No, exit" (which would exit Claude Code) — set `PTY_SHELL_SKIP_DIALOG_DISMISS=1` to turn the auto-accept off entirely.
|
|
547
|
-
|
|
548
|
-
```json
|
|
549
|
-
{
|
|
550
|
-
"gateway": {
|
|
551
|
-
"headless": false
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
This setting is hot-reloadable — new sessions pick it up without a restart.
|
|
557
|
-
|
|
558
|
-
### `gateway.selfHealing.autoRecover`
|
|
559
|
-
|
|
560
|
-
Opt-in self-healing for the turn-trace watchdog (Epic #195). When a turn stalls, the gateway always detects it, logs a scrubbed incident, and notifies the affected chat. This flag additionally controls whether the gateway may *act* on a stall.
|
|
561
|
-
|
|
562
|
-
| Value | Behaviour |
|
|
563
|
-
|-------|-----------|
|
|
564
|
-
| `false` *(default)* | Detection + incident logging + notification only — no automatic action |
|
|
565
|
-
| `true` | The watchdog may run a whitelisted recovery for a stalled turn: a keystroke into the TUI (esc / enter / arrow / menu selection), a session restart, a reversible safe-mode fallback to the headless backend, and — after a successful unblock — a guarded resend of the last message (only if the turn produced no output, so it is never double-submitted) |
|
|
566
|
-
|
|
567
|
-
Recovery actions are clamped to a per-stage whitelist and a per-turn budget, and any local triage treats the on-screen text as untrusted data validated against a closed schema. Safe-mode auto-fallback on a hard PTY failure is independent of this flag (it is always reversible and never presses keys). In-memory only — a gateway restart re-reads your real config.
|
|
568
|
-
|
|
569
|
-
```json
|
|
570
|
-
{
|
|
571
|
-
"gateway": {
|
|
572
|
-
"selfHealing": {
|
|
573
|
-
"autoRecover": true
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### `gateway.skillLearning`
|
|
580
|
-
|
|
581
|
-
Controls [skill self-improvement](#skill-self-improvement) — agents learning reusable skills from their own work. Telemetry capture is always on; the reviewer/writer/curator honor `enabled`.
|
|
582
|
-
|
|
583
|
-
| Field | Default | Description |
|
|
584
|
-
|-------|---------|-------------|
|
|
585
|
-
| `enabled` | `true` | Master switch for the reviewer/writer/curator (telemetry is captured regardless) |
|
|
586
|
-
| `mode` | `"auto"` | `auto` writes skills directly; `propose` queues them for approval instead |
|
|
587
|
-
| `minToolCalls` | `5` | Minimum tool calls in a turn before it's eligible for review |
|
|
588
|
-
| `reviewModel` | `claude-haiku-4-5-…` | Model used for the background review pass |
|
|
589
|
-
| `maxAutoSkills` | `50` | Cap on the number of non-pinned `origin: auto` skills kept per agent (pinned skills are never evicted and don't count toward the cap) |
|
|
590
|
-
| `maxAgeDays` | `30` | Curator prunes auto-skills older than this (with too few uses) |
|
|
591
|
-
| `minUsesToKeep` | `2` | Auto-skills used fewer times than this are prune candidates |
|
|
592
|
-
| `maxReviewsPerDay` | `20` | Per-day cap on background review runs |
|
|
593
|
-
| `pruneHour` / `pruneTimezone` | `3` / `UTC` | When the daily curator runs; `pruneTimezone` falls back to `gateway.timezone` when unset or invalid |
|
|
594
|
-
| `notify` | `true` | Push a per-write ping to every configured channel (see [notifications](#skill-self-improvement)); the `SKILLS_LEARNED.md` diary is written regardless |
|
|
595
|
-
|
|
596
|
-
```json
|
|
597
|
-
{
|
|
598
|
-
"gateway": {
|
|
599
|
-
"skillLearning": {
|
|
600
|
-
"enabled": true,
|
|
601
|
-
"mode": "auto",
|
|
602
|
-
"notify": true
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
Per-agent overrides are supported under the agent's own `skillLearning` block; unset fields fall back to the gateway default.
|
|
609
|
-
|
|
610
|
-
### `gateway.memory`
|
|
611
|
-
|
|
612
|
-
Memory budget discipline. Self-authored memory files (`MEMORY.md`, `USER.md`) that exceed a **soft** char budget get a loud over-budget banner prepended to their `CLAUDE.md` section at compose time — instead of a silent `[TRUNCATED]` — nudging the agent to consolidate. The banner reaches the agent on its next spawn (frozen-at-spawn, no restart) and self-heals once the file is back under budget. The banner lives only in the composed `CLAUDE.md`; the source file on disk is never rewritten with it.
|
|
613
|
-
|
|
614
|
-
| Field | Default | Description |
|
|
615
|
-
|-------|---------|-------------|
|
|
616
|
-
| `memoryBudgetChars` | `8000` | Soft budget for `MEMORY.md` (`0` = disabled) |
|
|
617
|
-
| `userBudgetChars` | `3000` | Soft budget for `USER.md` (`0` = disabled) |
|
|
618
|
-
| `overBudget` | `"warn"` | Banner severity: `warn` (⚠️) or `error` (🛑, stronger wording); an unknown value falls back to `warn` |
|
|
619
|
-
| `writeRouting` | `true` | Inject the **two-tier write contract** into the Memory Rule (`MEMORY.md` = durable facts; task-log → `memory/<topic>.md`) and let nightly dreaming route episodic ops out. `false` = kill-switch (exact pre-routing behavior) |
|
|
620
|
-
| `episodicArchiveDir` | `"memory"` | Workspace-relative dir episodic notes are written under (validated, path-traversal-guarded) |
|
|
621
|
-
|
|
622
|
-
```json
|
|
623
|
-
{
|
|
624
|
-
"gateway": {
|
|
625
|
-
"memory": {
|
|
626
|
-
"memoryBudgetChars": 8000,
|
|
627
|
-
"userBudgetChars": 3000,
|
|
628
|
-
"overBudget": "warn",
|
|
629
|
-
"writeRouting": true,
|
|
630
|
-
"episodicArchiveDir": "memory"
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
The soft budget sits well under the hard per-file limit (still applied as a context safety net); the banner is the primary over-budget signal for memory files.
|
|
637
|
-
|
|
638
|
-
**Write routing (planning-65).** `MEMORY.md` is injected into every prompt, so it should hold only **durable semantic facts** (preferences, standing rules, identity, lessons). **Episodic task-log** (completed work, PR/issue status, dated events) belongs in `memory/<topic>.md` — indexed and retrieved on demand via `memory_search`, never carried in-prompt. When `writeRouting` is on, the Memory Rule states this tier contract to the agent, and the nightly dreaming reviewer may emit `tier:"episodic"` ops that the applier appends to `memory/<topic>.md` (slug-validated + realpath-confined; a memory-only change ⇒ no session restart). To drain an existing over-budget `MEMORY.md`, run the one-shot migration `node dist/agent/dreaming/migrate-cli.js <workspaceDir> [--apply]` — a deterministic terminal sweep (compactor) plus a gated episodic route-out (`propose` writes `.dreaming/migration-plan.md`; `--apply` performs the moves). Pinned sections (`## User`, `## Feedback`, `## Preferences`) are never moved, and every relocated entry stays searchable via `memory_search` (recall preserved). **planning-67:** with `gateway.dreaming.autoRouteOut` on (the default), the nightly dream performs this same route-out **automatically** whenever `MEMORY.md` is over budget — no manual per-agent run — and every over-budget net-shrink `remove` now **relocates** its block to `memory/archive/pruned.md` (searchable) before cutting it, so no dream op ever silently forgets.
|
|
639
|
-
|
|
640
|
-
### `gateway.dreaming`
|
|
641
|
-
|
|
642
|
-
Nightly memory **dreaming** — background consolidation of an agent's long-term memory. A print-only `claude -p` reviewer (no tools, no `--dangerously-skip-permissions`) reads a lookback window of the agent's own session transcripts and proposes memory-consolidation ops. In **`auto`** mode (the default) a safe applier writes the ops to `MEMORY.md`/`USER.md` (rollback pre-image first; ordered apply with anchor re-resolution; bounded-loss + append-only fallback; net-negative when over budget) — a memory-only change, so no session is restarted. In **`propose`** mode the proposals are written **only** to a `DREAMS.md` diary + JSONL audit under `<workspace>/.dreaming/` — no memory file is modified (set `mode: "propose"` to keep this dry-run behavior).
|
|
643
|
-
|
|
644
|
-
| Field | Default | Description |
|
|
645
|
-
|-------|---------|-------------|
|
|
646
|
-
| `enabled` | `true` | Master switch (`false` ⇒ no scheduler, no run) |
|
|
647
|
-
| `mode` | `"auto"` | `auto` = apply ops via the safe applier (backup, bounded-loss, net-negative); `propose` = diary-only dry-run |
|
|
648
|
-
| `dreamHour` / `dreamTimezone` | `3` / `UTC` | When the nightly dream runs (invalid tz → `gateway.timezone`, then UTC); `dreamTimezone` falls back to `gateway.timezone` when unset or invalid |
|
|
649
|
-
| `dreamMinute` | `0` | Minute-of-hour the dream fires at, paired with `dreamHour` (0–59). Set with `staggerWindowMinutes: 0` to fire at an exact `HH:MM` (e.g. for a controlled re-test) |
|
|
650
|
-
| `quietMinutes` | `30` | Skip a run if a session was active within this window |
|
|
651
|
-
| `lookbackDays` | `3` | How far back to scan sessions |
|
|
652
|
-
| `maxChangesPerRun` | `3` | Cap on proposed ops per run (`0` ⇒ no-op) |
|
|
653
|
-
| `reviewModel` | `claude-haiku-4-5-…` | Cheap model for the reviewer |
|
|
654
|
-
| `promotionThreshold` / `minRecallCount` | `0.6` / `2` | Scoring thresholds for promoting a fact |
|
|
655
|
-
| `autoRouteOut` | `true` | planning-67: in `auto` mode, drain an **over-budget** `MEMORY.md` by routing its episodic task-log to `memory/<topic>.md` automatically each night (archive-safe, pinned excluded, idempotent) instead of a manual per-agent `migrate-cli`. `false` = kill-switch |
|
|
656
|
-
| `staggerWindowMinutes` | `30` | planning-68: spread agents' nightly runs across a window (a deterministic per-agent jitter is added to the delay) so they don't all fire at `dreamHour:00` together. Clamped `[0,55]`; `0` = disabled (all fire at `dreamHour:00`) |
|
|
657
|
-
| `staleness` | *(object)* | Archive staleness GC sub-config (planning-66) — see below |
|
|
658
|
-
|
|
659
|
-
Per-agent overrides are supported under the agent's own `dreaming` block; unset fields fall back to the gateway default. `enabled:false` or `maxChangesPerRun:0` makes a run a no-op.
|
|
660
|
-
|
|
661
|
-
> **⚠️ Upgrade note:** the default `mode` for both `gateway.dreaming` and `gateway.knowledge.shared` changed from `propose` (dry-run) to `auto` (configVersion 1.0.24). Once the K4 applier landed (backup + net-negative + bounded-loss + CAS + never-empty; memory-only write ⇒ no session restart), `auto` became the intended default: nightly dreaming now applies consolidation to `MEMORY.md`/`USER.md` and promotes durable memories to the shared vault. Like the `gateway.bind` migration, the migrator upgrades the *retired* `propose` default to `auto` once and logs a one-time warning; an explicit `mode` you set at 1.0.24+ is never touched. To keep dry-run, set `mode: "propose"` explicitly.
|
|
662
|
-
|
|
663
|
-
**Keeping `MEMORY.md` near budget (`auto` mode).** Two mechanisms stop the on-disk `MEMORY.md` from growing unbounded while preserving recall:
|
|
664
|
-
|
|
665
|
-
- **Deterministic compaction** — before the LLM reviewer, every `auto` run moves completed/terminal log entries out of `MEMORY.md` into `memory/archive/completed.md`, leaving a one-line pointer. It is **domain-agnostic** (not just dev): an entry is archived when its lead line carries an explicit done marker — an UPPERCASE status word (`DONE`, `COMPLETED`, `RESOLVED`, `CLOSED`, `CANCELLED`, `ARCHIVED`, `MERGED`, `SUPERSEDED`, `OBSOLETE`, `DEPRECATED`, `EXPIRED`, `SHIPPED`, `FINISHED`), a checked task box `[x]`, a ✅, or a ~~strikethrough~~ — and it works on both list bullets and `###` entry headers. The archive lives under `memory/` so it is still indexed and **searchable via `memory_search`** — the agent recalls completed work on demand instead of carrying its full changelog in-prompt. It is conservative (uppercase words only, so prose like "Closes #123", "we're not done", or an unchecked `[ ]` box is never archived), idempotent, and never drops an open/active item.
|
|
666
|
-
- **Budget-scaled pruning** — when `MEMORY.md` is over its soft budget, the reviewer is put in an explicit net-shrink mode (propose only length-reducing ops) and `maxChangesPerRun` scales up **for removals** (the add cap stays tight), so an over-budget file converges toward budget instead of trickling at a few edits per night.
|
|
667
|
-
- **Archive staleness GC (`gateway.dreaming.staleness`, planning-66)** — a deterministic pass that runs next to the compactor (auto mode) to keep the Lane-2 archive's **search quality** high. This is a **search-quality fix, not a prompt-budget one**: planning-65 already moved task-log off the injected prompt, so the point here is that `memory_search` should keep surfacing *current* truth instead of stale/superseded facts. Each nightly run **soft-invalidates** archive entries — superseded ones (a deterministic `supersedes/replaces/obsoletes #N` match, which finally populates the previously-inert `supersedes_key`) and aged-out ones (idle-since-last-**retrieval** past `staleTtlDays` and retrieved fewer than `minRetrievalKeep` times) — by **moving** them to `memory/archive/stale.md` and stamping `invalid_at`. It **never deletes**: a staled entry stays under `memory/` so it is still indexed and **searchable** (ยุบได้แต่ไม่ลืม). An entry that is **retrieved after** it was invalidated is **promoted back** to the active archive (the recall feedback loop — proof we aged it out too soon). Recall is fed by an append-only read-path log (`kb_retrieval_log`, gated by `recordRetrievals`) that the GC folds into each entry's `last_retrieved`. High-importance entries (`keepImportance`) and **pinned** files (`memory/pinned/**`) are never aged out; evergreen Lane-1 (`MEMORY.md`/`USER.md`) is structurally excluded. Every move is CAS-guarded with a timestamped backup, and — being a memory-only write — drops **no live session**. One run may soft-invalidate at most `staleness.maxInvalidationsPerRun` entries (default `50`), oldest-idle first, with the remainder resuming on later runs — aging is wall-clock driven, so without a ceiling the first run after anything that widens the GC's visibility (such as backfilling lifecycle rows for previously invisible sources) would relocate every already-expired entry in one night. Restores are never capped. Kill-switches: `staleness.enabled:false` (GC no-ops), `maxInvalidationsPerRun:0` (never invalidates, still restores) and `recordRetrievals:false` (age falls back to first-seen only).
|
|
668
|
-
|
|
669
|
-
### `gateway.knowledge`
|
|
670
|
-
|
|
671
|
-
**Two-lane memory** — a per-agent searchable knowledge archive so an agent can recall what does not fit in the always-injected core. A SQLite/FTS5 index (`agents/<id>/kb.sqlite`, built on Node's built-in `node:sqlite` — no new dependency) covers the agent's `memory/*.md` notes plus the evergreen `MEMORY.md`/`USER.md`. Every chunk is tagged with **fail-closed provenance** (`owner`/`agent`/`untrusted`/`system`; unclassified ⇒ `untrusted`). The index is refreshed by a detached subprocess at session spawn, entirely **off the gateway event loop**.
|
|
672
|
-
|
|
673
|
-
Two read-only MCP tools expose it to the agent: **`memory_search`** (keyword/FTS5 → ranked snippets with file+line, provenance, importance) and **`memory_get`** (bounded, path-traversal-guarded excerpt of a memory-scoped file). When `MEMORY.md` grows past its `gateway.memory` soft budget, compose injects a compact **auto-generated section index** + a pointer to `memory_search` instead of the truncated full text (**core-shrink**) — the on-disk file is never modified and its full content stays searchable. Whenever the archive is on, a short `--- MEMORY RETRIEVAL ---` note is also injected into every agent's system prompt so the tools stay discoverable at all times (not only when the file is over budget).
|
|
674
|
-
|
|
675
|
-
| Field | Default | Description |
|
|
676
|
-
|-------|---------|-------------|
|
|
677
|
-
| `archive.enabled` | `true` | Master switch (`false` ⇒ complete no-op, no DB created, no core-shrink) |
|
|
678
|
-
| `archive.tokenizer` | `"unicode61"` | FTS5 tokenizer (`"trigram"` for CJK/Thai) |
|
|
679
|
-
| `archive.chunkTokens` | `400` | Target chunk size in ~tokens |
|
|
680
|
-
| `archive.chunkOverlap` | `80` | Overlap between chunks (clamped below `chunkTokens`) |
|
|
681
|
-
| `shared.enabled` | `true` | Enable the cross-agent shared KB |
|
|
682
|
-
| `shared.project` | `"global"` | Sharing partition key (one safe path segment) — agents with the same value share one vault; `"global"` ⇒ shared-by-default |
|
|
683
|
-
| `shared.root` | `~/.claude-gateway/shared/kb` | Shared vault root dir (`<root>/<project>/`) |
|
|
684
|
-
| `shared.mode` | `"auto"` | Per-agent→shared promotion mode; `auto` = promote durable dreamed facts, `propose` = dry-run |
|
|
685
|
-
| `shared.graph` | `false` | Compile the memory-wiki graph + dashboards over the shared vault to `<vault>/reports/*.md` (opt-in). Independent of the dashboard **Knowledge base** tab, which computes its graph on-demand |
|
|
686
|
-
| `shared.staleness` | *(object)* | Shared-note TTL lifecycle GC; uses the same fields/defaults as `dreaming.staleness` (whole notes only; no numeric `supersedes #N` syntax) |
|
|
687
|
-
| `reflection.enabled` | `true` | Enable the singleton, per-shared-vault reflection scheduler (daily timer; see cadence note below) |
|
|
688
|
-
| `reflection.dayOfWeek` / `hour` / `minute` / `timezone` | `0` / `4` / `0` / `UTC` | `hour`/`minute` is the **daily** staleness-GC slot; `dayOfWeek` selects the weekday that additionally runs LLM consolidation (Sunday 04:00 UTC by default; invalid timezone falls back to `gateway.timezone`, then `UTC`) |
|
|
689
|
-
| `reflection.maxClustersPerRun` / `reviewModel` | `5` / `claude-haiku-4-5-…` | Hard cap on changed linked-note clusters per consolidation run and the bounded synthesis model |
|
|
690
|
-
|
|
691
|
-
**Shared KB.** A shared SQLite/FTS5 vault outside any single agent's workspace lets agents build a common knowledge base. Notes under `<root>/<project>/notes/*.md` are indexed and reachable via `memory_search` with `corpus:"shared"` (the shared vault) or `corpus:"all"` (this agent's memory + shared, merged by relevance). Concurrent writers are safe without a lock — atomic note writes (temp+rename) plus a cross-process `PRAGMA busy_timeout` on the index. Per-agent overrides under the agent's own `knowledge` block. The MCP layer runs under Bun, so the read tools query `kb.sqlite` via `bun:sqlite`. Two write paths feed the vault, sharing one freeform-name namespace (issue #386, no agent-id prefix, no ownership scoping): the nightly dreaming promoter (gated by `mode:"auto"`; it promotes only content that carries a real fact — content that is nothing but `MEMORY.md` index-pointer bullets is skipped, since those links resolve only inside the promoting agent's own workspace — and names each note after the proposal's `topic` slug when the reviewer supplied one, falling back to its `reason`, so a recurring fact updates the same note across nights instead of piling up near-duplicates; a fallback name that reads as an editing instruction rather than the name of a fact is passed over, and the note is named from the fact itself instead — the promotion is only abandoned when nothing nameable remains, and every skip is logged — including a write the note-size cap refuses and an unexpected write failure. A name that doesn't collide is checked against a near-duplicate search, but an unattended **merge** now also requires real token containment against the candidate — below that bar the fact gets its own note, since two notes are recoverable while two unrelated facts fused into one are not. `[[wikilink]]`s to related notes use a lower bar than merges, because a link is additive where a merge is destructive — and they are attached whether the fact merges or lands as a new note, so a note below the merge bar is never a disconnected graph node. Containment is scored against each candidate's full body rather than the matched chunk, though against a capped seed — the bar means "half of the fact's leading topic words are already here", not half of the whole fact. Retired `stale__*` notes are never merge targets; a recurrence of a retired name folds the retired body back in and removes the twin on both the create and the update path, because a retired note stays searchable and a twin beside a live note of the same name would answer every query twice forever. The twin is only dropped once the merged write lands (issue #398)) and the **`memory_shared_create`**/**`memory_shared_get`**/**`memory_shared_update`**/**`memory_shared_delete`** MCP tools, which let any agent create, read, update, or delete any note on demand regardless of `mode`. `memory_shared_create` warns instead of writing when it finds content-similar existing notes (pass `confirm:true` to proceed — related notes get `[[wikilink]]`ed into the new note rather than left disconnected); `memory_shared_update` warns instead of writing when the edit would drop 50%+ of the existing note's lines (same `confirm:true` escape hatch). Immediate reindex after every write or delete.
|
|
692
|
-
|
|
693
|
-
**Shared lifecycle + reflection (issues #392, #398).** Each shared note receives a stable whole-file lifecycle identity during indexing — including notes whose content has not changed since they were first indexed, which are backfilled from their source mtime so their real age is preserved. Its deterministic TTL GC runs **daily**, soft-invalidating aged low-recall notes by moving them to `notes/stale__<name>.md` (never deleting them from the searchable vault); a retrieval after invalidation restores the original active name. Shared `memory_search` and `memory_shared_get` reads feed the same append-only retrieval log as personal archive recall. The singleton reflection scheduler runs **once per resolved shared-vault root**, not once per agent, and fires **daily** at `hour:minute` (a fire that lands a hair early re-arms on the *next* day's slot rather than serving the same one twice): every fire runs the inexpensive TTL GC (no model call), while graph/LLM consolidation runs only on `dayOfWeek` — and even then is skipped when `kb_index_state.revision` has not changed since the prior consolidation. Weekly model spend is therefore unchanged, while a note that is retired and then retrieved returns to the active set within a day instead of up to a week. For changed vaults it clusters only active wikilink-connected notes deterministically, then makes at most `reflection.maxClustersPerRun` bounded reviewer calls to merge genuinely duplicate clusters; related-but-distinct notes remain merely linked.
|
|
694
|
-
|
|
695
|
-
**Knowledge base viewer.** The web dashboard's **Knowledge base** tab renders the shared vault as an Obsidian-style force-directed graph (nodes = notes sized by link degree and coloured by `type`; edges = `[[wiki-links]]`; contradicting claims and stale notes are flagged). It is fed by `GET /knowledge/graph`, which computes the model **on-demand** from the vault (no dependency on `shared.graph` or the nightly reindex). When the vault is empty it shows a clearly-labelled demo dataset (with a size selector for scale testing). A **source** selector switches the graph between the cross-agent Shared KB and any single agent's own Lane-2 memory (`workspace/memory`), a node **search** box filters the graph, and clicking a node opens its full note (fetched via `GET /knowledge/note`) rendered as Markdown below the graph.
|
|
696
|
-
|
|
697
|
-
**Nightly dreaming viewer.** A **Nightly dreaming** tab renders each agent's memory-consolidation audit trail (`.dreaming/DREAMS.md` + `promotions.jsonl`) as a newest-first timeline of runs — mode (propose/auto), outcome, the proposed/applied changes with scores + anchors, and per-run token/session counts — fed by `GET /knowledge/dreams` and filterable by agent. For a `propose`-mode run you can **accept** proposals directly from the tab: an **Accept** button per proposal (and **Accept all** per run) POSTs to `POST /knowledge/dreams/apply`, which applies the selected ops to `MEMORY.md`/`USER.md` through the same K4 safe applier auto mode uses (backup + bounded-loss + net-negative + CAS; memory-only ⇒ no restart) and — when the shared KB is `auto` — promotes applied `add`s to the shared vault. Accepts are idempotent (recorded to `.dreaming/accepted.jsonl`); applied proposals show ✓ and a proposal whose anchor has since drifted is safely skipped and stays pending for a later retry.
|
|
698
|
-
|
|
699
|
-
### `gateway.bind`
|
|
700
|
-
|
|
701
|
-
Network interface the HTTP/WebSocket server binds to. Defaults to `127.0.0.1` (localhost-only), so the dashboard and API are **not** exposed to the local network out of the box. Set to `0.0.0.0` to listen on all interfaces (for example when a containerized reverse proxy needs to reach the gateway). The `GATEWAY_BIND` environment variable, when set, takes precedence over this field.
|
|
702
|
-
|
|
703
|
-
> **⚠️ Binding to `0.0.0.0`? Configure an admin key in `gateway.api.keys`.** The
|
|
704
|
-
> monitoring surface (`/status`, `/processes`) and the dashboard require an
|
|
705
|
-
> **admin** API key (`admin: true`) or a dashboard session when keys are
|
|
706
|
-
> configured — a scoped or write-only key is rejected (`401`), because the
|
|
707
|
-
> dashboard grants cross-agent, host-wide power (including PTY keystroke injection
|
|
708
|
-
> into any session). The dashboard prompts for an admin key at `/dashboard` and
|
|
709
|
-
> stores an `HttpOnly` session cookie (issued only to an admin key). `/health`
|
|
710
|
-
> stays public but returns only `{"status":"ok"}` (no agent ids). With **no** keys
|
|
711
|
-
> configured the gateway **fails closed on a non-loopback bind**: `/status`,
|
|
712
|
-
> `/processes`, and `/dashboard` return `503` until you set `gateway.api.keys`
|
|
713
|
-
> (a startup warning is logged); if keys are set but **none is admin**, the
|
|
714
|
-
> dashboard is inaccessible and a startup warning is logged. On a loopback bind
|
|
715
|
-
> they stay open, so local keyless installs are unaffected. The gateway serves
|
|
716
|
-
> plain HTTP; put TLS in
|
|
717
|
-
> front (reverse proxy) so credentials are not sent in the clear.
|
|
718
|
-
|
|
719
|
-
```json
|
|
720
|
-
{
|
|
721
|
-
"gateway": {
|
|
722
|
-
"bind": "127.0.0.1"
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
> **⚠️ Upgrade note:** the default bind changed from `0.0.0.0` to `127.0.0.1` (configVersion 1.0.13). To avoid silently cutting off external access, the config migrator is **behavior-preserving**: whenever it upgrades a config that never set `gateway.bind`, it pins `bind` to `0.0.0.0` and logs a one-time warning, so a deployment that was reachable from another host stays reachable. This applies to *any* upgraded config with no `bind` key — including one already stamped `1.0.13` that never received a bind (an earlier version gated this on `< 1.0.13` and left such configs stuck on the `127.0.0.1` default). New installs (no prior config, so no migration runs) keep the secure `127.0.0.1` default. If you *want* localhost-only after upgrading, set `gateway.bind` to `127.0.0.1` explicitly (or the `GATEWAY_BIND` env var).
|
|
728
|
-
|
|
729
|
-
### `gateway.publicUrl`
|
|
730
|
-
|
|
731
|
-
Absolute, externally-reachable origin of the gateway (for example `https://gateway.example.com`, or `https://host.example.com/gateway` behind an ingress path prefix). The process cannot infer its own public URL — it binds localhost by default and sits behind a reverse proxy — so it must be set explicitly for features that hand out a phone-openable link. Currently that is the `/cli` terminal viewer; when `publicUrl` is unset, `/cli` replies that the viewer is not configured. Leave it blank to keep `/cli` disabled. A trailing slash is optional. Use an `https://` origin — Telegram Mini Apps require HTTPS.
|
|
732
|
-
|
|
733
|
-
The CLI does **not** route through this URL when it runs on the gateway's own host: both addresses are the same server, and the public one only adds a reverse-proxy hop that may enforce its own authentication. It talks to the local bind instead, keeping `publicUrl` as a fallback if that address cannot be reached. Pass `--url` to exercise the proxy path deliberately. See [CLI.md](./CLI.md) for the full precedence.
|
|
734
|
-
|
|
735
|
-
```json
|
|
736
|
-
{
|
|
737
|
-
"gateway": {
|
|
738
|
-
"publicUrl": "https://gateway.example.com"
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
### Terminal Viewer — interactive terminal mode
|
|
744
|
-
|
|
745
|
-
The dashboard's **Terminal Viewer** opens read-only (a live mirror of the PTY). A toggle in the top-right of the viewer switches it into an **interactive terminal**: keystrokes typed into the panel — printable characters, Enter, arrows, Ctrl-combos, Esc — are streamed into the live PTY, and the panel title changes to reflect the active mode. This is a per-browser client-side choice (Issue #201); there is no server config flag to enable it.
|
|
746
|
-
|
|
747
|
-
Because interactive mode turns a read-only view into a remote-write surface, access is protected upstream rather than by a feature flag:
|
|
748
|
-
|
|
749
|
-
- **Authentication** — the WebSocket requires a valid dashboard ticket or **admin** API key. The ticket is minted at `POST /api/v1/pty-stream-ticket`, which itself requires an admin API key or a valid dashboard session cookie — so an unauthenticated (or non-admin) caller cannot obtain one. The dashboard gets its session by logging in with an admin key at `/dashboard` (`HttpOnly` cookie); no token is embedded in the page.
|
|
750
|
-
- **`gateway.bind`** — the gateway binds to `127.0.0.1` (localhost) by default, so the dashboard is not reachable from the network out of the box. On a non-loopback bind (`0.0.0.0`), configure an admin key in `gateway.api.keys` so the dashboard and monitoring endpoints require an admin credential, and prefer a TLS-terminating reverse proxy so credentials are not sent in the clear.
|
|
751
|
-
|
|
752
|
-
Inbound frames are always bounded (text-only, size-capped) and are dropped for headless sessions (no PTY).
|
|
753
|
-
|
|
754
|
-
#### `/cli` — open the terminal viewer from chat
|
|
755
|
-
|
|
756
|
-
The `/cli` command (Telegram, Discord, LINE) opens the same live terminal viewer for **one agent**, without an admin key. It requires `gateway.publicUrl` and an agent running with `gateway.headless: false`. Unlike the admin dashboard, a `/cli` session is **agent-scoped**: its cookie and PTY ticket can only reach the originating agent's own sessions — never another agent, the process tree, or a cross-agent stream.
|
|
757
|
-
|
|
758
|
-
The viewer link is never a credential; unlocking it requires a proof tied to an allowlist-gated chat action:
|
|
759
|
-
|
|
760
|
-
- **Telegram** opens a Mini App and the gateway verifies Telegram's signed `initData` (HMAC with the agent's own bot token) — nothing secret rides in the URL, and the `initData` user must match the user who ran `/cli`.
|
|
761
|
-
- **Discord** and **LINE** send an open-viewer link plus an **Approve** button; the browser stays locked until you approve in the chat, so a leaked or forwarded link cannot be unlocked by anyone who cannot approve there.
|
|
762
|
-
|
|
763
|
-
The first browser to open a link owns it (opening the link in a second browser is rejected), the viewer defaults to read-only (toggle for input), and viewer sessions expire (30 min) — send `/cli` again to reconnect.
|
|
764
|
-
|
|
765
|
-
### `gateway.api.keys`
|
|
766
|
-
|
|
767
|
-
Each key has a `key` string (supports `${ENV_VAR}` interpolation), an optional `description`, and an `agents` field — either an array of agent IDs or `"*"` for full access. Keys support both `Authorization: Bearer` and `X-Api-Key` headers.
|
|
768
|
-
|
|
769
|
-
### Bot tokens
|
|
770
|
-
|
|
771
|
-
Tokens are stored per-agent at `~/.claude-gateway/agents/<id>/.env` and auto-loaded at startup **and before every config reload** — so an agent added to `config.json` while the gateway is running starts without a restart, even though its token only exists in a brand-new `.env`. Use `${AGENT_BOT_TOKEN}` syntax in config to reference them, or set them as shell environment variables. Lines are `KEY=value`; `#` comments and blank lines are ignored, and surrounding quotes are stripped, the same as in `~/.claude-gateway/.env`.
|
|
772
|
-
|
|
773
|
-
A variable you exported yourself always wins over the `.env` file and is never replaced by a reload. A token the gateway did read from a `.env` is refreshed when that file changes, so **rotating a token takes effect on the next config reload** rather than at the next restart. Note that only `config.json` is watched — editing a `.env` by hand applies on the following reload, while the MCP `agent_create` / `agent_update` tools write both files and so take effect immediately. If a `${VAR}` cannot be resolved from anywhere, that one agent is skipped — the rest of the gateway starts normally — and the skip is logged to `logs/gateway.log` with the name of the missing variable.
|
|
774
|
-
|
|
775
|
-
WhatsApp Cloud's credentials (`accessToken`, `phoneNumberId`, `appSecret`, `verifyToken`) are plain fields under the agent's `whatsapp_cloud` config block, not a dedicated "bot token" field — but they resolve through the exact same mechanism as Telegram/Discord bot tokens: reference them as `${VAR}` in `config.json` and put the value in the agent's `.env` (or export it as a shell variable), same as above.
|
|
776
|
-
|
|
777
|
-
---
|
|
778
|
-
|
|
779
|
-
## Architecture
|
|
780
|
-
|
|
781
|
-
```
|
|
782
|
-
┌─────────────────────────────────────────────────┐
|
|
783
|
-
│ Claude Gateway │
|
|
784
|
-
│ │
|
|
785
|
-
Telegram Bot A ──► TelegramReceiver(A) ──► AgentRunner(A) ─┬─► Session(chat:111) ──► Claude + MCP
|
|
786
|
-
├─► Session(chat:222) ──► Claude + MCP
|
|
787
|
-
Telegram Bot B ──► TelegramReceiver(B) ──► AgentRunner(B) ──┴─► Session(chat:333) ──► Claude + MCP
|
|
788
|
-
│
|
|
789
|
-
HTTP Client ──► POST /api/v1/.../messages ────────────────┴─► Session(api:uuid) ──► Claude
|
|
790
|
-
(sync JSON or SSE stream)
|
|
791
|
-
│ │
|
|
792
|
-
│ GatewayRouter (/health, /status, /ui, /api) │
|
|
793
|
-
│ CronScheduler (HEARTBEAT.md + REST API) │
|
|
794
|
-
│ TypingManager (live status indicators) │
|
|
795
|
-
└─────────────────────────────────────────────────┘
|
|
796
|
-
|
|
797
|
-
┌───────────────────────────────────┐
|
|
798
|
-
│ MCP Server (per session) │
|
|
799
|
-
│ mcp/server.ts │
|
|
800
|
-
│ │
|
|
801
|
-
│ telegram_reply │
|
|
802
|
-
│ telegram_react │
|
|
803
|
-
│ telegram_edit_message │
|
|
804
|
-
│ telegram_download_attachment │
|
|
805
|
-
│ cron_list / cron_create / ... │
|
|
806
|
-
│ skill_create / skill_delete / ... │
|
|
807
|
-
└───────────────────────────────────┘
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
Each agent runs a **dedicated TelegramReceiver** (single poller per bot token) and a **session pool** of isolated Claude subprocesses — one per chat or API session. Each session gets its own **MCP server** (`mcp/server.ts`) exposing channel-specific tools (Telegram reply, react, cron management, skill management). Sessions persist history via `SessionStore`, so Claude remembers the conversation even after idle restart.
|
|
811
|
-
|
|
812
|
-
### Session Pool
|
|
813
|
-
|
|
814
|
-
Each agent maintains a **session pool** — a separate Claude subprocess per chat ID (Telegram) or session UUID (API). Sessions are fully isolated: Claude sees only its own conversation history with no cross-session leakage.
|
|
815
|
-
|
|
816
|
-
```
|
|
817
|
-
TelegramReceiver (1 per agent, spawned by gateway)
|
|
818
|
-
- single long-poll connection per bot token
|
|
819
|
-
- handles access control (allowlist / pairing)
|
|
820
|
-
- runs as: bun mcp/tools/telegram/receiver-server.ts (RECEIVER_MODE)
|
|
821
|
-
- POSTs incoming messages to AgentRunner callback
|
|
822
|
-
|
|
823
|
-
AgentRunner (session pool manager)
|
|
824
|
-
├── SessionProcess(chat:111) ──► Claude subprocess + MCP server (SEND_ONLY)
|
|
825
|
-
├── SessionProcess(chat:222) ──► Claude subprocess + MCP server (SEND_ONLY)
|
|
826
|
-
└── SessionProcess(api:uuid) ──► Claude subprocess (no MCP — API-only)
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
### MCP Tool System
|
|
830
|
-
|
|
831
|
-
The MCP server (`mcp/server.ts`) uses a **modular multi-channel architecture**. Each channel is a separate module implementing `ChannelModule` or `ToolModule` interfaces:
|
|
832
|
-
|
|
833
|
-
| Module | Interface | Tools | Purpose |
|
|
834
|
-
|--------|-----------|-------|---------|
|
|
835
|
-
| `telegram` | `ChannelModule` | `telegram_reply`, `telegram_react`, `telegram_edit_message`, `telegram_download_attachment` | Send messages, reactions, edit messages in Telegram |
|
|
836
|
-
| `discord` | `ChannelModule` | `discord_reply`, `discord_react`, `discord_edit_message` | Send messages, reactions, edit messages in Discord |
|
|
837
|
-
| `cron` | `ToolModule` | `cron_list`, `cron_create`, `cron_update`, `cron_delete`, `cron_run`, `cron_get_runs` | Manage scheduled jobs via gateway REST API |
|
|
838
|
-
| `skills` | `ToolModule` | `skill_create`, `skill_delete`, `skill_install` | Create, delete, and install agent skills at runtime |
|
|
839
|
-
|
|
840
|
-
Tools are **prefixed by channel name** to avoid collisions. Each module controls its own visibility and lifecycle.
|
|
841
|
-
|
|
842
|
-
**Adding a new channel** (e.g. Slack) means implementing `ChannelModule` interface in `mcp/tools/slack/module.ts` and registering it in `server.ts`.
|
|
843
|
-
|
|
844
|
-
**Connectors** are the other half of the MCP picture: where the modules above are tools the gateway itself implements, a connector is an **external** MCP server the gateway injects into a session's `mcp-config.json`. The gateway stores only the connector definition, the per-connector secret (`~/.claude-gateway/mcp-token.env`) and the per-agent enablement — Claude Code then talks to that server directly. See [`gateway.customConnectors`](#gatewaycustomconnectors-optional) and [API.md](./API.md#connectors-api).
|
|
845
|
-
|
|
846
|
-
### Process Modes
|
|
847
|
-
|
|
848
|
-
| Mode | Process | Behaviour |
|
|
849
|
-
|------|---------|-----------|
|
|
850
|
-
| `TELEGRAM_RECEIVER_MODE` | `receiver-server.ts` | Polls Telegram, handles commands, POSTs to callback — **no MCP** |
|
|
851
|
-
| `TELEGRAM_SEND_ONLY` | `server.ts` | Exposes MCP tools (`telegram_*`, `cron_*`) — **no polling** |
|
|
852
|
-
|
|
853
|
-
#### Receiver lifecycle
|
|
854
|
-
|
|
855
|
-
Receivers are child processes, so they only stop when the gateway runs its
|
|
856
|
-
shutdown path. Two mechanisms keep them from outliving it:
|
|
857
|
-
|
|
858
|
-
- **`SIGTERM`, `SIGINT` and `SIGHUP` all run the same graceful shutdown.**
|
|
859
|
-
`SIGHUP` matters because Node's default action for it terminates the process
|
|
860
|
-
*without* running handlers — so before this was wired, closing a tmux pane or
|
|
861
|
-
dropping an SSH session killed the gateway and left every receiver reparented
|
|
862
|
-
to `init`. Teardown escalates `SIGTERM` → `SIGKILL` after a short grace period,
|
|
863
|
-
so a receiver wedged in an in-flight long-poll cannot survive it.
|
|
864
|
-
|
|
865
|
-
- **A boot-time sweep reclaims leftovers.** `SIGKILL` and the OOM killer can
|
|
866
|
-
never be handled in-process, so at startup the gateway terminates any
|
|
867
|
-
`receiver-server.ts` process that was spawned from *its own* installation and
|
|
868
|
-
has been reparented to `init` (proof that its supervisor is gone), logging how
|
|
869
|
-
many it reclaimed — and separately warning about any it could **not** reclaim,
|
|
870
|
-
since those are still running. Receivers belonging to another checkout on the
|
|
871
|
-
same host, or to a gateway that is still running, are never touched.
|
|
872
|
-
|
|
873
|
-
On a host where an ancestor is a child subreaper (`systemd --user`,
|
|
874
|
-
`docker run --init`/tini, s6), orphans reparent to that subreaper instead of to
|
|
875
|
-
`init` and the sweep finds nothing. Clean shutdown still works; what is lost is
|
|
876
|
-
the `SIGKILL`/OOM recovery — though such a host usually has a supervisor that
|
|
877
|
-
reaps the process group itself.
|
|
878
|
-
|
|
879
|
-
### Session Persistence
|
|
880
|
-
|
|
881
|
-
History is persisted to `SessionStore` (`.jsonl` files) after each message. When a session is spawned after an idle restart, history is injected into the initial prompt so Claude resumes the conversation seamlessly.
|
|
882
|
-
|
|
883
|
-
---
|
|
884
|
-
|
|
885
|
-
## Live Status Messages
|
|
886
|
-
|
|
887
|
-
While an agent is working, the gateway sends real-time status updates to Telegram showing what the agent is doing:
|
|
888
|
-
|
|
889
|
-
```
|
|
890
|
-
☑️ : 🧠 Analyzing the codebase structure...
|
|
891
|
-
☑️ : 📖 Reading: src/agent/runner.ts
|
|
892
|
-
☑️ : 🔍 Searching for: "sendMessage" in src/
|
|
893
|
-
🕐 : ✏️ Editing: mcp/tools/telegram/typing.ts
|
|
894
|
-
(elapsed: 2m 30s)
|
|
895
|
-
```
|
|
896
|
-
|
|
897
|
-
- **Tool tracking** — each tool call is displayed with a descriptive label (e.g. `📖 Reading: config.ts`, `⚡ Running: npm test`)
|
|
898
|
-
- **History** — previous steps shown with ☑️, current step with 🕐
|
|
899
|
-
- **Thinking** — agent's reasoning shown with 🧠
|
|
900
|
-
- **Elapsed time** — total time since the agent started working
|
|
901
|
-
- **Auto-cleanup** — status message is deleted when the agent finishes
|
|
902
|
-
|
|
903
|
-
Status updates are sent every 5-10 seconds (first update at 5s, then every 10s). A single
|
|
904
|
-
message is **edited in place** for the whole turn; a tick with nothing new to show issues no
|
|
905
|
-
update at all, and the message is replaced only if it is deleted or becomes uneditable.
|
|
906
|
-
|
|
907
|
-
---
|
|
908
|
-
|
|
909
|
-
## Command Line (CLI)
|
|
910
|
-
|
|
911
|
-
The `claude-gateway` binary doubles as a command-line client for a running gateway — a friendlier alternative to hand-built `curl` calls. It works the same whether the gateway was started with `make start`, pm2, or systemd (it resolves the target from your config). Run it with no arguments to see what it can do; **only `gateway start` boots the server**.
|
|
912
|
-
|
|
913
|
-
```bash
|
|
914
|
-
claude-gateway # help (never starts a server)
|
|
915
|
-
claude-gateway gateway start # run the gateway in the foreground
|
|
916
|
-
claude-gateway gateway status # is it running? which manager owns it?
|
|
917
|
-
claude-gateway gateway logs --follow # stream the gateway log (reads files, needs no server)
|
|
918
|
-
claude-gateway service install # run it as a systemd-user (or --manager pm2) service
|
|
919
|
-
claude-gateway service start|stop|restart # drive the installed service (found even if inactive)
|
|
920
|
-
claude-gateway app list # installed Docker-compose apps and their status
|
|
921
|
-
claude-gateway app install agent-note # install from the community registry
|
|
922
|
-
claude-gateway app start|stop|restart <name>
|
|
923
|
-
claude-gateway update check # newer claude-gateway published?
|
|
924
|
-
claude-gateway claude update # update Claude Code via its own updater
|
|
925
|
-
claude-gateway doctor # check config / key / connectivity
|
|
926
|
-
claude-gateway agents create # interactive wizard — new agent + optional channel
|
|
927
|
-
claude-gateway channels pending --agent alfred # incoming Telegram/Discord pairing requests
|
|
928
|
-
claude-gateway crons list # friendly <noun> <verb> commands
|
|
929
|
-
claude-gateway crons run <jobId>
|
|
930
|
-
claude-gateway debug-bundle # small redacted bundle for a stuck session (works even if the server is down)
|
|
931
|
-
claude-gateway api GET /v1/agents # escape hatch: call any endpoint directly
|
|
932
|
-
```
|
|
933
|
-
|
|
934
|
-
### Reading the logs
|
|
935
|
-
|
|
936
|
-
`gateway logs` reads the log files directly, so it answers whether or not the gateway is
|
|
937
|
-
running — which is usually exactly when you need it.
|
|
938
|
-
|
|
939
|
-
```bash
|
|
940
|
-
claude-gateway gateway logs # last 50 lines of logs/gateway.log
|
|
941
|
-
claude-gateway gateway logs --lines 200 --follow # more history, then stream
|
|
942
|
-
claude-gateway gateway logs --agent alfred # that agent's stream instead
|
|
943
|
-
claude-gateway gateway logs --json # the stored JSON lines, verbatim
|
|
944
|
-
```
|
|
945
|
-
|
|
946
|
-
Each line is stored as one JSON object and rendered as `<ts> <LEVEL> <message>` with `data`
|
|
947
|
-
appended; `--json` prints the stored line unchanged, for piping into `jq`. `--agent <id>` takes
|
|
948
|
-
any stream id in the log directory — agents (`alfred`), receivers (`alfred:receiver`), and
|
|
949
|
-
sessions (`alfred:session:<uuid>`) each get their own file — and an unknown id lists the ids that
|
|
950
|
-
do exist rather than reporting an empty result. `--follow` survives a rotation: when the file it
|
|
951
|
-
is watching is renamed away it reopens the new one instead of going quiet.
|
|
952
|
-
|
|
953
|
-
Unlike `debug-bundle`, this output is **not redacted** — it is the local file you could already
|
|
954
|
-
`cat`. Skim before pasting it anywhere.
|
|
955
|
-
|
|
956
|
-
> **Upgrading from < 1.8:** a service unit that runs the binary with no command still starts the
|
|
957
|
-
> gateway, with a deprecation warning. Point `ExecStart` at `claude-gateway gateway start`, or
|
|
958
|
-
> reinstall the unit with `claude-gateway service install`.
|
|
959
|
-
|
|
960
|
-
Working on the CLI itself? The globally installed `claude-gateway` is the published npm package, not
|
|
961
|
-
your checkout, so a bare `claude-gateway` still runs whatever version is on your `PATH`. Use `make cli`
|
|
962
|
-
to build and exercise the local sources instead:
|
|
963
|
-
|
|
964
|
-
```bash
|
|
965
|
-
make cli ARGS="--help"
|
|
966
|
-
make cli ARGS="gateway status"
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
Commands are **generated from the same route manifest the server mounts**, so every endpoint exposed as a friendly command stays in sync with the API automatically. Global flags: `--url`, `--key`, `--json`, `--data <json>`, `--help`.
|
|
970
|
-
|
|
971
|
-
See **[CLI.md](./CLI.md)** for the full command reference.
|
|
972
|
-
|
|
973
|
-
---
|
|
974
|
-
|
|
975
|
-
## HTTP API
|
|
976
|
-
|
|
977
|
-
> For day-to-day operation, prefer the **[CLI](#command-line-cli)** above (`claude-gateway <noun> <verb>`) — it resolves the URL and key for you and is easier to read. This section is the **raw HTTP reference** for programmatic clients and integrations.
|
|
978
|
-
|
|
979
|
-
When `gateway.api.keys` is configured, the gateway exposes a REST API for external clients.
|
|
980
|
-
|
|
981
|
-
Pass API key via `X-Api-Key: <key>` or `Authorization: Bearer <key>` header.
|
|
982
|
-
|
|
983
|
-
**Endpoints:**
|
|
984
|
-
|
|
985
|
-
| Method | Path | Description |
|
|
986
|
-
|--------|------|-------------|
|
|
987
|
-
| `GET` | `/api/v1/agents` | List agents accessible by the provided key |
|
|
988
|
-
| `POST` | `/api/v1/agents/:agentId/messages` | Send a message — sync JSON or SSE stream |
|
|
989
|
-
| `GET` | `/api/v1/crons` | List cron jobs accessible by key |
|
|
990
|
-
| `GET` | `/api/v1/crons/status` | Scheduler status |
|
|
991
|
-
| `POST` | `/api/v1/crons` | Create a scheduled job |
|
|
992
|
-
| `GET` | `/api/v1/crons/:id` | Get a single job |
|
|
993
|
-
| `PUT` | `/api/v1/crons/:id` | Update a job |
|
|
994
|
-
| `DELETE` | `/api/v1/crons/:id` | Delete a job |
|
|
995
|
-
| `POST` | `/api/v1/crons/:id/run` | Trigger a job manually |
|
|
996
|
-
| `GET` | `/api/v1/crons/:id/runs` | Get run history |
|
|
997
|
-
| `GET` | `/api/v1/agents/sessions` | List all sessions across all agents (admin key) |
|
|
998
|
-
| `GET` | `/api/v1/agents/:agentId/chats` | List chats for an agent |
|
|
999
|
-
| `DELETE` | `/api/v1/agents/:agentId/chats/:chatId` | Delete a chat and all its messages |
|
|
1000
|
-
| `GET` | `/api/v1/agents/:agentId/chats/:chatId/sessions` | List sessions for a specific chat |
|
|
1001
|
-
| `GET` | `/api/v1/agents/:agentId/chats/:chatId/messages` | Paginated message history (cursor-based) |
|
|
1002
|
-
| `POST` | `/api/v1/agents/:agentId/chats/:chatId/sessions/:sessionId/messages` | Inject a message into an existing session |
|
|
1003
|
-
| `POST` | `/api/v1/agents/:agentId/media` | Upload a media file (image or PDF) |
|
|
1004
|
-
| `GET` | `/api/v1/agents/:agentId/media/*` | Serve a media file by path |
|
|
1005
|
-
| `PUT` | `/api/v1/agents/:agentId/avatar` | Upload or replace agent avatar (admin/write) |
|
|
1006
|
-
| `DELETE` | `/api/v1/agents/:agentId/avatar` | Remove agent avatar (admin/write) |
|
|
1007
|
-
| `GET` | `/api/v1/agents/:agentId/avatar` | Serve agent avatar image |
|
|
1008
|
-
| `POST` | `/api/v1/agents/wizard/start` | Start wizard: generate agent workspace via Claude (admin) |
|
|
1009
|
-
| `PUT` | `/api/v1/agents/wizard/:wizardId/avatar` | Upload avatar to wizard before confirm (admin) |
|
|
1010
|
-
| `POST` | `/api/v1/agents/wizard/:wizardId/confirm` | Write workspace to disk and add agent to config (admin) |
|
|
1011
|
-
| `POST` | `/api/v1/agents/wizard/:wizardId/channel` | Verify bot token and generate pairing code (admin) |
|
|
1012
|
-
| `POST` | `/api/v1/agents/wizard/:wizardId/channel/verify` | Poll for pairing code confirmation (admin) |
|
|
1013
|
-
| `POST` | `/api/v1/agents/wizard/:wizardId/complete` | Skip channel and finalise wizard (admin) |
|
|
1014
|
-
| `GET` | `/api/v1/apps/registry` | Browse community app registry (admin key) |
|
|
1015
|
-
| `POST` | `/api/v1/apps/install` | Install app from registry, GitHub, or local path → `jobId` (admin) |
|
|
1016
|
-
| `POST` | `/api/v1/apps/inspect` | Preview a source's required/generated secrets before install, no install (admin) |
|
|
1017
|
-
| `GET` | `/api/v1/apps/jobs/:jobId` | Poll install/update job status and logs |
|
|
1018
|
-
| `GET` | `/api/v1/apps` | List installed apps |
|
|
1019
|
-
| `GET` | `/api/v1/apps/:name` | Get app info |
|
|
1020
|
-
| `DELETE` | `/api/v1/apps/:name` | Uninstall app (admin) |
|
|
1021
|
-
| `POST` | `/api/v1/apps/:name/start\|stop\|restart` | Start/stop/restart app containers (admin) |
|
|
1022
|
-
| `POST` | `/api/v1/apps/:name/update` | Blue-green update with auto-rollback → `jobId` (admin) |
|
|
1023
|
-
| `POST` | `/api/v1/apps/:name/reconfigure` | Change env vars / host ports on an installed app, with rollback → `jobId` (admin) |
|
|
1024
|
-
| `GET` | `/api/v1/connectors` | List connectors with connected state |
|
|
1025
|
-
| `GET` | `/api/v1/connectors/:id/status` | Connected state for one connector (for polling) |
|
|
1026
|
-
| `POST` | `/api/v1/connectors/:id/connect` | Store a pasted token (admin) |
|
|
1027
|
-
| `POST` | `/api/v1/connectors/:id/oauth/receive` | Accept a token pushed by an external control plane (admin) |
|
|
1028
|
-
| `DELETE` | `/api/v1/connectors/:id` | Disconnect a connector (admin) |
|
|
1029
|
-
| `POST` | `/api/v1/connectors/custom` | Add a user-pasted connector (admin) |
|
|
1030
|
-
| `POST` | `/api/v1/connectors/custom/:id/oauth/start` | Begin OAuth 2.1 + PKCE sign-in → `authorizeUrl` (admin) |
|
|
1031
|
-
| `GET` | `/oauth/mcp/callback` | OAuth redirect target (public — guarded by a single-use `state`) |
|
|
1032
|
-
| `GET` | `/app/:name/:portName/*` | Reverse proxy to installed app (no auth) |
|
|
1033
|
-
|
|
1034
|
-
**Wizard API** — create agents programmatically with the same flow as the interactive `claude-gateway agents create` terminal wizard. The wizard generates workspace files via Claude, writes them on confirm, and optionally pairs a Telegram/Discord bot. State is in-memory with a 30-minute TTL; nothing is written until `/confirm`. See [API.md](./API.md) for the full wizard flow.
|
|
1035
|
-
|
|
1036
|
-
See **[API.md](./API.md)** for full reference with request/response schemas and curl examples.
|
|
1037
|
-
|
|
1038
|
-
---
|
|
1039
|
-
|
|
1040
|
-
## App Store
|
|
1041
|
-
|
|
1042
|
-
Install Docker-compose apps on the gateway. Apps get a reverse-proxied HTTP endpoint, an optional Unix socket bridge for executing host scripts, and optional AI agent injection.
|
|
1043
|
-
|
|
1044
|
-
**Quick install from registry:**
|
|
1045
|
-
|
|
1046
|
-
```bash
|
|
1047
|
-
curl -X POST http://localhost:10850/api/v1/apps/install \
|
|
1048
|
-
-H "X-Api-Key: <admin-key>" \
|
|
1049
|
-
-H "Content-Type: application/json" \
|
|
1050
|
-
-d '{"registry_app": "getpod-manager", "env_vars": {"API_KEY": "<secret>"}}'
|
|
1051
|
-
```
|
|
1052
|
-
|
|
1053
|
-
**Poll until done:**
|
|
1054
|
-
|
|
1055
|
-
```bash
|
|
1056
|
-
curl http://localhost:10850/api/v1/apps/jobs/<jobId> -H "X-Api-Key: <key>" | jq .status
|
|
1057
|
-
```
|
|
1058
|
-
|
|
1059
|
-
**Or use the CLI**, which wraps the same endpoints (see [CLI.md](./CLI.md) for the full reference):
|
|
1060
|
-
|
|
1061
|
-
```bash
|
|
1062
|
-
claude-gateway app install agent-note --env-file ./agent-note.env --wait # follow the job to completion
|
|
1063
|
-
claude-gateway app list # installed apps + status
|
|
1064
|
-
claude-gateway app stop agent-note
|
|
1065
|
-
claude-gateway app uninstall agent-note --yes
|
|
1066
|
-
```
|
|
1067
|
-
|
|
1068
|
-
`--env-file` reads `KEY=VALUE` lines from a dotenv file. Prefer it over `--env` for anything secret:
|
|
1069
|
-
a value passed on the command line is readable by every local user in `/proc/<pid>/cmdline` while the
|
|
1070
|
-
install runs, and is written to your shell history. `--env` wins if both set the same variable.
|
|
1071
|
-
|
|
1072
|
-
**App is then live at** `/app/getpod-manager/<portName>/`.
|
|
1073
|
-
|
|
1074
|
-
Apps can also be installed from a GitHub URL (`github_url` + `commit`) or a local path (`local_path`) for development. Updates use a **blue-green swap with automatic rollback** — the old containers stay intact until the new version passes its healthcheck.
|
|
1075
|
-
|
|
1076
|
-
The swap carries live bind-mount data forward into the new app directory. A data directory the app's own container created is owned by that image's uid (postgres leaves its `pgdata` mode 0700), and `rename(2)` on a directory needs write permission on the directory itself — so the gateway user cannot move it. Those paths are moved by a throwaway root helper container instead, mounting the nearest common ancestor of the two app directories so the move stays a real rename rather than a copy; each escalation is logged in the job. If a rollback cannot move such a path back, the update does **not** restart the app on a half-restored directory: the `-failed-` directory holding the live data is kept, the job fails with `ROLLBACK FAILED`, and the log names the paths and the directory to recover them from. That directory is kept for good — the boot sweep that reclaims update scratch dirs skips release snapshots, because it deletes with `sudo rm -rf` and a snapshot can hold the only copy of a database. It reports them on the console instead. The pre-update image tags are restored before that decision and the private `cg-rollback-*` tags are kept, so finishing the recovery by hand starts the restored source on its own build, not on the failed release's.
|
|
1077
|
-
|
|
1078
|
-
**Reverse proxy configuration:**
|
|
1079
|
-
|
|
1080
|
-
The gateway proxies `/app/:name/:portName/*` to the app containers. Two env vars control how the gateway reaches them:
|
|
1081
|
-
|
|
1082
|
-
| Env var | Default | Description |
|
|
1083
|
-
|---------|---------|-------------|
|
|
1084
|
-
| `GATEWAY_BIND` | `127.0.0.1` | Gateway HTTP listen address. Overrides the `gateway.bind` config field when set. Defaults to localhost-only; set to `0.0.0.0` when a **containerized** reverse proxy (Caddy, nginx in Docker) needs to reach the gateway across container boundaries. A **host-network** proxy (Traefik on host) can keep the localhost default. |
|
|
1085
|
-
| `DOCKER_HOST` | _(system default)_ | Docker socket/TCP address. When set to `tcp://host:port` (e.g. DinD), the gateway automatically uses the host extracted from `DOCKER_HOST` to proxy to app containers instead of `127.0.0.1`. |
|
|
1086
|
-
|
|
1087
|
-
Example Caddyfile for apps behind Caddy in Docker:
|
|
1088
|
-
|
|
1089
|
-
```caddy
|
|
1090
|
-
handle /app* {
|
|
1091
|
-
reverse_proxy dev-server:10850
|
|
1092
|
-
}
|
|
1093
|
-
```
|
|
1094
|
-
|
|
1095
|
-
(`handle`, not `handle_path` — preserve the `/app` prefix so the gateway's router can match it.)
|
|
1096
|
-
|
|
1097
|
-
See **[API.md — App Store section](./API.md#app-store-api)** for the full reference including `app.yaml` schema, `gateway_api` host-script bridge, and agent injection.
|
|
1098
|
-
|
|
1099
|
-
---
|
|
1100
|
-
|
|
1101
|
-
## File Structure
|
|
1102
|
-
|
|
1103
|
-
### Project
|
|
1104
|
-
|
|
1105
|
-
```
|
|
1106
|
-
claude-gateway/
|
|
1107
|
-
├── Makefile ← make start / cli / mcp-install / release / pm2-* / system-*
|
|
1108
|
-
├── config.template.json ← config template (source of truth for migration)
|
|
1109
|
-
│
|
|
1110
|
-
├── src/ ← Gateway core (TypeScript, compiled to dist/)
|
|
1111
|
-
│ ├── index.ts ← entrypoint — loads config, starts agents
|
|
1112
|
-
│ ├── types.ts ← shared TypeScript types
|
|
1113
|
-
│ ├── logger.ts ← structured logging with per-agent files
|
|
1114
|
-
│ │
|
|
1115
|
-
│ ├── agent/ ← Agent management
|
|
1116
|
-
│ │ ├── runner.ts ← session pool manager (spawn/evict sessions)
|
|
1117
|
-
│ │ ├── workspace-loader.ts ← assembles CLAUDE.md from workspace files + skills
|
|
1118
|
-
│ │ └── context-isolation.ts ← context guard for session isolation
|
|
1119
|
-
│ │
|
|
1120
|
-
│ ├── session/ ← Session lifecycle
|
|
1121
|
-
│ │ ├── process.ts ← single Claude subprocess per session
|
|
1122
|
-
│ │ ├── store.ts ← persist/load conversation history (.jsonl)
|
|
1123
|
-
│ │ └── compactor.ts ← summarise + compact old history
|
|
1124
|
-
│ │
|
|
1125
|
-
│ ├── telegram/ ← Telegram integration
|
|
1126
|
-
│ │ ├── receiver.ts ← spawns TelegramReceiver subprocess per agent
|
|
1127
|
-
│ │ └── markdown.ts ← markdown/HTML utilities
|
|
1128
|
-
│ │
|
|
1129
|
-
│ ├── api/ ← HTTP API
|
|
1130
|
-
│ │ ├── gateway-router.ts ← HTTP server (/health, /status, /ui, /api)
|
|
1131
|
-
│ │ ├── router.ts ← REST API router (sync + SSE streaming)
|
|
1132
|
-
│ │ ├── auth.ts ← API key auth middleware (timing-safe)
|
|
1133
|
-
│ │ └── cron-router.ts ← Cron API router (auth + agent-scoped access)
|
|
1134
|
-
│ │
|
|
1135
|
-
│ ├── config/ ← Configuration
|
|
1136
|
-
│ │ ├── loader.ts ← load + validate config.json
|
|
1137
|
-
│ │ ├── migrator.ts ← auto-migration for config schema changes
|
|
1138
|
-
│ │ └── watcher.ts ← hot-reload config on file change
|
|
1139
|
-
│ │
|
|
1140
|
-
│ ├── cron/ ← Cron scheduling
|
|
1141
|
-
│ │ ├── manager.ts ← persistent cron job manager (REST + agentTurn)
|
|
1142
|
-
│ │ └── scheduler.ts ← heartbeat task scheduler
|
|
1143
|
-
│ │
|
|
1144
|
-
│ ├── heartbeat/ ← Proactive tasks
|
|
1145
|
-
│ │ ├── parser.ts ← parse HEARTBEAT.md YAML
|
|
1146
|
-
│ │ └── history.ts ← track scheduled task execution
|
|
1147
|
-
│ │
|
|
1148
|
-
│ ├── skills/ ← Agent skills system
|
|
1149
|
-
│ │ ├── index.ts ← re-exports (parser, loader, invoker, watcher)
|
|
1150
|
-
│ │ ├── parser.ts ← parse SKILL.md frontmatter + body
|
|
1151
|
-
│ │ ├── loader.ts ← load skills from directories, build registry
|
|
1152
|
-
│ │ ├── invoker.ts ← detect /skill-name in messages, inject context
|
|
1153
|
-
│ │ └── watcher.ts ← hot-reload skills on file changes (chokidar)
|
|
1154
|
-
│ │
|
|
1155
|
-
│ ├── history/ ← Persistent chat history (Layer 2)
|
|
1156
|
-
│ │ ├── db.ts ← SQLite WAL + FTS5 history DB (pruneOlderThan, listChats, search)
|
|
1157
|
-
│ │ ├── cleanup.ts ← daily retention scheduler (scheduleCleanup, resolveRetentionDays)
|
|
1158
|
-
│ │ ├── media-store.ts ← media file store with MIME allowlist and path traversal guard
|
|
1159
|
-
│ │ └── types.ts ← HistoryMessage, ChatSummary, SessionSummary types
|
|
1160
|
-
│ │
|
|
1161
|
-
│ ├── memory/ ← Long-term memory
|
|
1162
|
-
│ │ └── manager.ts ← memory persistence
|
|
1163
|
-
│ │
|
|
1164
|
-
│ ├── webhook/ ← Webhooks
|
|
1165
|
-
│ │ └── manager.ts ← webhook event dispatch
|
|
1166
|
-
│ │
|
|
1167
|
-
│ └── ui/ ← Dashboard
|
|
1168
|
-
│ └── web-ui.ts ← live HTML dashboard
|
|
1169
|
-
│
|
|
1170
|
-
├── scripts/
|
|
1171
|
-
│ ├── gen-cli.ts ← generates src/cli/commands.generated.ts + CLI.md from the route registry
|
|
1172
|
-
│ ├── mock-line-webhook.ts ← local LINE webhook simulator for dev testing
|
|
1173
|
-
│ ├── release.sh ← interactive release (make release)
|
|
1174
|
-
│ └── setup-claude-settings.js ← enables channelsEnabled in Claude Code
|
|
1175
|
-
│
|
|
1176
|
-
└── mcp/ ← MCP server (runs in Bun, separate node_modules)
|
|
1177
|
-
├── package.json ← dependencies: grammy, @modelcontextprotocol/sdk
|
|
1178
|
-
├── server.ts ← MCP entry point — registers all tool modules
|
|
1179
|
-
├── types.ts ← ChannelModule / ToolModule interfaces
|
|
1180
|
-
├── channel-manager.ts ← module lifecycle (init, start, stop, restart)
|
|
1181
|
-
├── router.ts ← route resolution + channel context rendering
|
|
1182
|
-
│
|
|
1183
|
-
└── tools/
|
|
1184
|
-
├── telegram/ ← Telegram channel module
|
|
1185
|
-
│ ├── module.ts ← ChannelModule: telegram_reply, react, edit, download
|
|
1186
|
-
│ ├── receiver-server.ts ← standalone receiver (polling mode, no MCP)
|
|
1187
|
-
│ ├── pure.ts ← markdown → Telegram HTML conversion
|
|
1188
|
-
│ ├── typing.ts ← typing indicator state
|
|
1189
|
-
│ └── skills/
|
|
1190
|
-
│ ├── access/SKILL.md ← /telegram:access skill
|
|
1191
|
-
│ └── configure/SKILL.md ← /telegram:configure skill
|
|
1192
|
-
│
|
|
1193
|
-
├── cron/ ← Cron tool module
|
|
1194
|
-
│ ├── module.ts ← ToolModule: cron_list, create, update, delete, run, get_runs
|
|
1195
|
-
│ ├── client.ts ← HTTP client for gateway cron REST API
|
|
1196
|
-
│ └── skills/
|
|
1197
|
-
│ └── cron/SKILL.md ← /cron skill
|
|
1198
|
-
│
|
|
1199
|
-
└── skills/ ← Skills tool module
|
|
1200
|
-
├── module.ts ← ToolModule: skill_create, skill_delete, skill_install
|
|
1201
|
-
└── handlers.ts ← skill CRUD + URL install handlers
|
|
1202
|
-
```
|
|
1203
|
-
|
|
1204
|
-
### Runtime data (`~/.claude-gateway/`)
|
|
1205
|
-
|
|
1206
|
-
```
|
|
1207
|
-
~/.claude-gateway/
|
|
1208
|
-
├── config.json ← gateway config
|
|
1209
|
-
├── mcp-token.env ← connector secrets, mode 0600 (see `gateway.customConnectors`)
|
|
1210
|
-
├── logs/
|
|
1211
|
-
│ ├── alfred.log
|
|
1212
|
-
│ ├── alfred.log.1 ← rotated generation (see `gateway.logs`)
|
|
1213
|
-
│ └── warrior.log
|
|
1214
|
-
├── shared-skills/ ← shared skills (synced to ~/.claude/skills/ on boot and on change)
|
|
1215
|
-
│ └── <skill-name>/
|
|
1216
|
-
│ └── SKILL.md ← skill definition (same format as agent skills)
|
|
1217
|
-
└── agents/
|
|
1218
|
-
└── alfred/
|
|
1219
|
-
├── .env ← bot token (auto-created by wizard)
|
|
1220
|
-
├── sessions/
|
|
1221
|
-
│ └── <chat_id>.jsonl ← conversation history (SessionStore)
|
|
1222
|
-
├── history.db ← SQLite chat history (Layer 2 — survives /compact)
|
|
1223
|
-
├── history-cleanup.log ← cleanup run log (max 1 MB, auto-rotated)
|
|
1224
|
-
├── media/ ← uploaded media files (served via /api/v1/agents/:id/media/*)
|
|
1225
|
-
└── workspace/
|
|
1226
|
-
├── CLAUDE.md ← auto-generated from workspace files, do not edit
|
|
1227
|
-
├── AGENTS.md ← agent identity, rules, capabilities
|
|
1228
|
-
├── IDENTITY.md ← name, emoji, avatar
|
|
1229
|
-
├── SOUL.md ← tone, personality, speaking style
|
|
1230
|
-
├── USER.md ← user profile and preferences
|
|
1231
|
-
├── MEMORY.md ← long-term memory (auto-appended)
|
|
1232
|
-
├── HEARTBEAT.md ← scheduled/proactive tasks
|
|
1233
|
-
├── skills/ ← agent-specific skills (hot-reloaded)
|
|
1234
|
-
│ └── <skill-name>/
|
|
1235
|
-
│ └── SKILL.md ← skill definition with frontmatter
|
|
1236
|
-
├── .sessions/ ← per-session MCP config
|
|
1237
|
-
│ └── <session_id>/
|
|
1238
|
-
│ └── mcp-config.json ← auto-generated MCP config for this session
|
|
1239
|
-
├── .telegram-state/
|
|
1240
|
-
│ └── access.json ← Telegram allowlist and pairing state
|
|
1241
|
-
└── .discord-state/
|
|
1242
|
-
└── access.json ← Discord allowlist and pairing state
|
|
1243
|
-
```
|
|
1244
|
-
|
|
1245
|
-
---
|
|
1246
|
-
|
|
1247
|
-
## Heartbeat / Scheduled Tasks
|
|
1248
|
-
|
|
1249
|
-
Define proactive tasks in `HEARTBEAT.md`:
|
|
1250
|
-
|
|
1251
|
-
```yaml
|
|
1252
|
-
tasks:
|
|
1253
|
-
- name: morning-brief
|
|
1254
|
-
cron: "0 8 * * *"
|
|
1255
|
-
prompt: "Give a brief morning summary."
|
|
1256
|
-
|
|
1257
|
-
- name: check-in
|
|
1258
|
-
interval: 6h
|
|
1259
|
-
prompt: "Check if there are any reminders to send."
|
|
1260
|
-
```
|
|
1261
|
-
|
|
1262
|
-
- `cron` — standard 5-field cron expression
|
|
1263
|
-
- `interval` — shorthand: `30m`, `1h`, `6h`, `1d`, `1w`
|
|
1264
|
-
- If the agent replies with `HEARTBEAT_OK` (case-insensitive), no message is sent to Telegram
|
|
1265
|
-
- `rateLimitMinutes` in config suppresses tasks if a proactive message was already sent recently (default: 30 min)
|
|
1266
|
-
|
|
1267
|
-
---
|
|
1268
|
-
|
|
1269
|
-
## Agent Skills
|
|
1270
|
-
|
|
1271
|
-
Skills are reusable capabilities defined as `SKILL.md` files with YAML frontmatter. They are injected into the agent's system prompt and can be invoked via `/skill-name` commands.
|
|
1272
|
-
|
|
1273
|
-
### Skill locations
|
|
1274
|
-
|
|
1275
|
-
| Location | Scope | Description |
|
|
1276
|
-
|----------|-------|-------------|
|
|
1277
|
-
| `workspace/skills/<name>/SKILL.md` | Per-agent | Agent-specific skills |
|
|
1278
|
-
| `~/.claude-gateway/shared-skills/<name>/SKILL.md` | All agents | Shared skills — synced to `~/.claude/skills/` at boot and on change |
|
|
1279
|
-
| `mcp/tools/<channel>/skills/<name>/SKILL.md` | All agents | Built-in channel skills (e.g. `/telegram:access`) |
|
|
1280
|
-
|
|
1281
|
-
### SKILL.md format
|
|
1282
|
-
|
|
1283
|
-
```yaml
|
|
1284
|
-
---
|
|
1285
|
-
name: my-skill
|
|
1286
|
-
description: What this skill does
|
|
1287
|
-
user_invocable: true # false = system-only, not shown to user
|
|
1288
|
-
argument_description: "[args]" # optional, shown in /skill-name [args]
|
|
1289
|
-
---
|
|
1290
|
-
|
|
1291
|
-
Skill instructions go here. Claude follows these instructions
|
|
1292
|
-
when the user invokes /my-skill.
|
|
1293
|
-
```
|
|
1294
|
-
|
|
1295
|
-
### Runtime skill management
|
|
1296
|
-
|
|
1297
|
-
Agents can manage skills at runtime via MCP tools:
|
|
1298
|
-
|
|
1299
|
-
| Tool | Description |
|
|
1300
|
-
|------|-------------|
|
|
1301
|
-
| `skill_create` | Create a new skill in the workspace |
|
|
1302
|
-
| `skill_delete` | Delete an existing skill |
|
|
1303
|
-
| `skill_install` | Install a skill from a GitHub URL or raw URL |
|
|
1304
|
-
|
|
1305
|
-
Skills are **hot-reloaded** — changes to skill files are detected automatically and the skill registry is updated without restarting the session.
|
|
1306
|
-
|
|
1307
|
-
### Shared skills sync
|
|
1308
|
-
|
|
1309
|
-
Skills placed in `~/.claude-gateway/shared-skills/` are automatically synced to `~/.claude/skills/` — the user-level directory that Claude Code scans for every session:
|
|
1310
|
-
|
|
1311
|
-
- **At boot** — gateway copies all shared skills before spawning any agent
|
|
1312
|
-
- **On change** — any add, edit, or delete under `shared-skills/` triggers a re-sync
|
|
1313
|
-
- **Cleanup** — each synced skill is tagged with a `.shared` marker file; if a skill is removed from `shared-skills/`, the marker is used to delete the stale copy from `~/.claude/skills/` automatically (user-installed skills without the marker are never touched)
|
|
1314
|
-
|
|
1315
|
-
This means adding a skill to `shared-skills/` makes it available to **all agents** without per-agent setup or a gateway restart.
|
|
1316
|
-
|
|
1317
|
-
### Skill self-improvement
|
|
1318
|
-
|
|
1319
|
-
Agents can **learn skills from their own work**. Telemetry is captured for every turn; when a turn does enough substantive work (default ≥ 5 tool calls) and the session goes idle, a lightweight background reviewer reads the transcript and decides whether a reusable skill should be **created or updated**. Written skills are **hot-reloaded** — usable in the next turn without a restart. Controlled by [`gateway.skillLearning`](#gatewayskilllearning) (enabled by default).
|
|
1320
|
-
|
|
1321
|
-
- **Provenance guard** — the writer only ever creates new `origin: auto` skills or edits skills it previously authored. Hand-written / user skills are never overwritten.
|
|
1322
|
-
- **Caps** — a per-day review cap and a maximum number of auto-skills bound the churn; a daily curator prunes the least-used auto-skills.
|
|
1323
|
-
- **Audit diary** — every automatic write appends a line to `<workspace>/SKILLS_LEARNED.md` (always on, offline, immutable).
|
|
1324
|
-
- **Notifications** — when `skillLearning.notify` is on (default), a short ping is fanned out to **every channel the agent has configured** (Telegram, Discord, and LINE when set up). Each channel resolves recipients from its own `.<channel>-state/access.json` allowlist. The web/`api` channel has no proactive push and is not notified. Bursts coalesce into a single digest.
|
|
1325
|
-
- **Progressive disclosure** — auto-skill descriptions are truncated in the CLAUDE.md skill menu to keep per-turn context small; the full skill body still loads on invoke.
|
|
1326
|
-
|
|
1327
|
-
Metrics are exposed via `GET /api/v1/agents/:agentId/skill-metrics` and the `skill_metrics` MCP tool (adoption funnel, cost-to-complete deltas, net-token ledger).
|
|
1328
|
-
|
|
1329
|
-
---
|
|
1330
|
-
|
|
1331
|
-
## Config Auto-Migration
|
|
1332
|
-
|
|
1333
|
-
When the config schema changes (new fields added in `config.template.json`), the gateway automatically detects and migrates your `config.json`:
|
|
1334
|
-
|
|
1335
|
-
- Preserves all existing values
|
|
1336
|
-
- Adds missing fields with defaults from the template
|
|
1337
|
-
- Migrates automatically on startup (no confirmation needed)
|
|
1338
|
-
- Tracks schema version for future migrations
|
|
1339
|
-
|
|
1340
|
-
---
|
|
1341
|
-
|
|
1342
|
-
## Pairing New Users
|
|
1343
|
-
|
|
1344
|
-
New agents default to `dmPolicy: "allowlist"` with the orthogonal `pairing`
|
|
1345
|
-
toggle **on**, so pairing works out of the box — no setup needed.
|
|
1346
|
-
|
|
1347
|
-
1. Ask the user to DM the bot — they receive a 6-character pairing code
|
|
1348
|
-
2. Approve it:
|
|
1349
|
-
```bash
|
|
1350
|
-
claude-gateway channels approve --agent alfred --channel discord --code abc123
|
|
1351
|
-
```
|
|
1352
|
-
(use `--channel telegram` for Telegram; omit `--channel` on `channels pending` to check both)
|
|
1353
|
-
3. The bot confirms pairing within 5 seconds
|
|
1354
|
-
4. Lock down after everyone is paired (optional) — turn the pairing toggle off
|
|
1355
|
-
so unknown senders are dropped silently (the base policy is already
|
|
1356
|
-
`allowlist`):
|
|
1357
|
-
```
|
|
1358
|
-
/gateway:discord-access dm-pairing off # Discord
|
|
1359
|
-
/telegram:access pairing off # Telegram
|
|
1360
|
-
```
|
|
1361
|
-
|
|
1362
|
-
`pairing` is an **orthogonal on/off toggle**, not a `dmPolicy` value: the base
|
|
1363
|
-
policy stays `open` | `allowlist` | `disabled`, and pairing layers on top of
|
|
1364
|
-
`allowlist`. A legacy `access.json` with `"dmPolicy": "pairing"` is migrated
|
|
1365
|
-
automatically on read to `{ dmPolicy: "allowlist", pairing: true }`.
|
|
1366
|
-
|
|
1367
|
-
To manage channels (add/remove Telegram or Discord) on an existing agent:
|
|
1368
|
-
```bash
|
|
1369
|
-
claude-gateway agents update # choose "Connect/update a channel" or "Disconnect a channel"
|
|
1370
|
-
```
|
|
1371
|
-
|
|
1372
|
-
---
|
|
1373
|
-
|
|
1374
|
-
## Channel Conditions & Limitations
|
|
1375
|
-
|
|
1376
|
-
Each channel gates inbound messages in two tiers — **DM/1:1** and **group** — and
|
|
1377
|
-
each has platform-level conditions that must be met *before* the gateway ever
|
|
1378
|
-
sees a message. If those aren't met the bot looks online but stays silent.
|
|
1379
|
-
|
|
1380
|
-
| Channel | Scope | Message reaches the bot when… | Access gate | Answers in group when… |
|
|
1381
|
-
|---------|-------|-------------------------------|-------------|------------------------|
|
|
1382
|
-
| **Telegram** | DM | always (long-polling) | `dmPolicy` + `pairing` → `allowFrom` | — |
|
|
1383
|
-
| | Group | bot is **Admin**, or **Privacy Mode is OFF** + re-added; otherwise only `/cmd`, @mentions, replies | `groupPolicy` + `groupAllowlist` | `requireMention` false, or @mentioned/replied |
|
|
1384
|
-
| **Discord** | DM | **Message Content Intent** enabled | `dmPolicy` + `pairing` → `allowFrom` | — |
|
|
1385
|
-
| | Guild | **Message Content Intent** + **View Channel** + **Read Message History** | `groupPolicy` + `guildAllowlist` (+ optional `channelAllowlist`/`roleAllowlist`) | `requireMention` false, or @mentioned/replied |
|
|
1386
|
-
| **LINE** | 1:1 | webhook delivered (valid signature) | `dmPolicy` | — |
|
|
1387
|
-
| | Group/Room | webhook delivered + bot is a member | `groupPolicy` + `groupAllowlist` | `requireMention` false, or **native** @mention |
|
|
1388
|
-
| **WhatsApp (Baileys)** | DM | number is linked (QR/pairing-code device link) | `dmPolicy` + `pairing` → `dmAllowlist` (per account) | — |
|
|
1389
|
-
| | Group | number is linked and is a member of the group | `groupPolicy` + `groupAllowlist` (per account) | `requireMention` false, or @mentioned |
|
|
1390
|
-
| **WhatsApp (Cloud API)** | DM | webhook delivered (valid `X-Hub-Signature-256`) | `dmPolicy` + `pairing` → `dmAllowlist` | — (DM-only, no group concept) |
|
|
1391
|
-
| **WeChat** | DM | iLink long-poll (`getupdates`) delivers a new message | `dmPolicy` + `pairing` → `dmAllowlist` | — (DM-only, no group concept) |
|
|
1392
|
-
|
|
1393
|
-
**Telegram limits**
|
|
1394
|
-
- Exactly one process may poll a bot token — a second poller causes `409 Conflict`.
|
|
1395
|
-
- Bot **commands are DM-only**; in groups they're silently dropped.
|
|
1396
|
-
- Group **Privacy Mode is ON by default** — see [Telegram Groups](#telegram-groups). Admin status bypasses it; a Privacy-Mode change only applies after the bot is removed and re-added.
|
|
1397
|
-
- Pairing codes: DM knocks reply the code privately; group knocks post the code in the group (needs a message that actually reaches the bot, i.e. Admin/Privacy-off).
|
|
1398
|
-
|
|
1399
|
-
**Discord limits**
|
|
1400
|
-
- **MESSAGE CONTENT INTENT** is a privileged intent — without it message text arrives empty, so the bot can neither answer nor pair. Enable it in the Developer Portal.
|
|
1401
|
-
- The bot needs channel permissions **View Channel**, **Read Message History**, **Send Messages** (+ **Create Public Threads** / **Send Messages in Threads** if `DISCORD_AUTO_THREAD=true`).
|
|
1402
|
-
- `channelAllowlist` / `roleAllowlist` are backend-only filters (no web UI) applied after the guild gate.
|
|
1403
|
-
|
|
1404
|
-
**LINE limits**
|
|
1405
|
-
- Inbound arrives via the Express **webhook**, not polling; the signature is verified over the **exact raw bytes**. Front it with the bun CORS proxy (see `/tunnel`) — never point cloudflared straight at the gateway, or chunked bodies break the signature and webhooks are dropped.
|
|
1406
|
-
- Handled inbound message types are **text, image, and file** (documents up to the 20 MB media cap). Sticker, video, audio, and location are ignored. LINE reports no MIME type for a file, so its extension is derived from the sender-supplied name and sanitized before use. A file the gateway cannot fetch (too large, empty, or a failed transfer) still reaches the agent — as a message that says the attachment is unavailable, rather than one that looks like a file waiting to be read.
|
|
1407
|
-
- Group/room `requireMention` uses LINE's **native mention** only (`mention.mentionees[].isSelf`). Typing the bot's name as plain text does **not** count, and `@All` does **not** count as a bot mention. LINE attaches mentions to **text messages only**, so an image or file posted in a group cannot satisfy the gate — send media in a DM, or set `requireMention: false` for that agent.
|
|
1408
|
-
- Delivery is **reply-token-first (free) → push fallback (metered)**. The single-use reply token lives only ~1 min; after that, replies consume the OA's monthly push quota.
|
|
1409
|
-
- Max **5 message objects** per reply/push request (the gateway auto-chunks to fit).
|
|
1410
|
-
|
|
1411
|
-
**WhatsApp limits**
|
|
1412
|
-
- Two independent modes, configured separately: the **Baileys** device-link bridge (`whatsapp` config block) and the **Cloud API** (`whatsapp_cloud` config block).
|
|
1413
|
-
- **Baileys** requires linking a device per number — QR code or a text pairing code, the same one-time handshake as WhatsApp Web. Multi-account: an agent can hold several linked numbers at once (`whatsapp.accounts[]`), each with its own DM/group policy and allowlist.
|
|
1414
|
-
- **Cloud API is DM-only** — a WhatsApp Business number has no group concept, so there's no `groupPolicy`/`groupAllowlist`/`requireMention` for it.
|
|
1415
|
-
- **Cloud API's inbound webhook requires a valid `X-Hub-Signature-256`** (HMAC-SHA256 of the raw body against `appSecret`); a bad or missing signature is rejected with `401` before the payload is parsed.
|
|
1416
|
-
- **Cloud API's 24-hour customer-service window**: free-form text replies only work within 24h of the user's last inbound message; outside that window only a pre-approved message template can reach them, and template sending is off by default (`templatesEnabled: false`) since it's the one send that can reach a user outside that window.
|
|
1417
|
-
- Inbound media cap is **20 MB** on both modes (same `MediaStore` cap LINE's file uploads use).
|
|
1418
|
-
|
|
1419
|
-
**WeChat limits**
|
|
1420
|
-
- Single personal account per agent, linked via **QR code only** (no pairing-code option, no multi-account support in v1) through Tencent's own iLink Bot API bridge (`wechat` config block) — Tencent's self-serve product, not a third-party bridge.
|
|
1421
|
-
- **DM-only** — no `groupPolicy`/`groupAllowlist`/`requireMention` fields exist because the iLink bridge cannot reliably deliver WeChat group events.
|
|
1422
|
-
- Inbound delivery is **long-polling** (`getupdates`, 35s timeout), not a webhook — there is no `/webhooks/wechat/...` route.
|
|
1423
|
-
- Outbound text is capped at **4000 characters** per message (iLink's documented limit); longer replies are auto-chunked on line boundaries with a short delay between chunks.
|
|
1424
|
-
- The whole channel can be disabled without a redeploy via `WECHAT_CHANNEL_DISABLED=true` (opt-out, enabled by default) — see [WeChat Channel API](API.md#wechat-channel-api).
|
|
1425
|
-
|
|
1426
|
-
---
|
|
1427
|
-
|
|
1428
|
-
## Telegram Groups
|
|
1429
|
-
|
|
1430
|
-
The bot can respond in Telegram groups and supergroups. A group must be in the
|
|
1431
|
-
agent's `groupAllowlist` before the bot will answer there.
|
|
1432
|
-
|
|
1433
|
-
### Delivery gotcha: Privacy Mode (read this first)
|
|
1434
|
-
|
|
1435
|
-
Telegram bots ship with **Privacy Mode ON** (`getMe` returns
|
|
1436
|
-
`can_read_all_group_messages: false`). A privacy-mode bot only *receives*, inside
|
|
1437
|
-
a group:
|
|
1438
|
-
|
|
1439
|
-
- messages that start with `/` (commands),
|
|
1440
|
-
- messages that @mention the bot's username, and
|
|
1441
|
-
- replies to the bot's own messages.
|
|
1442
|
-
|
|
1443
|
-
Everything else is filtered by Telegram **before it reaches the gateway** — the
|
|
1444
|
-
bot looks online but never sees the message, so it can neither answer nor mint a
|
|
1445
|
-
pairing code. On top of that, bot commands (`/start`, `/status`, …) are
|
|
1446
|
-
**DM-only**: the receiver silently drops them in groups so pairing codes can't
|
|
1447
|
-
leak to other members. Net effect in a default-privacy group: a plain message is
|
|
1448
|
-
invisible and a command is dropped, so nothing happens.
|
|
1449
|
-
|
|
1450
|
-
Do one of these so the bot actually receives group messages:
|
|
1451
|
-
|
|
1452
|
-
- **Promote the bot to Admin in the group (easiest).** An admin bot receives
|
|
1453
|
-
every message regardless of Privacy Mode — no BotFather change, no re-add. Any
|
|
1454
|
-
admin role works, even the most restricted.
|
|
1455
|
-
- **Disable Privacy Mode**, then **remove and re-add the bot** to the group (the
|
|
1456
|
-
new setting only applies on re-join): [@BotFather](https://t.me/BotFather) →
|
|
1457
|
-
`/setprivacy` → pick the bot → **Disable**.
|
|
1458
|
-
|
|
1459
|
-
### Register the group
|
|
1460
|
-
|
|
1461
|
-
Once the bot can receive group messages, add the group to `groupAllowlist` one of
|
|
1462
|
-
two ways.
|
|
1463
|
-
|
|
1464
|
-
**Option A — pairing code (recommended).** With `groupPolicy: "allowlist"` and
|
|
1465
|
-
`pairing: true` (both defaults), send any message in the group. The bot replies
|
|
1466
|
-
with a 6-character code. Approve it from a gateway agent session:
|
|
1467
|
-
|
|
1468
|
-
```
|
|
1469
|
-
/telegram:access pair <code>
|
|
1470
|
-
```
|
|
1471
|
-
|
|
1472
|
-
That adds the group id to `groupAllowlist` (the code also lands in the agent's
|
|
1473
|
-
`pending` as a `"kind": "group"` entry).
|
|
1474
|
-
|
|
1475
|
-
**Option B — edit `access.json` directly.** Get the group id by forwarding any
|
|
1476
|
-
group message to [@userinfobot](https://t.me/userinfobot) — a negative number
|
|
1477
|
-
like `-1001234567890` — then edit:
|
|
1478
|
-
|
|
1479
|
-
```
|
|
1480
|
-
~/.claude-gateway/agents/<your-agent-id>/workspace/.telegram-state/access.json
|
|
1481
|
-
```
|
|
1482
|
-
|
|
1483
|
-
```json
|
|
1484
|
-
{
|
|
1485
|
-
"dmPolicy": "allowlist",
|
|
1486
|
-
"pairing": true,
|
|
1487
|
-
"allowFrom": ["..."],
|
|
1488
|
-
"groupPolicy": "allowlist",
|
|
1489
|
-
"groupAllowlist": ["-1001234567890"],
|
|
1490
|
-
"requireMention": true
|
|
1491
|
-
}
|
|
1492
|
-
```
|
|
1493
|
-
|
|
1494
|
-
`access.json` is re-read on every inbound message — changes take effect
|
|
1495
|
-
immediately, no restart.
|
|
1496
|
-
|
|
1497
|
-
### Mention gate
|
|
1498
|
-
|
|
1499
|
-
`requireMention` is a single top-level boolean (default `true`):
|
|
1500
|
-
|
|
1501
|
-
- `true` — the bot answers in an allowlisted group only when @mentioned or
|
|
1502
|
-
replied to. This relies on Telegram delivering the @mention; if the bot ignores
|
|
1503
|
-
mentions, make it an Admin (see above).
|
|
1504
|
-
- `false` — the bot answers **every** message in an allowlisted group. This only
|
|
1505
|
-
does anything if the bot can *see* every message, i.e. you also promoted it to
|
|
1506
|
-
Admin or disabled Privacy Mode.
|
|
1507
|
-
|
|
1508
|
-
Toggle it with `/telegram:access group mention <on|off>`.
|
|
1509
|
-
|
|
1510
|
-
> **Legacy schema note:** older docs showed a per-group `"groups": { "<id>": {…} }`
|
|
1511
|
-
> map. That form is still accepted and auto-migrated on read to the flat
|
|
1512
|
-
> `groupAllowlist` + top-level `requireMention` shown above, but new setups should
|
|
1513
|
-
> use the flat schema. A per-group member restriction from the old schema is
|
|
1514
|
-
> preserved under `legacyGroupAllowFrom`; there is no command to edit it.
|
|
1515
|
-
|
|
1516
|
-
---
|
|
1517
|
-
|
|
1518
|
-
## Telegram Commands
|
|
1519
|
-
|
|
1520
|
-
Bot commands are **DM-only** — sent in a group they are silently ignored (this
|
|
1521
|
-
keeps pairing codes and session state from leaking to other members). Once
|
|
1522
|
-
paired, the following commands are available in a private chat:
|
|
1523
|
-
|
|
1524
|
-
**Session management**
|
|
1525
|
-
|
|
1526
|
-
| Command | Description |
|
|
1527
|
-
|---------|-------------|
|
|
1528
|
-
| `/session` | Show current session info (name, message count, context %) |
|
|
1529
|
-
| `/sessions` | List all sessions with inline keyboard — switch or delete |
|
|
1530
|
-
| `/new <name>` | Create a new session, optionally with a name |
|
|
1531
|
-
| `/rename <name>` | Rename the current session |
|
|
1532
|
-
| `/clear` | Clear current session history (with confirmation) |
|
|
1533
|
-
| `/compact` | Summarise old history and keep only recent messages |
|
|
1534
|
-
| `/stop` | Interrupt the in-flight turn (gateway sends SIGINT to the subprocess) |
|
|
1535
|
-
| `/restart` | Graceful session restart — shows a confirmation button; confirms and notifies when the session is back online |
|
|
1536
|
-
|
|
1537
|
-
**Agent**
|
|
1538
|
-
|
|
1539
|
-
| Command | Description |
|
|
1540
|
-
|---------|-------------|
|
|
1541
|
-
| `/model` | Show the current AI model. On Discord and LINE, `/model <id or alias>` also switches to any model in the list — an id the list does not contain is refused rather than written into `config.json`. **Direct messages only**: switching rewrites `config.json` for the whole agent and restarts every session in every chat, and the group access gates check the guild, channel and mention but never the user. Listing is unrestricted |
|
|
1542
|
-
| `/models` | Switch AI model. On Telegram this is an inline keyboard; selecting a model triggers a graceful restart and notifies when back online, and **Dismiss** closes the picker without changing the model. Discord and LINE have no inline keyboard, so they get the same list as text plus `/model <id or alias>` to pick from it |
|
|
1543
|
-
|
|
1544
|
-
The list behind both commands is the live catalog from `{ANTHROPIC_BASE_URL}/v1/models` when a base URL is configured, falling back to `gateway.models` in `config.json` — see [GET /api/v1/models](API.md#get-apiv1models). Before this, `config.json`'s list was written once at provisioning and never re-read, so a catalog that changed upstream could never reach the picker.
|
|
1545
|
-
|
|
1546
|
-
**Account**
|
|
1547
|
-
|
|
1548
|
-
| Command | Description |
|
|
1549
|
-
|---------|-------------|
|
|
1550
|
-
| `/start` | Pairing instructions |
|
|
1551
|
-
| `/status` | Check your pairing state |
|
|
1552
|
-
| `/help` | Show available commands |
|
|
1553
|
-
|
|
1554
|
-
---
|
|
1555
|
-
|
|
1556
|
-
## Monitoring
|
|
1557
|
-
|
|
1558
|
-
The gateway runs an HTTP server on port 10850 (set `PORT` env var to change, `GATEWAY_BIND` to set the bind address):
|
|
1559
|
-
|
|
1560
|
-
| Endpoint | Description |
|
|
1561
|
-
|----------|-------------|
|
|
1562
|
-
| `GET /health` | All agent IDs and running status |
|
|
1563
|
-
| `GET /status` | JSON stats per agent (sessions, uptime) |
|
|
1564
|
-
| `GET /ui` | Live HTML dashboard (auto-refreshes every 5s) |
|
|
1565
|
-
| `POST /api/v1/agents/:id/messages` | Send a message to an agent (requires API key) |
|
|
1566
|
-
| `GET /api/v1/agents` | List accessible agents (requires API key) |
|
|
1567
|
-
| `/api/v1/crons/*` | Cron job management — see [API.md](./API.md) |
|
|
1568
|
-
|
|
1569
|
-
---
|
|
1570
|
-
|
|
1571
|
-
## Development
|
|
1572
|
-
|
|
1573
|
-
```bash
|
|
1574
|
-
# Build TypeScript
|
|
1575
|
-
npm run build
|
|
1576
|
-
|
|
1577
|
-
# Unit tests only (fast, no external deps)
|
|
1578
|
-
npm run test:unit
|
|
1579
|
-
|
|
1580
|
-
# Integration tests
|
|
1581
|
-
npm run integration
|
|
1582
|
-
|
|
1583
|
-
# All tests
|
|
1584
|
-
npm test
|
|
1585
|
-
|
|
1586
|
-
# Type check without building
|
|
1587
|
-
npm run typecheck
|
|
1588
|
-
```
|
|
1589
|
-
|
|
1590
|
-
### Writing tests that wait
|
|
1591
|
-
|
|
1592
|
-
Two rules, enforced by `tests/unit/test-timing-hygiene.test.ts`:
|
|
1593
|
-
|
|
1594
|
-
- **Wait for a signal, never for a duration.** `createWatcher()` / `watchWorkspace()` /
|
|
1595
|
-
`watchSkills()` return a handle with a `ready` promise; the PTY wrapper announces itself
|
|
1596
|
-
with a `system/init` event. Sleeping "long enough" instead is a bet on how fast the machine
|
|
1597
|
-
is — and chokidar runs with `ignoreInitial: true`, so a write that lands before its initial
|
|
1598
|
-
scan finishes emits *nothing* and the test waits out its whole deadline for an event that
|
|
1599
|
-
will never arrive.
|
|
1600
|
-
- **Poll with the shared helper**, `tests/helpers/wait-for.ts`, rather than a local copy. Its
|
|
1601
|
-
timeout is a safety net sized so only a broken build hits it, and on a timeout it reports
|
|
1602
|
-
the predicate it was waiting on instead of a bare "timeout exceeded".
|
|
1603
|
-
|
|
1604
|
-
A fixed sleep is still fine for asserting that something *doesn't* happen — there the sleep
|
|
1605
|
-
only bounds how hard the test looks, so a slow machine can't turn correct behaviour red.
|
|
1606
|
-
|
|
1607
|
-
---
|
|
1608
|
-
|
|
1609
|
-
## Troubleshooting
|
|
1610
|
-
|
|
1611
|
-
**Agent fails to start**
|
|
1612
|
-
- Check workspace path exists and contains `AGENTS.md`
|
|
1613
|
-
- Check logs in `~/.claude-gateway/logs/<id>.log`
|
|
1614
|
-
|
|
1615
|
-
**Agent not responding to messages**
|
|
1616
|
-
- Verify `dmPolicy` in `access.json` — if `allowlist`, check the user's ID is in `allowFrom`
|
|
1617
|
-
- Ensure no other process is polling the same bot token (causes 409 Conflict)
|
|
1618
|
-
- Only `TelegramReceiver` polls Telegram — MCP session subprocesses run in `SEND_ONLY` mode (no polling)
|
|
1619
|
-
|
|
1620
|
-
**Bot silent in a Telegram group**
|
|
1621
|
-
- The group must be in `groupAllowlist` — see [Telegram Groups](#telegram-groups). An empty `pending` after messaging usually means the message never reached the bot.
|
|
1622
|
-
- Most common cause: **Privacy Mode** (default ON). A non-admin bot only receives commands, @mentions, and replies in groups — a plain message needed to mint the pairing code is filtered by Telegram. Promote the bot to Admin, or disable Privacy Mode in BotFather and re-add it.
|
|
1623
|
-
- `/start` and other commands are dropped in groups by design — use a normal message (or an @mention) to trigger the pairing code.
|
|
1624
|
-
- If `requireMention: true`, the bot only answers when @mentioned or replied to.
|
|
1625
|
-
|
|
1626
|
-
**Bot silent in a Discord server (guild)**
|
|
1627
|
-
- Enable the **MESSAGE CONTENT INTENT** in the Discord Developer Portal (Bot settings) — without it the bot receives events but empty message text, so it can't respond or pair.
|
|
1628
|
-
- The guild must be in `guildAllowlist` (`groupPolicy: allowlist`), and the bot needs **View Channel** + **Read Message History** in that channel.
|
|
1629
|
-
- If `requireMention: true`, the bot only answers when @mentioned or replied to.
|
|
1630
|
-
|
|
1631
|
-
**Session loses memory after restart**
|
|
1632
|
-
- History is persisted in `~/.claude-gateway/agents/<id>/sessions/<chat_id>.jsonl`
|
|
1633
|
-
- If the file is missing, the session starts fresh (no error)
|
|
1634
|
-
|
|
1635
|
-
**Personality not applied**
|
|
1636
|
-
- `CLAUDE.md` is auto-regenerated from workspace files on startup and on any file change
|
|
1637
|
-
- Trigger a reload by saving any `.md` file in the workspace
|
|
1638
|
-
|
|
1639
|
-
**Heartbeat not firing**
|
|
1640
|
-
- Verify `HEARTBEAT.md` YAML is valid
|
|
1641
|
-
- Check cron expression (5 fields: `min hour day month weekday`)
|
|
1642
|
-
- Check rate limit — default 30 min between proactive messages
|
|
57
|
+
Follow the [quickstart](https://0xmaxma.github.io/claude-gateway/guide/quickstart.html), then [enable orchestration](https://0xmaxma.github.io/claude-gateway/guide/orchestration.html), [connect a channel](https://0xmaxma.github.io/claude-gateway/guide/channels.html), and optionally [configure voice](https://0xmaxma.github.io/claude-gateway/guide/voice.html).
|
|
1643
58
|
|
|
1644
|
-
|
|
1645
|
-
- Check the key value matches exactly (env var interpolation uses `${VAR}` syntax)
|
|
1646
|
-
- Verify the key's `agents` list includes the target agent ID, or set `"agents": "*"`
|
|
59
|
+
## Learn more
|
|
1647
60
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
61
|
+
| Topic | Documentation |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| Configuration and credentials | [Configuration](https://0xmaxma.github.io/claude-gateway/reference/configuration.html) |
|
|
64
|
+
| Agents, workers, and task controls | [Orchestration](https://0xmaxma.github.io/claude-gateway/guide/orchestration.html) |
|
|
65
|
+
| Platform tokens and channel setup | [Channels](https://0xmaxma.github.io/claude-gateway/guide/channels.html) |
|
|
66
|
+
| STT, TTS, providers, and replay | [Voice](https://0xmaxma.github.io/claude-gateway/guide/voice.html) |
|
|
67
|
+
| Requests, responses, streams, and permissions | [API reference](https://0xmaxma.github.io/claude-gateway/api/) |
|
|
68
|
+
| Running, upgrading, and troubleshooting | [Operations](https://0xmaxma.github.io/claude-gateway/guide/operations.html) |
|
|
69
|
+
| Source builds and contributions | [Development](https://0xmaxma.github.io/claude-gateway/reference/development.html) |
|
|
1652
70
|
|
|
1653
|
-
|
|
1654
|
-
- First status update is sent after 5 seconds — very fast tasks may complete before it fires
|
|
1655
|
-
- Check that the MCP server is running in `SEND_ONLY` mode for session subprocesses
|
|
1656
|
-
- Verify the bot has permission to send messages in the chat
|
|
71
|
+
API documentation lives on the documentation website. [CLI command reference](https://0xmaxma.github.io/claude-gateway/reference/cli.html) is the generated command reference. To edit or preview this site, see [website development](website/README.md).
|