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>
75 lines
3.1 KiB
TypeScript
75 lines
3.1 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "bun:test";
|
|
import * as path from "node:path";
|
|
import { Agent } from "@oh-my-pi/pi-agent-core";
|
|
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
|
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import { submitInteractiveInput } from "@oh-my-pi/pi-coding-agent/main";
|
|
import { InteractiveMode } from "@oh-my-pi/pi-coding-agent/modes/interactive-mode";
|
|
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
|
|
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
|
|
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
|
|
import { TempDir } from "@oh-my-pi/pi-utils";
|
|
|
|
describe("issue #927 optimistic pending spinner", () => {
|
|
let authStorage: AuthStorage;
|
|
let mode: InteractiveMode;
|
|
let session: AgentSession;
|
|
let tempDir: TempDir;
|
|
|
|
beforeAll(() => {
|
|
initTheme();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
vi.spyOn(process.stdin, "resume").mockReturnValue(process.stdin);
|
|
vi.spyOn(process.stdin, "pause").mockReturnValue(process.stdin);
|
|
vi.spyOn(process.stdin, "setEncoding").mockReturnValue(process.stdin);
|
|
if (typeof process.stdin.setRawMode === "function") {
|
|
vi.spyOn(process.stdin, "setRawMode").mockReturnValue(process.stdin);
|
|
}
|
|
|
|
resetSettingsForTest();
|
|
tempDir = TempDir.createSync("@pi-issue-927-");
|
|
await Settings.init({ inMemory: true, cwd: tempDir.path() });
|
|
authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db"));
|
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
const model = modelRegistry.find("anthropic", "claude-sonnet-4-5");
|
|
if (!model) throw new Error("Expected claude-sonnet-4-5 test model");
|
|
|
|
session = new AgentSession({
|
|
agent: new Agent({ initialState: { model, systemPrompt: ["Test"], tools: [], messages: [] } }),
|
|
sessionManager: SessionManager.create(tempDir.path(), tempDir.path()),
|
|
settings: Settings.isolated(),
|
|
modelRegistry,
|
|
});
|
|
vi.spyOn(session, "prompt").mockResolvedValue(true);
|
|
mode = new InteractiveMode(session, "test");
|
|
mode.addMessageToChat = vi.fn();
|
|
mode.ui.requestRender = vi.fn();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
mode?.stop();
|
|
vi.restoreAllMocks();
|
|
await session?.dispose();
|
|
authStorage?.close();
|
|
tempDir?.removeSync();
|
|
resetSettingsForTest();
|
|
});
|
|
|
|
it("clears the optimistic loading animation when prompt returns without a model turn", async () => {
|
|
const input = mode.startPendingSubmission({ text: "/extension-no-turn" });
|
|
expect(mode.loadingAnimation).toBeDefined();
|
|
expect(mode.optimisticUserMessageSignature).toBe("/extension-no-turn\u00000");
|
|
|
|
await submitInteractiveInput(mode, session, input);
|
|
|
|
expect(mode.loadingAnimation).toBeUndefined();
|
|
expect(mode.optimisticUserMessageSignature).toBeUndefined();
|
|
expect(mode.locallySubmittedUserSignatures.has("/extension-no-turn\u00000")).toBe(false);
|
|
expect(mode.statusContainer.children.length).toBe(0);
|
|
});
|
|
});
|