fix(coding-agent): auto-name approved plan sessions
This commit is contained in:
@@ -41,7 +41,12 @@ import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slas
|
||||
import type { Goal, GoalModeState } from "../goals/state";
|
||||
import { resolveLocalUrlToPath } from "../internal-urls";
|
||||
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
||||
import { normalizePlanTitle, type PlanApprovalDetails, renameApprovedPlanFile } from "../plan-mode/approved-plan";
|
||||
import {
|
||||
humanizePlanTitle,
|
||||
normalizePlanTitle,
|
||||
type PlanApprovalDetails,
|
||||
renameApprovedPlanFile,
|
||||
} from "../plan-mode/approved-plan";
|
||||
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
||||
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
||||
type: "text",
|
||||
@@ -1440,6 +1445,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
options: {
|
||||
planFilePath: string;
|
||||
finalPlanFilePath: string;
|
||||
title: string;
|
||||
preserveContext?: boolean;
|
||||
compactBeforeExecute?: boolean;
|
||||
},
|
||||
@@ -1523,6 +1529,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
return;
|
||||
}
|
||||
|
||||
// Approved plans land in a fresh (or compacted) session whose first user-visible
|
||||
// turn is the synthetic plan-approved prompt — that path bypasses the
|
||||
// input-controller's title generation. Seed an auto-name from the plan title
|
||||
// so the session is not left unnamed. `setSessionName("auto")` is a no-op
|
||||
// when the user has already chosen a name (preserveContext paths).
|
||||
const seededName = humanizePlanTitle(options.title);
|
||||
if (seededName && !this.sessionManager.getSessionName()) {
|
||||
const applied = await this.sessionManager.setSessionName(seededName, "auto");
|
||||
if (applied) {
|
||||
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
||||
this.updateEditorBorderColor();
|
||||
}
|
||||
}
|
||||
|
||||
// markPlanReferenceSent fires only on the dispatch path so the synthetic
|
||||
// plan-approved prompt is the source of the reference injection.
|
||||
this.session.markPlanReferenceSent();
|
||||
@@ -1828,6 +1848,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
await this.#approvePlan(latestPlanContent, {
|
||||
planFilePath,
|
||||
finalPlanFilePath,
|
||||
title: details.title,
|
||||
preserveContext: choice !== "Approve and execute",
|
||||
compactBeforeExecute: choice === "Approve and compact context",
|
||||
});
|
||||
|
||||
@@ -37,6 +37,15 @@ export function normalizePlanTitle(title: string): { title: string; fileName: st
|
||||
return { title: normalizedTitle, fileName: withExtension };
|
||||
}
|
||||
|
||||
/** Humanize a normalized plan title for use as a session display name.
|
||||
* Replaces `-`/`_` separators with spaces and capitalizes the first letter.
|
||||
* Returns an empty string when the input collapses to whitespace. */
|
||||
export function humanizePlanTitle(title: string): string {
|
||||
const spaced = title.replace(/[-_]+/g, " ").trim();
|
||||
if (!spaced) return "";
|
||||
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
||||
}
|
||||
|
||||
interface RenameApprovedPlanFileOptions {
|
||||
planFilePath: string;
|
||||
finalPlanFilePath: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { renameApprovedPlanFile } from "@oh-my-pi/pi-coding-agent/plan-mode/approved-plan";
|
||||
import { humanizePlanTitle, renameApprovedPlanFile } from "@oh-my-pi/pi-coding-agent/plan-mode/approved-plan";
|
||||
|
||||
describe("renameApprovedPlanFile", () => {
|
||||
let tmpDir: string;
|
||||
@@ -45,3 +45,20 @@ describe("renameApprovedPlanFile", () => {
|
||||
await expect(fs.stat(path.join(artifactsDir, "local", "PLAN.md"))).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("humanizePlanTitle", () => {
|
||||
it("replaces separators with spaces and capitalizes", () => {
|
||||
expect(humanizePlanTitle("migrate-mcp-loader")).toBe("Migrate mcp loader");
|
||||
expect(humanizePlanTitle("fix_session_naming")).toBe("Fix session naming");
|
||||
expect(humanizePlanTitle("RefactorRouter")).toBe("RefactorRouter");
|
||||
});
|
||||
|
||||
it("collapses runs of separators", () => {
|
||||
expect(humanizePlanTitle("foo--bar__baz")).toBe("Foo bar baz");
|
||||
});
|
||||
|
||||
it("returns empty string for blank-ish input", () => {
|
||||
expect(humanizePlanTitle("")).toBe("");
|
||||
expect(humanizePlanTitle("---")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user