feat(cli): unify relay providers and heartbeat flag
parent
0e4379f075
commit
90a0bb5acb
|
|
@ -36,7 +36,7 @@ First Clawdis release after the Warelay rebrand. This is a semver-major because
|
||||||
|
|
||||||
### Docs
|
### Docs
|
||||||
- Added `docs/telegram.md` outlining the Telegram Bot API provider (grammY) and how it shares the `main` session. Default grammY throttler keeps Bot API calls under rate limits.
|
- Added `docs/telegram.md` outlining the Telegram Bot API provider (grammY) and how it shares the `main` session. Default grammY throttler keeps Bot API calls under rate limits.
|
||||||
- CLI exposes `relay:telegram` (grammY) and text/media sends via `--provider telegram`; webhook/proxy options documented.
|
- CLI relay now auto-starts WhatsApp and Telegram when configured (single `relay` command with `--provider` selector); text/media sends still use `--provider telegram`; webhook/proxy options documented.
|
||||||
|
|
||||||
## 1.5.0 — 2025-12-05
|
## 1.5.0 — 2025-12-05
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ First Clawdis release after the Warelay rebrand. This is a semver-major because
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Heartbeat interval default 10m for command mode; prompt `HEARTBEAT /think:high`; skips don’t refresh session; session `heartbeatIdleMinutes` support.
|
- Heartbeat interval default 10m for command mode; prompt `HEARTBEAT /think:high`; skips don’t refresh session; session `heartbeatIdleMinutes` support.
|
||||||
- Heartbeat tooling: `--session-id`, `--heartbeat-now`, and a relay helper `relay:heartbeat` for immediate startup probes.
|
- Heartbeat tooling: `--session-id`, `--heartbeat-now` (inline flag on `relay`) for immediate startup probes.
|
||||||
- Prompt structure: `sessionIntro` plus per-message `/think:high`; session idle up to 7 days.
|
- Prompt structure: `sessionIntro` plus per-message `/think:high`; session idle up to 7 days.
|
||||||
- Thinking directives: `/think:<level>`; Pi uses `--thinking`; others append cue; `/think:off` no-op.
|
- Thinking directives: `/think:<level>`; Pi uses `--thinking`; others append cue; `/think:off` no-op.
|
||||||
- Robustness: Baileys/WebSocket guards; global unhandled error handlers; WhatsApp LID mapping; hosted media MIME-sniffing and cleanup.
|
- Robustness: Baileys/WebSocket guards; global unhandled error handlers; WhatsApp LID mapping; hosted media MIME-sniffing and cleanup.
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ clawdis relay # Start listening
|
||||||
```
|
```
|
||||||
|
|
||||||
### Telegram (Bot API)
|
### Telegram (Bot API)
|
||||||
Bot-mode support (grammY only) shares the same `main` session as WhatsApp/WebChat, with groups kept isolated. Text and media send work via `clawdis send --provider telegram`; a relay is available via `clawdis relay:telegram` (TELEGRAM_BOT_TOKEN or telegram.botToken in config). Webhook mode: `--webhook --port … --webhook-secret … --webhook-url …` (or register via BotFather). See `docs/telegram.md` for setup and limits.
|
Bot-mode support (grammY only) shares the same `main` session as WhatsApp/WebChat, with groups kept isolated. Text and media send work via `clawdis send --provider telegram`. The unified `clawdis relay` starts WhatsApp and, when `TELEGRAM_BOT_TOKEN` or `telegram.botToken` is set, Telegram too (use `--provider` to force web|telegram|all). Webhook mode: `--webhook --port … --webhook-secret … --webhook-url …` (or register via BotFather). See `docs/telegram.md` for setup and limits.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|
@ -132,8 +132,7 @@ Bot-mode support (grammY only) shares the same `main` session as WhatsApp/WebCha
|
||||||
| `clawdis login` | Link WhatsApp Web via QR |
|
| `clawdis login` | Link WhatsApp Web via QR |
|
||||||
| `clawdis send` | Send a message (WhatsApp default; `--provider telegram` for bot mode, text + media) |
|
| `clawdis send` | Send a message (WhatsApp default; `--provider telegram` for bot mode, text + media) |
|
||||||
| `clawdis agent` | Talk directly to the agent (no WhatsApp send) |
|
| `clawdis agent` | Talk directly to the agent (no WhatsApp send) |
|
||||||
| `clawdis relay` | Start auto-reply loop |
|
| `clawdis relay` | Start auto-reply loop (WhatsApp + Telegram when configured) |
|
||||||
| `clawdis relay:telegram` | Start Telegram bot long-poll relay (Bot API) |
|
|
||||||
| `clawdis status` | Web session health + session store summary |
|
| `clawdis status` | Web session health + session store summary |
|
||||||
| `clawdis heartbeat` | Trigger a heartbeat |
|
| `clawdis heartbeat` | Trigger a heartbeat |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ Include `MEDIA:/path/to/file.png` in Claude's output to attach images. clawdis h
|
||||||
clawdis relay --provider web --verbose
|
clawdis relay --provider web --verbose
|
||||||
|
|
||||||
# With immediate heartbeat on startup
|
# With immediate heartbeat on startup
|
||||||
clawdis relay:heartbeat
|
clawdis relay --heartbeat-now
|
||||||
```
|
```
|
||||||
|
|
||||||
For backgrounding, run the relay under your preferred supervisor (e.g., launchd/systemd) and point it at the same `clawdis relay --provider web --verbose` command.
|
For backgrounding, run the relay under your preferred supervisor (e.g., launchd/systemd) and point it at the same `clawdis relay --provider web --verbose` command.
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,6 @@ Goal: add a simple heartbeat poll for command-based auto-replies (Pi/Tau) that o
|
||||||
- Expose CLI triggers:
|
- Expose CLI triggers:
|
||||||
- `clawdis heartbeat` (web provider, defaults to first `allowFrom`; optional `--to` override)
|
- `clawdis heartbeat` (web provider, defaults to first `allowFrom`; optional `--to` override)
|
||||||
- `--session-id <uuid>` forces resuming a specific session for that heartbeat
|
- `--session-id <uuid>` forces resuming a specific session for that heartbeat
|
||||||
- `clawdis relay:heartbeat` to run the relay loop with an immediate heartbeat
|
- `clawdis relay --heartbeat-now` to run the relay loop with an immediate heartbeat
|
||||||
- Relay supports `--heartbeat-now` to fire once at startup.
|
- Relay supports `--heartbeat-now` to fire once at startup.
|
||||||
- When multiple sessions are active or `allowFrom` is only `"*"`, require `--to <E.164>` or `--all` for manual heartbeats to avoid ambiguous targets.
|
- When multiple sessions are active or `allowFrom` is only `"*"`, require `--to <E.164>` or `--all` for manual heartbeats to avoid ambiguous targets.
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Status: ready for bot-mode use with grammY (long-poll + webhook). Text + media s
|
||||||
## How it will work (Bot API)
|
## How it will work (Bot API)
|
||||||
1) Create a bot with @BotFather and grab the token.
|
1) Create a bot with @BotFather and grab the token.
|
||||||
2) Configure Clawdis with `TELEGRAM_BOT_TOKEN` (or `telegram.botToken` in `~/.clawdis/clawdis.json`).
|
2) Configure Clawdis with `TELEGRAM_BOT_TOKEN` (or `telegram.botToken` in `~/.clawdis/clawdis.json`).
|
||||||
3) Run the relay with provider `telegram` via `clawdis relay:telegram` (grammY long-poll). Webhook mode: `clawdis relay:telegram --webhook --port 8787 --webhook-secret <secret>` (optionally `--webhook-url` when the public URL differs).
|
3) Run the relay; it auto-starts Telegram when the bot token is set. To force Telegram-only: `clawdis relay --provider telegram`. Webhook mode: `clawdis relay --provider telegram --webhook --port 8787 --webhook-secret <secret>` (optionally `--webhook-url` when the public URL differs).
|
||||||
4) Direct chats: user sends the first message; all subsequent turns land in the shared `main` session (default, no extra config).
|
4) Direct chats: user sends the first message; all subsequent turns land in the shared `main` session (default, no extra config).
|
||||||
5) Groups: add the bot, disable privacy mode (or make it admin) so it can read messages; group threads stay on `group:<chatId>` and require mention/command to trigger replies.
|
5) Groups: add the bot, disable privacy mode (or make it admin) so it can read messages; group threads stay on `group:<chatId>` and require mention/command to trigger replies.
|
||||||
6) Optional allowlist: reuse `inbound.allowFrom` for direct chats by chat id (`123456789` or `telegram:123456789`).
|
6) Optional allowlist: reuse `inbound.allowFrom` for direct chats by chat id (`123456789` or `telegram:123456789`).
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ describe("cli program", () => {
|
||||||
monitorWebProvider.mockResolvedValue(undefined);
|
monitorWebProvider.mockResolvedValue(undefined);
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
await program.parseAsync(
|
await program.parseAsync(
|
||||||
["relay", "--web-heartbeat", "90", "--heartbeat-now"],
|
["relay", "--web-heartbeat", "90", "--heartbeat-now", "--provider", "web"],
|
||||||
{
|
{
|
||||||
from: "user",
|
from: "user",
|
||||||
},
|
},
|
||||||
|
|
@ -69,28 +69,37 @@ describe("cli program", () => {
|
||||||
true,
|
true,
|
||||||
undefined,
|
undefined,
|
||||||
runtime,
|
runtime,
|
||||||
undefined,
|
expect.any(AbortSignal),
|
||||||
{ heartbeatSeconds: 90, replyHeartbeatNow: true },
|
{ heartbeatSeconds: 90, replyHeartbeatNow: true },
|
||||||
);
|
);
|
||||||
});
|
expect(monitorTelegramProvider).not.toHaveBeenCalled();
|
||||||
|
|
||||||
it("runs relay heartbeat command", async () => {
|
|
||||||
monitorWebProvider.mockResolvedValue(undefined);
|
|
||||||
const originalExit = runtime.exit;
|
|
||||||
runtime.exit = vi.fn();
|
|
||||||
const program = buildProgram();
|
|
||||||
await program.parseAsync(["relay:heartbeat"], { from: "user" });
|
|
||||||
expect(logWebSelfId).toHaveBeenCalled();
|
|
||||||
expect(runtime.exit).not.toHaveBeenCalled();
|
|
||||||
runtime.exit = originalExit;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("runs telegram relay when token set", async () => {
|
it("runs telegram relay when token set", async () => {
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
const prev = process.env.TELEGRAM_BOT_TOKEN;
|
const prev = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
process.env.TELEGRAM_BOT_TOKEN = "token123";
|
process.env.TELEGRAM_BOT_TOKEN = "token123";
|
||||||
await program.parseAsync(["relay:telegram"], { from: "user" });
|
await program.parseAsync(["relay", "--provider", "telegram"], {
|
||||||
expect(monitorTelegramProvider).toHaveBeenCalled();
|
from: "user",
|
||||||
|
});
|
||||||
|
expect(monitorTelegramProvider).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ token: "token123" }),
|
||||||
|
);
|
||||||
|
expect(monitorWebProvider).not.toHaveBeenCalled();
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("errors when telegram provider requested without token", async () => {
|
||||||
|
const program = buildProgram();
|
||||||
|
const prev = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = "";
|
||||||
|
await expect(
|
||||||
|
program.parseAsync(["relay", "--provider", "telegram"], {
|
||||||
|
from: "user",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow();
|
||||||
|
expect(runtime.error).toHaveBeenCalled();
|
||||||
|
expect(runtime.exit).toHaveBeenCalled();
|
||||||
process.env.TELEGRAM_BOT_TOKEN = prev;
|
process.env.TELEGRAM_BOT_TOKEN = prev;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -403,7 +403,13 @@ Examples:
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("relay")
|
.command("relay")
|
||||||
.description("Auto-reply to inbound WhatsApp messages (web provider)")
|
.description(
|
||||||
|
"Auto-reply to inbound messages across configured providers (web, Telegram)",
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
"--provider <auto|web|telegram|all>",
|
||||||
|
"Which providers to start: auto (default), web, telegram, or all",
|
||||||
|
)
|
||||||
.option(
|
.option(
|
||||||
"--web-heartbeat <seconds>",
|
"--web-heartbeat <seconds>",
|
||||||
"Heartbeat interval for web relay health logs (seconds)",
|
"Heartbeat interval for web relay health logs (seconds)",
|
||||||
|
|
@ -422,13 +428,30 @@ Examples:
|
||||||
"Run a heartbeat immediately when relay starts",
|
"Run a heartbeat immediately when relay starts",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
.option("--webhook", "Run Telegram webhook server instead of long-poll", false)
|
||||||
|
.option(
|
||||||
|
"--webhook-path <path>",
|
||||||
|
"Telegram webhook path (default /telegram-webhook when webhook enabled)",
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
"--webhook-secret <secret>",
|
||||||
|
"Secret token to verify Telegram webhook requests",
|
||||||
|
)
|
||||||
|
.option("--port <port>", "Port for Telegram webhook server (default 8787)")
|
||||||
|
.option(
|
||||||
|
"--webhook-url <url>",
|
||||||
|
"Public Telegram webhook URL to register (overrides localhost autodetect)",
|
||||||
|
)
|
||||||
.option("--verbose", "Verbose logging", false)
|
.option("--verbose", "Verbose logging", false)
|
||||||
.addHelpText(
|
.addHelpText(
|
||||||
"after",
|
"after",
|
||||||
`
|
`
|
||||||
Examples:
|
Examples:
|
||||||
clawdis relay # uses your linked web session
|
clawdis relay # starts WhatsApp; also Telegram if bot token set
|
||||||
clawdis relay --web-heartbeat 60 # override heartbeat interval
|
clawdis relay --provider web # force WhatsApp-only
|
||||||
|
clawdis relay --provider telegram # Telegram-only (needs TELEGRAM_BOT_TOKEN)
|
||||||
|
clawdis relay --heartbeat-now # send immediate agent heartbeat on start (web)
|
||||||
|
clawdis relay --web-heartbeat 60 # override WhatsApp heartbeat interval
|
||||||
# Troubleshooting: docs/refactor/web-relay-troubleshooting.md
|
# Troubleshooting: docs/refactor/web-relay-troubleshooting.md
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
|
@ -436,6 +459,50 @@ Examples:
|
||||||
setVerbose(Boolean(opts.verbose));
|
setVerbose(Boolean(opts.verbose));
|
||||||
const { file: logFile, level: logLevel } = getResolvedLoggerSettings();
|
const { file: logFile, level: logLevel } = getResolvedLoggerSettings();
|
||||||
defaultRuntime.log(info(`logs: ${logFile} (level ${logLevel})`));
|
defaultRuntime.log(info(`logs: ${logFile} (level ${logLevel})`));
|
||||||
|
|
||||||
|
const providerOpt = (opts.provider ?? "auto").toLowerCase();
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const telegramToken =
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN ?? cfg.telegram?.botToken;
|
||||||
|
|
||||||
|
let startWeb = false;
|
||||||
|
let startTelegram = false;
|
||||||
|
switch (providerOpt) {
|
||||||
|
case "web":
|
||||||
|
startWeb = true;
|
||||||
|
break;
|
||||||
|
case "telegram":
|
||||||
|
startTelegram = true;
|
||||||
|
break;
|
||||||
|
case "all":
|
||||||
|
startWeb = true;
|
||||||
|
startTelegram = true;
|
||||||
|
break;
|
||||||
|
case "auto":
|
||||||
|
default:
|
||||||
|
startWeb = true;
|
||||||
|
startTelegram = Boolean(telegramToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTelegram && !telegramToken) {
|
||||||
|
defaultRuntime.error(
|
||||||
|
danger(
|
||||||
|
"Telegram relay requires TELEGRAM_BOT_TOKEN or telegram.botToken in config",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
defaultRuntime.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startWeb && !startTelegram) {
|
||||||
|
defaultRuntime.error(
|
||||||
|
danger("No providers selected. Use --provider web|telegram|all."),
|
||||||
|
);
|
||||||
|
defaultRuntime.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const webHeartbeat =
|
const webHeartbeat =
|
||||||
opts.webHeartbeat !== undefined
|
opts.webHeartbeat !== undefined
|
||||||
? Number.parseInt(String(opts.webHeartbeat), 10)
|
? Number.parseInt(String(opts.webHeartbeat), 10)
|
||||||
|
|
@ -490,30 +557,37 @@ Examples:
|
||||||
defaultRuntime.exit(1);
|
defaultRuntime.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const webTuning: WebMonitorTuning = {};
|
const controller = new AbortController();
|
||||||
if (webHeartbeat !== undefined) webTuning.heartbeatSeconds = webHeartbeat;
|
const stopAll = () => controller.abort();
|
||||||
if (heartbeatNow) webTuning.replyHeartbeatNow = true;
|
process.once("SIGINT", stopAll);
|
||||||
const reconnect: WebMonitorTuning["reconnect"] = {};
|
|
||||||
if (webRetries !== undefined) reconnect.maxAttempts = webRetries;
|
const runners: Array<Promise<unknown>> = [];
|
||||||
if (webRetryInitial !== undefined) reconnect.initialMs = webRetryInitial;
|
|
||||||
if (webRetryMax !== undefined) reconnect.maxMs = webRetryMax;
|
if (startWeb) {
|
||||||
if (Object.keys(reconnect).length > 0) {
|
const webTuning: WebMonitorTuning = {};
|
||||||
webTuning.reconnect = reconnect;
|
if (webHeartbeat !== undefined)
|
||||||
}
|
webTuning.heartbeatSeconds = webHeartbeat;
|
||||||
logWebSelfId(defaultRuntime, true);
|
if (heartbeatNow) webTuning.replyHeartbeatNow = true;
|
||||||
const cfg = loadConfig();
|
const reconnect: WebMonitorTuning["reconnect"] = {};
|
||||||
const effectiveHeartbeat = resolveHeartbeatSeconds(
|
if (webRetries !== undefined) reconnect.maxAttempts = webRetries;
|
||||||
cfg,
|
if (webRetryInitial !== undefined)
|
||||||
webTuning.heartbeatSeconds,
|
reconnect.initialMs = webRetryInitial;
|
||||||
);
|
if (webRetryMax !== undefined) reconnect.maxMs = webRetryMax;
|
||||||
const effectivePolicy = resolveReconnectPolicy(cfg, webTuning.reconnect);
|
if (Object.keys(reconnect).length > 0) {
|
||||||
defaultRuntime.log(
|
webTuning.reconnect = reconnect;
|
||||||
info(
|
}
|
||||||
`Web relay health: heartbeat ${effectiveHeartbeat}s, retries ${effectivePolicy.maxAttempts || "∞"}, backoff ${effectivePolicy.initialMs}→${effectivePolicy.maxMs}ms x${effectivePolicy.factor} (jitter ${Math.round(effectivePolicy.jitter * 100)}%)`,
|
logWebSelfId(defaultRuntime, true);
|
||||||
),
|
const effectiveHeartbeat = resolveHeartbeatSeconds(
|
||||||
);
|
cfg,
|
||||||
try {
|
webTuning.heartbeatSeconds,
|
||||||
// Start loopback web chat server unless disabled.
|
);
|
||||||
|
const effectivePolicy = resolveReconnectPolicy(cfg, webTuning.reconnect);
|
||||||
|
defaultRuntime.log(
|
||||||
|
info(
|
||||||
|
`Web relay health: heartbeat ${effectiveHeartbeat}s, retries ${effectivePolicy.maxAttempts || "∞"}, backoff ${effectivePolicy.initialMs}→${effectivePolicy.maxMs}ms x${effectivePolicy.factor} (jitter ${Math.round(effectivePolicy.jitter * 100)}%)`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const webchatServer = await ensureWebChatServerFromConfig();
|
const webchatServer = await ensureWebChatServerFromConfig();
|
||||||
if (webchatServer) {
|
if (webchatServer) {
|
||||||
defaultRuntime.log(
|
defaultRuntime.log(
|
||||||
|
|
@ -523,146 +597,59 @@ Examples:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await monitorWebProvider(
|
runners.push(
|
||||||
Boolean(opts.verbose),
|
monitorWebProvider(
|
||||||
undefined,
|
Boolean(opts.verbose),
|
||||||
true,
|
undefined,
|
||||||
undefined,
|
true,
|
||||||
defaultRuntime,
|
undefined,
|
||||||
undefined,
|
defaultRuntime,
|
||||||
webTuning,
|
controller.signal,
|
||||||
);
|
webTuning,
|
||||||
return;
|
|
||||||
} catch (err) {
|
|
||||||
defaultRuntime.error(
|
|
||||||
danger(
|
|
||||||
`Web relay failed: ${String(err)}. Re-link with 'clawdis login --verbose'.`,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
defaultRuntime.exit(1);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
program
|
if (startTelegram) {
|
||||||
.command("relay:heartbeat")
|
const useWebhook = Boolean(opts.webhook);
|
||||||
.description("Run relay with an immediate heartbeat; requires web provider")
|
const telegramRunner = (async () => {
|
||||||
.option("--verbose", "Verbose logging", false)
|
|
||||||
.action(async (opts) => {
|
|
||||||
setVerbose(Boolean(opts.verbose));
|
|
||||||
const { file: logFile, level: logLevel } = getResolvedLoggerSettings();
|
|
||||||
defaultRuntime.log(info(`logs: ${logFile} (level ${logLevel})`));
|
|
||||||
|
|
||||||
logWebSelfId(defaultRuntime, true);
|
|
||||||
const cfg = loadConfig();
|
|
||||||
const effectiveHeartbeat = resolveHeartbeatSeconds(cfg, undefined);
|
|
||||||
const effectivePolicy = resolveReconnectPolicy(cfg, undefined);
|
|
||||||
defaultRuntime.log(
|
|
||||||
info(
|
|
||||||
`Web relay health: heartbeat ${effectiveHeartbeat}s, retries ${effectivePolicy.maxAttempts || "∞"}, backoff ${effectivePolicy.initialMs}→${effectivePolicy.maxMs}ms x${effectivePolicy.factor} (jitter ${Math.round(effectivePolicy.jitter * 100)}%)`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await monitorWebProvider(
|
|
||||||
Boolean(opts.verbose),
|
|
||||||
undefined,
|
|
||||||
true,
|
|
||||||
undefined,
|
|
||||||
defaultRuntime,
|
|
||||||
undefined,
|
|
||||||
{ replyHeartbeatNow: true },
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
defaultRuntime.error(
|
|
||||||
danger(
|
|
||||||
`Web relay failed: ${String(err)}. Re-link with 'clawdis login --provider web'.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
defaultRuntime.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
program
|
|
||||||
.command("relay:telegram")
|
|
||||||
.description("Auto-reply to Telegram (Bot API via grammY)")
|
|
||||||
.option("--verbose", "Verbose logging", false)
|
|
||||||
.option("--webhook", "Run webhook server instead of long-poll", false)
|
|
||||||
.option(
|
|
||||||
"--webhook-path <path>",
|
|
||||||
"Webhook path (default /telegram-webhook when webhook enabled)",
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
"--webhook-secret <secret>",
|
|
||||||
"Secret token to verify Telegram webhook requests",
|
|
||||||
)
|
|
||||||
.option("--port <port>", "Port for webhook server (default 8787)")
|
|
||||||
.option(
|
|
||||||
"--webhook-url <url>",
|
|
||||||
"Public webhook URL to register (overrides localhost autodetect)",
|
|
||||||
)
|
|
||||||
.addHelpText(
|
|
||||||
"after",
|
|
||||||
`
|
|
||||||
Examples:
|
|
||||||
clawdis relay:telegram # uses TELEGRAM_BOT_TOKEN env
|
|
||||||
TELEGRAM_BOT_TOKEN=xxx clawdis relay:telegram --verbose
|
|
||||||
TELEGRAM_BOT_TOKEN=xxx clawdis relay:telegram --webhook --port 9000 --webhook-secret secret
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
.action(async (opts) => {
|
|
||||||
setVerbose(Boolean(opts.verbose));
|
|
||||||
const token =
|
|
||||||
process.env.TELEGRAM_BOT_TOKEN ?? loadConfig().telegram?.botToken;
|
|
||||||
if (!token) {
|
|
||||||
defaultRuntime.error(
|
|
||||||
danger(
|
|
||||||
"Set TELEGRAM_BOT_TOKEN or telegram.botToken to use telegram relay",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
defaultRuntime.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const useWebhook = Boolean(opts.webhook);
|
|
||||||
if (useWebhook) {
|
|
||||||
const port = opts.port ? Number.parseInt(String(opts.port), 10) : 8787;
|
|
||||||
const path = opts.webhookPath ?? "/telegram-webhook";
|
|
||||||
try {
|
|
||||||
const { monitorTelegramProvider } = await import(
|
const { monitorTelegramProvider } = await import(
|
||||||
"../telegram/monitor.js"
|
"../telegram/monitor.js"
|
||||||
);
|
);
|
||||||
await monitorTelegramProvider({
|
const sharedOpts = {
|
||||||
token,
|
token: telegramToken,
|
||||||
useWebhook: true,
|
|
||||||
webhookPath: path,
|
|
||||||
webhookPort: port,
|
|
||||||
webhookSecret:
|
|
||||||
opts.webhookSecret ?? loadConfig().telegram?.webhookSecret,
|
|
||||||
runtime: defaultRuntime,
|
runtime: defaultRuntime,
|
||||||
proxyFetch: undefined,
|
abortSignal: controller.signal,
|
||||||
// register with provided public URL when given
|
} as const;
|
||||||
webhookUrl: opts.webhookUrl,
|
if (useWebhook) {
|
||||||
});
|
const port = opts.port
|
||||||
} catch (err) {
|
? Number.parseInt(String(opts.port), 10)
|
||||||
defaultRuntime.error(
|
: 8787;
|
||||||
danger(`Telegram webhook server failed: ${String(err)}`),
|
const path = opts.webhookPath ?? "/telegram-webhook";
|
||||||
);
|
return monitorTelegramProvider({
|
||||||
defaultRuntime.exit(1);
|
...sharedOpts,
|
||||||
}
|
useWebhook: true,
|
||||||
return;
|
webhookPath: path,
|
||||||
|
webhookPort: port,
|
||||||
|
webhookSecret: opts.webhookSecret ?? cfg.telegram?.webhookSecret,
|
||||||
|
webhookUrl: opts.webhookUrl ?? cfg.telegram?.webhookUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return monitorTelegramProvider(sharedOpts);
|
||||||
|
})();
|
||||||
|
runners.push(telegramRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await import("../telegram/monitor.js").then((m) =>
|
await Promise.all(runners);
|
||||||
m.monitorTelegramProvider({
|
|
||||||
token,
|
|
||||||
runtime: defaultRuntime,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
defaultRuntime.error(danger(`Telegram relay failed: ${String(err)}`));
|
defaultRuntime.error(danger(`Relay failed: ${String(err)}`));
|
||||||
defaultRuntime.exit(1);
|
defaultRuntime.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// relay is the single entry point; heartbeat/Telegram helpers removed.
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("status")
|
.command("status")
|
||||||
.description("Show web session health and recent session recipients")
|
.description("Show web session health and recent session recipients")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue