416c9947bd
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>
84 lines
2.8 KiB
TypeScript
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();
|
|
});
|
|
});
|