fix(tui): attached pasted file paths

Converted bracketed non-image filesystem path pastes into session-local attachment references while preserving the existing image path flow.

Added regression coverage for editor routing and controller local file attachment behavior.

Fixes #3360
This commit is contained in:
roboomp
2026-06-24 14:21:45 +00:00
parent c053afa087
commit 4f20d10454
5 changed files with 209 additions and 50 deletions
@@ -15,15 +15,17 @@ import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/typ
function createContext(options?: { threshold?: number; choice?: string; artifactsDir?: string }) {
const insertPaste = vi.fn();
const insertText = vi.fn();
const pasteText = vi.fn();
const requestRender = vi.fn();
const showStatus = vi.fn();
const showError = vi.fn();
const showHookSelector = vi.fn(async (_title: string, _options: unknown, _dialog?: unknown) => options?.choice);
const ctx = {
editor: { insertPaste, insertText } as unknown as InteractiveModeContext["editor"],
editor: { insertPaste, insertText, pasteText } as unknown as InteractiveModeContext["editor"],
ui: { requestRender } as unknown as InteractiveModeContext["ui"],
settings: { get: () => options?.threshold ?? 100 } as unknown as InteractiveModeContext["settings"],
sessionManager: {
getCwd: () => process.cwd(),
getArtifactsDir: () => options?.artifactsDir ?? null,
getSessionId: () => "test-session",
} as unknown as InteractiveModeContext["sessionManager"],
@@ -32,7 +34,10 @@ function createContext(options?: { threshold?: number; choice?: string; artifact
showError,
} as unknown as InteractiveModeContext;
const controller = new InputController(ctx);
return { controller, spies: { insertPaste, insertText, requestRender, showStatus, showError, showHookSelector } };
return {
controller,
spies: { insertPaste, insertText, pasteText, requestRender, showStatus, showError, showHookSelector },
};
}
afterEach(() => {
@@ -144,4 +149,36 @@ describe("InputController.presentLargePasteMenu file attachment", () => {
expect(await Bun.file(path.join(dir, "local", "attachment-1")).text()).toBe("previous");
expect(await Bun.file(path.join(dir, "local", "attachment-2")).text()).toBe("fresh");
});
it("attaches an existing pasted file as a local:// reference with its extension", async () => {
dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-file-paste-test-"));
const sourceDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-file-paste-source-"));
const sourcePath = path.join(sourceDir, "report.csv");
await Bun.write(sourcePath, "name,value\nalpha,1\n");
const { controller, spies } = createContext({ artifactsDir: dir });
try {
await controller.handleFilePathPaste(sourcePath);
} finally {
await fs.rm(sourceDir, { recursive: true, force: true });
}
expect(spies.insertText).toHaveBeenCalledWith("local://attachment-1.csv ");
expect(spies.pasteText).not.toHaveBeenCalled();
expect(spies.requestRender).toHaveBeenCalled();
expect(spies.showStatus).toHaveBeenCalledWith("Attached file as local://attachment-1.csv");
expect(await Bun.file(path.join(dir, "local", "attachment-1.csv")).text()).toBe("name,value\nalpha,1\n");
});
it("falls back to inline text when the pasted file path does not exist", async () => {
dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-file-paste-test-"));
const missing = path.join(dir, "missing.txt");
const { controller, spies } = createContext({ artifactsDir: dir });
await controller.handleFilePathPaste(missing);
expect(spies.insertText).not.toHaveBeenCalled();
expect(spies.pasteText).toHaveBeenCalledWith(missing);
expect(spies.showStatus).toHaveBeenCalledWith("Pasted file path was not found");
});
});