@oh-my-pi/pi-coding-agent 12.17.0 → 12.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/sdk.ts CHANGED
@@ -83,10 +83,6 @@ import {
83
83
  import { ToolContextStore } from "./tools/context";
84
84
  import { getGeminiImageTools } from "./tools/gemini-image";
85
85
  import { EventBus } from "./utils/event-bus";
86
- import { time } from "./utils/timings";
87
-
88
- /** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
89
- const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
90
86
 
91
87
  // Types
92
88
  export interface CreateAgentSessionOptions {
@@ -540,7 +536,6 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
540
536
  * ```
541
537
  */
542
538
  export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
543
- debugStartup("sdk:createAgentSession:entry");
544
539
  const cwd = options.cwd ?? getProjectDir();
545
540
  const agentDir = options.agentDir ?? getDefaultAgentDir();
546
541
  const eventBus = options.eventBus ?? new EventBus();
@@ -549,17 +544,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
549
544
  registerPythonCleanup();
550
545
 
551
546
  // Use provided or create AuthStorage and ModelRegistry
552
- const authStorage = options.authStorage ?? (await discoverAuthStorage(agentDir));
553
- const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
554
- if (!options.modelRegistry) {
555
- await modelRegistry.refresh();
556
- }
557
- time("discoverModels");
547
+ const { authStorage, modelRegistry } = await logger.timeAsync("discoverModels", async () => {
548
+ const authStorage = options.authStorage ?? (await discoverAuthStorage(agentDir));
549
+ const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
550
+ if (!options.modelRegistry) {
551
+ await modelRegistry.refresh();
552
+ }
553
+ return { authStorage, modelRegistry };
554
+ });
558
555
 
559
- const settings = options.settings ?? (await Settings.init({ cwd, agentDir }));
560
- time("settings");
561
- initializeWithSettings(settings);
562
- time("initializeWithSettings");
556
+ const settings = await logger.timeAsync(
557
+ "settings",
558
+ async () => options.settings ?? (await Settings.init({ cwd, agentDir })),
559
+ );
560
+ logger.time("initializeWithSettings", initializeWithSettings, settings);
563
561
  const skillsSettings = settings.getGroup("skills") as SkillsSettings;
564
562
  const discoveredSkillsPromise =
565
563
  options.skills === undefined ? discoverSkills(cwd, agentDir, skillsSettings) : undefined;
@@ -568,8 +566,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
568
566
  setPreferredSearchProvider(settings.get("providers.webSearch") ?? "auto");
569
567
  setPreferredImageProvider(settings.get("providers.image") ?? "auto");
570
568
 
571
- const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
572
- time("sessionManager");
569
+ const sessionManager = options.sessionManager ?? logger.time("sessionManager", SessionManager.create, cwd);
573
570
  const sessionId = sessionManager.getSessionId();
574
571
  const modelApiKeyAvailability = new Map<string, boolean>();
575
572
  const getModelAvailabilityKey = (candidate: Model): string =>
@@ -587,8 +584,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
587
584
  };
588
585
 
589
586
  // Check if session has existing data to restore
590
- const existingSession = sessionManager.buildSessionContext();
591
- time("loadSession");
587
+ const existingSession = logger.time("loadSession", () => sessionManager.buildSessionContext());
592
588
  const hasExistingSession = existingSession.messages.length > 0;
593
589
  const hasThinkingEntry = sessionManager.getBranch().some(entry => entry.type === "thinking_level_change");
594
590
 
@@ -667,46 +663,52 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
667
663
  skills = options.skills;
668
664
  skillWarnings = [];
669
665
  } else {
670
- const discovered = discoveredSkillsPromise ? await discoveredSkillsPromise : { skills: [], warnings: [] };
671
- time("discoverSkills");
666
+ const discovered = await logger.timeAsync("discoverSkills", async () =>
667
+ discoveredSkillsPromise ? await discoveredSkillsPromise : { skills: [], warnings: [] },
668
+ );
672
669
  skills = discovered.skills;
673
670
  skillWarnings = discovered.warnings;
674
671
  }
675
672
 
676
- debugStartup("sdk:discoverSkills");
677
-
678
673
  // Discover rules
679
- const ttsrSettings = settings.getGroup("ttsr");
680
- const ttsrManager = new TtsrManager(ttsrSettings);
681
- const rulesResult =
682
- options.rules !== undefined
683
- ? { items: options.rules, warnings: undefined }
684
- : await loadCapability<Rule>(ruleCapability.id, { cwd });
685
- const registeredTtsrRuleNames = new Set<string>();
686
- for (const rule of rulesResult.items) {
687
- if (rule.condition && rule.condition.length > 0) {
688
- if (ttsrManager.addRule(rule)) {
689
- registeredTtsrRuleNames.add(rule.name);
674
+ const { ttsrManager, rulesResult, registeredTtsrRuleNames } = await logger.timeAsync(
675
+ "discoverTtsrRules",
676
+ async () => {
677
+ const ttsrSettings = settings.getGroup("ttsr");
678
+ const ttsrManager = new TtsrManager(ttsrSettings);
679
+ const rulesResult =
680
+ options.rules !== undefined
681
+ ? { items: options.rules, warnings: undefined }
682
+ : await loadCapability<Rule>(ruleCapability.id, { cwd });
683
+ const registeredTtsrRuleNames = new Set<string>();
684
+ for (const rule of rulesResult.items) {
685
+ if (rule.condition && rule.condition.length > 0) {
686
+ if (ttsrManager.addRule(rule)) {
687
+ registeredTtsrRuleNames.add(rule.name);
688
+ }
689
+ }
690
690
  }
691
- }
692
- }
693
- if (existingSession.injectedTtsrRules.length > 0) {
694
- ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
695
- }
696
- time("discoverTtsrRules");
691
+ if (existingSession.injectedTtsrRules.length > 0) {
692
+ ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
693
+ }
694
+ return { ttsrManager, rulesResult, registeredTtsrRuleNames };
695
+ },
696
+ );
697
697
 
698
698
  // Filter rules for the rulebook (non-TTSR, non-alwaysApply, with descriptions)
699
- const rulebookRules = rulesResult.items.filter((rule: Rule) => {
700
- if (registeredTtsrRuleNames.has(rule.name)) return false;
701
- if (rule.alwaysApply) return false;
702
- if (!rule.description) return false;
703
- return true;
704
- });
705
- time("filterRulebookRules");
699
+ const rulebookRules = logger.time("filterRulebookRules", () =>
700
+ rulesResult.items.filter((rule: Rule) => {
701
+ if (registeredTtsrRuleNames.has(rule.name)) return false;
702
+ if (rule.alwaysApply) return false;
703
+ if (!rule.description) return false;
704
+ return true;
705
+ }),
706
+ );
706
707
 
707
- const contextFiles = options.contextFiles ?? (await discoverContextFiles(cwd, agentDir));
708
- debugStartup("sdk:discoverContextFiles");
709
- time("discoverContextFiles");
708
+ const contextFiles = await logger.timeAsync(
709
+ "discoverContextFiles",
710
+ async () => options.contextFiles ?? (await discoverContextFiles(cwd, agentDir)),
711
+ );
710
712
 
711
713
  let agent: Agent;
712
714
  let session: AgentSession;
@@ -782,35 +784,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
782
784
  options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
783
785
  );
784
786
 
785
- debugStartup("sdk:createTools:start");
786
787
  // Create built-in tools (already wrapped with meta notice formatting)
787
- const builtinTools = await createTools(toolSession, options.toolNames);
788
- debugStartup("sdk:createTools");
789
- time("createAllTools");
788
+ const builtinTools = await logger.timeAsync("createAllTools", () => createTools(toolSession, options.toolNames));
790
789
 
791
- debugStartup("sdk:discoverMCP:start");
792
790
  // Discover MCP tools from .mcp.json files
793
791
  let mcpManager: MCPManager | undefined;
794
792
  const enableMCP = options.enableMCP ?? true;
795
793
  const customTools: CustomTool[] = [];
796
794
  if (enableMCP) {
797
- const mcpResult = await discoverAndLoadMCPTools(cwd, {
798
- onConnecting: serverNames => {
799
- if (options.hasUI && serverNames.length > 0) {
800
- process.stderr.write(
801
- chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}
802
- `),
803
- );
804
- }
805
- },
806
- enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
807
- // Always filter Exa - we have native integration
808
- filterExa: true,
809
- cacheStorage: settings.getStorage(),
810
- authStorage,
811
- });
812
- time("discoverAndLoadMCPTools");
813
- debugStartup("sdk:discoverAndLoadMCPTools");
795
+ const mcpResult = await logger.timeAsync("discoverAndLoadMCPTools", () =>
796
+ discoverAndLoadMCPTools(cwd, {
797
+ onConnecting: serverNames => {
798
+ if (options.hasUI && serverNames.length > 0) {
799
+ process.stderr.write(`${chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}…`)}\n`);
800
+ }
801
+ },
802
+ enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
803
+ // Always filter Exa - we have native integration
804
+ filterExa: true,
805
+ // Filter browser MCP servers when builtin browser tool is active
806
+ filterBrowser: (settings.get("browser.enabled") as boolean) ?? false,
807
+ cacheStorage: settings.getStorage(),
808
+ authStorage,
809
+ }),
810
+ );
814
811
  mcpManager = mcpResult.manager;
815
812
  toolSession.mcpManager = mcpManager;
816
813
 
@@ -830,18 +827,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
830
827
  }
831
828
  }
832
829
 
833
- debugStartup("sdk:geminiImageTools:start");
834
830
  // Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
835
- const geminiImageTools = await getGeminiImageTools();
831
+ const geminiImageTools = await logger.timeAsync("getGeminiImageTools", getGeminiImageTools);
836
832
  if (geminiImageTools.length > 0) {
837
833
  customTools.push(...(geminiImageTools as unknown as CustomTool[]));
838
834
  }
839
- time("getGeminiImageTools");
840
835
 
841
836
  // Add specialized Exa web search tools if EXA_API_KEY is available
842
837
  const exaSettings = settings.getGroup("exa");
843
838
  if (exaSettings.enabled && exaSettings.enableSearch) {
844
- const exaSearchTools = await getSearchTools({
839
+ const exaSearchTools = await logger.timeAsync("getSearchTools", getSearchTools, {
845
840
  enableLinkedin: exaSettings.enableLinkedin as boolean,
846
841
  enableCompany: exaSettings.enableCompany as boolean,
847
842
  });
@@ -850,34 +845,34 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
850
845
  if (specializedTools.length > 0) {
851
846
  customTools.push(...specializedTools);
852
847
  }
853
- time("getSearchTools");
854
848
  }
855
849
 
856
- debugStartup("sdk:discoverCustomTools:start");
857
850
  // Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
858
851
  const builtInToolNames = builtinTools.map(t => t.name);
859
- const discoveredCustomTools = await discoverAndLoadCustomTools([], cwd, builtInToolNames);
852
+ const discoveredCustomTools = await logger.timeAsync(
853
+ "discoverAndLoadCustomTools",
854
+ discoverAndLoadCustomTools,
855
+ [],
856
+ cwd,
857
+ builtInToolNames,
858
+ );
860
859
  for (const { path, error } of discoveredCustomTools.errors) {
861
860
  logger.error("Custom tool load failed", { path, error });
862
861
  }
863
862
  if (discoveredCustomTools.tools.length > 0) {
864
863
  customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
865
864
  }
866
- time("discoverAndLoadCustomTools");
867
- debugStartup("sdk:discoverCustomTools:done");
868
865
 
869
866
  const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
870
867
  if (customTools.length > 0) {
871
868
  inlineExtensions.push(createCustomToolsExtension(customTools));
872
869
  }
873
870
 
874
- debugStartup("sdk:loadExtensions:start");
875
871
  // Load extensions (discovers from standard locations + configured paths)
876
872
  let extensionsResult: LoadExtensionsResult;
877
873
  if (options.disableExtensionDiscovery) {
878
874
  const configuredPaths = options.additionalExtensionPaths ?? [];
879
- extensionsResult = await loadExtensions(configuredPaths, cwd, eventBus);
880
- time("loadExtensions");
875
+ extensionsResult = await logger.timeAsync("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
881
876
  for (const { path, error } of extensionsResult.errors) {
882
877
  logger.error("Failed to load extension", { path, error });
883
878
  }
@@ -889,14 +884,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
889
884
  ...(options.additionalExtensionPaths ?? []),
890
885
  ...((settings.get("extensions") as string[]) ?? []),
891
886
  ];
892
- extensionsResult = await discoverAndLoadExtensions(
887
+ extensionsResult = await logger.timeAsync(
888
+ "discoverAndLoadExtensions",
889
+ discoverAndLoadExtensions,
893
890
  configuredPaths,
894
891
  cwd,
895
892
  eventBus,
896
- (settings.get("disabledExtensions") as string[]) ?? [],
897
893
  );
898
- time("discoverAndLoadExtensions");
899
- debugStartup("sdk:discoverAndLoadExtensions");
900
894
  for (const { path, error } of extensionsResult.errors) {
901
895
  logger.error("Failed to load extension", { path, error });
902
896
  }
@@ -957,7 +951,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
957
951
  break;
958
952
  }
959
953
  }
960
- time("findAvailableModel");
961
954
  if (model) {
962
955
  if (modelFallbackMessage) {
963
956
  modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
@@ -968,13 +961,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
968
961
  }
969
962
  }
970
963
 
971
- time("findModel");
972
964
  // Discover custom commands (TypeScript slash commands)
973
965
  const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
974
966
  ? { commands: [], errors: [] }
975
- : await loadCustomCommandsInternal({ cwd, agentDir });
967
+ : await logger.timeAsync("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
976
968
  if (!options.disableExtensionDiscovery) {
977
- time("discoverCustomCommands");
978
969
  for (const { path, error } of customCommandsResult.errors) {
979
970
  logger.error("Failed to load custom command", { path, error });
980
971
  }
@@ -1048,7 +1039,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1048
1039
  if (model?.provider === "cursor") {
1049
1040
  toolRegistry.delete("edit");
1050
1041
  }
1051
- time("combineTools");
1052
1042
 
1053
1043
  let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
1054
1044
  const cursorExecHandlers = new CursorExecHandlers({
@@ -1115,16 +1105,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1115
1105
  }
1116
1106
  }
1117
1107
 
1118
- const systemPrompt = await rebuildSystemPrompt(initialToolNames, toolRegistry);
1119
- debugStartup("sdk:buildSystemPrompt:done");
1120
- time("buildSystemPrompt");
1108
+ const systemPrompt = await logger.timeAsync(
1109
+ "buildSystemPrompt",
1110
+ rebuildSystemPrompt,
1111
+ initialToolNames,
1112
+ toolRegistry,
1113
+ );
1121
1114
 
1122
- const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
1123
- time("discoverPromptTemplates");
1115
+ const promptTemplates =
1116
+ options.promptTemplates ??
1117
+ (await logger.timeAsync("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir));
1124
1118
  toolSession.promptTemplates = promptTemplates;
1125
1119
 
1126
- const slashCommands = options.slashCommands ?? (await discoverSlashCommands(cwd));
1127
- time("discoverSlashCommands");
1120
+ const slashCommands =
1121
+ options.slashCommands ?? (await logger.timeAsync("discoverSlashCommands", discoverSlashCommands, cwd));
1128
1122
 
1129
1123
  // Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
1130
1124
  const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
@@ -1164,13 +1158,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1164
1158
  // Load and create secret obfuscator if secrets are enabled
1165
1159
  let obfuscator: SecretObfuscator | undefined;
1166
1160
  if (settings.get("secrets.enabled")) {
1167
- const fileEntries = await loadSecrets(cwd, agentDir);
1161
+ const fileEntries = await logger.timeAsync("loadSecrets", loadSecrets, cwd, agentDir);
1168
1162
  const envEntries = collectEnvSecrets();
1169
1163
  const allEntries = [...envEntries, ...fileEntries];
1170
1164
  if (allEntries.length > 0) {
1171
1165
  obfuscator = new SecretObfuscator(allEntries);
1172
1166
  }
1173
- time("loadSecrets");
1174
1167
  }
1175
1168
 
1176
1169
  // Final convertToLlm: chain block-images filter with secret obfuscation
@@ -1228,8 +1221,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1228
1221
  intentTracing: settings.get("tools.intentTracing") || $env.PI_INTENT_TRACING === "1",
1229
1222
  });
1230
1223
  cursorEventEmitter = event => agent.emitExternalEvent(event);
1231
- debugStartup("sdk:createAgent");
1232
- time("createAgent");
1233
1224
 
1234
1225
  // Restore messages if session has existing data
1235
1226
  if (hasExistingSession) {
@@ -1264,20 +1255,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1264
1255
  forceCopilotAgentInitiator,
1265
1256
  obfuscator,
1266
1257
  });
1267
- debugStartup("sdk:createAgentSession");
1268
- time("createAgentSession");
1269
1258
 
1270
1259
  if (model?.api === "openai-codex-responses") {
1271
1260
  try {
1272
- debugStartup("sdk:prewarmCodexWebsocket:start");
1273
- await prewarmOpenAICodexResponses(model, {
1261
+ await logger.timeAsync("prewarmCodexWebsocket", prewarmOpenAICodexResponses, model, {
1274
1262
  apiKey: await modelRegistry.getApiKey(model, sessionId),
1275
1263
  sessionId,
1276
1264
  preferWebsockets: preferOpenAICodexWebsockets,
1277
1265
  providerSessionState: session.providerSessionState,
1278
1266
  });
1279
- debugStartup("sdk:prewarmCodexWebsocket:done");
1280
- time("prewarmCodexWebsocket");
1281
1267
  } catch (error) {
1282
1268
  logger.debug("Codex websocket prewarm failed", {
1283
1269
  error: error instanceof Error ? error.message : String(error),
@@ -1291,17 +1277,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1291
1277
  let lspServers: CreateAgentSessionResult["lspServers"];
1292
1278
  if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
1293
1279
  try {
1294
- debugStartup("sdk:warmupLspServers:start");
1295
- const result = await warmupLspServers(cwd, {
1280
+ const result = await logger.timeAsync("warmupLspServers", warmupLspServers, cwd, {
1296
1281
  onConnecting: serverNames => {
1297
1282
  if (options.hasUI && serverNames.length > 0) {
1298
1283
  process.stderr.write(chalk.gray(`Starting LSP servers: ${serverNames.join(", ")}…\n`));
1299
1284
  }
1300
1285
  },
1301
1286
  });
1302
- debugStartup("sdk:warmupLspServers:done");
1303
1287
  lspServers = result.servers;
1304
- time("warmupLspServers");
1305
1288
  } catch (error) {
1306
1289
  logger.warn("LSP server warmup failed", { cwd, error: String(error) });
1307
1290
  }
@@ -1315,7 +1298,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1315
1298
  taskDepth,
1316
1299
  });
1317
1300
 
1318
- debugStartup("sdk:return");
1319
1301
  return {
1320
1302
  session,
1321
1303
  extensionsResult,
@@ -18,9 +18,6 @@ import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md
18
18
  import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
19
19
  import type { ToolName } from "./tools";
20
20
 
21
- /** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
22
- const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
23
-
24
21
  interface GitContext {
25
22
  isRepo: boolean;
26
23
  currentBranch: string;
@@ -61,6 +58,7 @@ export async function loadGitContext(cwd: string): Promise<GitContext | null> {
61
58
  stdout: "pipe",
62
59
  stderr: "ignore",
63
60
  timeout: timeout,
61
+ killSignal: "SIGKILL",
64
62
  });
65
63
  return untilAborted(abortSignal, async () => {
66
64
  const exitCode = await proc.exited;
@@ -354,21 +352,15 @@ async function saveGpuCache(info: GpuCache): Promise<void> {
354
352
  }
355
353
 
356
354
  async function getCachedGpu(): Promise<string | undefined> {
357
- debugStartup("system-prompt:getEnvironmentInfo:getCachedGpu:start");
358
- const cached = await loadGpuCache();
355
+ const cached = await logger.timeAsync("getCachedGpu:loadGpuCache", loadGpuCache);
359
356
  if (cached) return cached.gpu;
360
- debugStartup("system-prompt:getEnvironmentInfo:getGpuModel");
361
- const gpu = await getGpuModel();
362
- debugStartup("system-prompt:getEnvironmentInfo:saveGpuCache");
363
- if (gpu) await saveGpuCache({ gpu });
357
+ const gpu = await logger.timeAsync("getCachedGpu:getGpuModel", getGpuModel);
358
+ if (gpu) await logger.timeAsync("getCachedGpu:saveGpuCache", saveGpuCache, { gpu });
364
359
  return gpu ?? undefined;
365
360
  }
366
361
  async function getEnvironmentInfo(): Promise<Array<{ label: string; value: string }>> {
367
- debugStartup("system-prompt:getEnvironmentInfo:getCachedGpu");
368
- const gpu = await getCachedGpu();
369
- debugStartup("system-prompt:getEnvironmentInfo:getCpuInfo");
362
+ const gpu = await logger.timeAsync("getEnvironmentInfo:getCachedGpu", getCachedGpu);
370
363
  const cpus = os.cpus();
371
- debugStartup("system-prompt:getEnvironmentInfo:buildEntries");
372
364
  const entries: Array<{ label: string; value: string | undefined }> = [
373
365
  { label: "OS", value: `${os.platform()} ${os.release()}` },
374
366
  { label: "Distro", value: os.type() },
@@ -380,7 +372,6 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
380
372
  { label: "DE", value: getDesktopEnvironment() },
381
373
  { label: "WM", value: getWindowManager() },
382
374
  ];
383
- debugStartup("system-prompt:getEnvironmentInfo:done");
384
375
  return entries.filter((e): e is { label: string; value: string } => e.value != null && e.value !== "unknown");
385
376
  }
386
377
 
@@ -511,33 +502,23 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
511
502
  const preloadedSkills = providedPreloadedSkills;
512
503
 
513
504
  const prepPromise = (async () => {
514
- const systemPromptCustomizationPromise = (async () => {
515
- const customization = await loadSystemPromptFiles({ cwd: resolvedCwd });
516
- debugStartup("system-prompt:loadSystemPromptFiles:done");
517
- return customization;
518
- })();
505
+ const systemPromptCustomizationPromise = logger.timeAsync("loadSystemPromptFiles", loadSystemPromptFiles, {
506
+ cwd: resolvedCwd,
507
+ });
519
508
  const contextFilesPromise = providedContextFiles
520
509
  ? Promise.resolve(providedContextFiles)
521
- : loadProjectContextFiles({ cwd: resolvedCwd });
522
- const agentsMdSearchPromise = buildAgentsMdSearch(resolvedCwd);
510
+ : logger.timeAsync("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
511
+ const agentsMdSearchPromise = logger.timeAsync("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
523
512
  const skillsPromise: Promise<Skill[]> =
524
513
  providedSkills !== undefined
525
514
  ? Promise.resolve(providedSkills)
526
515
  : skillsSettings?.enabled !== false
527
516
  ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
528
517
  : Promise.resolve([]);
529
- const preloadedSkillContentsPromise = (async () => {
530
- debugStartup("system-prompt:loadPreloadedSkills:start");
531
- const loaded = preloadedSkills ? await loadPreloadedSkillContents(preloadedSkills) : [];
532
- debugStartup("system-prompt:loadPreloadedSkills:done");
533
- return loaded;
534
- })();
535
- const gitPromise = (async () => {
536
- debugStartup("system-prompt:loadGitContext:start");
537
- const loaded = await loadGitContext(resolvedCwd);
538
- debugStartup("system-prompt:loadGitContext:done");
539
- return loaded;
540
- })();
518
+ const preloadedSkillContentsPromise = preloadedSkills
519
+ ? await logger.timeAsync("loadPreloadedSkills", loadPreloadedSkillContents, preloadedSkills)
520
+ : [];
521
+ const gitPromise = logger.timeAsync("loadGitContext", loadGitContext, resolvedCwd);
541
522
 
542
523
  const [
543
524
  resolvedCustomPrompt,
@@ -677,9 +658,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
677
658
  });
678
659
  }
679
660
 
680
- debugStartup("system-prompt:getEnvironmentInfo:start");
681
- const environment = await getEnvironmentInfo();
682
- debugStartup("system-prompt:getEnvironmentInfo:done");
661
+ const environment = await logger.timeAsync("getEnvironmentInfo", getEnvironmentInfo);
683
662
  return renderPromptTemplate(systemPromptTemplate, {
684
663
  tools: toolNamesArray,
685
664
  toolDescriptions,
@@ -276,6 +276,50 @@ class BashInteractiveOverlayComponent implements Component {
276
276
  }
277
277
  }
278
278
 
279
+ const NO_PAGER_ENV = {
280
+ // Disable pagers so commands don't block on interactive views.
281
+ PAGER: "cat",
282
+ GIT_PAGER: "cat",
283
+ MANPAGER: "cat",
284
+ SYSTEMD_PAGER: "cat",
285
+ BAT_PAGER: "cat",
286
+ DELTA_PAGER: "cat",
287
+ GH_PAGER: "cat",
288
+ GLAB_PAGER: "cat",
289
+ PSQL_PAGER: "cat",
290
+ MYSQL_PAGER: "cat",
291
+ AWS_PAGER: "",
292
+ HOMEBREW_PAGER: "cat",
293
+ LESS: "FRX",
294
+ // Disable editor and terminal credential prompts.
295
+ GIT_EDITOR: "true",
296
+ VISUAL: "true",
297
+ EDITOR: "true",
298
+ GIT_TERMINAL_PROMPT: "0",
299
+ SSH_ASKPASS: "/usr/bin/false",
300
+ CI: "1",
301
+ // Package manager defaults for unattended execution.
302
+ npm_config_yes: "true",
303
+ npm_config_update_notifier: "false",
304
+ npm_config_fund: "false",
305
+ npm_config_audit: "false",
306
+ npm_config_progress: "false",
307
+ PNPM_DISABLE_SELF_UPDATE_CHECK: "true",
308
+ PNPM_UPDATE_NOTIFIER: "false",
309
+ YARN_ENABLE_TELEMETRY: "0",
310
+ YARN_ENABLE_PROGRESS_BARS: "0",
311
+ // Cross-language/tooling non-interactive defaults.
312
+ CARGO_TERM_PROGRESS_WHEN: "never",
313
+ DEBIAN_FRONTEND: "noninteractive",
314
+ PIP_NO_INPUT: "1",
315
+ PIP_DISABLE_PIP_VERSION_CHECK: "1",
316
+ TF_INPUT: "0",
317
+ TF_IN_AUTOMATION: "1",
318
+ GH_PROMPT_DISABLED: "1",
319
+ COMPOSER_NO_INTERACTION: "1",
320
+ CLOUDSDK_CORE_DISABLE_PROMPTS: "1",
321
+ };
322
+
279
323
  export async function runInteractiveBashPty(
280
324
  ui: NonNullable<AgentToolContext["ui"]>,
281
325
  options: {
@@ -346,54 +390,14 @@ export async function runInteractiveBashPty(
346
390
  timeoutMs: options.timeoutMs,
347
391
  env: {
348
392
  ...options.env,
349
- // Disable pagers so commands don't block on interactive views.
350
- PAGER: "cat",
351
- GIT_PAGER: "cat",
352
- MANPAGER: "cat",
353
- SYSTEMD_PAGER: "cat",
354
- BAT_PAGER: "cat",
355
- DELTA_PAGER: "cat",
356
- GH_PAGER: "cat",
357
- GLAB_PAGER: "cat",
358
- PSQL_PAGER: "cat",
359
- MYSQL_PAGER: "cat",
360
- AWS_PAGER: "",
361
- HOMEBREW_PAGER: "cat",
362
- LESS: "FRX",
363
- // Disable editor and terminal credential prompts.
364
- GIT_EDITOR: "true",
365
- VISUAL: "true",
366
- EDITOR: "true",
367
- GIT_TERMINAL_PROMPT: "0",
368
- SSH_ASKPASS: "/usr/bin/false",
369
- CI: "1",
370
- // Package manager defaults for unattended execution.
371
- npm_config_yes: "true",
372
- npm_config_update_notifier: "false",
373
- npm_config_fund: "false",
374
- npm_config_audit: "false",
375
- npm_config_progress: "false",
376
- PNPM_DISABLE_SELF_UPDATE_CHECK: "true",
377
- PNPM_UPDATE_NOTIFIER: "false",
378
- YARN_ENABLE_TELEMETRY: "0",
379
- YARN_ENABLE_PROGRESS_BARS: "0",
380
- // Cross-language/tooling non-interactive defaults.
381
- CARGO_TERM_PROGRESS_WHEN: "never",
382
- DEBIAN_FRONTEND: "noninteractive",
383
- PIP_NO_INPUT: "1",
384
- PIP_DISABLE_PIP_VERSION_CHECK: "1",
385
- TF_INPUT: "0",
386
- TF_IN_AUTOMATION: "1",
387
- GH_PROMPT_DISABLED: "1",
388
- COMPOSER_NO_INTERACTION: "1",
389
- CLOUDSDK_CORE_DISABLE_PROMPTS: "1",
393
+ ...NO_PAGER_ENV,
390
394
  },
391
395
  signal: options.signal,
392
396
  cols,
393
397
  rows,
394
398
  },
395
399
  (err, chunk) => {
396
- if (err || !chunk) return;
400
+ if (finished || err || !chunk) return;
397
401
  component.appendOutput(chunk);
398
402
  const normalizedChunk = normalizeCaptureChunk(chunk);
399
403
  pendingChunks = pendingChunks.then(() => sink.push(normalizedChunk)).catch(() => {});
package/src/tools/bash.ts CHANGED
@@ -17,7 +17,7 @@ import { CachedOutputBlock } from "../tui/output-block";
17
17
  import type { ToolSession } from ".";
18
18
  import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
19
19
  import { checkBashInterception } from "./bash-interceptor";
20
- import { applyHeadTail, normalizeBashCommand } from "./bash-normalize";
20
+ import { applyHeadTail } from "./bash-normalize";
21
21
  import { expandInternalUrls } from "./bash-skill-urls";
22
22
  import type { OutputMeta } from "./output-meta";
23
23
  import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
@@ -75,13 +75,11 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
75
75
  onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
76
76
  ctx?: AgentToolContext,
77
77
  ): Promise<AgentToolResult<BashToolDetails>> {
78
- // Normalize command: strip head/tail pipes and 2>&1
79
- const normalized = normalizeBashCommand(rawCommand);
80
- let command = normalized.command;
78
+ let command = rawCommand;
81
79
 
82
- // Merge explicit params with extracted ones (explicit takes precedence)
83
- const headLines = head ?? normalized.headLines;
84
- const tailLines = tail ?? normalized.tailLines;
80
+ // Only apply explicit head/tail params from tool input.
81
+ const headLines = head;
82
+ const tailLines = tail;
85
83
 
86
84
  // Check interception if enabled and available tools are known
87
85
  if (this.session.settings.get("bashInterceptor.enabled")) {
@@ -140,6 +138,7 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
140
138
  })
141
139
  : await executeBash(command, {
142
140
  cwd: commandCwd,
141
+ sessionKey: this.session.getSessionId?.() ?? undefined,
143
142
  timeout: timeoutMs,
144
143
  signal,
145
144
  env: extraEnv,