From d23e67933150bb6c2ac9c6e8c8ff2b22572bf10c Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Mon, 23 Mar 2026 11:35:12 -0700 Subject: [PATCH] Remove flask ASCII art and update harness internals Co-Authored-By: Claude Opus 4.6 (1M context) --- extensions/research-tools.ts | 398 ++++++++++++++-------------------- package-lock.json | 68 +++--- package.json | 16 +- scripts/patch-embedded-pi.mjs | 38 ++-- src/cli.ts | 3 +- src/pi/settings.ts | 11 +- tests/pi-settings.test.ts | 18 ++ 7 files changed, 254 insertions(+), 298 deletions(-) create mode 100644 tests/pi-settings.test.ts diff --git a/extensions/research-tools.ts b/extensions/research-tools.ts index 8e49de4..0b723b6 100644 --- a/extensions/research-tools.ts +++ b/extensions/research-tools.ts @@ -44,16 +44,7 @@ const FEYNMAN_AGENT_LOGO = [ "╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝", ]; -const FEYNMAN_MARK_ART = [ - " .-.", - " /___\\\\", - " |:::|", - " |:::|", - " .-'`:::::`'-.", - " /:::::::::::::\\\\", - " \\\\:::::::::::://", - " '-.______.-'", -]; +const FEYNMAN_MARK_ART: string[] = []; const FEYNMAN_RESEARCH_TOOLS = [ "alpha_search", @@ -754,34 +745,6 @@ Recommended contents: `; } -type HelpCommand = { - usage: string; - description: string; -}; - -function buildFeynmanHelpSections(pi: ExtensionAPI): Array<{ title: string; commands: HelpCommand[] }> { - const commands = pi.getCommands(); - const promptCommands = sortCommands(commands.filter((command) => command.source === "prompt")).map((command) => ({ - usage: `/${command.name}`, - description: command.description ?? "Prompt workflow", - })); - const extensionCommands = sortCommands(commands.filter((command) => command.source === "extension")).map((command) => ({ - usage: `/${command.name}`, - description: command.description ?? "Extension command", - })); - - return [ - { - title: "Prompt Workflows", - commands: promptCommands, - }, - { - title: "Commands", - commands: extensionCommands, - }, - ]; -} - export default function researchTools(pi: ExtensionAPI): void { let skillSummaryPromise: Promise | undefined; let agentSummaryPromise: Promise | undefined; @@ -801,186 +764,168 @@ export default function researchTools(pi: ExtensionAPI): void { ctx.ui.setHeader((_tui, theme) => ({ render(width: number): string[] { const maxAvailableWidth = Math.max(width - 2, 1); - const preferredWidth = Math.min(136, Math.max(72, maxAvailableWidth)); - const cardWidth = Math.min(maxAvailableWidth, preferredWidth); - const innerWidth = cardWidth - 2; - const outerPadding = " ".repeat(Math.max(0, Math.floor((width - cardWidth) / 2))); - const title = truncateForWidth(` Feynman Research Agent v${FEYNMAN_VERSION} `, innerWidth); - const titledBorder = buildTitledBorder(innerWidth, title); - const modelLabel = getCurrentModelLabel(ctx); - const sessionLabel = ctx.sessionManager.getSessionName()?.trim() || ctx.sessionManager.getSessionId(); - const directoryLabel = formatHeaderPath(ctx.cwd); - const recentActivity = getRecentActivitySummary(ctx); - const lines: string[] = []; + const preferredWidth = Math.min(136, Math.max(72, maxAvailableWidth)); + const cardWidth = Math.min(maxAvailableWidth, preferredWidth); + const innerWidth = cardWidth - 2; + const outerPadding = " ".repeat(Math.max(0, Math.floor((width - cardWidth) / 2))); + const title = truncateForWidth(` Feynman Research Agent v${FEYNMAN_VERSION} `, innerWidth); + const titledBorder = buildTitledBorder(innerWidth, title); + const modelLabel = getCurrentModelLabel(ctx); + const sessionLabel = ctx.sessionManager.getSessionName()?.trim() || ctx.sessionManager.getSessionId(); + const directoryLabel = formatHeaderPath(ctx.cwd); + const recentActivity = getRecentActivitySummary(ctx); + const lines: string[] = []; - const push = (line: string): void => { - lines.push(`${outerPadding}${line}`); - }; - - const renderBoxLine = (content: string): string => - `${theme.fg("borderMuted", "│")}${content}${theme.fg("borderMuted", "│")}`; - const renderDivider = (): string => - `${theme.fg("borderMuted", "├")}${theme.fg("borderMuted", "─".repeat(innerWidth))}${theme.fg("borderMuted", "┤")}`; - const styleAccentCell = (text: string, cellWidth: number): string => - theme.fg("accent", theme.bold(padCell(text, cellWidth))); - const styleMutedCell = (text: string, cellWidth: number): string => - theme.fg("muted", padCell(text, cellWidth)); - const styleSuccessCell = (text: string, cellWidth: number): string => - theme.fg("success", theme.bold(padCell(text, cellWidth))); - const styleWarningCell = (text: string, cellWidth: number): string => - theme.fg("warning", theme.bold(padCell(text, cellWidth))); - - push(""); - for (const logoLine of FEYNMAN_AGENT_LOGO) { - push(theme.fg("accent", theme.bold(centerText(logoLine, cardWidth)))); - } - push(""); - push( - theme.fg("borderMuted", `╭${titledBorder.left}`) + - theme.fg("accent", theme.bold(title)) + - theme.fg("borderMuted", `${titledBorder.right}╮`), - ); - - if (innerWidth < 72) { - const activityLines = wrapForWidth(recentActivity, innerWidth, 2); - push(renderBoxLine(padCell("", innerWidth))); - push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Research session ready", innerWidth))))); - push(renderBoxLine(padCell(`model: ${modelLabel}`, innerWidth))); - push(renderBoxLine(padCell(`session: ${sessionLabel}`, innerWidth))); - push(renderBoxLine(padCell(`directory: ${directoryLabel}`, innerWidth))); - push(renderDivider()); - push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Available tools", innerWidth))))); - for (const toolLine of toolSummary.lines.slice(0, 4)) { - push(renderBoxLine(padCell(toolLine, innerWidth))); - } - push(renderDivider()); - push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Slash Commands", innerWidth))))); - for (const commandLine of commandSummary.lines.slice(0, 4)) { - push(renderBoxLine(padCell(commandLine, innerWidth))); - } - push(renderDivider()); - push(renderBoxLine(theme.fg("success", theme.bold(padCell("Research Skills", innerWidth))))); - for (const skillLine of skillSummary.lines.slice(0, 4)) { - push(renderBoxLine(padCell(skillLine, innerWidth))); - } - if (agentSummary.lines.length > 0) { - push(renderDivider()); - push(renderBoxLine(theme.fg("warning", theme.bold(padCell("Project Agents", innerWidth))))); - for (const agentLine of agentSummary.lines.slice(0, 3)) { - push(renderBoxLine(padCell(agentLine, innerWidth))); - } - } - push(renderDivider()); - push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Recent activity", innerWidth))))); - for (const activityLine of activityLines.length > 0 ? activityLines : ["No messages yet in this session."]) { - push(renderBoxLine(padCell(activityLine, innerWidth))); - } - push(renderDivider()); - push( - renderBoxLine( - padCell( - `${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills · /help`, - innerWidth, - ), - ), - ); - } else { - const leftWidth = Math.min(44, Math.max(30, Math.floor(innerWidth * 0.36))); - const rightWidth = innerWidth - leftWidth - 3; - const activityLines = wrapForWidth(recentActivity, innerWidth, 2); - const wrappedToolLines = toolSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 3)); - const wrappedCommandLines = commandSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4)); - const wrappedSkillLines = skillSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4)); - const wrappedAgentLines = agentSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4)); - const wrappedModelLines = wrapForWidth(`model: ${modelLabel}`, leftWidth, 2); - const wrappedDirectoryLines = wrapForWidth(`directory: ${directoryLabel}`, leftWidth, 2); - const wrappedSessionLines = wrapForWidth(`session: ${sessionLabel}`, leftWidth, 2); - const wrappedFooterLines = wrapForWidth( - `${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills · /help`, - leftWidth, - 2, - ); - const leftLines = [ - ...FEYNMAN_MARK_ART.map((line) => centerText(line, leftWidth)), - "", - centerText("Research shell ready", leftWidth), - "", - ...wrappedModelLines, - ...wrappedDirectoryLines, - ...wrappedSessionLines, - "", - ...wrappedFooterLines, - ]; - const rightLines = [ - "Available Tools", - ...wrappedToolLines, - "", - "Slash Commands", - ...wrappedCommandLines, - "", - "Research Skills", - ...wrappedSkillLines, - ...(wrappedAgentLines.length > 0 ? ["", "Project Agents", ...wrappedAgentLines] : []), - "", - "Recent Activity", - ...(activityLines.length > 0 ? activityLines : ["No messages yet in this session."]), - ]; - const row = ( - left: string, - right: string, - options?: { leftAccent?: boolean; rightAccent?: boolean; leftMuted?: boolean; rightMuted?: boolean }, - ): string => { - const leftCell = options?.leftAccent - ? styleAccentCell(left, leftWidth) - : options?.leftMuted - ? styleMutedCell(left, leftWidth) - : padCell(left, leftWidth); - const rightCell = options?.rightAccent - ? styleAccentCell(right, rightWidth) - : options?.rightMuted - ? styleMutedCell(right, rightWidth) - : padCell(right, rightWidth); - return renderBoxLine(`${leftCell}${theme.fg("borderMuted", " │ ")}${rightCell}`); + const push = (line: string): void => { + lines.push(`${outerPadding}${line}`); }; - push(renderBoxLine(padCell("", innerWidth))); - for (let index = 0; index < Math.max(leftLines.length, rightLines.length); index += 1) { - const left = leftLines[index] ?? ""; - const right = rightLines[index] ?? ""; - const isLogoLine = index < FEYNMAN_MARK_ART.length; - const isRightSectionHeading = - right === "Available Tools" || right === "Slash Commands" || right === "Research Skills" || right === "Project Agents" || - right === "Recent Activity"; - const isResearchHeading = right === "Research Skills"; - const isAgentHeading = right === "Project Agents"; - const isFooterLine = left.includes("/help"); - push( - (() => { - const leftCell = isLogoLine - ? styleAccentCell(left, leftWidth) - : !isFooterLine && index >= FEYNMAN_MARK_ART.length + 2 - ? styleMutedCell(left, leftWidth) - : padCell(left, leftWidth); - const rightCell = isResearchHeading - ? styleSuccessCell(right, rightWidth) - : isAgentHeading - ? styleWarningCell(right, rightWidth) - : isRightSectionHeading - ? styleAccentCell(right, rightWidth) - : right.length > 0 - ? styleMutedCell(right, rightWidth) - : padCell(right, rightWidth); - return renderBoxLine(`${leftCell}${theme.fg("borderMuted", " │ ")}${rightCell}`); - })(), - ); - } - } + const renderBoxLine = (content: string): string => + `${theme.fg("borderMuted", "│")}${content}${theme.fg("borderMuted", "│")}`; + const renderDivider = (): string => + `${theme.fg("borderMuted", "├")}${theme.fg("borderMuted", "─".repeat(innerWidth))}${theme.fg("borderMuted", "┤")}`; + const styleAccentCell = (text: string, cellWidth: number): string => + theme.fg("accent", theme.bold(padCell(text, cellWidth))); + const styleMutedCell = (text: string, cellWidth: number): string => + theme.fg("muted", padCell(text, cellWidth)); + const styleSuccessCell = (text: string, cellWidth: number): string => + theme.fg("success", theme.bold(padCell(text, cellWidth))); + const styleWarningCell = (text: string, cellWidth: number): string => + theme.fg("warning", theme.bold(padCell(text, cellWidth))); - push(theme.fg("borderMuted", `╰${"─".repeat(innerWidth)}╯`)); - push(""); - return lines; - }, - invalidate() {}, + push(""); + for (const logoLine of FEYNMAN_AGENT_LOGO) { + push(theme.fg("accent", theme.bold(centerText(logoLine, cardWidth)))); + } + push(""); + push( + theme.fg("borderMuted", `╭${titledBorder.left}`) + + theme.fg("accent", theme.bold(title)) + + theme.fg("borderMuted", `${titledBorder.right}╮`), + ); + + if (innerWidth < 72) { + const activityLines = wrapForWidth(recentActivity, innerWidth, 2); + push(renderBoxLine(padCell("", innerWidth))); + push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Research session ready", innerWidth))))); + push(renderBoxLine(padCell(`model: ${modelLabel}`, innerWidth))); + push(renderBoxLine(padCell(`session: ${sessionLabel}`, innerWidth))); + push(renderBoxLine(padCell(`directory: ${directoryLabel}`, innerWidth))); + push(renderDivider()); + push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Available tools", innerWidth))))); + for (const toolLine of toolSummary.lines.slice(0, 4)) { + push(renderBoxLine(padCell(toolLine, innerWidth))); + } + push(renderDivider()); + push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Slash Commands", innerWidth))))); + for (const commandLine of commandSummary.lines.slice(0, 4)) { + push(renderBoxLine(padCell(commandLine, innerWidth))); + } + push(renderDivider()); + push(renderBoxLine(theme.fg("success", theme.bold(padCell("Research Skills", innerWidth))))); + for (const skillLine of skillSummary.lines.slice(0, 4)) { + push(renderBoxLine(padCell(skillLine, innerWidth))); + } + if (agentSummary.lines.length > 0) { + push(renderDivider()); + push(renderBoxLine(theme.fg("warning", theme.bold(padCell("Project Agents", innerWidth))))); + for (const agentLine of agentSummary.lines.slice(0, 3)) { + push(renderBoxLine(padCell(agentLine, innerWidth))); + } + } + push(renderDivider()); + push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Recent activity", innerWidth))))); + for (const activityLine of activityLines.length > 0 ? activityLines : ["No messages yet in this session."]) { + push(renderBoxLine(padCell(activityLine, innerWidth))); + } + push(renderDivider()); + push( + renderBoxLine( + padCell( + `${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills`, + innerWidth, + ), + ), + ); + } else { + const leftWidth = Math.min(44, Math.max(30, Math.floor(innerWidth * 0.36))); + const rightWidth = innerWidth - leftWidth - 3; + const activityLines = wrapForWidth(recentActivity, innerWidth, 2); + const wrappedToolLines = toolSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 3)); + const wrappedCommandLines = commandSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4)); + const wrappedSkillLines = skillSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4)); + const wrappedAgentLines = agentSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4)); + const wrappedModelLines = wrapForWidth(`model: ${modelLabel}`, leftWidth, 2); + const wrappedDirectoryLines = wrapForWidth(`directory: ${directoryLabel}`, leftWidth, 2); + const wrappedSessionLines = wrapForWidth(`session: ${sessionLabel}`, leftWidth, 2); + const wrappedFooterLines = wrapForWidth( + `${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills`, + leftWidth, + 2, + ); + const leftLines = [ + ...FEYNMAN_MARK_ART.map((line) => centerText(line, leftWidth)), + "", + centerText("Research shell ready", leftWidth), + "", + ...wrappedModelLines, + ...wrappedDirectoryLines, + ...wrappedSessionLines, + "", + ...wrappedFooterLines, + ]; + const rightLines = [ + "Available Tools", + ...wrappedToolLines, + "", + "Slash Commands", + ...wrappedCommandLines, + "", + "Research Skills", + ...wrappedSkillLines, + ...(wrappedAgentLines.length > 0 ? ["", "Project Agents", ...wrappedAgentLines] : []), + "", + "Recent Activity", + ...(activityLines.length > 0 ? activityLines : ["No messages yet in this session."]), + ]; + + push(renderBoxLine(padCell("", innerWidth))); + for (let index = 0; index < Math.max(leftLines.length, rightLines.length); index += 1) { + const left = leftLines[index] ?? ""; + const right = rightLines[index] ?? ""; + const isLogoLine = index < FEYNMAN_MARK_ART.length; + const isRightSectionHeading = + right === "Available Tools" || right === "Slash Commands" || right === "Research Skills" || right === "Project Agents" || + right === "Recent Activity"; + const isResearchHeading = right === "Research Skills"; + const isAgentHeading = right === "Project Agents"; + push( + (() => { + const leftCell = isLogoLine + ? styleAccentCell(left, leftWidth) + : index >= FEYNMAN_MARK_ART.length + 2 + ? styleMutedCell(left, leftWidth) + : padCell(left, leftWidth); + const rightCell = isResearchHeading + ? styleSuccessCell(right, rightWidth) + : isAgentHeading + ? styleWarningCell(right, rightWidth) + : isRightSectionHeading + ? styleAccentCell(right, rightWidth) + : right.length > 0 + ? styleMutedCell(right, rightWidth) + : padCell(right, rightWidth); + return renderBoxLine(`${leftCell}${theme.fg("borderMuted", " │ ")}${rightCell}`); + })(), + ); + } + } + + push(theme.fg("borderMuted", `╰${"─".repeat(innerWidth)}╯`)); + push(""); + return lines; + }, + invalidate() {}, })); } @@ -1028,31 +973,6 @@ export default function researchTools(pi: ExtensionAPI): void { }, }); - pi.registerCommand("help", { - description: "Show grouped Feynman commands and prefill the editor with a selected command.", - handler: async (_args, ctx) => { - const sections = buildFeynmanHelpSections(pi); - const items = sections.flatMap((section) => [ - `--- ${section.title} ---`, - ...section.commands.map((command) => `${command.usage} — ${command.description}`), - ]).filter((item, index, array) => { - if (!item.startsWith("---")) { - return true; - } - return array[index + 1] !== undefined && !array[index + 1].startsWith("---"); - }); - - const selected = await ctx.ui.select("Feynman Help", items); - if (!selected || selected.startsWith("---")) { - return; - } - - const usage = selected.split(" — ")[0]; - ctx.ui.setEditorText(usage); - ctx.ui.notify(`Prefilled ${usage}`, "info"); - }, - }); - pi.registerCommand("init", { description: "Initialize AGENTS.md and session-log folders for a research project.", handler: async (_args, ctx) => { diff --git a/package-lock.json b/package-lock.json index b635b2b..21ffef5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,21 +10,21 @@ "hasInstallScript": true, "dependencies": { "@companion-ai/alpha-hub": "^0.1.2", - "@mariozechner/pi-ai": "^0.56.1", - "@mariozechner/pi-coding-agent": "^0.56.1", - "@sinclair/typebox": "^0.34.41", - "dotenv": "^16.4.7" + "@mariozechner/pi-ai": "^0.62.0", + "@mariozechner/pi-coding-agent": "^0.62.0", + "@sinclair/typebox": "^0.34.48", + "dotenv": "^17.3.1" }, "bin": { "feynman": "bin/feynman.js" }, "devDependencies": { - "@types/node": "^24.3.0", - "tsx": "^4.20.5", - "typescript": "^5.7.3" + "@types/node": "^25.5.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3" }, "engines": { - "node": ">=20.6.0" + "node": ">=20.18.1" } }, "../alpha-hub/cli": { @@ -1441,21 +1441,21 @@ } }, "node_modules/@mariozechner/pi-agent-core": { - "version": "0.56.3", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.56.3.tgz", - "integrity": "sha512-TsI1zENf3wqqKPaERnj486Q4i6Y/y6lAZipLNcfDYUDxDrLwNfQ9EW9xukkbJfTZ8zjG3VZ2pBZe3C7wM51dVQ==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.62.0.tgz", + "integrity": "sha512-SBjqgDrgKOaC+IGzFGB3jXQErv9H1QMYnWFvUg6ra6dG0ZgWFBUZb6unidngWLsmaxSDWes6KeKiVFMsr2VSEQ==", "license": "MIT", "dependencies": { - "@mariozechner/pi-ai": "^0.56.3" + "@mariozechner/pi-ai": "^0.62.0" }, "engines": { "node": ">=20.0.0" } }, "node_modules/@mariozechner/pi-ai": { - "version": "0.56.3", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.56.3.tgz", - "integrity": "sha512-l4J+cVyVeBLAlGOY/osGDvsbTz0DySCQmR171G6SdbPvIeLGhIi6siZ+zHwq91GJYjv/wtu/08M08ag2mGZKeA==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.62.0.tgz", + "integrity": "sha512-mJgryZ5RgBQG++tiETMtCQQJoH2MAhKetCfqI98NMvGydu7L9x2qC2JekQlRaAgIlTgv4MRH1UXHMEs4UweE/Q==", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.73.0", @@ -1480,15 +1480,15 @@ } }, "node_modules/@mariozechner/pi-coding-agent": { - "version": "0.56.3", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.56.3.tgz", - "integrity": "sha512-yHgnadye+TT/4NWKBirZUjw/LWdNWTa7M4HJdX2RxRbwuj4q7RZ0Aqy+lQbOHEPDQYhxK3kZb9hjiAbbGficZQ==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.62.0.tgz", + "integrity": "sha512-f1NnExqsHuA6w8UVlBtPsvTBhdkMc0h1JD9SzGCdWTLou5GHJr2JIP6DlwV9IKWAnM+sAelaoFez+14wLP2zOQ==", "license": "MIT", "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.56.3", - "@mariozechner/pi-ai": "^0.56.3", - "@mariozechner/pi-tui": "^0.56.3", + "@mariozechner/pi-agent-core": "^0.62.0", + "@mariozechner/pi-ai": "^0.62.0", + "@mariozechner/pi-tui": "^0.62.0", "@silvia-odwyer/photon-node": "^0.3.4", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", @@ -1516,9 +1516,9 @@ } }, "node_modules/@mariozechner/pi-tui": { - "version": "0.56.3", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.56.3.tgz", - "integrity": "sha512-eZ1P9QRKHp78hwx+lITr/mujZqe+eCwL/bOS9vXXkFP070RW4VYum0j7TJ4BrFEH/nNkXRS1tYCXYU05une1bA==", + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.62.0.tgz", + "integrity": "sha512-/At11PPe8l319MnUoK4wN5L/uVCU6bDdiIUzH8Ez0stOkjSF6isRXScZ+RMM+6iCKsD4muBTX8Cmcif+3/UWHA==", "license": "MIT", "dependencies": { "@types/mime-types": "^2.1.4", @@ -2307,12 +2307,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/retry": { @@ -2647,9 +2647,9 @@ } }, "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3947,9 +3947,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/web-streams-polyfill": { diff --git a/package.json b/package.json index a08c509..96afa87 100644 --- a/package.json +++ b/package.json @@ -47,18 +47,18 @@ }, "dependencies": { "@companion-ai/alpha-hub": "^0.1.2", - "@mariozechner/pi-ai": "^0.56.1", - "@mariozechner/pi-coding-agent": "^0.56.1", - "@sinclair/typebox": "^0.34.41", - "dotenv": "^16.4.7" + "@mariozechner/pi-ai": "^0.62.0", + "@mariozechner/pi-coding-agent": "^0.62.0", + "@sinclair/typebox": "^0.34.48", + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^24.3.0", - "tsx": "^4.20.5", - "typescript": "^5.7.3" + "@types/node": "^25.5.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3" }, "engines": { - "node": ">=20.6.0" + "node": ">=20.18.1" }, "repository": { "type": "git", diff --git a/scripts/patch-embedded-pi.mjs b/scripts/patch-embedded-pi.mjs index 74b201a..fbfe45d 100644 --- a/scripts/patch-embedded-pi.mjs +++ b/scripts/patch-embedded-pi.mjs @@ -8,6 +8,7 @@ const appRoot = resolve(here, ".."); const piPackageRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent"); const packageJsonPath = resolve(piPackageRoot, "package.json"); const cliPath = resolve(piPackageRoot, "dist", "cli.js"); +const bunCliPath = resolve(piPackageRoot, "dist", "bun", "cli.js"); const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js"); const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js"); const piTuiRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-tui"); @@ -85,10 +86,14 @@ if (existsSync(packageJsonPath)) { } } -if (existsSync(cliPath)) { - const cliSource = readFileSync(cliPath, "utf8"); +for (const entryPath of [cliPath, bunCliPath]) { + if (!existsSync(entryPath)) { + continue; + } + + const cliSource = readFileSync(entryPath, "utf8"); if (cliSource.includes('process.title = "pi";')) { - writeFileSync(cliPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8"); + writeFileSync(entryPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8"); } } @@ -112,7 +117,7 @@ if (existsSync(interactiveThemePath)) { " return {", ' borderColor: (text) => " ".repeat(text.length),', ' bgColor: (text) => theme.bg("userMessageBg", text),', - ' placeholderText: "Type your message or /help for commands",', + ' placeholderText: "Type your message",', ' placeholder: (text) => theme.fg("dim", text),', " selectList: getSelectListTheme(),", " };", @@ -127,9 +132,11 @@ if (existsSync(interactiveThemePath)) { if (existsSync(editorPath)) { let editorSource = readFileSync(editorPath, "utf8"); - const importOriginal = 'import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";'; - const importReplacement = 'import { applyBackgroundToLine, getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";'; - if (!editorSource.includes("applyBackgroundToLine") && editorSource.includes(importOriginal)) { + const importOriginal = + 'import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";'; + const importReplacement = + 'import { applyBackgroundToLine, getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";'; + if (editorSource.includes(importOriginal)) { editorSource = editorSource.replace(importOriginal, importReplacement); } const desiredRender = [ @@ -168,6 +175,13 @@ if (existsSync(editorPath)) { " const result = [];", ' const leftPadding = " ".repeat(paddingX);', " const rightPadding = leftPadding;", + " const renderBorderLine = (indicator) => {", + " const remaining = width - visibleWidth(indicator);", + " if (remaining >= 0) {", + ' return this.borderColor(indicator + "─".repeat(remaining));', + " }", + " return this.borderColor(truncateToWidth(indicator, width));", + " };", " // Render top padding row. When background fill is active, mimic the user-message block", " // instead of the stock editor chrome.", " if (bgColor) {", @@ -181,8 +195,7 @@ if (existsSync(editorPath)) { " }", " else if (this.scrollOffset > 0) {", " const indicator = `─── ↑ ${this.scrollOffset} more `;", - " const remaining = width - visibleWidth(indicator);", - ' result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));', + " result.push(renderBorderLine(indicator));", " }", " else {", " result.push(horizontal.repeat(width));", @@ -205,7 +218,7 @@ if (existsSync(editorPath)) { " if (isPlaceholderLine) {", " const marker = emitCursorMarker ? CURSOR_MARKER : \"\";", " const rawPlaceholder = this.theme.placeholderText;", - " const graphemes = [...segmenter.segment(rawPlaceholder)];", + " const graphemes = [...this.segment(rawPlaceholder)];", ' const firstGrapheme = graphemes[0]?.segment ?? " ";', " const restRaw = rawPlaceholder.slice(firstGrapheme.length);", ' const restStyled = typeof this.theme.placeholder === "function"', @@ -222,7 +235,7 @@ if (existsSync(editorPath)) { " if (after.length > 0) {", " // Cursor is on a character (grapheme) - replace it with highlighted version", " // Get the first grapheme from 'after'", - " const afterGraphemes = [...segmenter.segment(after)];", + " const afterGraphemes = [...this.segment(after)];", ' const firstGrapheme = afterGraphemes[0]?.segment || "";', " const restAfter = after.slice(firstGrapheme.length);", ' const cursor = `\\x1b[7m${firstGrapheme}\\x1b[27m`;', @@ -260,8 +273,7 @@ if (existsSync(editorPath)) { " }", " else if (linesBelow > 0) {", " const indicator = `─── ↓ ${linesBelow} more `;", - " const remaining = width - visibleWidth(indicator);", - ' const bottomLine = this.borderColor(indicator + "─".repeat(Math.max(0, remaining)));', + " const bottomLine = renderBorderLine(indicator);", " result.push(bottomLine);", " }", " else {", diff --git a/src/cli.ts b/src/cli.ts index 0c49b39..3a56095 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -105,7 +105,7 @@ function printHelp(): void { printInfo("--alpha-logout Clear alphaXiv auth and exit"); printInfo("--alpha-status Show alphaXiv auth status and exit"); printInfo("--model provider:model Force a specific model"); - printInfo("--thinking level off | low | medium | high"); + printInfo("--thinking level off | minimal | low | medium | high | xhigh"); printInfo("--cwd /path/to/workdir Working directory for tools"); printInfo("--session-dir /path Session storage directory"); printInfo("--doctor Alias for `feynman doctor`"); @@ -113,7 +113,6 @@ function printHelp(): void { printSection("REPL"); printInfo("Inside the REPL, slash workflows come from the live prompt-template and extension command set."); - printInfo("Use `/help` in chat to browse the commands actually loaded in this session."); } async function handleAlphaCommand(action: string | undefined): Promise { diff --git a/src/pi/settings.ts b/src/pi/settings.ts index 8fbfb86..0a901d3 100644 --- a/src/pi/settings.ts +++ b/src/pi/settings.ts @@ -3,7 +3,7 @@ import { dirname } from "node:path"; import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; -export type ThinkingLevel = "off" | "low" | "medium" | "high"; +export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; export function parseModelSpec(spec: string, modelRegistry: ModelRegistry) { const trimmed = spec.trim(); @@ -27,7 +27,14 @@ export function normalizeThinkingLevel(value: string | undefined): ThinkingLevel } const normalized = value.toLowerCase(); - if (normalized === "off" || normalized === "low" || normalized === "medium" || normalized === "high") { + if ( + normalized === "off" || + normalized === "minimal" || + normalized === "low" || + normalized === "medium" || + normalized === "high" || + normalized === "xhigh" + ) { return normalized; } diff --git a/tests/pi-settings.test.ts b/tests/pi-settings.test.ts new file mode 100644 index 0000000..8a7c5cb --- /dev/null +++ b/tests/pi-settings.test.ts @@ -0,0 +1,18 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { normalizeThinkingLevel } from "../src/pi/settings.js"; + +test("normalizeThinkingLevel accepts the latest Pi thinking levels", () => { + assert.equal(normalizeThinkingLevel("off"), "off"); + assert.equal(normalizeThinkingLevel("minimal"), "minimal"); + assert.equal(normalizeThinkingLevel("low"), "low"); + assert.equal(normalizeThinkingLevel("medium"), "medium"); + assert.equal(normalizeThinkingLevel("high"), "high"); + assert.equal(normalizeThinkingLevel("xhigh"), "xhigh"); +}); + +test("normalizeThinkingLevel rejects unknown values", () => { + assert.equal(normalizeThinkingLevel("turbo"), undefined); + assert.equal(normalizeThinkingLevel(undefined), undefined); +});