


























@@ -0,0 +1,148 @@
1+import { execFile } from "node:child_process";
2+import fs from "node:fs/promises";
3+import os from "node:os";
4+import path from "node:path";
5+import { setTimeout as delay } from "node:timers/promises";
6+import { promisify } from "node:util";
7+import { assert, connectGateway, waitFor } from "./mcp-channels-harness.ts";
8+9+const execFileAsync = promisify(execFile);
10+11+type CronJob = { id?: string };
12+type CronRunResult = { ok?: boolean; enqueued?: boolean; runId?: string };
13+14+async function readProbePid(pidPath: string): Promise<number | undefined> {
15+try {
16+const raw = (await fs.readFile(pidPath, "utf-8")).trim();
17+const pid = Number.parseInt(raw, 10);
18+return Number.isInteger(pid) && pid > 0 ? pid : undefined;
19+} catch {
20+return undefined;
21+}
22+}
23+24+async function describeProbePid(pid: number): Promise<string | undefined> {
25+try {
26+const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "args="]);
27+const args = stdout.trim();
28+return args.length > 0 ? args : undefined;
29+} catch {
30+return undefined;
31+}
32+}
33+34+async function waitForProbePid(pidPath: string): Promise<number | undefined> {
35+const startedAt = Date.now();
36+while (Date.now() - startedAt < 60_000) {
37+const pid = await readProbePid(pidPath);
38+if (pid) {
39+return pid;
40+}
41+await delay(100);
42+}
43+return undefined;
44+}
45+46+async function waitForProbeExit(pid: number): Promise<void> {
47+const startedAt = Date.now();
48+while (Date.now() - startedAt < 30_000) {
49+const args = await describeProbePid(pid);
50+if (!args || !args.includes("openclaw-cron-mcp-cleanup-probe")) {
51+return;
52+}
53+await delay(100);
54+}
55+const args = await describeProbePid(pid);
56+throw new Error(`cron MCP probe process still alive after run: pid=${pid} args=${args}`);
57+}
58+59+async function main() {
60+const gatewayUrl = process.env.GW_URL?.trim();
61+const gatewayToken = process.env.GW_TOKEN?.trim();
62+const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || path.join(os.homedir(), ".openclaw");
63+const pidPath = path.join(stateDir, "cron-mcp-cleanup", "probe.pid");
64+assert(gatewayUrl, "missing GW_URL");
65+assert(gatewayToken, "missing GW_TOKEN");
66+67+const gateway = await connectGateway({ url: gatewayUrl, token: gatewayToken });
68+try {
69+const job = await gateway.request<CronJob>("cron.add", {
70+name: "cron mcp cleanup docker e2e",
71+enabled: true,
72+schedule: { kind: "every", everyMs: 60_000 },
73+sessionTarget: "isolated",
74+wakeMode: "next-heartbeat",
75+payload: {
76+kind: "agentTurn",
77+message: "Use available context and then stop.",
78+timeoutSeconds: 12,
79+lightContext: true,
80+},
81+delivery: { mode: "none" },
82+});
83+assert(job.id, `cron.add did not return an id: ${JSON.stringify(job)}`);
84+85+const run = await gateway.request<CronRunResult>("cron.run", {
86+id: job.id,
87+mode: "force",
88+});
89+assert(
90+run.ok === true && run.enqueued === true,
91+`cron.run was not enqueued: ${JSON.stringify(run)}`,
92+);
93+94+const started = await waitFor(
95+"cron started event",
96+() =>
97+gateway.events.find(
98+(entry) =>
99+entry.event === "cron" &&
100+entry.payload.jobId === job.id &&
101+entry.payload.action === "started",
102+)?.payload,
103+60_000,
104+);
105+assert(started, "missing cron started event");
106+107+const pid = await waitForProbePid(pidPath);
108+assert(
109+pid,
110+`cron MCP probe did not start; missing pid file at ${pidPath}; events=${JSON.stringify(
111+ gateway.events.slice(-10),
112+ )}`,
113+);
114+const initialArgs = await describeProbePid(pid);
115+assert(
116+initialArgs?.includes("openclaw-cron-mcp-cleanup-probe"),
117+`cron MCP probe pid did not look like the test server: pid=${pid} args=${initialArgs}`,
118+);
119+120+const finished = await waitFor(
121+"cron finished event",
122+() =>
123+gateway.events.find(
124+(entry) =>
125+entry.event === "cron" &&
126+entry.payload.jobId === job.id &&
127+entry.payload.action === "finished",
128+)?.payload,
129+90_000,
130+);
131+assert(finished, "missing cron finished event");
132+133+await waitForProbeExit(pid);
134+process.stdout.write(
135+JSON.stringify({
136+ok: true,
137+jobId: job.id,
138+runId: run.runId,
139+ pid,
140+status: finished.status,
141+}) + "\n",
142+);
143+} finally {
144+await gateway.close();
145+}
146+}
147+148+await main();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。