/** * Auto-naming via `pi -p` (S-09a). * * After a configurable number of user messages, spawn a cheap `pi -p` call * to generate a short session name from the conversation context. * The result is stored as the tmux session's @description. * * Gated by [autoname] enabled in config.toml (T-1.7 wires the config; * until then defaults are used). * * Owner: T-1.4 */ import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { setDescription } from "../tmux/manager.js"; const execFileAsync = promisify(execFile); export interface AutonameConfig { enabled: boolean; triggerAfter: number; // number of user messages before naming model: string; // e.g. "claude-haiku-4-5" } export const DEFAULT_AUTONAME_CONFIG: AutonameConfig = { enabled: true, triggerAfter: 3, model: "claude-haiku-4-5", }; /** * Attempt to auto-name a session using `pi -p`. * If pi is not on PATH or the call fails, silently no-ops. * * @param sessionId tmux session name to set @description on * @param context recent conversation context (short excerpt) * @param cfg autoname configuration */ export async function autoname( sessionId: string, context: string, cfg: AutonameConfig = DEFAULT_AUTONAME_CONFIG, ): Promise { if (!cfg.enabled) return; const prompt = `Give a 2-4 word title for this conversation. Reply with only the title, no punctuation.\n\n${context.slice(0, 800)}`; try { const { stdout } = await execFileAsync( "pi", [ "-p", "--model", cfg.model, "--no-session", "--no-tools", "--no-extensions", "--no-skills", "--offline", prompt, ], { timeout: 15_000 }, ); const name = stdout.trim().slice(0, 60); // cap at 60 chars if (name) { await setDescription(sessionId, name); } } catch { // Autoname failures are non-fatal } }