import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent"; import type { TreeLabelerSettings } from "./types.ts"; function normalize(value: string, maxChars: number): string | undefined { const cleaned = value .replace(/\u001b\[[0-?]*[ -\/]*[@-~]/g, "") .replace(/[\u0000-\u001f\u007f]+/g, " ") .replace(/\s+/g, " ") .trim() .replace(/^[`'\"]+|[`'\"]+$/g, "") .trim(); if (!cleaned) return undefined; return cleaned.length <= maxChars ? cleaned : `${cleaned.slice(0, Math.max(1, maxChars - 1)).trimEnd()}…`; } function messageText(entry: SessionEntry): string | undefined { if (entry.type !== "message" || entry.message.role !== "user") return undefined; const content = entry.message.content; if (typeof content === "string") return content; if (!Array.isArray(content)) return undefined; return content .filter((block): block is { type: "text"; text: string } => block.type === "text") .map((block) => block.text) .join(" "); } export function deriveCheckedOutSectionName( branch: SessionEntry[], getLabel: (entryId: string) => string | undefined, settings: TreeLabelerSettings["sessionNaming"], ): string | undefined { if (!settings.enabled) return undefined; for (let index = branch.length - 1; index >= 0; index--) { const label = getLabel(branch[index].id); if (label) return normalize(label, settings.maxChars); } if (!settings.fallbackToMessage) return undefined; for (let index = branch.length - 1; index >= 0; index--) { const text = messageText(branch[index]); if (text) return normalize(text, settings.maxChars); } return undefined; } export function syncSessionName(pi: ExtensionAPI, ctx: ExtensionContext, settings: TreeLabelerSettings): string | undefined { const name = deriveCheckedOutSectionName( ctx.sessionManager.getBranch(), (entryId) => ctx.sessionManager.getLabel(entryId), settings.sessionNaming, ); if (name && name !== ctx.sessionManager.getSessionName()) pi.setSessionName(name); return name; }