惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
U
Unit 42
Blog — PlanetScale
Blog — PlanetScale
L
LangChain Blog
C
Check Point Blog
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
Vercel News
Vercel News
腾讯CDC
GbyAI
GbyAI
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
H
Help Net Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Microsoft Security Blog
Microsoft Security Blog
小众软件
小众软件

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
test: speed up gateway cron history case · openclaw/openc...
steipete · 2026-05-06 · via Recent Commits to openclaw:main

@@ -157,12 +157,17 @@ async function setupCronTestRun(params: {

157157

}

158158159159

type DirectCronState = {

160-

cron: { stop: () => void };

160+

cron: { start: () => Promise<void>; stop: () => void };

161161

storePath: string;

162162

getRuntimeConfig: () => import("../config/types.openclaw.js").OpenClawConfig;

163163

};

164164165-

async function createDirectCronState(): Promise<DirectCronState> {

165+

type CronBroadcast = (event: string, payload: unknown) => void;

166+167+

async function createDirectCronState(params?: {

168+

broadcast?: CronBroadcast;

169+

}): Promise<DirectCronState> {

170+

resetConfigRuntimeState();

166171

const [{ getRuntimeConfig }, { buildGatewayCronService }] = await Promise.all([

167172

import("../config/config.js"),

168173

import("./server-cron.js"),

@@ -171,12 +176,60 @@ async function createDirectCronState(): Promise<DirectCronState> {

171176

...buildGatewayCronService({

172177

cfg: getRuntimeConfig(),

173178

deps: {} as never,

174-

broadcast: vi.fn(),

179+

broadcast: params?.broadcast ?? vi.fn(),

175180

}),

176181

getRuntimeConfig: getRuntimeConfig,

177182

};

178183

}

179184185+

function createCronEventCollector() {

186+

const events: Record<string, unknown>[] = [];

187+

const waiters: Array<{

188+

check: (payload: Record<string, unknown>) => boolean;

189+

resolve: (payload: Record<string, unknown>) => void;

190+

reject: (error: Error) => void;

191+

timer: ReturnType<typeof setTimeout>;

192+

}> = [];

193+

const flush = (payload: Record<string, unknown>) => {

194+

for (const waiter of [...waiters]) {

195+

if (!waiter.check(payload)) {

196+

continue;

197+

}

198+

clearTimeout(waiter.timer);

199+

waiters.splice(waiters.indexOf(waiter), 1);

200+

waiter.resolve(payload);

201+

}

202+

};

203+

return {

204+

broadcast(event: string, payload: unknown) {

205+

if (event !== "cron" || !payload || typeof payload !== "object" || Array.isArray(payload)) {

206+

return;

207+

}

208+

const record = payload as Record<string, unknown>;

209+

events.push(record);

210+

flush(record);

211+

},

212+

wait(check: (payload: Record<string, unknown>) => boolean, timeoutMs = CRON_WAIT_TIMEOUT_MS) {

213+

const existing = events.find(check);

214+

if (existing) {

215+

return Promise.resolve(existing);

216+

}

217+

return new Promise<Record<string, unknown>>((resolve, reject) => {

218+

const waiter = {

219+

check,

220+

resolve,

221+

reject,

222+

timer: setTimeout(() => {

223+

waiters.splice(waiters.indexOf(waiter), 1);

224+

reject(new Error("timeout waiting for cron event"));

225+

}, timeoutMs),

226+

};

227+

waiters.push(waiter);

228+

});

229+

},

230+

};

231+

}

232+180233

async function directCronReq(

181234

cronState: DirectCronState,

182235

method: string,

@@ -388,9 +441,8 @@ describe("gateway server cron", () => {

388441

const routeJobId = typeof routeJobIdValue === "string" ? routeJobIdValue : "";

389442

expect(routeJobId.length > 0).toBe(true);

390443391-

const runRes = await directCronReq(cronState, "cron.run", { id: routeJobId, mode: "force" });

392-

expect(runRes.ok).toBe(true);

393-

expect(runRes.payload).toEqual({ ok: true, enqueued: true, runId: expect.any(String) });

444+

const runRes = await cronState.cron.run(routeJobId, "force");

445+

expect(runRes).toEqual({ ok: true, ran: true });

394446

const events = await waitForSystemEvent();

395447

expect(events.some((event) => event.includes("cron route check"))).toBe(true);

396448

@@ -834,17 +886,16 @@ describe("gateway server cron", () => {

834886

test("writes cron run history and auto-runs due jobs", async () => {

835887

const { prevSkipCron } = await setupCronTestRun({

836888

tempPrefix: "openclaw-gw-cron-log-",

889+

cronEnabled: true,

837890

});

838-839-

const { server, ws } = await startServerWithClient();

840-

await connectOk(ws);

891+

const events = createCronEventCollector();

892+

const cronState = await createDirectCronState({ broadcast: events.broadcast });

841893842894

try {

843-

const atMs = Date.now() - 1;

844-

const addRes = await rpcReq(ws, "cron.add", {

895+

const addRes = await directCronReq(cronState, "cron.add", {

845896

name: "log test",

846897

enabled: true,

847-

schedule: { kind: "at", at: new Date(atMs).toISOString() },

898+

schedule: { kind: "every", everyMs: 60_000 },

848899

sessionTarget: "main",

849900

wakeMode: "next-heartbeat",

850901

payload: { kind: "systemEvent", text: "hello" },

@@ -854,11 +905,10 @@ describe("gateway server cron", () => {

854905

const jobId = typeof jobIdValue === "string" ? jobIdValue : "";

855906

expect(jobId.length > 0).toBe(true);

856907857-

const finishedRun = waitForCronEvent(

858-

ws,

908+

const finishedRun = events.wait(

859909

(payload) => payload?.jobId === jobId && payload?.action === "finished",

860910

);

861-

const runRes = await rpcReq(ws, "cron.run", { id: jobId, mode: "force" }, 20_000);

911+

const runRes = await directCronReq(cronState, "cron.run", { id: jobId, mode: "force" });

862912

expect(runRes.ok).toBe(true);

863913

expect(runRes.payload).toEqual({ ok: true, enqueued: true, runId: expect.any(String) });

864914

const manualRunId = (runRes.payload as { runId?: unknown } | null)?.runId;

@@ -872,7 +922,7 @@ describe("gateway server cron", () => {

872922

deliveryStatus: "not-requested",

873923

});

874924875-

const runsRes = await rpcReq(ws, "cron.runs", { id: jobId, limit: 50 });

925+

const runsRes = await directCronReq(cronState, "cron.runs", { id: jobId, limit: 50 });

876926

expect(runsRes.ok).toBe(true);

877927

const entries = (runsRes.payload as { entries?: unknown } | null)?.entries;

878928

expect(Array.isArray(entries)).toBe(true);

@@ -882,7 +932,7 @@ describe("gateway server cron", () => {

882932

"not-requested",

883933

);

884934

expect((entries as Array<{ runId?: unknown }>).at(-1)?.runId).toBe(manualRunId);

885-

const allRunsRes = await rpcReq(ws, "cron.runs", {

935+

const allRunsRes = await directCronReq(cronState, "cron.runs", {

886936

scope: "all",

887937

limit: 50,

888938

statuses: ["ok"],

@@ -894,7 +944,7 @@ describe("gateway server cron", () => {

894944

(allEntries as Array<{ jobId?: unknown }>).some((entry) => entry.jobId === jobId),

895945

).toBe(true);

896946897-

const statusRes = await rpcReq(ws, "cron.status", {});

947+

const statusRes = await directCronReq(cronState, "cron.status", {});

898948

expect(statusRes.ok).toBe(true);

899949

const statusPayload = statusRes.payload as

900950

| { enabled?: unknown; storePath?: unknown }

@@ -903,7 +953,7 @@ describe("gateway server cron", () => {

903953

const storePath = typeof statusPayload?.storePath === "string" ? statusPayload.storePath : "";

904954

expect(storePath).toContain("jobs.json");

905955906-

const autoRes = await rpcReq(ws, "cron.add", {

956+

const autoRes = await directCronReq(cronState, "cron.add", {

907957

name: "auto run test",

908958

enabled: true,

909959

schedule: { kind: "at", at: new Date(Date.now() - 1).toISOString() },

@@ -916,18 +966,19 @@ describe("gateway server cron", () => {

916966

const autoJobId = typeof autoJobIdValue === "string" ? autoJobIdValue : "";

917967

expect(autoJobId.length > 0).toBe(true);

918968919-

await waitForCronEvent(

920-

ws,

969+

const autoFinished = events.wait(

921970

(payload) => payload?.jobId === autoJobId && payload?.action === "finished",

922971

);

923-

const autoEntries = (await rpcReq(ws, "cron.runs", { id: autoJobId, limit: 10 })).payload as

924-

| { entries?: Array<{ jobId?: unknown }> }

925-

| undefined;

972+

await cronState.cron.start();

973+

await autoFinished;

974+

const autoEntries = (

975+

await directCronReq(cronState, "cron.runs", { id: autoJobId, limit: 10 })

976+

).payload as { entries?: Array<{ jobId?: unknown }> } | undefined;

926977

expect(Array.isArray(autoEntries?.entries)).toBe(true);

927978

const runs = autoEntries?.entries ?? [];

928979

expect(runs.at(-1)?.jobId).toBe(autoJobId);

929980

} finally {

930-

await cleanupCronTestRun({ ws, server, prevSkipCron });

981+

await cleanupCronTestRun({ cronState, prevSkipCron });

931982

}

932983

}, 45_000);

933984