




















@@ -151,15 +151,69 @@ type MockNodeInvokeParams = {
151151params?: Record<string, unknown>;
152152};
153153154-function expectSystemRunInvoke(params: { invokeTimeoutMs: number; runTimeoutMs: number }) {
155-expect(callGatewayToolMock).toHaveBeenCalledWith(
156-"node.invoke",
157-expect.objectContaining({ timeoutMs: params.invokeTimeoutMs }),
158-expect.objectContaining({
159-command: "system.run",
160-params: expect.objectContaining({ timeoutMs: params.runTimeoutMs }),
161-}),
154+type GatewayToolCall = {
155+method: string;
156+options: { timeoutMs?: number };
157+params?: MockNodeInvokeParams;
158+callOptions?: unknown;
159+};
160+161+function requireGatewayCall(index: number): GatewayToolCall {
162+const call = callGatewayToolMock.mock.calls[index];
163+if (!call) {
164+throw new Error(`expected gateway call at index ${index}`);
165+}
166+const [method, options, params, callOptions] = call as [
167+string,
168+{ timeoutMs?: number },
169+MockNodeInvokeParams | undefined,
170+unknown,
171+];
172+return { method, options, params, callOptions };
173+}
174+175+function requireGatewayCommand(command: string): GatewayToolCall {
176+const call = callGatewayToolMock.mock.calls.find(
177+([method, , params]) =>
178+method === "node.invoke" && (params as MockNodeInvokeParams | undefined)?.command === command,
162179);
180+if (!call) {
181+throw new Error(`expected gateway command ${command}`);
182+}
183+const [method, options, params, callOptions] = call as [
184+string,
185+{ timeoutMs?: number },
186+MockNodeInvokeParams | undefined,
187+unknown,
188+];
189+return { method, options, params, callOptions };
190+}
191+192+function requireRunParams(call: GatewayToolCall): Record<string, unknown> {
193+expect(call.method).toBe("node.invoke");
194+expect(call.params?.command).toBe("system.run");
195+const params = call.params?.params;
196+if (!params) {
197+throw new Error("expected system.run params");
198+}
199+return params;
200+}
201+202+function requireRegisteredApprovalRequest(): Record<string, unknown> {
203+const calls = registerExecApprovalRequestForHostOrThrowMock.mock.calls as unknown as [
204+Record<string, unknown>,
205+][];
206+const firstCall = calls[0];
207+if (!firstCall) {
208+throw new Error("expected approval request registration");
209+}
210+return firstCall[0];
211+}
212+213+function expectSystemRunInvoke(params: { invokeTimeoutMs: number; runTimeoutMs: number }) {
214+const call = requireGatewayCommand("system.run");
215+expect(call.options.timeoutMs).toBe(params.invokeTimeoutMs);
216+expect(requireRunParams(call).timeoutMs).toBe(params.runTimeoutMs);
163217}
164218165219describe("executeNodeHostCommand", () => {
@@ -278,35 +332,24 @@ describe("executeNodeHostCommand", () => {
278332});
279333280334expect(result.details?.status).toBe("approval-pending");
281-expect(registerExecApprovalRequestForHostOrThrowMock).toHaveBeenCalledWith(
282-expect.objectContaining({
283-systemRunPlan: preparedPlan,
284-}),
285-);
335+expect(requireRegisteredApprovalRequest().systemRunPlan).toEqual(preparedPlan);
286336287337await vi.waitFor(() => {
288338expect(callGatewayToolMock).toHaveBeenCalledTimes(3);
289339});
290340291-expect(callGatewayToolMock).toHaveBeenNthCalledWith(
292-3,
293-"node.invoke",
294-expect.objectContaining({ timeoutMs: 35_000 }),
295-expect.objectContaining({
296-command: "system.run",
297-params: expect.objectContaining({
298-approved: true,
299-approvalDecision: "allow-once",
300-systemRunPlan: preparedPlan,
301-timeoutMs: 30_000,
302-turnSourceChannel: "telegram",
303-turnSourceTo: "telegram:12345",
304-turnSourceAccountId: "work",
305-turnSourceThreadId: "42",
306-}),
307-}),
308-{ scopes: ["operator.write", "operator.approvals"] },
309-);
341+const call = requireGatewayCall(2);
342+expect(call.options.timeoutMs).toBe(35_000);
343+expect(call.callOptions).toEqual({ scopes: ["operator.write", "operator.approvals"] });
344+const runParams = requireRunParams(call);
345+expect(runParams.approved).toBe(true);
346+expect(runParams.approvalDecision).toBe("allow-once");
347+expect(runParams.systemRunPlan).toEqual(preparedPlan);
348+expect(runParams.timeoutMs).toBe(30_000);
349+expect(runParams.turnSourceChannel).toBe("telegram");
350+expect(runParams.turnSourceTo).toBe("telegram:12345");
351+expect(runParams.turnSourceAccountId).toBe("work");
352+expect(runParams.turnSourceThreadId).toBe("42");
310353});
311354312355it("builds a local systemRunPlan when approval is required and the node omits prepare", async () => {
@@ -347,25 +390,14 @@ describe("executeNodeHostCommand", () => {
347390agentId: "requested-agent",
348391sessionKey: "requested-session",
349392};
350-expect(registerExecApprovalRequestForHostOrThrowMock).toHaveBeenCalledWith(
351-expect.objectContaining({
352-systemRunPlan: expectedPlan,
353-}),
354-);
393+expect(requireRegisteredApprovalRequest().systemRunPlan).toEqual(expectedPlan);
355394356395await vi.waitFor(() => {
357-expect(callGatewayToolMock).toHaveBeenCalledWith(
358-"node.invoke",
359-expect.anything(),
360-expect.objectContaining({
361-command: "system.run",
362-params: expect.objectContaining({
363-rawCommand: expectedPlan.commandText,
364-systemRunPlan: expectedPlan,
365-}),
366-}),
367-{ scopes: ["operator.write", "operator.approvals"] },
368-);
396+const call = requireGatewayCommand("system.run");
397+expect(call.callOptions).toEqual({ scopes: ["operator.write", "operator.approvals"] });
398+const runParams = requireRunParams(call);
399+expect(runParams.rawCommand).toBe(expectedPlan.commandText);
400+expect(runParams.systemRunPlan).toEqual(expectedPlan);
369401});
370402});
371403@@ -385,28 +417,14 @@ describe("executeNodeHostCommand", () => {
385417});
386418387419expect(callGatewayToolMock).toHaveBeenCalledTimes(1);
388-expect(callGatewayToolMock).toHaveBeenCalledWith(
389-"node.invoke",
390-expect.objectContaining({ timeoutMs: 35_000 }),
391-expect.objectContaining({
392-command: "system.run",
393-params: expect.objectContaining({
394-command: ["/bin/sh", "-lc", "bun ./script.ts"],
395-rawCommand: "bun ./script.ts",
396-suppressNotifyOnExit: true,
397-timeoutMs: 30_000,
398-}),
399-}),
400-);
401-expect(callGatewayToolMock).toHaveBeenCalledWith(
402-"node.invoke",
403-expect.anything(),
404-expect.objectContaining({
405-params: expect.not.objectContaining({
406-systemRunPlan: expect.anything(),
407-}),
408-}),
409-);
420+const call = requireGatewayCall(0);
421+expect(call.options.timeoutMs).toBe(35_000);
422+const runParams = requireRunParams(call);
423+expect(runParams.command).toEqual(["/bin/sh", "-lc", "bun ./script.ts"]);
424+expect(runParams.rawCommand).toBe("bun ./script.ts");
425+expect(runParams.suppressNotifyOnExit).toBe(true);
426+expect(runParams.timeoutMs).toBe(30_000);
427+expect(Object.hasOwn(runParams, "systemRunPlan")).toBe(false);
410428});
411429412430it("rejects disconnected node targets before invoking system.run", async () => {
@@ -471,12 +489,14 @@ describe("executeNodeHostCommand", () => {
471489});
472490473491expect(result.content).toEqual([{ type: "text", text: "(no output)" }]);
474-expect(result.details).toMatchObject({
475-status: "completed",
476-exitCode: 0,
477-aggregated: "",
478-cwd: "/tmp/work",
479-});
492+const details = result.details;
493+expect(details?.status).toBe("completed");
494+if (details?.status !== "completed") {
495+throw new Error(`expected completed details, got ${String(details?.status)}`);
496+}
497+expect(details.exitCode).toBe(0);
498+expect(details.aggregated).toBe("");
499+expect(details.cwd).toBe("/tmp/work");
480500});
481501482502it("forwards explicit timeouts to node system.run", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。