From 4492a3f3040fe277f13132a0cf111e161082994f Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 22 Dec 2025 19:28:26 +0100 Subject: [PATCH] Release v0.27.1 --- package-lock.json | 40 +++++++++++------------ packages/agent/package.json | 6 ++-- packages/ai/package.json | 2 +- packages/coding-agent/CHANGELOG.md | 10 ++++++ packages/coding-agent/package.json | 8 ++--- packages/coding-agent/src/core/sdk.ts | 16 +++++++++ packages/coding-agent/src/core/skills.ts | 5 +++ packages/coding-agent/src/core/timings.ts | 25 ++++++++++++++ packages/coding-agent/src/main.ts | 15 +++++++++ packages/mom/package.json | 8 ++--- packages/pods/package.json | 4 +-- packages/proxy/package.json | 2 +- packages/tui/package.json | 2 +- packages/web-ui/example/package.json | 2 +- packages/web-ui/package.json | 6 ++-- 15 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 packages/coding-agent/src/core/timings.ts diff --git a/package-lock.json b/package-lock.json index 12c23782..89d42d1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6447,11 +6447,11 @@ }, "packages/agent": { "name": "@mariozechner/pi-agent-core", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-tui": "^0.27.0" + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-tui": "^0.27.1" }, "devDependencies": { "@types/node": "^24.3.0", @@ -6481,7 +6481,7 @@ }, "packages/ai": { "name": "@mariozechner/pi-ai", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.71.2", @@ -6523,12 +6523,12 @@ }, "packages/coding-agent": { "name": "@mariozechner/pi-coding-agent", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.27.0", - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-tui": "^0.27.0", + "@mariozechner/pi-agent-core": "^0.27.1", + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-tui": "^0.27.1", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", @@ -6568,13 +6568,13 @@ }, "packages/mom": { "name": "@mariozechner/pi-mom", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.27.0", - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-coding-agent": "^0.27.0", + "@mariozechner/pi-agent-core": "^0.27.1", + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-coding-agent": "^0.27.1", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", @@ -6613,10 +6613,10 @@ }, "packages/pods": { "name": "@mariozechner/pi", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.27.0", + "@mariozechner/pi-agent-core": "^0.27.1", "chalk": "^5.5.0" }, "bin": { @@ -6629,7 +6629,7 @@ }, "packages/proxy": { "name": "@mariozechner/pi-proxy", - "version": "0.27.0", + "version": "0.27.1", "dependencies": { "@hono/node-server": "^1.14.0", "hono": "^4.6.16" @@ -6645,7 +6645,7 @@ }, "packages/tui": { "name": "@mariozechner/pi-tui", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { "@types/mime-types": "^2.1.4", @@ -6689,12 +6689,12 @@ }, "packages/web-ui": { "name": "@mariozechner/pi-web-ui", - "version": "0.27.0", + "version": "0.27.1", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-tui": "^0.27.0", + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-tui": "^0.27.1", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", @@ -6715,7 +6715,7 @@ }, "packages/web-ui/example": { "name": "pi-web-ui-example", - "version": "1.15.0", + "version": "1.15.1", "dependencies": { "@mariozechner/mini-lit": "^0.2.0", "@mariozechner/pi-ai": "file:../../ai", diff --git a/packages/agent/package.json b/packages/agent/package.json index 0ee0ba40..38501559 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-agent-core", - "version": "0.27.0", + "version": "0.27.1", "description": "General-purpose agent with transport abstraction, state management, and attachment support", "type": "module", "main": "./dist/index.js", @@ -18,8 +18,8 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-tui": "^0.27.0" + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-tui": "^0.27.1" }, "keywords": [ "ai", diff --git a/packages/ai/package.json b/packages/ai/package.json index 88bd043f..08093f99 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-ai", - "version": "0.27.0", + "version": "0.27.1", "description": "Unified LLM API with automatic model discovery and provider configuration", "type": "module", "main": "./dist/index.js", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a77c2310..79e6a1a2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,16 @@ ## [Unreleased] +## [0.27.1] - 2025-12-22 + +### Fixed + +- **Skill discovery performance**: Skip `node_modules` directories when recursively scanning for skills. Fixes ~60ms startup delay when skill directories contain npm dependencies. + +### Added + +- **Startup timing instrumentation**: Set `PI_TIMING=1` to see startup performance breakdown (interactive mode only). + ## [0.27.0] - 2025-12-22 ### Breaking diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 7dcbad5a..ef7b29a8 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-coding-agent", - "version": "0.27.0", + "version": "0.27.1", "description": "Coding agent CLI with read, bash, edit, write tools and session management", "type": "module", "piConfig": { @@ -39,9 +39,9 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.27.0", - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-tui": "^0.27.0", + "@mariozechner/pi-agent-core": "^0.27.1", + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-tui": "^0.27.1", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index 50535040..9d13786b 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -54,6 +54,7 @@ import { buildSystemPrompt as buildSystemPromptInternal, loadProjectContextFiles as loadContextFilesInternal, } from "./system-prompt.js"; +import { time } from "./timings.js"; import { allTools, bashTool, @@ -468,12 +469,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // Configure OAuth storage for this agentDir configureOAuthStorage(agentDir); + time("configureOAuthStorage"); const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir); + time("settingsManager"); const sessionManager = options.sessionManager ?? SessionManager.create(cwd, agentDir); + time("sessionManager"); // Check if session has existing data to restore const existingSession = sessionManager.loadSession(); + time("loadSession"); const hasExistingSession = existingSession.messages.length > 0; let model = options.model; @@ -511,6 +516,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // Fall back to first available if (!model) { const available = await discoverAvailableModels(); + time("discoverAvailableModels"); if (available.length === 0) { throw new Error( "No models available. Set an API key environment variable " + @@ -543,10 +549,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} const getApiKey = options.getApiKey ?? defaultGetApiKey(); const skills = options.skills ?? discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings()); + time("discoverSkills"); const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir); + time("discoverContextFiles"); const builtInTools = options.tools ?? createCodingTools(cwd); + time("createCodingTools"); let customToolsResult: { tools: LoadedCustomTool[]; setUIContext: (ctx: any, hasUI: boolean) => void }; if (options.customTools !== undefined) { @@ -564,6 +573,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // Discover custom tools, merging with additional paths const configuredPaths = [...settingsManager.getCustomToolPaths(), ...(options.additionalCustomToolPaths ?? [])]; const result = await discoverAndLoadCustomTools(configuredPaths, cwd, Object.keys(allTools), agentDir); + time("discoverAndLoadCustomTools"); for (const { path, error } of result.errors) { console.error(`Failed to load custom tool "${path}": ${error}`); } @@ -580,6 +590,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // Discover hooks, merging with additional paths const configuredPaths = [...settingsManager.getHookPaths(), ...(options.additionalHookPaths ?? [])]; const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd, agentDir); + time("discoverAndLoadHooks"); for (const { path, error } of errors) { console.error(`Failed to load hook "${path}": ${error}`); } @@ -589,6 +600,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } let allToolsArray: Tool[] = [...builtInTools, ...customToolsResult.tools.map((lt) => lt.tool as unknown as Tool)]; + time("combineTools"); if (hookRunner) { allToolsArray = wrapToolsWithHooks(allToolsArray, hookRunner) as Tool[]; } @@ -600,6 +612,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} skills, contextFiles, }); + time("buildSystemPrompt"); if (options.systemPrompt === undefined) { systemPrompt = defaultPrompt; @@ -610,6 +623,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd, agentDir); + time("discoverSlashCommands"); const agent = new Agent({ initialState: { @@ -634,6 +648,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} }, }), }); + time("createAgent"); // Restore messages if session has existing data if (hasExistingSession) { @@ -650,6 +665,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} customTools: customToolsResult.tools, skillsSettings: settingsManager.getSkillsSettings(), }); + time("createAgentSession"); return { session, diff --git a/packages/coding-agent/src/core/skills.ts b/packages/coding-agent/src/core/skills.ts index c388d32b..2a90ddf6 100644 --- a/packages/coding-agent/src/core/skills.ts +++ b/packages/coding-agent/src/core/skills.ts @@ -182,6 +182,11 @@ function loadSkillsFromDirInternal(dir: string, source: string, format: SkillFor continue; } + // Skip node_modules to avoid scanning dependencies + if (entry.name === "node_modules") { + continue; + } + if (entry.isSymbolicLink()) { continue; } diff --git a/packages/coding-agent/src/core/timings.ts b/packages/coding-agent/src/core/timings.ts new file mode 100644 index 00000000..4ef5fc8c --- /dev/null +++ b/packages/coding-agent/src/core/timings.ts @@ -0,0 +1,25 @@ +/** + * Central timing instrumentation for startup profiling. + * Enable with PI_TIMING=1 environment variable. + */ + +const ENABLED = process.env.PI_TIMING === "1"; +const timings: Array<{ label: string; ms: number }> = []; +let lastTime = Date.now(); + +export function time(label: string): void { + if (!ENABLED) return; + const now = Date.now(); + timings.push({ label, ms: now - lastTime }); + lastTime = now; +} + +export function printTimings(): void { + if (!ENABLED || timings.length === 0) return; + console.error("\n--- Startup Timings ---"); + for (const t of timings) { + console.error(` ${t.label}: ${t.ms}ms`); + } + console.error(` TOTAL: ${timings.reduce((a, b) => a + b.ms, 0)}ms`); + console.error("------------------------\n"); +} diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 0987d3f6..121e4b3b 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -23,6 +23,7 @@ import { resolveModelScope, type ScopedModel } from "./core/model-resolver.js"; import { type CreateAgentSessionOptions, configureOAuthStorage, createAgentSession } from "./core/sdk.js"; import { SessionManager } from "./core/session-manager.js"; import { SettingsManager } from "./core/settings-manager.js"; +import { printTimings, time } from "./core/timings.js"; import { allTools } from "./core/tools/index.js"; import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js"; import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js"; @@ -246,9 +247,12 @@ function buildSessionOptions( } export async function main(args: string[]) { + time("start"); configureOAuthStorage(); + time("configureOAuthStorage"); const parsed = parseArgs(args); + time("parseArgs"); if (parsed.version) { console.log(VERSION); @@ -286,28 +290,35 @@ export async function main(args: string[]) { const cwd = process.cwd(); const { initialMessage, initialAttachments } = await prepareInitialMessage(parsed); + time("prepareInitialMessage"); const isInteractive = !parsed.print && parsed.mode === undefined; const mode = parsed.mode || "text"; const settingsManager = SettingsManager.create(cwd); + time("SettingsManager.create"); initTheme(settingsManager.getTheme(), isInteractive); + time("initTheme"); let scopedModels: ScopedModel[] = []; if (parsed.models && parsed.models.length > 0) { scopedModels = await resolveModelScope(parsed.models); + time("resolveModelScope"); } // Create session manager based on CLI flags let sessionManager = createSessionManager(parsed, cwd); + time("createSessionManager"); // Handle --resume: show session picker if (parsed.resume) { const sessions = SessionManager.list(cwd); + time("SessionManager.list"); if (sessions.length === 0) { console.log(chalk.dim("No sessions found")); return; } const selectedPath = await selectSession(sessions); + time("selectSession"); if (!selectedPath) { console.log(chalk.dim("No session selected")); return; @@ -316,7 +327,9 @@ export async function main(args: string[]) { } const sessionOptions = buildSessionOptions(parsed, scopedModels, sessionManager); + time("buildSessionOptions"); const { session, customToolsResult, modelFallbackMessage } = await createAgentSession(sessionOptions); + time("createAgentSession"); if (!isInteractive && !session.model) { console.error(chalk.red("No models available.")); @@ -356,7 +369,9 @@ export async function main(args: string[]) { } const fdPath = await ensureTool("fd"); + time("ensureTool(fd)"); + printTimings(); await runInteractiveMode( session, VERSION, diff --git a/packages/mom/package.json b/packages/mom/package.json index 2e6d7f9b..8fc5f598 100644 --- a/packages/mom/package.json +++ b/packages/mom/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-mom", - "version": "0.27.0", + "version": "0.27.1", "description": "Slack bot that delegates messages to the pi coding agent", "type": "module", "bin": { @@ -21,9 +21,9 @@ }, "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.27.0", - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-coding-agent": "^0.27.0", + "@mariozechner/pi-agent-core": "^0.27.1", + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-coding-agent": "^0.27.1", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", diff --git a/packages/pods/package.json b/packages/pods/package.json index 203411f4..49864b7b 100644 --- a/packages/pods/package.json +++ b/packages/pods/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi", - "version": "0.27.0", + "version": "0.27.1", "description": "CLI tool for managing vLLM deployments on GPU pods", "type": "module", "bin": { @@ -34,7 +34,7 @@ "node": ">=20.0.0" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.27.0", + "@mariozechner/pi-agent-core": "^0.27.1", "chalk": "^5.5.0" }, "devDependencies": {} diff --git a/packages/proxy/package.json b/packages/proxy/package.json index ba7fdf01..1e0524cb 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-proxy", - "version": "0.27.0", + "version": "0.27.1", "type": "module", "description": "CORS and authentication proxy for pi-ai", "main": "dist/index.js", diff --git a/packages/tui/package.json b/packages/tui/package.json index e21aa3ee..0e0c77fe 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-tui", - "version": "0.27.0", + "version": "0.27.1", "description": "Terminal User Interface library with differential rendering for efficient text-based applications", "type": "module", "main": "dist/index.js", diff --git a/packages/web-ui/example/package.json b/packages/web-ui/example/package.json index c5b20382..abc98a88 100644 --- a/packages/web-ui/example/package.json +++ b/packages/web-ui/example/package.json @@ -1,6 +1,6 @@ { "name": "pi-web-ui-example", - "version": "1.15.0", + "version": "1.15.1", "private": true, "type": "module", "scripts": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index 8c73fd8a..33004c53 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-web-ui", - "version": "0.27.0", + "version": "0.27.1", "description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai", "type": "module", "main": "dist/index.js", @@ -18,8 +18,8 @@ }, "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.27.0", - "@mariozechner/pi-tui": "^0.27.0", + "@mariozechner/pi-ai": "^0.27.1", + "@mariozechner/pi-tui": "^0.27.1", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0",