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

推荐订阅源

月光博客
月光博客
Martin Fowler
Martin Fowler
Last Week in AI
Last Week in AI
罗磊的独立博客
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
V
Visual Studio Blog
Hugging Face - Blog
Hugging Face - Blog
雷峰网
雷峰网
博客园_首页
人人都是产品经理
人人都是产品经理
量子位
美团技术团队
The Cloudflare Blog
小众软件
小众软件
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
博客园 - Franky

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
fix: silence cron exec completion noise · openclaw/opencl...
goldmar · 2026-04-25 · via Recent Commits to openclaw:main

@@ -2,6 +2,9 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

2233

const requestHeartbeatNowMock = vi.hoisted(() => vi.fn());

44

const enqueueSystemEventMock = vi.hoisted(() => vi.fn());

5+

const supervisorMock = vi.hoisted(() => ({

6+

spawn: vi.fn(),

7+

}));

5869

vi.mock("../infra/heartbeat-wake.js", () => ({

710

requestHeartbeatNow: requestHeartbeatNowMock,

@@ -11,22 +14,38 @@ vi.mock("../infra/system-events.js", () => ({

1114

enqueueSystemEvent: enqueueSystemEventMock,

1215

}));

131617+

vi.mock("../process/supervisor/index.js", () => ({

18+

getProcessSupervisor: () => ({

19+

spawn: supervisorMock.spawn,

20+

}),

21+

}));

22+23+

let markBackgrounded: typeof import("./bash-process-registry.js").markBackgrounded;

1424

let buildExecExitOutcome: typeof import("./bash-tools.exec-runtime.js").buildExecExitOutcome;

1525

let detectCursorKeyMode: typeof import("./bash-tools.exec-runtime.js").detectCursorKeyMode;

1626

let emitExecSystemEvent: typeof import("./bash-tools.exec-runtime.js").emitExecSystemEvent;

1727

let formatExecFailureReason: typeof import("./bash-tools.exec-runtime.js").formatExecFailureReason;

1828

let resolveExecTarget: typeof import("./bash-tools.exec-runtime.js").resolveExecTarget;

29+

let runExecProcess: typeof import("./bash-tools.exec-runtime.js").runExecProcess;

19302031

beforeAll(async () => {

32+

({ markBackgrounded } = await import("./bash-process-registry.js"));

2133

({

2234

buildExecExitOutcome,

2335

detectCursorKeyMode,

2436

emitExecSystemEvent,

2537

formatExecFailureReason,

2638

resolveExecTarget,

39+

runExecProcess,

2740

} = await import("./bash-tools.exec-runtime.js"));

2841

});

294243+

beforeEach(() => {

44+

requestHeartbeatNowMock.mockClear();

45+

enqueueSystemEventMock.mockClear();

46+

supervisorMock.spawn.mockReset();

47+

});

48+3049

describe("detectCursorKeyMode", () => {

3150

it("returns null when no toggle found", () => {

3251

expect(detectCursorKeyMode("hello world")).toBe(null);

@@ -295,6 +314,84 @@ describe("resolveExecTarget", () => {

295314

});

296315

});

297316317+

describe("exec notifyOnExit suppression", () => {

318+

async function runBackgroundedExit(params: {

319+

reason: "manual-cancel" | "overall-timeout";

320+

stdout?: string;

321+

}) {

322+

supervisorMock.spawn.mockImplementationOnce(

323+

async (input: { onStdout?: (chunk: string) => void }) => {

324+

if (params.stdout) {

325+

input.onStdout?.(params.stdout);

326+

}

327+

return {

328+

runId: "run-1",

329+

startedAtMs: Date.now(),

330+

pid: 123,

331+

wait: async () => {

332+

await new Promise((resolve) => setImmediate(resolve));

333+

return {

334+

reason: params.reason,

335+

exitCode: null,

336+

exitSignal: "SIGKILL",

337+

durationMs: 10,

338+

stdout: "",

339+

stderr: "",

340+

timedOut: params.reason === "overall-timeout",

341+

noOutputTimedOut: false,

342+

};

343+

},

344+

cancel: vi.fn(),

345+

};

346+

},

347+

);

348+349+

const run = await runExecProcess({

350+

command: "sleep 999",

351+

workdir: "/tmp",

352+

env: {},

353+

usePty: false,

354+

warnings: [],

355+

maxOutput: 1000,

356+

pendingMaxOutput: 1000,

357+

notifyOnExit: true,

358+

notifyOnExitEmptySuccess: false,

359+

sessionKey: "agent:main:main",

360+

timeoutSec: null,

361+

});

362+

markBackgrounded(run.session);

363+

return await run.promise;

364+

}

365+366+

it("keeps manual-cancelled no-output background execs silent", async () => {

367+

const outcome = await runBackgroundedExit({ reason: "manual-cancel" });

368+369+

expect(outcome.status).toBe("failed");

370+

expect(enqueueSystemEventMock).not.toHaveBeenCalled();

371+

expect(requestHeartbeatNowMock).not.toHaveBeenCalled();

372+

});

373+374+

it("notifies for manual-cancelled background execs with output", async () => {

375+

await runBackgroundedExit({ reason: "manual-cancel", stdout: "partial output\n" });

376+377+

expect(enqueueSystemEventMock).toHaveBeenCalledWith(

378+

expect.stringContaining("partial output"),

379+

expect.objectContaining({ sessionKey: "agent:main:main" }),

380+

);

381+

expect(requestHeartbeatNowMock).toHaveBeenCalled();

382+

});

383+384+

it("still notifies for no-output background exec timeouts", async () => {

385+

await runBackgroundedExit({ reason: "overall-timeout" });

386+387+

expect(enqueueSystemEventMock).toHaveBeenCalledWith(

388+

expect.stringContaining("Exec failed"),

389+

expect.objectContaining({ sessionKey: "agent:main:main" }),

390+

);

391+

expect(requestHeartbeatNowMock).toHaveBeenCalled();

392+

});

393+

});

394+298395

describe("emitExecSystemEvent", () => {

299396

beforeEach(() => {

300397

requestHeartbeatNowMock.mockClear();