mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-08 21:22:54 +00:00
fix(crestodian): bound rescue approval expiry
This commit is contained in:
@@ -297,6 +297,52 @@ describe("Crestodian rescue message", () => {
|
||||
expect(audit.details?.senderId).toBe("user:owner");
|
||||
});
|
||||
|
||||
it("does not queue persistent rescue approval when expiry would exceed the Date range", async () => {
|
||||
const tempDir = await makeStateDir("overflow-expiry-");
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(8_640_000_000_000_000));
|
||||
try {
|
||||
const cfg: OpenClawConfig = { crestodian: { rescue: { enabled: true } } };
|
||||
|
||||
await expect(
|
||||
runRescue("/crestodian restart gateway", cfg, commandContext()),
|
||||
).resolves.toContain("expiry clock is invalid");
|
||||
|
||||
await expect(fs.readdir(path.join(tempDir, "crestodian", "rescue-pending"))).rejects.toThrow(
|
||||
/ENOENT/,
|
||||
);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects pending rescue approvals with invalid persisted expiry", async () => {
|
||||
const tempDir = await makeStateDir("invalid-expiry-");
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
|
||||
const cfg: OpenClawConfig = { crestodian: { rescue: { enabled: true } } };
|
||||
const deps = { runGatewayRestart: vi.fn(async () => {}) };
|
||||
|
||||
await expect(
|
||||
runRescue("/crestodian restart gateway", cfg, commandContext(), deps),
|
||||
).resolves.toContain("Reply /crestodian yes to apply");
|
||||
const pendingDir = path.join(tempDir, "crestodian", "rescue-pending");
|
||||
const [pendingFile] = await fs.readdir(pendingDir);
|
||||
if (!pendingFile) {
|
||||
throw new Error("expected pending rescue file");
|
||||
}
|
||||
const pendingPath = path.join(pendingDir, pendingFile);
|
||||
const pending = JSON.parse(await fs.readFile(pendingPath, "utf8")) as { expiresAt?: string };
|
||||
pending.expiresAt = "not-a-date";
|
||||
await fs.writeFile(pendingPath, `${JSON.stringify(pending, null, 2)}\n`, "utf8");
|
||||
|
||||
await expect(runRescue("/crestodian yes", cfg, commandContext(), deps)).resolves.toBe(
|
||||
"No pending Crestodian rescue change is waiting for approval.",
|
||||
);
|
||||
expect(deps.runGatewayRestart).not.toHaveBeenCalled();
|
||||
await expect(fs.stat(pendingPath)).rejects.toThrow(/ENOENT/);
|
||||
});
|
||||
|
||||
it("queues and applies agent creation through conversational approval", async () => {
|
||||
const tempDir = await makeStateDir("agent-");
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", tempDir);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { resolveStateDir } from "../config/paths.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { tryReadJson, writeJson } from "../infra/json-files.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "../shared/number-coercion.js";
|
||||
import {
|
||||
executeCrestodianOperation,
|
||||
formatCrestodianPersistentPlan,
|
||||
@@ -86,7 +87,9 @@ async function readPending(
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
if (Date.parse(parsed.expiresAt) <= now.getTime()) {
|
||||
const expiresAtMs = asDateTimestampMs(Date.parse(parsed.expiresAt));
|
||||
const nowMs = asDateTimestampMs(now.getTime());
|
||||
if (expiresAtMs === undefined || nowMs === undefined || expiresAtMs <= nowMs) {
|
||||
await fs.rm(pendingPath, { force: true });
|
||||
return null;
|
||||
}
|
||||
@@ -182,11 +185,18 @@ export async function runCrestodianRescueMessage(
|
||||
}
|
||||
if (isPersistentCrestodianOperation(operation)) {
|
||||
const now = new Date();
|
||||
const expiresAt = new Date(now.getTime() + policy.pendingTtlMinutes * 60_000);
|
||||
const nowMs = asDateTimestampMs(now.getTime());
|
||||
const expiresAtMs =
|
||||
nowMs === undefined
|
||||
? undefined
|
||||
: resolveExpiresAtMsFromDurationMs(policy.pendingTtlMinutes * 60_000, { nowMs });
|
||||
if (expiresAtMs === undefined) {
|
||||
return "Crestodian rescue could not create a pending approval because the expiry clock is invalid.";
|
||||
}
|
||||
await writePending(pendingPath, {
|
||||
id: randomUUID(),
|
||||
createdAt: now.toISOString(),
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
expiresAt: new Date(expiresAtMs).toISOString(),
|
||||
operation,
|
||||
auditDetails: buildAuditDetails(input),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user