




















@@ -360,6 +360,29 @@ function mockNoApprovalRouteRegistration() {
360360});
361361}
362362363+function requireRecord(value: unknown, label: string): Record<string, unknown> {
364+if (!value || typeof value !== "object" || Array.isArray(value)) {
365+throw new Error(`expected ${label}`);
366+}
367+return value as Record<string, unknown>;
368+}
369+370+function expectRecordFields(
371+record: Record<string, unknown> | undefined,
372+expected: Record<string, unknown>,
373+) {
374+if (!record) {
375+throw new Error("expected record");
376+}
377+for (const [key, value] of Object.entries(expected)) {
378+if (Array.isArray(value)) {
379+expect(record[key]).toEqual(value);
380+} else {
381+expect(record[key]).toBe(value);
382+}
383+}
384+}
385+363386describe("exec approvals", () => {
364387let previousHome: string | undefined;
365388let previousUserProfile: string | undefined;
@@ -463,17 +486,15 @@ describe("exec approvals", () => {
463486interval: 1,
464487})
465488.toBe(approvalId);
466-expect(
467-(invokeParams as { params?: { suppressNotifyOnExit?: boolean } } | undefined)?.params,
468-).toMatchObject({
469-suppressNotifyOnExit: true,
470-});
471-await expect
472-.poll(() => agentParams, { timeout: 2000, interval: 1 })
473-.toMatchObject({
474-message: expect.stringContaining(`id=${approvalId}`),
475-sessionKey: "agent:main:main",
476-});
489+const nodeInvokeParams = requireRecord(
490+requireRecord(invokeParams, "node invoke").params,
491+"node invoke params",
492+);
493+expect(nodeInvokeParams.suppressNotifyOnExit).toBe(true);
494+await expect.poll(() => agentParams !== undefined, { timeout: 2000, interval: 1 }).toBe(true);
495+const agent = requireRecord(agentParams, "agent followup params");
496+expect(String(agent.message)).toContain(`id=${approvalId}`);
497+expect(agent.sessionKey).toBe("agent:main:main");
477498});
478499479500it("skips approval when node allowlist is satisfied", async () => {
@@ -702,9 +723,10 @@ describe("exec approvals", () => {
702723});
703724704725expect(durable.details.status).toBe("approval-pending");
705-expect(durable.details).toMatchObject({
706-allowedDecisions: ["allow-once", "deny"],
707-});
726+expect(requireRecord(durable.details, "durable details").allowedDecisions).toEqual([
727+"allow-once",
728+"deny",
729+]);
708730expect(getResultText(durable)).toContain("Reply with: /approve ");
709731expect(getResultText(durable)).toContain("allow-once|deny");
710732expect(getResultText(durable)).not.toContain("allow-once|allow-always|deny");
@@ -824,9 +846,10 @@ describe("exec approvals", () => {
824846});
825847826848expect(result.details.status).toBe("approval-pending");
827-expect(result.details).toMatchObject({
828-allowedDecisions: ["allow-once", "deny"],
829-});
849+expect(requireRecord(result.details, "result details").allowedDecisions).toEqual([
850+"allow-once",
851+"deny",
852+]);
830853expect(calls).toContain("exec.approval.request");
831854});
832855@@ -941,13 +964,11 @@ describe("exec approvals", () => {
941964942965expect(result.details.status).toBe("approval-pending");
943966await expect.poll(() => agentCalls.length, { timeout: 3000, interval: 1 }).toBe(1);
944-expect(agentCalls[0]).toEqual(
945-expect.objectContaining({
946-sessionKey: "agent:main:main",
947-deliver: false,
948-idempotencyKey: expect.stringContaining("exec-approval-followup:"),
949-}),
950-);
967+expectRecordFields(agentCalls[0], {
968+sessionKey: "agent:main:main",
969+deliver: false,
970+});
971+expect(String(agentCalls[0]?.idempotencyKey)).toContain("exec-approval-followup:");
951972expect(typeof agentCalls[0]?.message).toBe("string");
952973expect(agentCalls[0]?.message).toContain(
953974"An async command the user already approved has completed.",
@@ -982,18 +1003,16 @@ describe("exec approvals", () => {
98210039831004expect(result.details.status).toBe("approval-pending");
9841005await expect.poll(() => agentCalls.length, { timeout: 3000, interval: 1 }).toBe(1);
985-expect(agentCalls[0]).toEqual(
986-expect.objectContaining({
987-sessionKey: "agent:main:discord:channel:123",
988-deliver: true,
989-bestEffortDeliver: true,
990-channel: "discord",
991-to: "123",
992-accountId: "default",
993-threadId: "456",
994-idempotencyKey: expect.stringContaining("exec-approval-followup:"),
995-}),
996-);
1006+expectRecordFields(agentCalls[0], {
1007+sessionKey: "agent:main:discord:channel:123",
1008+deliver: true,
1009+bestEffortDeliver: true,
1010+channel: "discord",
1011+to: "123",
1012+accountId: "default",
1013+threadId: "456",
1014+});
1015+expect(String(agentCalls[0]?.idempotencyKey)).toContain("exec-approval-followup:");
9971016expect(typeof agentCalls[0]?.message).toBe("string");
9981017expect(agentCalls[0]?.message).toContain(
9991018"If the task requires more steps, continue from this result before replying to the user.",
@@ -1045,17 +1064,15 @@ describe("exec approvals", () => {
10451064resolveDecision?.({ decision: "allow-once" });
1046106510471066await expect.poll(() => agentCalls.length, { timeout: 3000, interval: 1 }).toBe(1);
1048-expect(agentCalls[0]).toEqual(
1049-expect.objectContaining({
1050-sessionKey: "agent:main:discord:channel:123",
1051-deliver: true,
1052-bestEffortDeliver: true,
1053-channel: "discord",
1054-to: "123",
1055-accountId: "default",
1056-threadId: "456",
1057-}),
1058-);
1067+expectRecordFields(agentCalls[0], {
1068+sessionKey: "agent:main:discord:channel:123",
1069+deliver: true,
1070+bestEffortDeliver: true,
1071+channel: "discord",
1072+to: "123",
1073+accountId: "default",
1074+threadId: "456",
1075+});
10591076expect(typeof agentCalls[0]?.message).toBe("string");
10601077expect(agentCalls[0]?.message).toContain(
10611078"If the task requires more steps, continue from this result before replying to the user.",
@@ -1089,12 +1106,10 @@ describe("exec approvals", () => {
10891106expect(result.details.status).toBe("approval-pending");
1090110710911108await expect.poll(() => agentCalls.length, { timeout: 3000, interval: 1 }).toBe(1);
1092-expect(agentCalls[0]).toEqual(
1093-expect.objectContaining({
1094-sessionKey: "agent:main:main",
1095-deliver: false,
1096-}),
1097-);
1109+expectRecordFields(agentCalls[0], {
1110+sessionKey: "agent:main:main",
1111+deliver: false,
1112+});
10981113expect(agentCalls[0]?.message).toContain("webchat-ok");
10991114});
11001115@@ -1375,11 +1390,11 @@ describe("exec approvals", () => {
13751390expect(result.details.status).toBe("completed");
13761391expect(getResultText(result)).toContain("cron-ok");
137713921378-expect(vi.mocked(callGatewayTool)).toHaveBeenCalledWith(
1379-"exec.approval.request",
1380-expect.anything(),
1381- expect.anything(),
1382-expect.objectContaining({ expectFinal: false }),
1393+const approvalRequestCall = vi
1394+.mocked(callGatewayTool)
1395+.mock.calls.find(([method]) => method === "exec.approval.request");
1396+expect(requireRecord(approvalRequestCall?.[3], "approval request options").expectFinal).toBe(
1397+false,
13831398);
13841399expect(
13851400vi
@@ -1448,17 +1463,13 @@ describe("exec approvals", () => {
1448146314491464expect(result.details.status).toBe("completed");
14501465expect(getResultText(result)).toContain("cron-node-ok");
1451-expect(systemRunInvoke).toMatchObject({
1452-command: "system.run",
1453-params: {
1454-approved: true,
1455-approvalDecision: "allow-once",
1456-systemRunPlan: preparedPlan,
1457-},
1458-});
1459-expect((systemRunInvoke as { params?: { runId?: string } }).params?.runId).toEqual(
1460-expect.any(String),
1461-);
1466+const systemRun = requireRecord(systemRunInvoke, "system.run invoke");
1467+expect(systemRun.command).toBe("system.run");
1468+const params = requireRecord(systemRun.params, "system.run params");
1469+expect(params.approved).toBe(true);
1470+expect(params.approvalDecision).toBe("allow-once");
1471+expect(params.systemRunPlan).toStrictEqual(preparedPlan);
1472+expect(params.runId).toBeTypeOf("string");
14621473});
1463147414641475it("explains cron no-route denials with a host-policy fix hint", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。