diff --git a/src/crestodian/rescue-message.test.ts b/src/crestodian/rescue-message.test.ts index 4545040f508..af5b2752303 100644 --- a/src/crestodian/rescue-message.test.ts +++ b/src/crestodian/rescue-message.test.ts @@ -1,12 +1,14 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { CommandContext } from "../auto-reply/reply/commands-types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { extractCrestodianRescueMessage, runCrestodianRescueMessage } from "./rescue-message.js"; const originalStateDir = process.env.OPENCLAW_STATE_DIR; +let tempRoot = ""; +let tempDirId = 0; type TestConfig = Record; @@ -101,6 +103,12 @@ vi.mock("../config/model-input.js", () => ({ typeof model === "string" ? model : model?.primary, })); +async function makeStateDir(prefix: string): Promise { + const dir = path.join(tempRoot, `${prefix}${tempDirId++}`); + await fs.mkdir(dir, { recursive: true }); + return dir; +} + function commandContext(overrides: Partial = {}): CommandContext { return { surface: "whatsapp", @@ -134,6 +142,10 @@ async function runRescue( } describe("Crestodian rescue message", () => { + beforeAll(async () => { + tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-rescue-")); + }); + beforeEach(() => { mockConfig.reset(); }); @@ -146,6 +158,12 @@ describe("Crestodian rescue message", () => { } }); + afterAll(async () => { + if (tempRoot) { + await fs.rm(tempRoot, { recursive: true, force: true }); + } + }); + it("recognizes the Crestodian rescue command", () => { expect(extractCrestodianRescueMessage("/crestodian status")).toBe("status"); expect(extractCrestodianRescueMessage("/crestodian")).toBe(""); @@ -179,7 +197,7 @@ describe("Crestodian rescue message", () => { }); it("queues and applies persistent writes through conversational approval", async () => { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-rescue-")); + const tempDir = await makeStateDir("models-"); vi.stubEnv("OPENCLAW_STATE_DIR", tempDir); const cfg: OpenClawConfig = { crestodian: { rescue: { enabled: true } } }; @@ -203,7 +221,7 @@ describe("Crestodian rescue message", () => { }); it("queues and applies gateway restart through conversational approval", async () => { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-rescue-gateway-")); + const tempDir = await makeStateDir("gateway-"); vi.stubEnv("OPENCLAW_STATE_DIR", tempDir); const cfg: OpenClawConfig = { crestodian: { rescue: { enabled: true } } }; const deps = { runGatewayRestart: vi.fn(async () => {}) }; @@ -229,7 +247,7 @@ describe("Crestodian rescue message", () => { }); it("queues and applies agent creation through conversational approval", async () => { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "crestodian-rescue-agent-")); + const tempDir = await makeStateDir("agent-"); vi.stubEnv("OPENCLAW_STATE_DIR", tempDir); const cfg: OpenClawConfig = { crestodian: { rescue: { enabled: true } } }; const deps = { runAgentsAdd: vi.fn(async () => {}) }; diff --git a/src/crestodian/rescue-policy.ts b/src/crestodian/rescue-policy.ts index de1f0860a82..98cdd4d272e 100644 --- a/src/crestodian/rescue-policy.ts +++ b/src/crestodian/rescue-policy.ts @@ -1,6 +1,5 @@ -import { resolveAgentConfig } from "../agents/agent-scope.js"; -import { resolveSandboxConfigForAgent } from "../agents/sandbox/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { normalizeAgentId } from "../routing/session-key.js"; export type CrestodianRescueDecision = | { @@ -33,9 +32,27 @@ function resolvePendingTtlMinutes(value: unknown): number { return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 15; } +function resolveAgentEntry(cfg: OpenClawConfig, agentId?: string) { + if (!agentId) { + return undefined; + } + const id = normalizeAgentId(agentId); + return cfg.agents?.list?.find( + (entry) => entry !== null && typeof entry === "object" && normalizeAgentId(entry.id) === id, + ); +} + function resolveScopedExecConfig(cfg: OpenClawConfig, agentId?: string) { - const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined; - return agentConfig?.tools?.exec; + return resolveAgentEntry(cfg, agentId)?.tools?.exec; +} + +function resolveScopedSandboxMode( + cfg: OpenClawConfig, + agentId?: string, +): "off" | "non-main" | "all" { + return ( + resolveAgentEntry(cfg, agentId)?.sandbox?.mode ?? cfg.agents?.defaults?.sandbox?.mode ?? "off" + ); } function isYoloHostPosture(cfg: OpenClawConfig, agentId?: string): boolean { @@ -53,8 +70,7 @@ export function resolveCrestodianRescuePolicy( const configuredEnabled = rescue?.enabled ?? "auto"; const ownerDmOnly = rescue?.ownerDmOnly ?? true; const pendingTtlMinutes = resolvePendingTtlMinutes(rescue?.pendingTtlMinutes); - const sandbox = resolveSandboxConfigForAgent(input.cfg, input.agentId); - const sandboxActive = sandbox.mode !== "off"; + const sandboxActive = resolveScopedSandboxMode(input.cfg, input.agentId) !== "off"; const yolo = !sandboxActive && isYoloHostPosture(input.cfg, input.agentId); const enabled = configuredEnabled === "auto" ? yolo : configuredEnabled;