/** * Edit Session Extension * * Adds /edit-session command to open the current session JSONL in your default editor. * * Usage: * /edit-session - Open current session file in $EDITOR * * The editor is determined by $VISUAL, then $EDITOR, then falls back to 'vi'. */ import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent"; import type { TUI, KeybindingsManager, Component } from "@mariozechner/pi-tui"; import { spawnSync } from "node:child_process"; export default function editSessionExtension(pi: ExtensionAPI) { pi.registerCommand("edit-session", { description: "Open session JSONL in default editor ($VISUAL/$EDITOR/vi)", handler: async (_args: string, ctx: ExtensionCommandContext) => { const sessionFile = ctx.sessionManager.getSessionFile(); if (!sessionFile) { ctx.ui.notify("No session file (ephemeral mode)", "error"); return; } const editorCmd = process.env.VISUAL || process.env.EDITOR; if (!editorCmd) { ctx.ui.notify("No editor configured. Set $VISUAL or $EDITOR.", "error"); return; } // Factory function to create component and run editor const factory = ( tui: TUI, _theme: Theme, _kb: KeybindingsManager, done: (result: void) => void ): Component => { // Stop TUI to release terminal (same as Ctrl+G) tui.stop(); // Split editor command to support args (e.g., "code --wait") const [editor, ...editorArgs] = editorCmd.split(" "); // Spawn editor synchronously with inherited stdio const result = spawnSync(editor, [...editorArgs, sessionFile], { stdio: "inherit", shell: process.platform === "win32", }); // Restart TUI tui.start(); // Force full re-render since external editor uses alternate screen tui.requestRender(true); if (result.status !== 0 && result.status !== null) { ctx.ui.notify(`Editor exited with code ${result.status}`, "warning"); } done(undefined); // Return dummy component return createDummyComponent(); }; // Use ctx.ui.custom to get access to the TUI await ctx.ui.custom(factory); // Signal that we're about to reload the session (so confirm-destructive skips) pi.events.emit("edit-session:reload", undefined); // Reload the session by switching to the same file (forces re-read from disk) ctx.ui.notify("Reloading session...", "info"); const result = await ctx.switchSession(sessionFile); if (result.cancelled) { // If still cancelled (by another extension), just notify without warning ctx.ui.notify("Session reload skipped. Run /reload to apply changes.", "info"); } else { ctx.ui.notify("Session reloaded with changes.", "info"); } }, }); } function createDummyComponent(): Component { return { render: (_width: number): string[] => [], invalidate: (): void => {}, }; }