























11import fs from "node:fs";
22import os from "node:os";
33import path from "node:path";
4-import { beforeAll, describe, expect, it, vi } from "vitest";
4+import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
55import type { OpenClawConfig } from "../config/config.js";
66import type { ExecApprovalsResolved } from "../infra/exec-approvals.js";
77import type { SafeBinProfileFixture } from "../infra/exec-safe-bin-policy.js";
88import { withEnvAsync } from "../test-utils/env.js";
9+import { resetProcessRegistryForTests } from "./bash-process-registry.js";
9101011let createOpenClawCodingTools: typeof import("./pi-tools.js").createOpenClawCodingTools;
111212-beforeAll(async () => {
13-await withEnvAsync(
14-{
15-OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(os.tmpdir(), "openclaw-test-no-bundled-extensions"),
16-},
17-async () => {
18-({ createOpenClawCodingTools } = await import("./pi-tools.js"));
19-},
20-);
21-});
22-23-vi.mock("../infra/shell-env.js", async () => {
24-const mod =
25-await vi.importActual<typeof import("../infra/shell-env.js")>("../infra/shell-env.js");
26-return {
27- ...mod,
28-getShellPathFromLoginShell: vi.fn(() => null),
29-resolveShellEnvFallbackTimeoutMs: vi.fn(() => 50),
30-};
31-});
32-33-vi.mock("../plugins/tools.js", () => ({
34-copyPluginToolMeta: vi.fn((_from, to) => to),
35-resolvePluginTools: () => [],
36-getPluginToolMeta: () => undefined,
37-}));
38-39-vi.mock("../infra/exec-approvals.js", async () => {
40-const mod = await vi.importActual<typeof import("../infra/exec-approvals.js")>(
41-"../infra/exec-approvals.js",
42-);
43-const approvals: ExecApprovalsResolved = {
13+const { mockExecApprovals, supervisorSpawnMock } = vi.hoisted(() => {
14+const execApprovals = {
4415path: "/tmp/exec-approvals.json",
4516socketPath: "/tmp/exec-approvals.sock",
4617token: "token",
@@ -74,7 +45,123 @@ vi.mock("../infra/exec-approvals.js", async () => {
7445agents: {},
7546},
7647};
77-return { ...mod, resolveExecApprovals: () => approvals };
48+return {
49+mockExecApprovals: execApprovals,
50+supervisorSpawnMock: vi.fn(
51+async (input: { argv?: string[]; onStdout?: (chunk: string) => void }) => {
52+input.onStdout?.(`${input.argv?.join(" ") ?? ""}\n`);
53+return {
54+runId: "safe-bins-test-run",
55+pid: 1234,
56+startedAtMs: Date.now(),
57+stdin: undefined,
58+wait: async () => ({
59+reason: "exit" as const,
60+exitCode: 0,
61+exitSignal: null,
62+durationMs: 1,
63+stdout: "",
64+stderr: "",
65+timedOut: false,
66+noOutputTimedOut: false,
67+}),
68+cancel: vi.fn(),
69+};
70+},
71+),
72+};
73+});
74+75+beforeAll(async () => {
76+await withEnvAsync(
77+{
78+OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(os.tmpdir(), "openclaw-test-no-bundled-extensions"),
79+},
80+async () => {
81+({ createOpenClawCodingTools } = await import("./pi-tools.js"));
82+},
83+);
84+});
85+86+beforeEach(() => {
87+supervisorSpawnMock.mockClear();
88+});
89+90+vi.mock("../infra/shell-env.js", async () => {
91+const mod =
92+await vi.importActual<typeof import("../infra/shell-env.js")>("../infra/shell-env.js");
93+return {
94+ ...mod,
95+getShellPathFromLoginShell: vi.fn(() => null),
96+resolveShellEnvFallbackTimeoutMs: vi.fn(() => 50),
97+};
98+});
99+100+vi.mock("../process/supervisor/index.js", () => ({
101+getProcessSupervisor: () => ({
102+spawn: supervisorSpawnMock,
103+cancel: vi.fn(),
104+cancelScope: vi.fn(),
105+reconcileOrphans: vi.fn(),
106+getRecord: vi.fn(),
107+}),
108+}));
109+110+vi.mock("./channel-tools.js", () => ({
111+copyChannelAgentToolMeta: vi.fn((_from, to) => to),
112+listChannelAgentTools: () => [],
113+}));
114+115+vi.mock("./openclaw-tools.js", () => ({
116+createOpenClawTools: () => [],
117+}));
118+119+vi.mock("./bash-tools.exec-host-shared.js", async () => {
120+const mod = await vi.importActual<typeof import("./bash-tools.exec-host-shared.js")>(
121+"./bash-tools.exec-host-shared.js",
122+);
123+return {
124+ ...mod,
125+resolveExecHostApprovalContext: () => ({
126+approvals: mockExecApprovals,
127+hostSecurity: "allowlist",
128+hostAsk: "off",
129+askFallback: "deny",
130+}),
131+};
132+});
133+134+vi.mock("../plugins/tools.js", () => ({
135+copyPluginToolMeta: vi.fn((_from, to) => to),
136+resolvePluginTools: () => [],
137+getPluginToolMeta: () => undefined,
138+}));
139+140+vi.mock("@mariozechner/pi-coding-agent", () => ({
141+AuthStorage: vi.fn(),
142+CURRENT_SESSION_VERSION: 1,
143+ModelRegistry: vi.fn(),
144+SessionManager: vi.fn(),
145+SettingsManager: vi.fn(),
146+codingTools: [],
147+createEditTool: vi.fn(),
148+createReadTool: vi.fn(),
149+createWriteTool: vi.fn(),
150+estimateTokens: vi.fn(() => 0),
151+formatSkillsForPrompt: vi.fn(() => ""),
152+readTool: undefined,
153+}));
154+155+vi.mock("../infra/exec-approvals.js", async () => {
156+const mod = await vi.importActual<typeof import("../infra/exec-approvals.js")>(
157+"../infra/exec-approvals.js",
158+);
159+const approvals = mockExecApprovals as ExecApprovalsResolved;
160+return {
161+ ...mod,
162+loadExecApprovals: () => approvals.file,
163+resolveExecApprovals: () => approvals,
164+};
78165});
7916680167type ExecToolResult = {
@@ -118,6 +205,9 @@ async function createSafeBinsExecTool(params: {
118205119206const tools = createOpenClawCodingTools({
120207config: cfg,
208+exec: {
209+notifyOnExit: false,
210+},
121211sessionKey: "agent:main:main",
122212workspaceDir: tmpDir,
123213agentDir: path.join(tmpDir, "agent"),
@@ -138,9 +228,18 @@ async function withSafeBinsExecTool(
138228}
139229const ctx = await createSafeBinsExecTool(params);
140230try {
141-await run(ctx);
231+await withEnvAsync(
232+{
233+OPENCLAW_SHELL_ENV_TIMEOUT_MS: "1",
234+SHELL: "/bin/sh",
235+},
236+async () => {
237+await run(ctx);
238+},
239+);
142240} finally {
143241fs.rmSync(ctx.tmpDir, { recursive: true, force: true });
242+resetProcessRegistryForTests();
144243}
145244}
146245@@ -165,6 +264,7 @@ describe("createOpenClawCodingTools safeBins", () => {
165264const resultDetails = result.details as { status?: string };
166265expect(resultDetails.status).toBe("completed");
167266expect(text).toContain(marker);
267+expect(supervisorSpawnMock).toHaveBeenCalledOnce();
168268},
169269);
170270});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。