






















@@ -4,6 +4,7 @@ import path from "node:path";
44import { setImmediate as setImmediatePromise } from "node:timers/promises";
55import { afterAll, beforeEach, describe, expect, test, vi } from "vitest";
66import type WebSocket from "ws";
7+import { resetConfigRuntimeState } from "../config/config.js";
78import type { GuardedFetchOptions } from "../infra/net/fetch-guard.js";
89import {
910connectOk,
@@ -185,29 +186,37 @@ async function directCronReq(
185186let result:
186187| { ok: boolean; payload?: unknown; error?: { code?: string; message?: string } }
187188| undefined;
188-await cronHandlers[method]({
189-req: {} as never,
190- params,
191-respond: (ok, payload, error) => {
192-result = {
193- ok,
194- payload,
195- error,
196-};
197-},
198-context: {
199-cron: cronState.cron,
200-cronStorePath: cronState.storePath,
201-logGateway: {
202-info: vi.fn(),
203-warn: vi.fn(),
204-error: vi.fn(),
205-},
206-getRuntimeConfig: cronState.getRuntimeConfig,
207-} as never,
208-client: null,
209-isWebchatConnect: () => false,
210-});
189+const respond = (ok: boolean, payload?: unknown, error?: { code?: string; message?: string }) => {
190+result = {
191+ ok,
192+ payload,
193+ error,
194+};
195+};
196+try {
197+await cronHandlers[method]({
198+req: {} as never,
199+ params,
200+ respond,
201+context: {
202+cron: cronState.cron,
203+cronStorePath: cronState.storePath,
204+logGateway: {
205+info: vi.fn(),
206+warn: vi.fn(),
207+error: vi.fn(),
208+},
209+getRuntimeConfig: cronState.getRuntimeConfig,
210+} as never,
211+client: null,
212+isWebchatConnect: () => false,
213+});
214+} catch (err) {
215+respond(false, undefined, {
216+code: "unavailable",
217+message: err instanceof Error ? err.message : String(err),
218+});
219+}
211220if (!result) {
212221throw new Error(`${method} did not respond`);
213222}
@@ -222,8 +231,12 @@ function expectCronJobIdFromResponse(response: { ok?: unknown; payload?: unknown
222231return id;
223232}
224233225-async function addMainSystemEventCronJob(params: { ws: WebSocket; name: string; text?: string }) {
226-const response = await rpcReq(params.ws, "cron.add", {
234+async function addMainSystemEventCronJobDirect(params: {
235+cronState: DirectCronState;
236+name: string;
237+text?: string;
238+}) {
239+const response = await directCronReq(params.cronState, "cron.add", {
227240name: params.name,
228241enabled: true,
229242schedule: { kind: "every", everyMs: 60_000 },
@@ -263,6 +276,7 @@ async function writeCronConfig(config: unknown) {
263276expect(typeof configPath).toBe("string");
264277await fs.mkdir(path.dirname(configPath as string), { recursive: true });
265278await fs.writeFile(configPath as string, JSON.stringify(config, null, 2), "utf-8");
279+resetConfigRuntimeState();
266280}
267281268282async function runCronJobForce(ws: WebSocket, id: string) {
@@ -322,11 +336,10 @@ describe("gateway server cron", () => {
322336cronEnabled: false,
323337});
324338325-const { server, ws } = await startServerWithClient();
326-await connectOk(ws);
339+const cronState = await createDirectCronState();
327340328341try {
329-const addRes = await rpcReq(ws, "cron.add", {
342+const addRes = await directCronReq(cronState, "cron.add", {
330343name: "daily",
331344enabled: true,
332345schedule: { kind: "every", everyMs: 60_000 },
@@ -339,7 +352,7 @@ describe("gateway server cron", () => {
339352const dailyJobId = (addRes.payload as { id?: unknown } | null)?.id;
340353expect(typeof dailyJobId).toBe("string");
341354342-const listRes = await rpcReq(ws, "cron.list", {
355+const listRes = await directCronReq(cronState, "cron.list", {
343356includeDisabled: true,
344357});
345358expect(listRes.ok).toBe(true);
@@ -362,7 +375,7 @@ describe("gateway server cron", () => {
362375});
363376364377const routeAtMs = Date.now() - 1;
365-const routeRes = await rpcReq(ws, "cron.add", {
378+const routeRes = await directCronReq(cronState, "cron.add", {
366379name: "route test",
367380enabled: true,
368381schedule: { kind: "at", at: new Date(routeAtMs).toISOString() },
@@ -375,14 +388,14 @@ describe("gateway server cron", () => {
375388const routeJobId = typeof routeJobIdValue === "string" ? routeJobIdValue : "";
376389expect(routeJobId.length > 0).toBe(true);
377390378-const runRes = await rpcReq(ws, "cron.run", { id: routeJobId, mode: "force" }, 20_000);
391+const runRes = await directCronReq(cronState, "cron.run", { id: routeJobId, mode: "force" });
379392expect(runRes.ok).toBe(true);
380393expect(runRes.payload).toEqual({ ok: true, enqueued: true, runId: expect.any(String) });
381394const events = await waitForSystemEvent();
382395expect(events.some((event) => event.includes("cron route check"))).toBe(true);
383396384397const wrappedAtMs = Date.now() + 1000;
385-const wrappedRes = await rpcReq(ws, "cron.add", {
398+const wrappedRes = await directCronReq(cronState, "cron.add", {
386399data: {
387400name: "wrapped",
388401schedule: { at: new Date(wrappedAtMs).toISOString() },
@@ -397,10 +410,13 @@ describe("gateway server cron", () => {
397410expect(wrappedPayload?.wakeMode).toBe("now");
398411expect((wrappedPayload?.schedule as { kind?: unknown } | undefined)?.kind).toBe("at");
399412400-const patchJobId = await addMainSystemEventCronJob({ ws, name: "patch test" });
413+const patchJobId = await addMainSystemEventCronJobDirect({
414+ cronState,
415+name: "patch test",
416+});
401417402418const atMs = Date.now() + 1_000;
403-const updateRes = await rpcReq(ws, "cron.update", {
419+const updateRes = await directCronReq(cronState, "cron.update", {
404420id: patchJobId,
405421patch: {
406422schedule: { at: new Date(atMs).toISOString() },
@@ -414,7 +430,7 @@ describe("gateway server cron", () => {
414430expect(updated?.schedule?.kind).toBe("at");
415431expect(updated?.payload?.kind).toBe("systemEvent");
416432417-const mergeRes = await rpcReq(ws, "cron.add", {
433+const mergeRes = await directCronReq(cronState, "cron.add", {
418434name: "patch merge",
419435enabled: true,
420436schedule: { kind: "every", everyMs: 60_000 },
@@ -427,7 +443,7 @@ describe("gateway server cron", () => {
427443const mergeJobId = typeof mergeJobIdValue === "string" ? mergeJobIdValue : "";
428444expect(mergeJobId.length > 0).toBe(true);
429445430-const noTimeoutRes = await rpcReq(ws, "cron.add", {
446+const noTimeoutRes = await directCronReq(cronState, "cron.add", {
431447name: "no-timeout payload",
432448enabled: true,
433449schedule: { kind: "every", everyMs: 60_000 },
@@ -447,7 +463,7 @@ describe("gateway server cron", () => {
447463expect(noTimeoutPayload?.payload?.kind).toBe("agentTurn");
448464expect(noTimeoutPayload?.payload?.timeoutSeconds).toBe(0);
449465450-const mergeUpdateRes = await rpcReq(ws, "cron.update", {
466+const mergeUpdateRes = await directCronReq(cronState, "cron.update", {
451467id: mergeJobId,
452468patch: {
453469delivery: { mode: "announce", channel: "telegram", to: "19098680" },
@@ -467,7 +483,7 @@ describe("gateway server cron", () => {
467483expect(merged?.delivery?.channel).toBe("telegram");
468484expect(merged?.delivery?.to).toBe("19098680");
469485470-const modelOnlyPatchRes = await rpcReq(ws, "cron.update", {
486+const modelOnlyPatchRes = await directCronReq(cronState, "cron.update", {
471487id: mergeJobId,
472488patch: {
473489payload: {
@@ -489,7 +505,7 @@ describe("gateway server cron", () => {
489505expect(modelOnlyPatched?.payload?.message).toBe("hello");
490506expect(modelOnlyPatched?.payload?.model).toBe("anthropic/claude-sonnet-4-6");
491507492-const deliveryPatchRes = await rpcReq(ws, "cron.update", {
508+const deliveryPatchRes = await directCronReq(cronState, "cron.update", {
493509id: mergeJobId,
494510patch: {
495511delivery: {
@@ -514,19 +530,25 @@ describe("gateway server cron", () => {
514530expect(deliveryPatched?.delivery?.to).toBe("+15550001111");
515531expect(deliveryPatched?.delivery?.bestEffort).toBe(true);
516532517-const rejectJobId = await addMainSystemEventCronJob({ ws, name: "patch reject" });
533+const rejectJobId = await addMainSystemEventCronJobDirect({
534+ cronState,
535+name: "patch reject",
536+});
518537519-const rejectUpdateRes = await rpcReq(ws, "cron.update", {
538+const rejectUpdateRes = await directCronReq(cronState, "cron.update", {
520539id: rejectJobId,
521540patch: {
522541payload: { kind: "agentTurn", message: "nope" },
523542},
524543});
525544expect(rejectUpdateRes.ok).toBe(false);
526545527-const jobId = await addMainSystemEventCronJob({ ws, name: "jobId test" });
546+const jobId = await addMainSystemEventCronJobDirect({
547+ cronState,
548+name: "jobId test",
549+});
528550529-const jobIdUpdateRes = await rpcReq(ws, "cron.update", {
551+const jobIdUpdateRes = await directCronReq(cronState, "cron.update", {
530552 jobId,
531553patch: {
532554schedule: { at: new Date(Date.now() + 2_000).toISOString() },
@@ -535,9 +557,12 @@ describe("gateway server cron", () => {
535557});
536558expect(jobIdUpdateRes.ok).toBe(true);
537559538-const disableJobId = await addMainSystemEventCronJob({ ws, name: "disable test" });
560+const disableJobId = await addMainSystemEventCronJobDirect({
561+ cronState,
562+name: "disable test",
563+});
539564540-const disableUpdateRes = await rpcReq(ws, "cron.update", {
565+const disableUpdateRes = await directCronReq(cronState, "cron.update", {
541566id: disableJobId,
542567patch: { enabled: false },
543568});
@@ -546,8 +571,7 @@ describe("gateway server cron", () => {
546571expect(disabled?.enabled).toBe(false);
547572} finally {
548573await cleanupCronTestRun({
549- ws,
550- server,
574+ cronState,
551575 prevSkipCron,
552576clearSessionConfig: true,
553577});
@@ -619,11 +643,10 @@ describe("gateway server cron", () => {
619643},
620644});
621645622-const { server, ws } = await startServerWithClient();
623-await connectOk(ws);
646+const cronState = await createDirectCronState();
624647625648try {
626-const addRes = await rpcReq(ws, "cron.add", {
649+const addRes = await directCronReq(cronState, "cron.add", {
627650name: "main default agent",
628651enabled: true,
629652schedule: { kind: "every", everyMs: 60_000 },
@@ -637,7 +660,7 @@ describe("gateway server cron", () => {
637660const jobId = typeof jobIdValue === "string" ? jobIdValue : "";
638661expect(jobId.length > 0).toBe(true);
639662640-const updateRes = await rpcReq(ws, "cron.update", {
663+const updateRes = await directCronReq(cronState, "cron.update", {
641664id: jobId,
642665patch: {
643666delivery: { mode: "announce", channel: "telegram", to: "19098680" },
@@ -648,7 +671,7 @@ describe("gateway server cron", () => {
648671const updated = updateRes.payload as { delivery?: unknown } | undefined;
649672expect(updated?.delivery).toBeUndefined();
650673} finally {
651-await cleanupCronTestRun({ ws, server, prevSkipCron });
674+await cleanupCronTestRun({ cronState, prevSkipCron });
652675}
653676});
654677@@ -716,11 +739,10 @@ describe("gateway server cron", () => {
716739},
717740});
718741719-const { server, ws } = await startServerWithClient();
720-await connectOk(ws);
742+const cronState = await createDirectCronState();
721743722744try {
723-const addRes = await rpcReq(ws, "cron.add", {
745+const addRes = await directCronReq(cronState, "cron.add", {
724746name: "main default agent drift",
725747enabled: true,
726748schedule: { kind: "every", everyMs: 60_000 },
@@ -734,8 +756,7 @@ describe("gateway server cron", () => {
734756const jobId = typeof jobIdValue === "string" ? jobIdValue : "";
735757expect(jobId.length > 0).toBe(true);
736758737-const { writeConfigFile } = await import("../config/config.js");
738-await writeConfigFile({
759+await writeCronConfig({
739760session: {
740761mainKey: "main",
741762},
@@ -749,22 +770,15 @@ describe("gateway server cron", () => {
749770},
750771});
751772752-let agentIds: string[] = [];
753-for (let i = 0; i < 20; i += 1) {
754-const agentsRes = await rpcReq<{ agents?: Array<{ id?: string }> }>(ws, "agents.list", {});
755-expect(agentsRes.ok).toBe(true);
756-agentIds = (agentsRes.payload?.agents ?? [])
757-.map((agent) => agent.id)
758-.filter((id): id is string => typeof id === "string");
759-if (agentIds.includes("main") && agentIds.includes("ops")) {
760-break;
761-}
762-await yieldToEventLoop();
763-}
773+const agentIds =
774+cronState
775+.getRuntimeConfig()
776+.agents?.list?.map((agent) => agent.id)
777+.filter((id): id is string => typeof id === "string") ?? [];
764778expect(agentIds).toContain("main");
765779expect(agentIds).toContain("ops");
766780767-const updateRes = await rpcReq(ws, "cron.update", {
781+const updateRes = await directCronReq(cronState, "cron.update", {
768782id: jobId,
769783patch: {
770784delivery: { mode: "announce", channel: "telegram", to: "19098680" },
@@ -776,7 +790,7 @@ describe("gateway server cron", () => {
776790}
777791expect(updateRes.ok).toBe(true);
778792} finally {
779-await cleanupCronTestRun({ ws, server, prevSkipCron });
793+await cleanupCronTestRun({ cronState, prevSkipCron });
780794}
781795});
782796此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。