Skip to content

Commit e708a87

Browse files
committed
fix(commands): bound private approval route expiry
1 parent 2dacc6d commit e708a87

4 files changed

Lines changed: 36 additions & 3 deletions

File tree

src/auto-reply/reply/commands-diagnostics.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
deliverPrivateCommandReply,
1616
readCommandDeliveryTarget,
1717
readCommandMessageThreadId,
18+
resolvePrivateCommandApprovalRouteExpiresAtMs,
1819
resolvePrivateCommandRouteTargets,
1920
type PrivateCommandRouteTarget,
2021
} from "./commands-private-route.js";
@@ -256,7 +257,7 @@ function buildDiagnosticsApprovalRequest(params: HandleCommandsParams): ExecAppr
256257
turnSourceThreadId: readCommandMessageThreadId(params) ?? null,
257258
},
258259
createdAtMs: now,
259-
expiresAtMs: now + 5 * 60_000,
260+
expiresAtMs: resolvePrivateCommandApprovalRouteExpiresAtMs(now),
260261
};
261262
}
262263

src/auto-reply/reply/commands-export-trajectory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
deliverPrivateCommandReply,
2525
readCommandDeliveryTarget,
2626
readCommandMessageThreadId,
27+
resolvePrivateCommandApprovalRouteExpiresAtMs,
2728
resolvePrivateCommandRouteTargets,
2829
type PrivateCommandRouteTarget,
2930
} from "./commands-private-route.js";
@@ -216,7 +217,7 @@ function buildTrajectoryExportApprovalRequest(
216217
turnSourceThreadId: readCommandMessageThreadId(params) ?? null,
217218
},
218219
createdAtMs: now,
219-
expiresAtMs: now + 5 * 60_000,
220+
expiresAtMs: resolvePrivateCommandApprovalRouteExpiresAtMs(now),
220221
};
221222
}
222223

src/auto-reply/reply/commands-private-route.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import type { ChannelPlugin } from "../../channels/plugins/types.public.js";
33
import type { OpenClawConfig } from "../../config/config.js";
44
import type { ExecApprovalRequest } from "../../infra/exec-approvals.js";
55
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
6+
import { MAX_DATE_TIMESTAMP_MS } from "../../shared/number-coercion.js";
67
import {
78
createChannelTestPluginBase,
89
createTestRegistry,
910
} from "../../test-utils/channel-plugins.js";
1011
import type { MsgContext } from "../templating.js";
11-
import { resolvePrivateCommandRouteTargets } from "./commands-private-route.js";
12+
import {
13+
resolvePrivateCommandApprovalRouteExpiresAtMs,
14+
resolvePrivateCommandRouteTargets,
15+
} from "./commands-private-route.js";
1216
import type { HandleCommandsParams } from "./commands-types.js";
1317

1418
function createApprovalChannelPlugin(params: {
@@ -144,6 +148,22 @@ afterEach(() => {
144148
resetPluginRuntimeStateForTest();
145149
});
146150

151+
describe("resolvePrivateCommandApprovalRouteExpiresAtMs", () => {
152+
it("returns a bounded five-minute route expiry for valid clocks", () => {
153+
expect(resolvePrivateCommandApprovalRouteExpiresAtMs(1_800_000_000_000)).toBe(
154+
1_800_000_300_000,
155+
);
156+
});
157+
158+
it("expires private command routes immediately for invalid clocks", () => {
159+
expect(resolvePrivateCommandApprovalRouteExpiresAtMs(Number.NaN)).toBe(0);
160+
});
161+
162+
it("expires private command routes immediately when expiry would exceed Date bounds", () => {
163+
expect(resolvePrivateCommandApprovalRouteExpiresAtMs(MAX_DATE_TIMESTAMP_MS)).toBe(0);
164+
});
165+
});
166+
147167
describe("resolvePrivateCommandRouteTargets", () => {
148168
it("prefers a same-surface private owner route even when another owner route is listed first", async () => {
149169
registerApprovalChannelPlugins([

src/auto-reply/reply/commands-private-route.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
resolveChannelApprovalAdapter,
55
} from "../../channels/plugins/index.js";
66
import type { ExecApprovalRequest } from "../../infra/exec-approvals.js";
7+
import { resolveExpiresAtMsFromDurationMs } from "../../shared/number-coercion.js";
78
import {
89
normalizeLowercaseStringOrEmpty,
910
normalizeOptionalString,
@@ -20,6 +21,16 @@ export type PrivateCommandRouteTarget = {
2021
threadId?: string | number | null;
2122
};
2223

24+
const PRIVATE_COMMAND_APPROVAL_ROUTE_TTL_MS = 5 * 60_000;
25+
const EXPIRED_PRIVATE_COMMAND_APPROVAL_ROUTE_EXPIRES_AT_MS = 0;
26+
27+
export function resolvePrivateCommandApprovalRouteExpiresAtMs(nowMs = Date.now()): number {
28+
return (
29+
resolveExpiresAtMsFromDurationMs(PRIVATE_COMMAND_APPROVAL_ROUTE_TTL_MS, { nowMs }) ??
30+
EXPIRED_PRIVATE_COMMAND_APPROVAL_ROUTE_EXPIRES_AT_MS
31+
);
32+
}
33+
2334
export async function resolvePrivateCommandRouteTargets(params: {
2435
commandParams: HandleCommandsParams;
2536
request: ExecApprovalRequest;

0 commit comments

Comments
 (0)