Files
oh-my-pi/packages/coding-agent/test/slash-commands/force.test.ts
T
can1357 ea9c46262f feat(coding-agent): introduced /force command and ToolChoiceQueue for directive management
- Added `/force` slash command and `ToolChoiceQueue` system for managing tool-choice directives with lifecycle callbacks and requeue semantics.
- Added `setForcedToolChoice()`, `peekQueueInvoker()`, `buildToolChoice()`, `steer()`, and `getToolChoiceQueue()` methods to AgentSession and ToolSession APIs.
- Refactored tool-choice override mechanism from simple override to queue-based system with generator directives, lifecycle callbacks, and requeue preservation.
- Removed `PendingActionStore` class and replaced with `ToolChoiceQueue`; updated `ResolveTool` and custom tool loader to use queue invokers.
- Fixed tool-choice queue cleanup on agent loop abort and requeue semantics to preserve callbacks across abort cycles.
2026-04-08 22:10:37 +02:00

112 lines
3.9 KiB
TypeScript

import { describe, expect, it, vi } from "bun:test";
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
import { executeBuiltinSlashCommand } from "@oh-my-pi/pi-coding-agent/slash-commands/builtin-registry";
function createRuntimeHarness(overrides?: { setForcedToolChoice?: (toolName: string) => void }) {
const setForcedToolChoice = vi.fn(overrides?.setForcedToolChoice ?? ((_toolName: string) => {}));
const setText = vi.fn();
const showStatus = vi.fn();
const showError = vi.fn();
const ctx = {
editor: { setText } as unknown as InteractiveModeContext["editor"],
session: { setForcedToolChoice } as unknown as InteractiveModeContext["session"],
showStatus,
showError,
} as unknown as InteractiveModeContext;
return {
runtime: {
ctx,
handleBackgroundCommand: () => {},
},
setForcedToolChoice,
setText,
showStatus,
showError,
};
}
describe("/force slash command", () => {
it("forces the next round tool with colon syntax", async () => {
const harness = createRuntimeHarness();
const handled = await executeBuiltinSlashCommand("/force:write", harness.runtime);
expect(handled).toBe(true);
expect(harness.setForcedToolChoice).toHaveBeenCalledWith("write");
expect(harness.showStatus).toHaveBeenCalledWith("Next turn forced to use write.");
expect(harness.showError).not.toHaveBeenCalled();
expect(harness.setText).toHaveBeenCalledWith("");
});
it("shows usage when tool name is missing", async () => {
const harness = createRuntimeHarness();
const handled = await executeBuiltinSlashCommand("/force", harness.runtime);
expect(handled).toBe(true);
expect(harness.setForcedToolChoice).not.toHaveBeenCalled();
expect(harness.showError).toHaveBeenCalledWith("Usage: /force:<tool-name> [prompt]");
expect(harness.setText).toHaveBeenCalledWith("");
});
it("returns remaining prompt text when provided after tool name", async () => {
const harness = createRuntimeHarness();
const result = await executeBuiltinSlashCommand("/force:write fix the tests", harness.runtime);
expect(result).toBe("fix the tests");
expect(harness.setForcedToolChoice).toHaveBeenCalledWith("write");
expect(harness.showStatus).toHaveBeenCalledWith("Next turn forced to use write.");
expect(harness.showError).not.toHaveBeenCalled();
expect(harness.setText).toHaveBeenCalledWith("");
});
it("forces tool with space syntax", async () => {
const harness = createRuntimeHarness();
const result = await executeBuiltinSlashCommand("/force write", harness.runtime);
expect(result).toBe(true);
expect(harness.setForcedToolChoice).toHaveBeenCalledWith("write");
});
it("returns remaining prompt with space syntax", async () => {
const harness = createRuntimeHarness();
const result = await executeBuiltinSlashCommand("/force write fix the tests", harness.runtime);
expect(result).toBe("fix the tests");
expect(harness.setForcedToolChoice).toHaveBeenCalledWith("write");
});
it("surfaces session validation errors", async () => {
const harness = createRuntimeHarness({
setForcedToolChoice: () => {
throw new Error('Tool "write" is not currently active.');
},
});
const handled = await executeBuiltinSlashCommand("/force:write", harness.runtime);
expect(handled).toBe(true);
expect(harness.showError).toHaveBeenCalledWith('Tool "write" is not currently active.');
expect(harness.showStatus).not.toHaveBeenCalled();
expect(harness.setText).toHaveBeenCalledWith("");
});
it("does not pass through prompt when tool validation fails", async () => {
const harness = createRuntimeHarness({
setForcedToolChoice: () => {
throw new Error('Tool "write" is not currently active.');
},
});
const result = await executeBuiltinSlashCommand("/force:write fix stuff", harness.runtime);
expect(result).toBe(true);
expect(harness.showError).toHaveBeenCalledWith('Tool "write" is not currently active.');
});
});