























@@ -8,6 +8,9 @@ const state = vi.hoisted(() => ({
88buildAcpResultMock: vi.fn(),
99createAcpVisibleTextAccumulatorMock: vi.fn(),
1010persistAcpTurnTranscriptMock: vi.fn(),
11+resolveAcpAgentPolicyErrorMock: vi.fn(),
12+resolveAcpDispatchPolicyErrorMock: vi.fn(),
13+resolveAcpExplicitTurnPolicyErrorMock: vi.fn(),
1114runWithModelFallbackMock: vi.fn(),
1215runAgentAttemptMock: vi.fn(),
1316resolveEffectiveModelFallbacksMock: vi.fn().mockReturnValue(undefined),
@@ -83,12 +86,16 @@ vi.mock("./command/session.js", () => ({
8386vi.mock("./command/types.js", () => ({}));
84878588vi.mock("../acp/policy.js", () => ({
86-resolveAcpAgentPolicyError: () => null,
87-resolveAcpDispatchPolicyError: () => null,
89+resolveAcpAgentPolicyError: (...args: unknown[]) => state.resolveAcpAgentPolicyErrorMock(...args),
90+resolveAcpDispatchPolicyError: (...args: unknown[]) =>
91+state.resolveAcpDispatchPolicyErrorMock(...args),
92+resolveAcpExplicitTurnPolicyError: (...args: unknown[]) =>
93+state.resolveAcpExplicitTurnPolicyErrorMock(...args),
8894}));
89959096vi.mock("../acp/runtime/errors.js", () => ({
91-toAcpRuntimeError: vi.fn(),
97+toAcpRuntimeError: ({ error }: { error: unknown }) =>
98+error instanceof Error ? error : new Error(String(error)),
9299}));
9310094101vi.mock("../acp/runtime/session-identifiers.js", () => ({
@@ -404,6 +411,9 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
404411beforeEach(() => {
405412vi.clearAllMocks();
406413state.acpResolveSessionMock.mockReturnValue(null);
414+state.resolveAcpAgentPolicyErrorMock.mockReturnValue(null);
415+state.resolveAcpDispatchPolicyErrorMock.mockReturnValue(null);
416+state.resolveAcpExplicitTurnPolicyErrorMock.mockReturnValue(null);
407417state.acpRunTurnMock.mockImplementation(async (params: unknown) => {
408418const onEvent = (params as { onEvent?: (event: unknown) => void }).onEvent;
409419onEvent?.({ type: "text_delta", stream: "output", text: "done" });
@@ -629,6 +639,55 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
629639expect(transcriptParams.transcriptBody).not.toContain(INTERNAL_RUNTIME_CONTEXT_END);
630640});
631641642+it("allows manual ACP spawn turns when ACP dispatch is disabled", async () => {
643+state.acpResolveSessionMock.mockReturnValue({
644+kind: "ready",
645+meta: {
646+agent: "claude",
647+cwd: "/tmp/workspace",
648+},
649+});
650+state.resolveAcpDispatchPolicyErrorMock.mockReturnValue(
651+new Error("ACP dispatch is disabled by policy (`acp.dispatch.enabled=false`)."),
652+);
653+654+await agentCommand({
655+message: "bootstrap ACP child",
656+sessionKey: "agent:main",
657+senderIsOwner: true,
658+acpTurnSource: "manual_spawn",
659+});
660+661+expect(state.resolveAcpExplicitTurnPolicyErrorMock).toHaveBeenCalledTimes(1);
662+expect(state.resolveAcpDispatchPolicyErrorMock).not.toHaveBeenCalled();
663+expect(state.acpRunTurnMock).toHaveBeenCalledTimes(1);
664+});
665+666+it("keeps ordinary ACP turns blocked when ACP dispatch is disabled", async () => {
667+state.acpResolveSessionMock.mockReturnValue({
668+kind: "ready",
669+meta: {
670+agent: "claude",
671+cwd: "/tmp/workspace",
672+},
673+});
674+state.resolveAcpDispatchPolicyErrorMock.mockReturnValue(
675+new Error("ACP dispatch is disabled by policy (`acp.dispatch.enabled=false`)."),
676+);
677+678+await expect(
679+agentCommand({
680+message: "automatic ACP turn",
681+sessionKey: "agent:main",
682+senderIsOwner: true,
683+}),
684+).rejects.toThrow("ACP dispatch is disabled");
685+686+expect(state.resolveAcpExplicitTurnPolicyErrorMock).not.toHaveBeenCalled();
687+expect(state.resolveAcpDispatchPolicyErrorMock).toHaveBeenCalledTimes(1);
688+expect(state.acpRunTurnMock).not.toHaveBeenCalled();
689+});
690+632691it("flips hasSessionModelOverride on provider-only switch with same model", async () => {
633692setupModelSwitchRetry({
634693provider: "openai",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。