Files
oh-my-pi/packages/coding-agent/test/slash-commands/plan-history.test.ts
T
oldschoola 715eb0792c fix: store slash commands in input history (#3148)
Previously only 4 commands (/plan, /goal, /mcp, /ssh) stored their text
in history via per-handler addToHistory calls. All other built-in slash
commands were silently skipped because executeBuiltinSlashCommand returned
true before the input controller's addToHistory was reached.

- Centralize history recording in the input controller after successful
  slash command dispatch, for both Enter and Ctrl+Enter submit paths.
- Remove all 10 per-command addToHistory calls from slash command handlers
  to prevent duplicates.
- Add shouldSkipHistory() security filter to exclude commands that may
  carry secrets: /login <url> (OAuth callback with code=/state= params)
  and /mcp add --token <token> (bearer token).
- Add regression tests for the security filter (8 cases).
- Update 7 existing test files to remove handler-level addToHistory
  assertions (now the input controller's responsibility).
2026-06-23 14:11:12 -07:00

126 lines
4.3 KiB
TypeScript

import { describe, expect, it, mock } 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";
/**
* Build a minimal ctx that simulates plan/goal-mode handlers.
*
* `confirmExit` controls what handlePlanModeCommand does when plan mode is
* already active: `true` simulates the user confirming exit (mode flips off);
* `false` simulates cancel (mode stays on).
*/
function createPlanHarness(opts: { planModeEnabled: boolean; confirmExit: boolean }) {
const state = { planModeEnabled: opts.planModeEnabled };
const addToHistory = mock((_text: string) => {});
const setText = mock((_text: string) => {});
const ctx = {
editor: { addToHistory, setText } as unknown as InteractiveModeContext["editor"],
get planModeEnabled() {
return state.planModeEnabled;
},
handlePlanModeCommand: mock(async (_initialPrompt?: string) => {
// Mirror interactive-mode.ts: if already active, confirm → exit; else enter.
if (state.planModeEnabled) {
if (opts.confirmExit) state.planModeEnabled = false;
return;
}
state.planModeEnabled = true;
}),
} as unknown as InteractiveModeContext;
return {
runtime: { ctx },
state,
addToHistory,
setText,
};
}
function createGoalHarness(opts: { goalModeEnabled: boolean; dropOnCall: boolean }) {
const state = { goalModeEnabled: opts.goalModeEnabled };
const addToHistory = mock((_text: string) => {});
const setText = mock((_text: string) => {});
const ctx = {
editor: { addToHistory, setText } as unknown as InteractiveModeContext["editor"],
get goalModeEnabled() {
return state.goalModeEnabled;
},
handleGoalModeCommand: mock(async (rest?: string) => {
if (state.goalModeEnabled && opts.dropOnCall) state.goalModeEnabled = false;
else if (!state.goalModeEnabled && rest) state.goalModeEnabled = true;
}),
} as unknown as InteractiveModeContext;
return {
runtime: { ctx },
state,
addToHistory,
setText,
};
}
describe("/plan handler when already active", () => {
it("exits plan mode when user confirms exit", async () => {
const h = createPlanHarness({ planModeEnabled: true, confirmExit: true });
const handled = await executeBuiltinSlashCommand("/plan hello world", h.runtime);
expect(handled).toBe(true);
// Sanity check: exit was confirmed, so plan mode is now off.
expect(h.state.planModeEnabled).toBe(false);
expect(h.setText).toHaveBeenCalledWith("");
});
it("keeps plan mode active when user cancels exit", async () => {
const h = createPlanHarness({ planModeEnabled: true, confirmExit: false });
const handled = await executeBuiltinSlashCommand("/plan hello world", h.runtime);
expect(handled).toBe(true);
// Cancel: plan mode stays active.
expect(h.state.planModeEnabled).toBe(true);
expect(h.setText).toHaveBeenCalledWith("");
});
it("enters plan mode when invoked for the first time", async () => {
const h = createPlanHarness({ planModeEnabled: false, confirmExit: false });
await executeBuiltinSlashCommand("/plan hello world", h.runtime);
expect(h.state.planModeEnabled).toBe(true);
expect(h.setText).toHaveBeenCalledWith("");
});
});
describe("/goal handler when already active", () => {
it("drops goal mode when handler clears it", async () => {
// Simulates the user invoking a drop path that turns goal mode off
// inside handleGoalModeCommand. Without capturing state up-front,
// the post-call check would miss this case.
const h = createGoalHarness({ goalModeEnabled: true, dropOnCall: true });
const handled = await executeBuiltinSlashCommand("/goal new objective", h.runtime);
expect(handled).toBe(true);
expect(h.state.goalModeEnabled).toBe(false);
});
it("keeps goal mode active when handler does not clear it", async () => {
const h = createGoalHarness({ goalModeEnabled: true, dropOnCall: false });
await executeBuiltinSlashCommand("/goal new objective", h.runtime);
expect(h.state.goalModeEnabled).toBe(true);
});
it("enters goal mode when invoked for the first time", async () => {
const h = createGoalHarness({ goalModeEnabled: false, dropOnCall: false });
await executeBuiltinSlashCommand("/goal new objective", h.runtime);
expect(h.state.goalModeEnabled).toBe(true);
});
});