Files
oh-my-pi/packages/coding-agent/test/main-interactive-input.test.ts
T
Theo Mathieu 416c9947bd fix(acp): finish prompt turn when extension/custom command is handled locally
Extension commands (e.g. /sonnet) and TypeScript custom commands that
consume the input without calling the LLM return early from
session.prompt() with no agent turn. In ACP mode this left the pending
prompt promise unresolved, hanging the client forever.

Change session.prompt() to return Promise<boolean>: true when the LLM
was invoked, false when the command was fully handled locally.
#runPromptOrCommand calls #finishPrompt immediately on a false return so
the ACP turn completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 08:26:07 +02:00

84 lines
2.8 KiB
TypeScript

import { describe, expect, it, vi } from "bun:test";
import { submitInteractiveInput } from "@oh-my-pi/pi-coding-agent/main";
import type { SubmittedUserInput } from "@oh-my-pi/pi-coding-agent/modes/types";
function createInput(overrides: Partial<SubmittedUserInput> = {}): SubmittedUserInput {
return {
text: "hello",
images: undefined,
cancelled: false,
started: false,
...overrides,
};
}
describe("submitInteractiveInput", () => {
it("routes already-started synthetic continue submissions to a hidden developer prompt", async () => {
const mode = {
markPendingSubmissionStarted: vi.fn(() => false),
finishPendingSubmission: vi.fn(),
showError: vi.fn(),
checkShutdownRequested: vi.fn(async () => {}),
};
const session = {
prompt: vi.fn(async () => true),
promptCustomMessage: vi.fn(async () => {}),
};
const input = createInput({ text: "resume now", started: true, synthetic: true });
await submitInteractiveInput(mode, session, input);
expect(mode.markPendingSubmissionStarted).not.toHaveBeenCalled();
expect(session.prompt).toHaveBeenCalledWith("resume now", { synthetic: true, expandPromptTemplates: false });
expect(mode.finishPendingSubmission).toHaveBeenCalledWith(input);
expect(mode.showError).not.toHaveBeenCalled();
});
it("skips prompting when optimistic submission was cancelled before start", async () => {
const mode = {
markPendingSubmissionStarted: vi.fn(() => false),
finishPendingSubmission: vi.fn(),
showError: vi.fn(),
checkShutdownRequested: vi.fn(async () => {}),
};
const session = {
prompt: vi.fn(async () => true),
promptCustomMessage: vi.fn(async () => {}),
};
const input = createInput();
await submitInteractiveInput(mode, session, input);
expect(mode.markPendingSubmissionStarted).toHaveBeenCalledWith(input);
expect(session.prompt).not.toHaveBeenCalled();
expect(mode.finishPendingSubmission).toHaveBeenCalledWith(input);
expect(mode.showError).not.toHaveBeenCalled();
});
it("routes hidden custom submissions through promptCustomMessage", async () => {
const mode = {
markPendingSubmissionStarted: vi.fn(() => true),
finishPendingSubmission: vi.fn(),
showError: vi.fn(),
checkShutdownRequested: vi.fn(async () => {}),
};
const session = {
prompt: vi.fn(async () => true),
promptCustomMessage: vi.fn(async () => {}),
};
const input = createInput({ text: "continue goal", customType: "goal-continuation" });
await submitInteractiveInput(mode, session, input);
expect(session.prompt).not.toHaveBeenCalled();
expect(session.promptCustomMessage).toHaveBeenCalledWith({
customType: "goal-continuation",
content: "continue goal",
display: false,
attribution: "agent",
});
expect(mode.finishPendingSubmission).toHaveBeenCalledWith(input);
expect(mode.showError).not.toHaveBeenCalled();
});
});