Gateway: start providers and route sends to their surface
This commit is contained in:
@@ -19,6 +19,8 @@ vi.mock("../commands/agent.js", () => ({
|
|||||||
agentCommand: vi.fn().mockResolvedValue(undefined),
|
agentCommand: vi.fn().mockResolvedValue(undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
process.env.CLAWDIS_SKIP_PROVIDERS = "1";
|
||||||
|
|
||||||
async function getFreePort(): Promise<number> {
|
async function getFreePort(): Promise<number> {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const server = createServer();
|
const server = createServer();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { createDefaultDeps } from "../cli/deps.js";
|
|||||||
import { agentCommand } from "../commands/agent.js";
|
import { agentCommand } from "../commands/agent.js";
|
||||||
import { getHealthSnapshot } from "../commands/health.js";
|
import { getHealthSnapshot } from "../commands/health.js";
|
||||||
import { getStatusSummary } from "../commands/status.js";
|
import { getStatusSummary } from "../commands/status.js";
|
||||||
|
import { loadConfig } from "../config/config.js";
|
||||||
|
import { isVerbose } from "../globals.js";
|
||||||
import { onAgentEvent } from "../infra/agent-events.js";
|
import { onAgentEvent } from "../infra/agent-events.js";
|
||||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||||
import {
|
import {
|
||||||
@@ -13,9 +15,11 @@ import {
|
|||||||
} from "../infra/system-presence.js";
|
} from "../infra/system-presence.js";
|
||||||
import { logError } from "../logger.js";
|
import { logError } from "../logger.js";
|
||||||
import { getResolvedLoggerSettings } from "../logging.js";
|
import { getResolvedLoggerSettings } from "../logging.js";
|
||||||
import { isVerbose } from "../globals.js";
|
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
|
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
|
||||||
import { sendMessageWhatsApp } from "../web/outbound.js";
|
import { sendMessageWhatsApp } from "../web/outbound.js";
|
||||||
|
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
||||||
|
import { sendMessageTelegram } from "../telegram/send.js";
|
||||||
import {
|
import {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
type ErrorShape,
|
type ErrorShape,
|
||||||
@@ -94,8 +98,53 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
|
|||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
maxPayload: MAX_PAYLOAD_BYTES,
|
maxPayload: MAX_PAYLOAD_BYTES,
|
||||||
});
|
});
|
||||||
|
const providerAbort = new AbortController();
|
||||||
|
const providerTasks: Array<Promise<unknown>> = [];
|
||||||
const clients = new Set<Client>();
|
const clients = new Set<Client>();
|
||||||
|
|
||||||
|
const startProviders = async () => {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const telegramToken =
|
||||||
|
process.env.TELEGRAM_BOT_TOKEN ?? cfg.telegram?.botToken ?? "";
|
||||||
|
|
||||||
|
if (await webAuthExists()) {
|
||||||
|
defaultRuntime.log("gateway: starting WhatsApp Web provider");
|
||||||
|
providerTasks.push(
|
||||||
|
monitorWebProvider(
|
||||||
|
isVerbose(),
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
defaultRuntime,
|
||||||
|
providerAbort.signal,
|
||||||
|
).catch((err) => logError(`web provider exited: ${String(err)}`)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
defaultRuntime.log(
|
||||||
|
"gateway: skipping WhatsApp Web provider (no linked session)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegramToken.trim().length > 0) {
|
||||||
|
defaultRuntime.log("gateway: starting Telegram provider");
|
||||||
|
providerTasks.push(
|
||||||
|
monitorTelegramProvider({
|
||||||
|
token: telegramToken.trim(),
|
||||||
|
runtime: defaultRuntime,
|
||||||
|
abortSignal: providerAbort.signal,
|
||||||
|
useWebhook: Boolean(cfg.telegram?.webhookUrl),
|
||||||
|
webhookUrl: cfg.telegram?.webhookUrl,
|
||||||
|
webhookSecret: cfg.telegram?.webhookSecret,
|
||||||
|
webhookPath: cfg.telegram?.webhookPath,
|
||||||
|
}).catch((err) => logError(`telegram provider exited: ${String(err)}`)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
defaultRuntime.log(
|
||||||
|
"gateway: skipping Telegram provider (no TELEGRAM_BOT_TOKEN/config)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const broadcast = (
|
const broadcast = (
|
||||||
event: string,
|
event: string,
|
||||||
payload: unknown,
|
payload: unknown,
|
||||||
@@ -399,16 +448,32 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
|
|||||||
}
|
}
|
||||||
const to = params.to.trim();
|
const to = params.to.trim();
|
||||||
const message = params.message.trim();
|
const message = params.message.trim();
|
||||||
|
const provider = (params.provider ?? "whatsapp").toLowerCase();
|
||||||
try {
|
try {
|
||||||
const result = await sendMessageWhatsApp(to, message, {
|
const result =
|
||||||
mediaUrl: params.mediaUrl,
|
provider === "telegram"
|
||||||
verbose: isVerbose(),
|
? await sendMessageTelegram(to, message, {
|
||||||
});
|
mediaUrl: params.mediaUrl,
|
||||||
const payload = {
|
verbose: isVerbose(),
|
||||||
runId: idem,
|
})
|
||||||
messageId: result.messageId,
|
: await sendMessageWhatsApp(to, message, {
|
||||||
toJid: result.toJid ?? `${to}@s.whatsapp.net`,
|
mediaUrl: params.mediaUrl,
|
||||||
};
|
verbose: isVerbose(),
|
||||||
|
});
|
||||||
|
const payload =
|
||||||
|
provider === "telegram"
|
||||||
|
? {
|
||||||
|
runId: idem,
|
||||||
|
messageId: result.messageId,
|
||||||
|
chatId: result.chatId,
|
||||||
|
provider,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
runId: idem,
|
||||||
|
messageId: result.messageId,
|
||||||
|
toJid: result.toJid ?? `${to}@s.whatsapp.net`,
|
||||||
|
provider,
|
||||||
|
};
|
||||||
dedupe.set(`send:${idem}`, { ts: Date.now(), ok: true, payload });
|
dedupe.set(`send:${idem}`, { ts: Date.now(), ok: true, payload });
|
||||||
respond(true, payload, undefined);
|
respond(true, payload, undefined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -524,8 +589,17 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
|
|||||||
);
|
);
|
||||||
defaultRuntime.log(`gateway log file: ${getResolvedLoggerSettings().file}`);
|
defaultRuntime.log(`gateway log file: ${getResolvedLoggerSettings().file}`);
|
||||||
|
|
||||||
|
// Launch configured providers (WhatsApp Web, Telegram) so gateway replies via the
|
||||||
|
// surface the message came from. Tests can opt out via CLAWDIS_SKIP_PROVIDERS.
|
||||||
|
if (process.env.CLAWDIS_SKIP_PROVIDERS !== "1") {
|
||||||
|
void startProviders();
|
||||||
|
} else {
|
||||||
|
defaultRuntime.log("gateway: skipping provider start (CLAWDIS_SKIP_PROVIDERS=1)");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
close: async () => {
|
close: async () => {
|
||||||
|
providerAbort.abort();
|
||||||
broadcast("shutdown", {
|
broadcast("shutdown", {
|
||||||
reason: "gateway stopping",
|
reason: "gateway stopping",
|
||||||
restartExpectedMs: null,
|
restartExpectedMs: null,
|
||||||
@@ -547,6 +621,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
clients.clear();
|
clients.clear();
|
||||||
|
await Promise.allSettled(providerTasks);
|
||||||
await new Promise<void>((resolve) => wss.close(() => resolve()));
|
await new Promise<void>((resolve) => wss.close(() => resolve()));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user