From 62bbaa8e8e817706f42b46adfccd9caf9d689c6f Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 17 Mar 2026 10:23:53 -0400 Subject: [PATCH] feat: integrate hashline edit mode into active workflow (#870) (#872) --- .../pi-coding-agent/src/core/agent-session.ts | 36 +++++++++++++++++++ packages/pi-coding-agent/src/core/sdk.ts | 18 +++++++++- .../src/core/settings-manager.ts | 11 ++++++ .../src/core/slash-commands.ts | 1 + packages/pi-coding-agent/src/index.ts | 13 +++++++ .../src/modes/interactive/interactive-mode.ts | 27 ++++++++++++++ 6 files changed, 105 insertions(+), 1 deletion(-) diff --git a/packages/pi-coding-agent/src/core/agent-session.ts b/packages/pi-coding-agent/src/core/agent-session.ts index bd7c00fe8..b33b165a0 100644 --- a/packages/pi-coding-agent/src/core/agent-session.ts +++ b/packages/pi-coding-agent/src/core/agent-session.ts @@ -774,6 +774,42 @@ export class AgentSession { ); } + /** + * Switch edit mode between standard (text-match) and hashline (LINE#ID anchors). + * Swaps the active read/edit tools and rebuilds the system prompt. + */ + setEditMode(mode: "standard" | "hashline"): void { + this.settingsManager.setEditMode(mode); + + // Get current active tool registry keys + const currentKeys = new Set(); + for (const [key, tool] of this._toolRegistry.entries()) { + if (this.agent.state.tools.includes(tool)) { + currentKeys.add(key); + } + } + + // Swap read tools + if (mode === "hashline") { + currentKeys.delete("read"); + currentKeys.add("hashline_read"); + currentKeys.delete("edit"); + currentKeys.add("hashline_edit"); + } else { + currentKeys.delete("hashline_read"); + currentKeys.add("read"); + currentKeys.delete("hashline_edit"); + currentKeys.add("edit"); + } + + this.setActiveToolsByName([...currentKeys]); + } + + /** Current edit mode */ + get editMode(): "standard" | "hashline" { + return this.settingsManager.getEditMode(); + } + /** All messages including custom types like BashExecutionMessage */ get messages(): AgentMessage[] { return this.agent.state.messages; diff --git a/packages/pi-coding-agent/src/core/sdk.ts b/packages/pi-coding-agent/src/core/sdk.ts index 1c8050795..97e8c5f5e 100644 --- a/packages/pi-coding-agent/src/core/sdk.ts +++ b/packages/pi-coding-agent/src/core/sdk.ts @@ -30,6 +30,12 @@ import { editTool, findTool, grepTool, + hashlineCodingTools, + hashlineEditTool, + hashlineReadTool, + createHashlineCodingTools, + createHashlineEditTool, + createHashlineReadTool, lsTool, readOnlyTools, readTool, @@ -119,6 +125,13 @@ export { createGrepTool, createFindTool, createLsTool, + // Hashline edit mode + hashlineCodingTools, + hashlineEditTool, + hashlineReadTool, + createHashlineCodingTools, + createHashlineEditTool, + createHashlineReadTool, }; // Helper Functions @@ -238,7 +251,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} thinkingLevel = "off"; } - const defaultActiveToolNames: ToolName[] = ["read", "bash", "edit", "write", "lsp"]; + const editMode = settingsManager.getEditMode(); + const defaultActiveToolNames: ToolName[] = editMode === "hashline" + ? ["hashline_read", "bash", "hashline_edit", "write", "lsp"] + : ["read", "bash", "edit", "write", "lsp"]; const initialActiveToolNames: ToolName[] = options.tools ? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allTools) : defaultActiveToolNames; diff --git a/packages/pi-coding-agent/src/core/settings-manager.ts b/packages/pi-coding-agent/src/core/settings-manager.ts index 665b556b2..c7c73de30 100644 --- a/packages/pi-coding-agent/src/core/settings-manager.ts +++ b/packages/pi-coding-agent/src/core/settings-manager.ts @@ -142,6 +142,7 @@ export interface Settings { taskIsolation?: TaskIsolationSettings; fallback?: FallbackSettings; modelDiscovery?: ModelDiscoverySettings; + editMode?: "standard" | "hashline"; // Edit tool mode: "standard" (text match) or "hashline" (LINE#ID anchors). Default: "standard" } /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */ @@ -1127,4 +1128,14 @@ export class SettingsManager { this.markModified("modelDiscovery", "enabled"); this.save(); } + + getEditMode(): "standard" | "hashline" { + return this.settings.editMode ?? "standard"; + } + + setEditMode(mode: "standard" | "hashline"): void { + this.globalSettings.editMode = mode; + this.markModified("editMode"); + this.save(); + } } diff --git a/packages/pi-coding-agent/src/core/slash-commands.ts b/packages/pi-coding-agent/src/core/slash-commands.ts index 8c2800811..beacd41b9 100644 --- a/packages/pi-coding-agent/src/core/slash-commands.ts +++ b/packages/pi-coding-agent/src/core/slash-commands.ts @@ -36,5 +36,6 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray = [ { name: "resume", description: "Resume a different session" }, { name: "reload", description: "Reload extensions, skills, prompts, and themes" }, { name: "thinking", description: "Set thinking level (off/minimal/low/medium/high/xhigh)" }, + { name: "edit-mode", description: "Toggle edit mode (standard/hashline)" }, { name: "quit", description: "Quit pi" }, ]; diff --git a/packages/pi-coding-agent/src/index.ts b/packages/pi-coding-agent/src/index.ts index a53d45f6a..c76ba2c2d 100644 --- a/packages/pi-coding-agent/src/index.ts +++ b/packages/pi-coding-agent/src/index.ts @@ -279,6 +279,19 @@ export { type WriteToolInput, type WriteToolOptions, writeTool, + // Hashline edit mode tools + hashlineEditTool, + hashlineReadTool, + hashlineCodingTools, + createHashlineEditTool, + createHashlineReadTool, + createHashlineCodingTools, + type HashlineEditInput, + type HashlineEditToolDetails, + type HashlineEditToolOptions, + type HashlineReadToolDetails, + type HashlineReadToolInput, + type HashlineReadToolOptions, } from "./core/tools/index.js"; // Main entry point export { main } from "./main.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts index 9a01cc926..5faa9efa9 100644 --- a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts @@ -2063,6 +2063,12 @@ export class InteractiveMode { this.handleThinkingCommand(arg); return; } + if (text === "/edit-mode" || text.startsWith("/edit-mode ")) { + const arg = text.startsWith("/edit-mode ") ? text.slice(11).trim() : undefined; + this.editor.setText(""); + this.handleEditModeCommand(arg); + return; + } if (text === "/debug") { this.handleDebugCommand(); this.editor.setText(""); @@ -2891,6 +2897,27 @@ export class InteractiveMode { this.showThinkingSelector(); } + private handleEditModeCommand(arg?: string): void { + const modes = ["standard", "hashline"] as const; + + if (arg) { + const mode = arg.toLowerCase(); + if (!modes.includes(mode as typeof modes[number])) { + this.showStatus(`Invalid edit mode "${arg}". Available: standard, hashline`); + return; + } + this.session.setEditMode(mode as "standard" | "hashline"); + this.showStatus(`Edit mode: ${mode}${mode === "hashline" ? " (LINE#ID anchored edits)" : " (text-match edits)"}`); + return; + } + + // Toggle + const current = this.session.editMode; + const next = current === "standard" ? "hashline" : "standard"; + this.session.setEditMode(next); + this.showStatus(`Edit mode: ${next}${next === "hashline" ? " (LINE#ID anchored edits)" : " (text-match edits)"}`); + } + private showThinkingSelector(): void { const availableLevels = this.session.getAvailableThinkingLevels(); this.showSelector((done) => {