test: stabilize gateway tests

This commit is contained in:
Peter Steinberger
2026-01-04 04:16:38 +01:00
parent 3c4c2aa98c
commit 24aa3e3311
21 changed files with 192 additions and 104 deletions

View File

@@ -129,7 +129,7 @@ describe("sessions tools", () => {
callGatewayMock.mockReset(); callGatewayMock.mockReset();
const calls: Array<{ method?: string; params?: unknown }> = []; const calls: Array<{ method?: string; params?: unknown }> = [];
let agentCallCount = 0; let agentCallCount = 0;
let historyCallCount = 0; let _historyCallCount = 0;
let sendCallCount = 0; let sendCallCount = 0;
let lastWaitedRunId: string | undefined; let lastWaitedRunId: string | undefined;
const replyByRunId = new Map<string, string>(); const replyByRunId = new Map<string, string>();
@@ -165,7 +165,7 @@ describe("sessions tools", () => {
return { runId: params?.runId ?? "run-1", status: "ok" }; return { runId: params?.runId ?? "run-1", status: "ok" };
} }
if (request.method === "chat.history") { if (request.method === "chat.history") {
historyCallCount += 1; _historyCallCount += 1;
const text = const text =
(lastWaitedRunId && replyByRunId.get(lastWaitedRunId)) ?? ""; (lastWaitedRunId && replyByRunId.get(lastWaitedRunId)) ?? "";
return { return {
@@ -193,9 +193,7 @@ describe("sessions tools", () => {
const tool = createClawdisTools({ const tool = createClawdisTools({
agentSessionKey: requesterKey, agentSessionKey: requesterKey,
agentSurface: "discord", agentSurface: "discord",
}).find( }).find((candidate) => candidate.name === "sessions_send");
(candidate) => candidate.name === "sessions_send",
);
expect(tool).toBeDefined(); expect(tool).toBeDefined();
if (!tool) throw new Error("missing sessions_send tool"); if (!tool) throw new Error("missing sessions_send tool");
@@ -236,8 +234,9 @@ describe("sessions tools", () => {
(call) => (call) =>
typeof (call.params as { extraSystemPrompt?: string }) typeof (call.params as { extraSystemPrompt?: string })
?.extraSystemPrompt === "string" && ?.extraSystemPrompt === "string" &&
(call.params as { extraSystemPrompt?: string }) (
?.extraSystemPrompt?.includes("Agent-to-agent message context"), call.params as { extraSystemPrompt?: string }
)?.extraSystemPrompt?.includes("Agent-to-agent message context"),
), ),
).toBe(true); ).toBe(true);
expect( expect(
@@ -245,8 +244,9 @@ describe("sessions tools", () => {
(call) => (call) =>
typeof (call.params as { extraSystemPrompt?: string }) typeof (call.params as { extraSystemPrompt?: string })
?.extraSystemPrompt === "string" && ?.extraSystemPrompt === "string" &&
(call.params as { extraSystemPrompt?: string }) (
?.extraSystemPrompt?.includes("Agent-to-agent reply step"), call.params as { extraSystemPrompt?: string }
)?.extraSystemPrompt?.includes("Agent-to-agent reply step"),
), ),
).toBe(true); ).toBe(true);
expect( expect(
@@ -254,8 +254,9 @@ describe("sessions tools", () => {
(call) => (call) =>
typeof (call.params as { extraSystemPrompt?: string }) typeof (call.params as { extraSystemPrompt?: string })
?.extraSystemPrompt === "string" && ?.extraSystemPrompt === "string" &&
(call.params as { extraSystemPrompt?: string }) (
?.extraSystemPrompt?.includes("Agent-to-agent announce step"), call.params as { extraSystemPrompt?: string }
)?.extraSystemPrompt?.includes("Agent-to-agent announce step"),
), ),
).toBe(true); ).toBe(true);
expect(waitCalls).toHaveLength(8); expect(waitCalls).toHaveLength(8);
@@ -285,7 +286,11 @@ describe("sessions tools", () => {
agentCallCount += 1; agentCallCount += 1;
const runId = `run-${agentCallCount}`; const runId = `run-${agentCallCount}`;
const params = request.params as const params = request.params as
| { message?: string; sessionKey?: string; extraSystemPrompt?: string } | {
message?: string;
sessionKey?: string;
extraSystemPrompt?: string;
}
| undefined; | undefined;
let reply = "initial"; let reply = "initial";
if (params?.extraSystemPrompt?.includes("Agent-to-agent reply step")) { if (params?.extraSystemPrompt?.includes("Agent-to-agent reply step")) {
@@ -359,8 +364,9 @@ describe("sessions tools", () => {
call.method === "agent" && call.method === "agent" &&
typeof (call.params as { extraSystemPrompt?: string }) typeof (call.params as { extraSystemPrompt?: string })
?.extraSystemPrompt === "string" && ?.extraSystemPrompt === "string" &&
(call.params as { extraSystemPrompt?: string }) (
?.extraSystemPrompt?.includes("Agent-to-agent reply step"), call.params as { extraSystemPrompt?: string }
)?.extraSystemPrompt?.includes("Agent-to-agent reply step"),
); );
expect(replySteps).toHaveLength(2); expect(replySteps).toHaveLength(2);
expect(sendParams).toMatchObject({ expect(sendParams).toMatchObject({

View File

@@ -2784,7 +2784,9 @@ function buildAgentToAgentReplyContext(params: {
? `Agent 1 (requester) surface: ${params.requesterSurface}.` ? `Agent 1 (requester) surface: ${params.requesterSurface}.`
: undefined, : undefined,
`Agent 2 (target) session: ${params.targetSessionKey}.`, `Agent 2 (target) session: ${params.targetSessionKey}.`,
params.targetChannel ? `Agent 2 (target) surface: ${params.targetChannel}.` : undefined, params.targetChannel
? `Agent 2 (target) surface: ${params.targetChannel}.`
: undefined,
`If you want to stop the ping-pong, reply exactly "${REPLY_SKIP_TOKEN}".`, `If you want to stop the ping-pong, reply exactly "${REPLY_SKIP_TOKEN}".`,
].filter(Boolean); ].filter(Boolean);
return lines.join("\n"); return lines.join("\n");
@@ -2808,7 +2810,9 @@ function buildAgentToAgentAnnounceContext(params: {
? `Agent 1 (requester) surface: ${params.requesterSurface}.` ? `Agent 1 (requester) surface: ${params.requesterSurface}.`
: undefined, : undefined,
`Agent 2 (target) session: ${params.targetSessionKey}.`, `Agent 2 (target) session: ${params.targetSessionKey}.`,
params.targetChannel ? `Agent 2 (target) surface: ${params.targetChannel}.` : undefined, params.targetChannel
? `Agent 2 (target) surface: ${params.targetChannel}.`
: undefined,
`Original request: ${params.originalMessage}`, `Original request: ${params.originalMessage}`,
params.roundOneReply params.roundOneReply
? `Round 1 reply: ${params.roundOneReply}` ? `Round 1 reply: ${params.roundOneReply}`
@@ -2892,34 +2896,35 @@ function createSessionsSendTool(opts?: {
const requesterSurface = opts?.agentSurface; const requesterSurface = opts?.agentSurface;
const maxPingPongTurns = resolvePingPongTurns(cfg); const maxPingPongTurns = resolvePingPongTurns(cfg);
const resolveAnnounceTarget = async (): Promise<AnnounceTarget | null> => { const resolveAnnounceTarget =
const parsed = resolveAnnounceTargetFromKey(resolvedKey); async (): Promise<AnnounceTarget | null> => {
if (parsed) return parsed; const parsed = resolveAnnounceTargetFromKey(resolvedKey);
try { if (parsed) return parsed;
const list = (await callGateway({ try {
method: "sessions.list", const list = (await callGateway({
params: { method: "sessions.list",
includeGlobal: true, params: {
includeUnknown: true, includeGlobal: true,
limit: 200, includeUnknown: true,
}, limit: 200,
})) as { sessions?: Array<Record<string, unknown>> }; },
const sessions = Array.isArray(list?.sessions) ? list.sessions : []; })) as { sessions?: Array<Record<string, unknown>> };
const match = const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
sessions.find((entry) => entry?.key === resolvedKey) ?? const match =
sessions.find((entry) => entry?.key === displayKey); sessions.find((entry) => entry?.key === resolvedKey) ??
const channel = sessions.find((entry) => entry?.key === displayKey);
typeof match?.lastChannel === "string" const channel =
? match.lastChannel typeof match?.lastChannel === "string"
: undefined; ? match.lastChannel
const to = : undefined;
typeof match?.lastTo === "string" ? match.lastTo : undefined; const to =
if (channel && to) return { channel, to }; typeof match?.lastTo === "string" ? match.lastTo : undefined;
} catch { if (channel && to) return { channel, to };
// ignore; fall through to null } catch {
} // ignore; fall through to null
return null; }
}; return null;
};
const readLatestAssistantReply = async ( const readLatestAssistantReply = async (
sessionKeyToRead: string, sessionKeyToRead: string,

View File

@@ -13,13 +13,13 @@ import { sessionsHandlers } from "./server-methods/sessions.js";
import { skillsHandlers } from "./server-methods/skills.js"; import { skillsHandlers } from "./server-methods/skills.js";
import { systemHandlers } from "./server-methods/system.js"; import { systemHandlers } from "./server-methods/system.js";
import { talkHandlers } from "./server-methods/talk.js"; import { talkHandlers } from "./server-methods/talk.js";
import { voicewakeHandlers } from "./server-methods/voicewake.js";
import { webHandlers } from "./server-methods/web.js";
import { wizardHandlers } from "./server-methods/wizard.js";
import type { import type {
GatewayRequestHandlers, GatewayRequestHandlers,
GatewayRequestOptions, GatewayRequestOptions,
} from "./server-methods/types.js"; } from "./server-methods/types.js";
import { voicewakeHandlers } from "./server-methods/voicewake.js";
import { webHandlers } from "./server-methods/web.js";
import { wizardHandlers } from "./server-methods/wizard.js";
const handlers: GatewayRequestHandlers = { const handlers: GatewayRequestHandlers = {
...connectHandlers, ...connectHandlers,

View File

@@ -54,7 +54,9 @@ function ensureAgentJobListener() {
? (evt.data.endedAt as number) ? (evt.data.endedAt as number)
: undefined; : undefined;
const error = const error =
typeof evt.data?.error === "string" ? (evt.data.error as string) : undefined; typeof evt.data?.error === "string"
? (evt.data.error as string)
: undefined;
agentRunStarts.delete(evt.runId); agentRunStarts.delete(evt.runId);
recordAgentJobSnapshot({ recordAgentJobSnapshot({
runId: evt.runId, runId: evt.runId,
@@ -115,7 +117,9 @@ export async function waitForAgentJob(params: {
? (evt.data.endedAt as number) ? (evt.data.endedAt as number)
: undefined; : undefined;
const error = const error =
typeof evt.data?.error === "string" ? (evt.data.error as string) : undefined; typeof evt.data?.error === "string"
? (evt.data.error as string)
: undefined;
const snapshot: AgentJobSnapshot = { const snapshot: AgentJobSnapshot = {
runId: evt.runId, runId: evt.runId,
state: state === "error" ? "error" : "done", state: state === "error" ? "error" : "done",

View File

@@ -1,22 +1,22 @@
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import { agentCommand } from "../../commands/agent.js"; import { agentCommand } from "../../commands/agent.js";
import { loadConfig } from "../../config/config.js";
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
import { registerAgentRunContext } from "../../infra/agent-events.js"; import { registerAgentRunContext } from "../../infra/agent-events.js";
import { defaultRuntime } from "../../runtime.js"; import { defaultRuntime } from "../../runtime.js";
import { normalizeE164 } from "../../utils.js";
import { loadConfig } from "../../config/config.js";
import { saveSessionStore, type SessionEntry } from "../../config/sessions.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js"; import { resolveSendPolicy } from "../../sessions/send-policy.js";
import { normalizeE164 } from "../../utils.js";
import { import {
type AgentWaitParams,
ErrorCodes, ErrorCodes,
errorShape, errorShape,
formatValidationErrors, formatValidationErrors,
validateAgentParams, validateAgentParams,
validateAgentWaitParams, validateAgentWaitParams,
type AgentWaitParams,
} from "../protocol/index.js"; } from "../protocol/index.js";
import { formatForLog } from "../ws-log.js";
import { loadSessionEntry } from "../session-utils.js"; import { loadSessionEntry } from "../session-utils.js";
import { formatForLog } from "../ws-log.js";
import { waitForAgentJob } from "./agent-job.js"; import { waitForAgentJob } from "./agent-job.js";
import type { GatewayRequestHandlers } from "./types.js"; import type { GatewayRequestHandlers } from "./types.js";
@@ -67,7 +67,8 @@ export const agentHandlers: GatewayRequestHandlers = {
let cfgForAgent: ReturnType<typeof loadConfig> | undefined; let cfgForAgent: ReturnType<typeof loadConfig> | undefined;
if (requestedSessionKey) { if (requestedSessionKey) {
const { cfg, storePath, store, entry } = loadSessionEntry(requestedSessionKey); const { cfg, storePath, store, entry } =
loadSessionEntry(requestedSessionKey);
cfgForAgent = cfg; cfgForAgent = cfg;
const now = Date.now(); const now = Date.now();
const sessionId = entry?.sessionId ?? randomUUID(); const sessionId = entry?.sessionId ?? randomUUID();
@@ -132,13 +133,17 @@ export const agentHandlers: GatewayRequestHandlers = {
const lastChannel = sessionEntry?.lastChannel; const lastChannel = sessionEntry?.lastChannel;
const lastTo = const lastTo =
typeof sessionEntry?.lastTo === "string" ? sessionEntry.lastTo.trim() : ""; typeof sessionEntry?.lastTo === "string"
? sessionEntry.lastTo.trim()
: "";
const resolvedChannel = (() => { const resolvedChannel = (() => {
if (requestedChannel === "last") { if (requestedChannel === "last") {
// WebChat is not a deliverable surface. Treat it as "unset" for routing, // WebChat is not a deliverable surface. Treat it as "unset" for routing,
// so VoiceWake and CLI callers don't get stuck with deliver=false. // so VoiceWake and CLI callers don't get stuck with deliver=false.
return lastChannel && lastChannel !== "webchat" ? lastChannel : "whatsapp"; return lastChannel && lastChannel !== "webchat"
? lastChannel
: "whatsapp";
} }
if ( if (
requestedChannel === "whatsapp" || requestedChannel === "whatsapp" ||
@@ -150,7 +155,9 @@ export const agentHandlers: GatewayRequestHandlers = {
) { ) {
return requestedChannel; return requestedChannel;
} }
return lastChannel && lastChannel !== "webchat" ? lastChannel : "whatsapp"; return lastChannel && lastChannel !== "webchat"
? lastChannel
: "whatsapp";
})(); })();
const resolvedTo = (() => { const resolvedTo = (() => {

View File

@@ -2,9 +2,10 @@ import { randomUUID } from "node:crypto";
import { resolveThinkingDefault } from "../../agents/model-selection.js"; import { resolveThinkingDefault } from "../../agents/model-selection.js";
import { agentCommand } from "../../commands/agent.js"; import { agentCommand } from "../../commands/agent.js";
import { saveSessionStore, type SessionEntry } from "../../config/sessions.js"; import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
import { defaultRuntime } from "../../runtime.js"; import { defaultRuntime } from "../../runtime.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js"; import { resolveSendPolicy } from "../../sessions/send-policy.js";
import { buildMessageWithAttachments } from "../chat-attachments.js";
import { import {
ErrorCodes, ErrorCodes,
errorShape, errorShape,
@@ -21,7 +22,6 @@ import {
resolveSessionModelRef, resolveSessionModelRef,
} from "../session-utils.js"; } from "../session-utils.js";
import { formatForLog } from "../ws-log.js"; import { formatForLog } from "../ws-log.js";
import { buildMessageWithAttachments } from "../chat-attachments.js";
import type { GatewayRequestHandlers } from "./types.js"; import type { GatewayRequestHandlers } from "./types.js";
export const chatHandlers: GatewayRequestHandlers = { export const chatHandlers: GatewayRequestHandlers = {
@@ -49,7 +49,8 @@ export const chatHandlers: GatewayRequestHandlers = {
const defaultLimit = 200; const defaultLimit = 200;
const requested = typeof limit === "number" ? limit : defaultLimit; const requested = typeof limit === "number" ? limit : defaultLimit;
const max = Math.min(hardMax, requested); const max = Math.min(hardMax, requested);
const sliced = rawMessages.length > max ? rawMessages.slice(-max) : rawMessages; const sliced =
rawMessages.length > max ? rawMessages.slice(-max) : rawMessages;
const capped = capArrayByJsonBytes( const capped = capArrayByJsonBytes(
sliced, sliced,
MAX_CHAT_HISTORY_MESSAGES_BYTES, MAX_CHAT_HISTORY_MESSAGES_BYTES,
@@ -102,7 +103,10 @@ export const chatHandlers: GatewayRequestHandlers = {
respond( respond(
false, false,
undefined, undefined,
errorShape(ErrorCodes.INVALID_REQUEST, "runId does not match sessionKey"), errorShape(
ErrorCodes.INVALID_REQUEST,
"runId does not match sessionKey",
),
); );
return; return;
} }
@@ -123,8 +127,18 @@ export const chatHandlers: GatewayRequestHandlers = {
context.bridgeSendToSession(sessionKey, "chat", payload); context.bridgeSendToSession(sessionKey, "chat", payload);
respond(true, { ok: true, aborted: true }); respond(true, { ok: true, aborted: true });
}, },
"chat.send": async ({ params, respond, context, client, isWebchatConnect }) => { "chat.send": async ({
if (client && isWebchatConnect(client.connect) && !context.hasConnectedMobileNode()) { params,
respond,
context,
client,
isWebchatConnect,
}) => {
if (
client &&
isWebchatConnect(client.connect) &&
!context.hasConnectedMobileNode()
) {
respond( respond(
false, false,
undefined, undefined,
@@ -220,7 +234,10 @@ export const chatHandlers: GatewayRequestHandlers = {
respond( respond(
false, false,
undefined, undefined,
errorShape(ErrorCodes.INVALID_REQUEST, "send blocked by session policy"), errorShape(
ErrorCodes.INVALID_REQUEST,
"send blocked by session policy",
),
); );
return; return;
} }

View File

@@ -1,8 +1,8 @@
import type { CronJobCreate, CronJobPatch } from "../../cron/types.js";
import { import {
readCronRunLogEntries, readCronRunLogEntries,
resolveCronRunLogPath, resolveCronRunLogPath,
} from "../../cron/run-log.js"; } from "../../cron/run-log.js";
import type { CronJobCreate, CronJobPatch } from "../../cron/types.js";
import { import {
ErrorCodes, ErrorCodes,
errorShape, errorShape,
@@ -102,7 +102,10 @@ export const cronHandlers: GatewayRequestHandlers = {
id: string; id: string;
patch: Record<string, unknown>; patch: Record<string, unknown>;
}; };
const job = await context.cron.update(p.id, p.patch as unknown as CronJobPatch); const job = await context.cron.update(
p.id,
p.patch as unknown as CronJobPatch,
);
respond(true, job, undefined); respond(true, job, undefined);
}, },
"cron.remove": async ({ params, respond, context }) => { "cron.remove": async ({ params, respond, context }) => {

View File

@@ -13,7 +13,9 @@ export const healthHandlers: GatewayRequestHandlers = {
if (cached && now - cached.ts < HEALTH_REFRESH_INTERVAL_MS) { if (cached && now - cached.ts < HEALTH_REFRESH_INTERVAL_MS) {
respond(true, cached, undefined, { cached: true }); respond(true, cached, undefined, { cached: true });
void refreshHealthSnapshot({ probe: false }).catch((err) => void refreshHealthSnapshot({ probe: false }).catch((err) =>
logHealth.error(`background health refresh failed: ${formatError(err)}`), logHealth.error(
`background health refresh failed: ${formatError(err)}`,
),
); );
return; return;
} }

View File

@@ -23,7 +23,11 @@ export const modelsHandlers: GatewayRequestHandlers = {
const models = await context.loadGatewayModelCatalog(); const models = await context.loadGatewayModelCatalog();
respond(true, { models }, undefined); respond(true, { models }, undefined);
} catch (err) { } catch (err) {
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, String(err))); respond(
false,
undefined,
errorShape(ErrorCodes.UNAVAILABLE, String(err)),
);
} }
}, },
}; };

View File

@@ -244,7 +244,11 @@ export const nodeHandlers: GatewayRequestHandlers = {
); );
return; return;
} }
respond(true, { nodeId: updated.nodeId, displayName: updated.displayName }, undefined); respond(
true,
{ nodeId: updated.nodeId, displayName: updated.displayName },
undefined,
);
} catch (err) { } catch (err) {
respond( respond(
false, false,
@@ -449,7 +453,9 @@ export const nodeHandlers: GatewayRequestHandlers = {
try { try {
const paramsJSON = const paramsJSON =
"params" in p && p.params !== undefined ? JSON.stringify(p.params) : null; "params" in p && p.params !== undefined
? JSON.stringify(p.params)
: null;
const res = await context.bridge.invoke({ const res = await context.bridge.invoke({
nodeId, nodeId,
command, command,

View File

@@ -1,12 +1,16 @@
import type { ClawdisConfig } from "../../config/config.js";
import {
loadConfig,
readConfigFileSnapshot,
writeConfigFile,
} from "../../config/config.js";
import { type DiscordProbe, probeDiscord } from "../../discord/probe.js"; import { type DiscordProbe, probeDiscord } from "../../discord/probe.js";
import { type IMessageProbe, probeIMessage } from "../../imessage/probe.js"; import { type IMessageProbe, probeIMessage } from "../../imessage/probe.js";
import type { ClawdisConfig } from "../../config/config.js";
import { loadConfig, readConfigFileSnapshot, writeConfigFile } from "../../config/config.js";
import { webAuthExists } from "../../providers/web/index.js"; import { webAuthExists } from "../../providers/web/index.js";
import { getWebAuthAgeMs, readWebSelfId } from "../../web/session.js";
import { probeSignal, type SignalProbe } from "../../signal/probe.js"; import { probeSignal, type SignalProbe } from "../../signal/probe.js";
import { probeTelegram, type TelegramProbe } from "../../telegram/probe.js"; import { probeTelegram, type TelegramProbe } from "../../telegram/probe.js";
import { resolveTelegramToken } from "../../telegram/token.js"; import { resolveTelegramToken } from "../../telegram/token.js";
import { getWebAuthAgeMs, readWebSelfId } from "../../web/session.js";
import { import {
ErrorCodes, ErrorCodes,
errorShape, errorShape,
@@ -35,7 +39,8 @@ export const providersHandlers: GatewayRequestHandlers = {
typeof timeoutMsRaw === "number" ? Math.max(1000, timeoutMsRaw) : 10_000; typeof timeoutMsRaw === "number" ? Math.max(1000, timeoutMsRaw) : 10_000;
const cfg = loadConfig(); const cfg = loadConfig();
const telegramCfg = cfg.telegram; const telegramCfg = cfg.telegram;
const telegramEnabled = Boolean(telegramCfg) && telegramCfg?.enabled !== false; const telegramEnabled =
Boolean(telegramCfg) && telegramCfg?.enabled !== false;
const { token: telegramToken, source: tokenSource } = telegramEnabled const { token: telegramToken, source: tokenSource } = telegramEnabled
? resolveTelegramToken(cfg) ? resolveTelegramToken(cfg)
: { token: "", source: "none" as const }; : { token: "", source: "none" as const };
@@ -55,9 +60,7 @@ export const providersHandlers: GatewayRequestHandlers = {
const discordEnvToken = discordEnabled const discordEnvToken = discordEnabled
? process.env.DISCORD_BOT_TOKEN?.trim() ? process.env.DISCORD_BOT_TOKEN?.trim()
: ""; : "";
const discordConfigToken = discordEnabled const discordConfigToken = discordEnabled ? discordCfg?.token?.trim() : "";
? discordCfg?.token?.trim()
: "";
const discordToken = discordEnvToken || discordConfigToken || ""; const discordToken = discordEnvToken || discordConfigToken || "";
const discordTokenSource = discordEnvToken const discordTokenSource = discordEnvToken
? "env" ? "env"
@@ -203,7 +206,11 @@ export const providersHandlers: GatewayRequestHandlers = {
delete nextCfg.telegram; delete nextCfg.telegram;
} }
await writeConfigFile(nextCfg); await writeConfigFile(nextCfg);
respond(true, { cleared: hadToken, envToken: Boolean(envToken) }, undefined); respond(
true,
{ cleared: hadToken, envToken: Boolean(envToken) },
undefined,
);
} catch (err) { } catch (err) {
respond( respond(
false, false,

View File

@@ -1,7 +1,7 @@
import { loadConfig } from "../../config/config.js";
import { sendMessageDiscord } from "../../discord/index.js"; import { sendMessageDiscord } from "../../discord/index.js";
import { shouldLogVerbose } from "../../globals.js"; import { shouldLogVerbose } from "../../globals.js";
import { sendMessageIMessage } from "../../imessage/index.js"; import { sendMessageIMessage } from "../../imessage/index.js";
import { loadConfig } from "../../config/config.js";
import { sendMessageSignal } from "../../signal/index.js"; import { sendMessageSignal } from "../../signal/index.js";
import { sendMessageTelegram } from "../../telegram/send.js"; import { sendMessageTelegram } from "../../telegram/send.js";
import { resolveTelegramToken } from "../../telegram/token.js"; import { resolveTelegramToken } from "../../telegram/token.js";

View File

@@ -423,22 +423,35 @@ export const sessionsHandlers: GatewayRequestHandlers = {
const { storePath, store, entry } = loadSessionEntry(key); const { storePath, store, entry } = loadSessionEntry(key);
const sessionId = entry?.sessionId; const sessionId = entry?.sessionId;
if (!sessionId) { if (!sessionId) {
respond(true, { ok: true, key, compacted: false, reason: "no sessionId" }, undefined); respond(
true,
{ ok: true, key, compacted: false, reason: "no sessionId" },
undefined,
);
return; return;
} }
const filePath = resolveSessionTranscriptCandidates(sessionId, storePath).find( const filePath = resolveSessionTranscriptCandidates(
(candidate) => fs.existsSync(candidate), sessionId,
); storePath,
).find((candidate) => fs.existsSync(candidate));
if (!filePath) { if (!filePath) {
respond(true, { ok: true, key, compacted: false, reason: "no transcript" }, undefined); respond(
true,
{ ok: true, key, compacted: false, reason: "no transcript" },
undefined,
);
return; return;
} }
const raw = fs.readFileSync(filePath, "utf-8"); const raw = fs.readFileSync(filePath, "utf-8");
const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0); const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
if (lines.length <= maxLines) { if (lines.length <= maxLines) {
respond(true, { ok: true, key, compacted: false, kept: lines.length }, undefined); respond(
true,
{ ok: true, key, compacted: false, kept: lines.length },
undefined,
);
return; return;
} }

View File

@@ -1,6 +1,6 @@
import { DEFAULT_AGENT_WORKSPACE_DIR } from "../../agents/workspace.js";
import { installSkill } from "../../agents/skills-install.js"; import { installSkill } from "../../agents/skills-install.js";
import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js"; import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js";
import { DEFAULT_AGENT_WORKSPACE_DIR } from "../../agents/workspace.js";
import type { ClawdisConfig } from "../../config/config.js"; import type { ClawdisConfig } from "../../config/config.js";
import { loadConfig, writeConfigFile } from "../../config/config.js"; import { loadConfig, writeConfigFile } from "../../config/config.js";
import { resolveUserPath } from "../../utils.js"; import { resolveUserPath } from "../../utils.js";
@@ -64,7 +64,9 @@ export const skillsHandlers: GatewayRequestHandlers = {
respond( respond(
result.ok, result.ok,
result, result,
result.ok ? undefined : errorShape(ErrorCodes.UNAVAILABLE, result.message), result.ok
? undefined
: errorShape(ErrorCodes.UNAVAILABLE, result.message),
); );
}, },
"skills.update": async ({ params, respond }) => { "skills.update": async ({ params, respond }) => {
@@ -115,6 +117,10 @@ export const skillsHandlers: GatewayRequestHandlers = {
skills, skills,
}; };
await writeConfigFile(nextConfig); await writeConfigFile(nextConfig);
respond(true, { ok: true, skillKey: p.skillKey, config: current }, undefined); respond(
true,
{ ok: true, skillKey: p.skillKey, config: current },
undefined,
);
}, },
}; };

View File

@@ -65,7 +65,8 @@ export const systemHandlers: GatewayRequestHandlers = {
Number.isFinite(params.lastInputSeconds) Number.isFinite(params.lastInputSeconds)
? params.lastInputSeconds ? params.lastInputSeconds
: undefined; : undefined;
const reason = typeof params.reason === "string" ? params.reason : undefined; const reason =
typeof params.reason === "string" ? params.reason : undefined;
const tags = const tags =
Array.isArray(params.tags) && Array.isArray(params.tags) &&
params.tags.every((t) => typeof t === "string") params.tags.every((t) => typeof t === "string")

View File

@@ -8,7 +8,11 @@ import type { GatewayRequestHandlers } from "./types.js";
export const talkHandlers: GatewayRequestHandlers = { export const talkHandlers: GatewayRequestHandlers = {
"talk.mode": ({ params, respond, context, client, isWebchatConnect }) => { "talk.mode": ({ params, respond, context, client, isWebchatConnect }) => {
if (client && isWebchatConnect(client.connect) && !context.hasConnectedMobileNode()) { if (
client &&
isWebchatConnect(client.connect) &&
!context.hasConnectedMobileNode()
) {
respond( respond(
false, false,
undefined, undefined,

View File

@@ -4,9 +4,13 @@ import type { HealthSummary } from "../../commands/health.js";
import type { CronService } from "../../cron/service.js"; import type { CronService } from "../../cron/service.js";
import type { startNodeBridgeServer } from "../../infra/bridge/server.js"; import type { startNodeBridgeServer } from "../../infra/bridge/server.js";
import type { WizardSession } from "../../wizard/session.js"; import type { WizardSession } from "../../wizard/session.js";
import type { ConnectParams, ErrorShape, RequestFrame } from "../protocol/index.js"; import type {
import type { DedupeEntry } from "../server-shared.js"; ConnectParams,
ErrorShape,
RequestFrame,
} from "../protocol/index.js";
import type { ProviderRuntimeSnapshot } from "../server-providers.js"; import type { ProviderRuntimeSnapshot } from "../server-providers.js";
import type { DedupeEntry } from "../server-shared.js";
export type GatewayClient = { export type GatewayClient = {
connect: ConnectParams; connect: ConnectParams;

View File

@@ -1,4 +1,7 @@
import { loadVoiceWakeConfig, setVoiceWakeTriggers } from "../../infra/voicewake.js"; import {
loadVoiceWakeConfig,
setVoiceWakeTriggers,
} from "../../infra/voicewake.js";
import { ErrorCodes, errorShape } from "../protocol/index.js"; import { ErrorCodes, errorShape } from "../protocol/index.js";
import { normalizeVoiceWakeTriggers } from "../server-utils.js"; import { normalizeVoiceWakeTriggers } from "../server-utils.js";
import { formatForLog } from "../ws-log.js"; import { formatForLog } from "../ws-log.js";

View File

@@ -1,7 +1,6 @@
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import { WizardSession } from "../../wizard/session.js";
import { defaultRuntime } from "../../runtime.js"; import { defaultRuntime } from "../../runtime.js";
import { WizardSession } from "../../wizard/session.js";
import { import {
ErrorCodes, ErrorCodes,
errorShape, errorShape,
@@ -39,7 +38,8 @@ export const wizardHandlers: GatewayRequestHandlers = {
const sessionId = randomUUID(); const sessionId = randomUUID();
const opts = { const opts = {
mode: params.mode as "local" | "remote" | undefined, mode: params.mode as "local" | "remote" | undefined,
workspace: typeof params.workspace === "string" ? params.workspace : undefined, workspace:
typeof params.workspace === "string" ? params.workspace : undefined,
}; };
const session = new WizardSession((prompter) => const session = new WizardSession((prompter) =>
context.wizardRunner(opts, defaultRuntime, prompter), context.wizardRunner(opts, defaultRuntime, prompter),

View File

@@ -214,9 +214,7 @@ describe("gateway server cron", () => {
testState.cronStorePath = undefined; testState.cronStorePath = undefined;
}); });
test( test("enables cron scheduler by default and runs due jobs automatically", async () => {
"enables cron scheduler by default and runs due jobs automatically",
async () => {
const dir = await fs.mkdtemp( const dir = await fs.mkdtemp(
path.join(os.tmpdir(), "clawdis-gw-cron-default-on-"), path.join(os.tmpdir(), "clawdis-gw-cron-default-on-"),
); );
@@ -307,7 +305,5 @@ describe("gateway server cron", () => {
testState.cronStorePath = undefined; testState.cronStorePath = undefined;
await fs.rm(dir, { recursive: true, force: true }); await fs.rm(dir, { recursive: true, force: true });
} }
}, }, 15_000);
15_000,
);
}); });

View File

@@ -366,7 +366,7 @@ export function installGatewayTestHooks() {
piSdkMock.enabled = false; piSdkMock.enabled = false;
piSdkMock.discoverCalls = 0; piSdkMock.discoverCalls = 0;
piSdkMock.models = []; piSdkMock.models = [];
}, 20_000); }, 60_000);
afterEach(async () => { afterEach(async () => {
process.env.HOME = previousHome; process.env.HOME = previousHome;