Release v0.27.1

This commit is contained in:
Mario Zechner 2025-12-22 19:28:26 +01:00
parent 093bcecf95
commit 4492a3f304
15 changed files with 111 additions and 40 deletions

View file

@ -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,

View file

@ -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;
}

View file

@ -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");
}

View file

@ -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,