feat: integrate hashline edit mode into active workflow (#870) (#872)

This commit is contained in:
Tom Boucher 2026-03-17 10:23:53 -04:00 committed by GitHub
parent a8eb66b8b3
commit 62bbaa8e8e
6 changed files with 105 additions and 1 deletions

View file

@ -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<string>();
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;

View file

@ -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;

View file

@ -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();
}
}

View file

@ -36,5 +36,6 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
{ 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" },
];

View file

@ -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";

View file

@ -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) => {