fix: align telegram token resolution
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
- macOS packaging: move rpath config into swift build for reliability (#69) — thanks @petter-b
|
- macOS packaging: move rpath config into swift build for reliability (#69) — thanks @petter-b
|
||||||
- macOS: prioritize main bundle for device resources to prevent crash (#73) — thanks @petter-b
|
- macOS: prioritize main bundle for device resources to prevent crash (#73) — thanks @petter-b
|
||||||
- macOS remote: route settings through gateway config and avoid local config reads in remote mode.
|
- macOS remote: route settings through gateway config and avoid local config reads in remote mode.
|
||||||
|
- Telegram: align token resolution for cron/agent/CLI sends (env/config/tokenFile) to prevent isolated delivery failures (#76).
|
||||||
- Chat UI: clear composer input immediately and allow clear while editing to prevent duplicate sends (#72) — thanks @hrdwdmrbl
|
- Chat UI: clear composer input immediately and allow clear while editing to prevent duplicate sends (#72) — thanks @hrdwdmrbl
|
||||||
- Restart: use systemd on Linux (and report actual restart method) instead of always launchctl.
|
- Restart: use systemd on Linux (and report actual restart method) instead of always launchctl.
|
||||||
- Gateway relay: detect Bun binaries via execPath to resolve packaged assets on macOS.
|
- Gateway relay: detect Bun binaries via execPath to resolve packaged assets on macOS.
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function mockConfig(
|
|||||||
storePath: string,
|
storePath: string,
|
||||||
routingOverrides?: Partial<NonNullable<ClawdisConfig["routing"]>>,
|
routingOverrides?: Partial<NonNullable<ClawdisConfig["routing"]>>,
|
||||||
agentOverrides?: Partial<NonNullable<ClawdisConfig["agent"]>>,
|
agentOverrides?: Partial<NonNullable<ClawdisConfig["agent"]>>,
|
||||||
|
telegramOverrides?: Partial<NonNullable<ClawdisConfig["telegram"]>>,
|
||||||
) {
|
) {
|
||||||
configSpy.mockReturnValue({
|
configSpy.mockReturnValue({
|
||||||
agent: {
|
agent: {
|
||||||
@@ -60,6 +61,7 @@ function mockConfig(
|
|||||||
},
|
},
|
||||||
session: { store: storePath, mainKey: "main" },
|
session: { store: storePath, mainKey: "main" },
|
||||||
routing: routingOverrides ? { ...routingOverrides } : undefined,
|
routing: routingOverrides ? { ...routingOverrides } : undefined,
|
||||||
|
telegram: telegramOverrides ? { ...telegramOverrides } : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,4 +200,46 @@ describe("agentCommand", () => {
|
|||||||
expect(callArgs?.prompt).toBe("ping");
|
expect(callArgs?.prompt).toBe("ping");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes telegram token when delivering", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const store = path.join(home, "sessions.json");
|
||||||
|
mockConfig(home, store, undefined, undefined, { botToken: "t-1" });
|
||||||
|
const deps = {
|
||||||
|
sendMessageWhatsApp: vi.fn(),
|
||||||
|
sendMessageTelegram: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ messageId: "t1", chatId: "123" }),
|
||||||
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevTelegramToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = "";
|
||||||
|
try {
|
||||||
|
await agentCommand(
|
||||||
|
{
|
||||||
|
message: "hi",
|
||||||
|
to: "123",
|
||||||
|
deliver: true,
|
||||||
|
provider: "telegram",
|
||||||
|
},
|
||||||
|
runtime,
|
||||||
|
deps,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(deps.sendMessageTelegram).toHaveBeenCalledWith(
|
||||||
|
"123",
|
||||||
|
"ok",
|
||||||
|
expect.objectContaining({ token: "t-1" }),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (prevTelegramToken === undefined) {
|
||||||
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
} else {
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = prevTelegramToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
|
|
||||||
type AgentCommandOpts = {
|
type AgentCommandOpts = {
|
||||||
@@ -218,6 +219,8 @@ export async function agentCommand(
|
|||||||
? buildWorkspaceSkillSnapshot(workspaceDir, { config: cfg })
|
? buildWorkspaceSkillSnapshot(workspaceDir, { config: cfg })
|
||||||
: sessionEntry?.skillsSnapshot;
|
: sessionEntry?.skillsSnapshot;
|
||||||
|
|
||||||
|
const { token: telegramToken } = resolveTelegramToken(cfg);
|
||||||
|
|
||||||
if (skillsSnapshot && sessionStore && sessionKey && needsSkillsSnapshot) {
|
if (skillsSnapshot && sessionStore && sessionKey && needsSkillsSnapshot) {
|
||||||
const current = sessionEntry ?? {
|
const current = sessionEntry ?? {
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -544,6 +547,7 @@ export async function agentCommand(
|
|||||||
for (const chunk of chunkText(text, 4000)) {
|
for (const chunk of chunkText(text, 4000)) {
|
||||||
await deps.sendMessageTelegram(telegramTarget, chunk, {
|
await deps.sendMessageTelegram(telegramTarget, chunk, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
token: telegramToken || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -554,6 +558,7 @@ export async function agentCommand(
|
|||||||
await deps.sendMessageTelegram(telegramTarget, caption, {
|
await deps.sendMessageTelegram(telegramTarget, caption, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
mediaUrl: url,
|
mediaUrl: url,
|
||||||
|
token: telegramToken || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
|
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||||
import { type DiscordProbe, probeDiscord } from "../discord/probe.js";
|
import { type DiscordProbe, probeDiscord } from "../discord/probe.js";
|
||||||
@@ -7,6 +5,7 @@ import { callGateway } from "../gateway/call.js";
|
|||||||
import { info } from "../globals.js";
|
import { info } from "../globals.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { probeTelegram, type TelegramProbe } from "../telegram/probe.js";
|
import { probeTelegram, type TelegramProbe } from "../telegram/probe.js";
|
||||||
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
||||||
import {
|
import {
|
||||||
getWebAuthAgeMs,
|
getWebAuthAgeMs,
|
||||||
@@ -55,25 +54,6 @@ export type HealthSummary = {
|
|||||||
|
|
||||||
const DEFAULT_TIMEOUT_MS = 10_000;
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
||||||
|
|
||||||
function loadTelegramToken(cfg: ReturnType<typeof loadConfig>): string {
|
|
||||||
const env = process.env.TELEGRAM_BOT_TOKEN?.trim();
|
|
||||||
if (env) return env;
|
|
||||||
|
|
||||||
const tokenFile = cfg.telegram?.tokenFile?.trim();
|
|
||||||
if (tokenFile) {
|
|
||||||
try {
|
|
||||||
if (fs.existsSync(tokenFile)) {
|
|
||||||
const token = fs.readFileSync(tokenFile, "utf-8").trim();
|
|
||||||
if (token) return token;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Ignore errors; health should be non-fatal.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg.telegram?.botToken?.trim() ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getHealthSnapshot(
|
export async function getHealthSnapshot(
|
||||||
timeoutMs?: number,
|
timeoutMs?: number,
|
||||||
): Promise<HealthSummary> {
|
): Promise<HealthSummary> {
|
||||||
@@ -95,7 +75,7 @@ export async function getHealthSnapshot(
|
|||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const cappedTimeout = Math.max(1000, timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
const cappedTimeout = Math.max(1000, timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
||||||
const telegramToken = loadTelegramToken(cfg);
|
const { token: telegramToken } = resolveTelegramToken(cfg);
|
||||||
const telegramConfigured = telegramToken.trim().length > 0;
|
const telegramConfigured = telegramToken.trim().length > 0;
|
||||||
const telegramProxy = cfg.telegram?.proxy;
|
const telegramProxy = cfg.telegram?.proxy;
|
||||||
const telegramProbe = telegramConfigured
|
const telegramProbe = telegramConfigured
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import type { CliDeps } from "../cli/deps.js";
|
|||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { sendCommand } from "./send.js";
|
import { sendCommand } from "./send.js";
|
||||||
|
|
||||||
|
let testConfig: Record<string, unknown> = {};
|
||||||
|
vi.mock("../config/config.js", () => ({
|
||||||
|
loadConfig: () => testConfig,
|
||||||
|
}));
|
||||||
|
|
||||||
const callGatewayMock = vi.fn();
|
const callGatewayMock = vi.fn();
|
||||||
vi.mock("../gateway/call.js", () => ({
|
vi.mock("../gateway/call.js", () => ({
|
||||||
callGateway: (...args: unknown[]) => callGatewayMock(...args),
|
callGateway: (...args: unknown[]) => callGatewayMock(...args),
|
||||||
@@ -16,6 +21,7 @@ const originalDiscordToken = process.env.DISCORD_BOT_TOKEN;
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env.TELEGRAM_BOT_TOKEN = "token-abc";
|
process.env.TELEGRAM_BOT_TOKEN = "token-abc";
|
||||||
process.env.DISCORD_BOT_TOKEN = "token-discord";
|
process.env.DISCORD_BOT_TOKEN = "token-discord";
|
||||||
|
testConfig = {};
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -75,6 +81,7 @@ describe("sendCommand", () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue({ messageId: "t1", chatId: "123" }),
|
.mockResolvedValue({ messageId: "t1", chatId: "123" }),
|
||||||
});
|
});
|
||||||
|
testConfig = { telegram: { botToken: "token-abc" } };
|
||||||
await sendCommand(
|
await sendCommand(
|
||||||
{ to: "123", message: "hi", provider: "telegram" },
|
{ to: "123", message: "hi", provider: "telegram" },
|
||||||
deps,
|
deps,
|
||||||
@@ -88,6 +95,26 @@ describe("sendCommand", () => {
|
|||||||
expect(deps.sendMessageWhatsApp).not.toHaveBeenCalled();
|
expect(deps.sendMessageWhatsApp).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses config token for telegram when env is missing", async () => {
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = "";
|
||||||
|
testConfig = { telegram: { botToken: "cfg-token" } };
|
||||||
|
const deps = makeDeps({
|
||||||
|
sendMessageTelegram: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ messageId: "t1", chatId: "123" }),
|
||||||
|
});
|
||||||
|
await sendCommand(
|
||||||
|
{ to: "123", message: "hi", provider: "telegram" },
|
||||||
|
deps,
|
||||||
|
runtime,
|
||||||
|
);
|
||||||
|
expect(deps.sendMessageTelegram).toHaveBeenCalledWith(
|
||||||
|
"123",
|
||||||
|
"hi",
|
||||||
|
expect.objectContaining({ token: "cfg-token" }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("routes to discord provider", async () => {
|
it("routes to discord provider", async () => {
|
||||||
const deps = makeDeps({
|
const deps = makeDeps({
|
||||||
sendMessageDiscord: vi
|
sendMessageDiscord: vi
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { CliDeps } from "../cli/deps.js";
|
import type { CliDeps } from "../cli/deps.js";
|
||||||
|
import { loadConfig } from "../config/config.js";
|
||||||
import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
|
import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
|
||||||
import { success } from "../globals.js";
|
import { success } from "../globals.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
|
|
||||||
export async function sendCommand(
|
export async function sendCommand(
|
||||||
opts: {
|
opts: {
|
||||||
@@ -25,8 +27,9 @@ export async function sendCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (provider === "telegram") {
|
if (provider === "telegram") {
|
||||||
|
const { token } = resolveTelegramToken(loadConfig());
|
||||||
const result = await deps.sendMessageTelegram(opts.to, opts.message, {
|
const result = await deps.sendMessageTelegram(opts.to, opts.message, {
|
||||||
token: process.env.TELEGRAM_BOT_TOKEN,
|
token: token || undefined,
|
||||||
mediaUrl: opts.media,
|
mediaUrl: opts.media,
|
||||||
});
|
});
|
||||||
runtime.log(
|
runtime.log(
|
||||||
|
|||||||
@@ -53,14 +53,19 @@ async function writeSessionStore(home: string) {
|
|||||||
return storePath;
|
return storePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCfg(home: string, storePath: string): ClawdisConfig {
|
function makeCfg(
|
||||||
return {
|
home: string,
|
||||||
|
storePath: string,
|
||||||
|
overrides: Partial<ClawdisConfig> = {},
|
||||||
|
): ClawdisConfig {
|
||||||
|
const base: ClawdisConfig = {
|
||||||
agent: {
|
agent: {
|
||||||
model: "anthropic/claude-opus-4-5",
|
model: "anthropic/claude-opus-4-5",
|
||||||
workspace: path.join(home, "clawd"),
|
workspace: path.join(home, "clawd"),
|
||||||
},
|
},
|
||||||
session: { store: storePath, mainKey: "main" },
|
session: { store: storePath, mainKey: "main" },
|
||||||
} as ClawdisConfig;
|
} as ClawdisConfig;
|
||||||
|
return { ...base, ...overrides };
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeJob(payload: CronJob["payload"]): CronJob {
|
function makeJob(payload: CronJob["payload"]): CronJob {
|
||||||
@@ -91,6 +96,7 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
sendMessageWhatsApp: vi.fn(),
|
sendMessageWhatsApp: vi.fn(),
|
||||||
sendMessageTelegram: vi.fn(),
|
sendMessageTelegram: vi.fn(),
|
||||||
sendMessageDiscord: vi.fn(),
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
};
|
};
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
payloads: [{ text: "first" }, { text: " " }, { text: " last " }],
|
payloads: [{ text: "first" }, { text: " " }, { text: " last " }],
|
||||||
@@ -121,6 +127,7 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
sendMessageWhatsApp: vi.fn(),
|
sendMessageWhatsApp: vi.fn(),
|
||||||
sendMessageTelegram: vi.fn(),
|
sendMessageTelegram: vi.fn(),
|
||||||
sendMessageDiscord: vi.fn(),
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
};
|
};
|
||||||
const long = "a".repeat(2001);
|
const long = "a".repeat(2001);
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
@@ -152,6 +159,7 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
sendMessageWhatsApp: vi.fn(),
|
sendMessageWhatsApp: vi.fn(),
|
||||||
sendMessageTelegram: vi.fn(),
|
sendMessageTelegram: vi.fn(),
|
||||||
sendMessageDiscord: vi.fn(),
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
};
|
};
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
payloads: [{ text: "hello" }],
|
payloads: [{ text: "hello" }],
|
||||||
@@ -190,6 +198,7 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
sendMessageWhatsApp: vi.fn(),
|
sendMessageWhatsApp: vi.fn(),
|
||||||
sendMessageTelegram: vi.fn(),
|
sendMessageTelegram: vi.fn(),
|
||||||
sendMessageDiscord: vi.fn(),
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
};
|
};
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
payloads: [{ text: "hello" }],
|
payloads: [{ text: "hello" }],
|
||||||
@@ -220,6 +229,60 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes telegram token from config for delivery", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const storePath = await writeSessionStore(home);
|
||||||
|
const deps: CliDeps = {
|
||||||
|
sendMessageWhatsApp: vi.fn(),
|
||||||
|
sendMessageTelegram: vi.fn().mockResolvedValue({
|
||||||
|
messageId: "t1",
|
||||||
|
chatId: "123",
|
||||||
|
}),
|
||||||
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
|
};
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
|
payloads: [{ text: "hello from cron" }],
|
||||||
|
meta: {
|
||||||
|
durationMs: 5,
|
||||||
|
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevTelegramToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = "";
|
||||||
|
try {
|
||||||
|
const res = await runCronIsolatedAgentTurn({
|
||||||
|
cfg: makeCfg(home, storePath, { telegram: { botToken: "t-1" } }),
|
||||||
|
deps,
|
||||||
|
job: makeJob({
|
||||||
|
kind: "agentTurn",
|
||||||
|
message: "do it",
|
||||||
|
deliver: true,
|
||||||
|
channel: "telegram",
|
||||||
|
to: "123",
|
||||||
|
}),
|
||||||
|
message: "do it",
|
||||||
|
sessionKey: "cron:job-1",
|
||||||
|
lane: "cron",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status).toBe("ok");
|
||||||
|
expect(deps.sendMessageTelegram).toHaveBeenCalledWith(
|
||||||
|
"123",
|
||||||
|
"hello from cron",
|
||||||
|
expect.objectContaining({ token: "t-1" }),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (prevTelegramToken === undefined) {
|
||||||
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||||
|
} else {
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN = prevTelegramToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("delivers via discord when configured", async () => {
|
it("delivers via discord when configured", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const storePath = await writeSessionStore(home);
|
const storePath = await writeSessionStore(home);
|
||||||
@@ -230,6 +293,7 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
messageId: "d1",
|
messageId: "d1",
|
||||||
channelId: "chan",
|
channelId: "chan",
|
||||||
}),
|
}),
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
};
|
};
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
payloads: [{ text: "hello from cron" }],
|
payloads: [{ text: "hello from cron" }],
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
saveSessionStore,
|
saveSessionStore,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
import type { CronJob } from "./types.js";
|
import type { CronJob } from "./types.js";
|
||||||
|
|
||||||
@@ -206,6 +207,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
? params.job.payload.to
|
? params.job.payload.to
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
const { token: telegramToken } = resolveTelegramToken(params.cfg);
|
||||||
|
|
||||||
const base =
|
const base =
|
||||||
`[cron:${params.job.id} ${params.job.name}] ${params.message}`.trim();
|
`[cron:${params.job.id} ${params.job.name}] ${params.message}`.trim();
|
||||||
@@ -352,6 +354,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
for (const chunk of chunkText(payload.text ?? "", 4000)) {
|
for (const chunk of chunkText(payload.text ?? "", 4000)) {
|
||||||
await params.deps.sendMessageTelegram(chatId, chunk, {
|
await params.deps.sendMessageTelegram(chatId, chunk, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
token: telegramToken || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -362,6 +365,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
await params.deps.sendMessageTelegram(chatId, caption, {
|
await params.deps.sendMessageTelegram(chatId, caption, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
mediaUrl: url,
|
mediaUrl: url,
|
||||||
|
token: telegramToken || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ import { probeSignal, type SignalProbe } from "../signal/probe.js";
|
|||||||
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
||||||
import { probeTelegram, type TelegramProbe } from "../telegram/probe.js";
|
import { probeTelegram, type TelegramProbe } from "../telegram/probe.js";
|
||||||
import { sendMessageTelegram } from "../telegram/send.js";
|
import { sendMessageTelegram } from "../telegram/send.js";
|
||||||
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { normalizeE164, resolveUserPath } from "../utils.js";
|
import { normalizeE164, resolveUserPath } from "../utils.js";
|
||||||
import type { WebProviderStatus } from "../web/auto-reply.js";
|
import type { WebProviderStatus } from "../web/auto-reply.js";
|
||||||
import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js";
|
import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js";
|
||||||
@@ -292,33 +293,6 @@ const telegramRuntimeEnv = runtimeForLogger(logTelegram);
|
|||||||
const discordRuntimeEnv = runtimeForLogger(logDiscord);
|
const discordRuntimeEnv = runtimeForLogger(logDiscord);
|
||||||
const signalRuntimeEnv = runtimeForLogger(logSignal);
|
const signalRuntimeEnv = runtimeForLogger(logSignal);
|
||||||
|
|
||||||
function loadTelegramToken(
|
|
||||||
config: ClawdisConfig,
|
|
||||||
opts: { logMissing?: boolean } = {},
|
|
||||||
): string {
|
|
||||||
if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
||||||
return process.env.TELEGRAM_BOT_TOKEN.trim();
|
|
||||||
}
|
|
||||||
if (config.telegram?.tokenFile) {
|
|
||||||
const filePath = config.telegram.tokenFile;
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
if (opts.logMissing) {
|
|
||||||
logTelegram.warn(`telegram.tokenFile not found: ${filePath}`);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return fs.readFileSync(filePath, "utf-8").trim();
|
|
||||||
} catch (err) {
|
|
||||||
if (opts.logMissing) {
|
|
||||||
logTelegram.warn(`telegram.tokenFile read failed: ${String(err)}`);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config.telegram?.botToken?.trim() ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveBonjourCliPath(): string | undefined {
|
function resolveBonjourCliPath(): string | undefined {
|
||||||
const envPath = process.env.CLAWDIS_CLI_PATH?.trim();
|
const envPath = process.env.CLAWDIS_CLI_PATH?.trim();
|
||||||
if (envPath) return envPath;
|
if (envPath) return envPath;
|
||||||
@@ -1956,7 +1930,9 @@ export async function startGatewayServer(
|
|||||||
logTelegram.info("skipping provider start (telegram.enabled=false)");
|
logTelegram.info("skipping provider start (telegram.enabled=false)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const telegramToken = loadTelegramToken(cfg, { logMissing: true });
|
const { token: telegramToken } = resolveTelegramToken(cfg, {
|
||||||
|
logMissingFile: (message) => logTelegram.warn(message),
|
||||||
|
});
|
||||||
if (!telegramToken.trim()) {
|
if (!telegramToken.trim()) {
|
||||||
telegramRuntime = {
|
telegramRuntime = {
|
||||||
...telegramRuntime,
|
...telegramRuntime,
|
||||||
@@ -1964,7 +1940,7 @@ export async function startGatewayServer(
|
|||||||
lastError: "not configured",
|
lastError: "not configured",
|
||||||
};
|
};
|
||||||
logTelegram.info(
|
logTelegram.info(
|
||||||
"skipping provider start (no TELEGRAM_BOT_TOKEN/config)",
|
"skipping provider start (no TELEGRAM_BOT_TOKEN/telegram config)",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4058,14 +4034,8 @@ export async function startGatewayServer(
|
|||||||
? Math.max(1000, timeoutMsRaw)
|
? Math.max(1000, timeoutMsRaw)
|
||||||
: 10_000;
|
: 10_000;
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const envToken = process.env.TELEGRAM_BOT_TOKEN?.trim();
|
const { token: telegramToken, source: tokenSource } =
|
||||||
const configToken = cfg.telegram?.botToken?.trim();
|
resolveTelegramToken(cfg);
|
||||||
const telegramToken = envToken || configToken || "";
|
|
||||||
const tokenSource = envToken
|
|
||||||
? "env"
|
|
||||||
: configToken
|
|
||||||
? "config"
|
|
||||||
: "none";
|
|
||||||
let telegramProbe: TelegramProbe | undefined;
|
let telegramProbe: TelegramProbe | undefined;
|
||||||
let lastProbeAt: number | null = null;
|
let lastProbeAt: number | null = null;
|
||||||
if (probe && telegramToken) {
|
if (probe && telegramToken) {
|
||||||
@@ -6023,7 +5993,7 @@ export async function startGatewayServer(
|
|||||||
try {
|
try {
|
||||||
if (provider === "telegram") {
|
if (provider === "telegram") {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const token = loadTelegramToken(cfg);
|
const { token } = resolveTelegramToken(cfg);
|
||||||
const result = await sendMessageTelegram(to, message, {
|
const result = await sendMessageTelegram(to, message, {
|
||||||
mediaUrl: params.mediaUrl,
|
mediaUrl: params.mediaUrl,
|
||||||
verbose: isVerbose(),
|
verbose: isVerbose(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { type ClawdisConfig, loadConfig } from "../config/config.js";
|
import { type ClawdisConfig, loadConfig } from "../config/config.js";
|
||||||
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
import {
|
import {
|
||||||
getWebAuthAgeMs,
|
getWebAuthAgeMs,
|
||||||
@@ -35,12 +35,8 @@ export async function buildProviderSummary(
|
|||||||
if (!telegramEnabled) {
|
if (!telegramEnabled) {
|
||||||
lines.push(chalk.cyan("Telegram: disabled"));
|
lines.push(chalk.cyan("Telegram: disabled"));
|
||||||
} else {
|
} else {
|
||||||
const telegramToken =
|
const { token: telegramToken } = resolveTelegramToken(effective);
|
||||||
process.env.TELEGRAM_BOT_TOKEN ?? effective.telegram?.botToken;
|
const telegramConfigured = Boolean(telegramToken);
|
||||||
const telegramTokenFile = effective.telegram?.tokenFile?.trim();
|
|
||||||
const telegramConfigured =
|
|
||||||
Boolean(telegramToken) ||
|
|
||||||
Boolean(telegramTokenFile ? fs.existsSync(telegramTokenFile) : false);
|
|
||||||
lines.push(
|
lines.push(
|
||||||
telegramConfigured
|
telegramConfigured
|
||||||
? chalk.green("Telegram: configured")
|
? chalk.green("Telegram: configured")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { loadConfig } from "../config/config.js";
|
|||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { createTelegramBot } from "./bot.js";
|
import { createTelegramBot } from "./bot.js";
|
||||||
import { makeProxyFetch } from "./proxy.js";
|
import { makeProxyFetch } from "./proxy.js";
|
||||||
|
import { resolveTelegramToken } from "./token.js";
|
||||||
import { startTelegramWebhook } from "./webhook.js";
|
import { startTelegramWebhook } from "./webhook.js";
|
||||||
|
|
||||||
export type MonitorTelegramOpts = {
|
export type MonitorTelegramOpts = {
|
||||||
@@ -17,10 +18,12 @@ export type MonitorTelegramOpts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
|
export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
|
||||||
const token = (opts.token ?? process.env.TELEGRAM_BOT_TOKEN)?.trim();
|
const { token } = resolveTelegramToken(loadConfig(), {
|
||||||
|
envToken: opts.token,
|
||||||
|
});
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"TELEGRAM_BOT_TOKEN or telegram.botToken is required for Telegram gateway",
|
"TELEGRAM_BOT_TOKEN or telegram.botToken/tokenFile is required for Telegram gateway",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
src/telegram/token.test.ts
Normal file
59
src/telegram/token.test.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { ClawdisConfig } from "../config/config.js";
|
||||||
|
import { resolveTelegramToken } from "./token.js";
|
||||||
|
|
||||||
|
function withTempDir(): string {
|
||||||
|
return fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-telegram-token-"));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("resolveTelegramToken", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers env token over config", () => {
|
||||||
|
vi.stubEnv("TELEGRAM_BOT_TOKEN", "env-token");
|
||||||
|
const cfg = { telegram: { botToken: "cfg-token" } } as ClawdisConfig;
|
||||||
|
const res = resolveTelegramToken(cfg);
|
||||||
|
expect(res.token).toBe("env-token");
|
||||||
|
expect(res.source).toBe("env");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses tokenFile when configured", () => {
|
||||||
|
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
|
||||||
|
const dir = withTempDir();
|
||||||
|
const tokenFile = path.join(dir, "token.txt");
|
||||||
|
fs.writeFileSync(tokenFile, "file-token\n", "utf-8");
|
||||||
|
const cfg = { telegram: { tokenFile } } as ClawdisConfig;
|
||||||
|
const res = resolveTelegramToken(cfg);
|
||||||
|
expect(res.token).toBe("file-token");
|
||||||
|
expect(res.source).toBe("tokenFile");
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to config token when no env or tokenFile", () => {
|
||||||
|
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
|
||||||
|
const cfg = { telegram: { botToken: "cfg-token" } } as ClawdisConfig;
|
||||||
|
const res = resolveTelegramToken(cfg);
|
||||||
|
expect(res.token).toBe("cfg-token");
|
||||||
|
expect(res.source).toBe("config");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not fall back to config when tokenFile is missing", () => {
|
||||||
|
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
|
||||||
|
const dir = withTempDir();
|
||||||
|
const tokenFile = path.join(dir, "missing-token.txt");
|
||||||
|
const cfg = {
|
||||||
|
telegram: { tokenFile, botToken: "cfg-token" },
|
||||||
|
} as ClawdisConfig;
|
||||||
|
const res = resolveTelegramToken(cfg);
|
||||||
|
expect(res.token).toBe("");
|
||||||
|
expect(res.source).toBe("none");
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
50
src/telegram/token.ts
Normal file
50
src/telegram/token.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
import type { ClawdisConfig } from "../config/config.js";
|
||||||
|
|
||||||
|
export type TelegramTokenSource = "env" | "tokenFile" | "config" | "none";
|
||||||
|
|
||||||
|
export type TelegramTokenResolution = {
|
||||||
|
token: string;
|
||||||
|
source: TelegramTokenSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResolveTelegramTokenOpts = {
|
||||||
|
envToken?: string | null;
|
||||||
|
logMissingFile?: (message: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveTelegramToken(
|
||||||
|
cfg?: ClawdisConfig,
|
||||||
|
opts: ResolveTelegramTokenOpts = {},
|
||||||
|
): TelegramTokenResolution {
|
||||||
|
const envToken = (opts.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim();
|
||||||
|
if (envToken) {
|
||||||
|
return { token: envToken, source: "env" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenFile = cfg?.telegram?.tokenFile?.trim();
|
||||||
|
if (tokenFile) {
|
||||||
|
if (!fs.existsSync(tokenFile)) {
|
||||||
|
opts.logMissingFile?.(`telegram.tokenFile not found: ${tokenFile}`);
|
||||||
|
return { token: "", source: "none" };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const token = fs.readFileSync(tokenFile, "utf-8").trim();
|
||||||
|
if (token) {
|
||||||
|
return { token, source: "tokenFile" };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
opts.logMissingFile?.(`telegram.tokenFile read failed: ${String(err)}`);
|
||||||
|
return { token: "", source: "none" };
|
||||||
|
}
|
||||||
|
return { token: "", source: "none" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const configToken = cfg?.telegram?.botToken?.trim();
|
||||||
|
if (configToken) {
|
||||||
|
return { token: configToken, source: "config" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token: "", source: "none" };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user