test: stabilize gateway tests
This commit is contained in:
@@ -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({
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 = (() => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user