@oh-my-pi/pi-coding-agent 12.17.2 → 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/CHANGELOG.md +23 -0
- package/package.json +7 -7
- package/src/capability/index.ts +5 -8
- package/src/ipy/executor.ts +42 -32
- package/src/ipy/gateway-coordinator.ts +5 -8
- package/src/ipy/kernel.ts +28 -47
- package/src/main.ts +56 -61
- package/src/modes/controllers/extension-ui-controller.ts +28 -6
- package/src/modes/interactive-mode.ts +15 -18
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +7 -0
- package/src/sdk.ts +97 -117
- package/src/system-prompt.ts +14 -36
- package/src/tools/bash-interactive.ts +46 -42
- package/src/tools/gemini-image.ts +2 -11
- package/src/tools/index.ts +18 -27
- package/src/web/search/providers/gemini.ts +2 -24
- package/src/utils/timings.ts +0 -26
|
@@ -48,7 +48,7 @@ export class ExtensionUiController {
|
|
|
48
48
|
setWorkingMessage: message => this.ctx.setWorkingMessage(message),
|
|
49
49
|
setWidget: (key, content) => this.setHookWidget(key, content),
|
|
50
50
|
setTitle: title => setTerminalTitle(title),
|
|
51
|
-
custom: (factory,
|
|
51
|
+
custom: (factory, options) => this.showHookCustom(factory, options),
|
|
52
52
|
setEditorText: text => this.ctx.editor.setText(text),
|
|
53
53
|
pasteToEditor: text => {
|
|
54
54
|
this.ctx.editor.handleInput(`\x1b[200~${text}\x1b[201~`);
|
|
@@ -702,25 +702,47 @@ export class ExtensionUiController {
|
|
|
702
702
|
keybindings: KeybindingsManager,
|
|
703
703
|
done: (result: T) => void,
|
|
704
704
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
705
|
+
options?: { overlay?: boolean },
|
|
705
706
|
): Promise<T> {
|
|
706
707
|
const savedText = this.ctx.editor.getText();
|
|
707
708
|
const keybindings = KeybindingsManager.inMemory();
|
|
708
709
|
|
|
709
710
|
const { promise, resolve } = Promise.withResolvers<T>();
|
|
710
|
-
let component: Component & { dispose?(): void };
|
|
711
|
+
let component: (Component & { dispose?(): void }) | undefined;
|
|
712
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
713
|
+
let closed = false;
|
|
711
714
|
|
|
712
715
|
const close = (result: T) => {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
716
|
+
if (closed) return;
|
|
717
|
+
closed = true;
|
|
718
|
+
component?.dispose?.();
|
|
719
|
+
overlayHandle?.hide();
|
|
720
|
+
overlayHandle = undefined;
|
|
721
|
+
if (!options?.overlay) {
|
|
722
|
+
this.ctx.editorContainer.clear();
|
|
723
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
724
|
+
this.ctx.editor.setText(savedText);
|
|
725
|
+
}
|
|
717
726
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
718
727
|
this.ctx.ui.requestRender();
|
|
719
728
|
resolve(result);
|
|
720
729
|
};
|
|
721
730
|
|
|
722
731
|
Promise.try(() => factory(this.ctx.ui, theme, keybindings, close)).then(c => {
|
|
732
|
+
if (closed) {
|
|
733
|
+
c.dispose?.();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
723
736
|
component = c;
|
|
737
|
+
if (options?.overlay) {
|
|
738
|
+
overlayHandle = this.ctx.ui.showOverlay(component, {
|
|
739
|
+
anchor: "bottom-center",
|
|
740
|
+
width: "100%",
|
|
741
|
+
maxHeight: "100%",
|
|
742
|
+
margin: 0,
|
|
743
|
+
});
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
724
746
|
this.ctx.editorContainer.clear();
|
|
725
747
|
this.ctx.editorContainer.addChild(component);
|
|
726
748
|
this.ctx.ui.setFocus(component);
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
Text,
|
|
16
16
|
TUI,
|
|
17
17
|
} from "@oh-my-pi/pi-tui";
|
|
18
|
-
import {
|
|
18
|
+
import { hsvToRgb, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
19
19
|
import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
20
20
|
import chalk from "chalk";
|
|
21
21
|
import { KeybindingsManager } from "../config/keybindings";
|
|
@@ -57,9 +57,6 @@ import { getEditorTheme, getMarkdownTheme, onThemeChange, theme } from "./theme/
|
|
|
57
57
|
import type { CompactionQueuedMessage, InteractiveModeContext, TodoItem } from "./types";
|
|
58
58
|
import { UiHelpers } from "./utils/ui-helpers";
|
|
59
59
|
|
|
60
|
-
/** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
|
|
61
|
-
const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
|
|
62
|
-
|
|
63
60
|
const TODO_FILE_NAME = "todos.json";
|
|
64
61
|
const EDITOR_MAX_HEIGHT_MIN = 6;
|
|
65
62
|
const EDITOR_MAX_HEIGHT_MAX = 18;
|
|
@@ -258,28 +255,29 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
258
255
|
|
|
259
256
|
async init(): Promise<void> {
|
|
260
257
|
if (this.isInitialized) return;
|
|
261
|
-
debugStartup("InteractiveMode.init:entry");
|
|
262
258
|
|
|
263
|
-
this.keybindings = await KeybindingsManager.create();
|
|
264
|
-
debugStartup("InteractiveMode.init:keybindings");
|
|
259
|
+
this.keybindings = await logger.timeAsync("InteractiveMode.init:keybindings", () => KeybindingsManager.create());
|
|
265
260
|
|
|
266
261
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
267
262
|
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
268
|
-
debugStartup("InteractiveMode.init:cleanupRegistered");
|
|
269
263
|
|
|
270
|
-
await
|
|
271
|
-
|
|
264
|
+
await logger.timeAsync("InteractiveMode.init:slashCommands", () =>
|
|
265
|
+
this.refreshSlashCommandState(getProjectDir()),
|
|
266
|
+
);
|
|
272
267
|
|
|
273
268
|
// Get current model info for welcome screen
|
|
274
269
|
const modelName = this.session.model?.name ?? "Unknown";
|
|
275
270
|
const providerName = this.session.model?.provider ?? "Unknown";
|
|
276
271
|
|
|
277
272
|
// Get recent sessions
|
|
278
|
-
const recentSessions =
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
273
|
+
const recentSessions = await logger.timeAsync("InteractiveMode.init:recentSessions", () =>
|
|
274
|
+
getRecentSessions(this.sessionManager.getSessionDir()).then(sessions =>
|
|
275
|
+
sessions.map(s => ({
|
|
276
|
+
name: s.name,
|
|
277
|
+
timeAgo: s.timeAgo,
|
|
278
|
+
})),
|
|
279
|
+
),
|
|
280
|
+
);
|
|
283
281
|
|
|
284
282
|
// Convert LSP servers to welcome format
|
|
285
283
|
const lspServerInfo =
|
|
@@ -293,9 +291,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
293
291
|
|
|
294
292
|
if (!startupQuiet) {
|
|
295
293
|
// Add welcome header
|
|
296
|
-
debugStartup("InteractiveMode.init:welcomeComponent:start");
|
|
297
294
|
const welcome = new WelcomeComponent(this.#version, modelName, providerName, recentSessions, lspServerInfo);
|
|
298
|
-
debugStartup("InteractiveMode.init:welcomeComponent:created");
|
|
299
295
|
|
|
300
296
|
// Setup UI layout
|
|
301
297
|
this.ui.addChild(new Spacer(1));
|
|
@@ -1248,8 +1244,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1248
1244
|
keybindings: KeybindingsManager,
|
|
1249
1245
|
done: (result: T) => void,
|
|
1250
1246
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
1247
|
+
options?: { overlay?: boolean },
|
|
1251
1248
|
): Promise<T> {
|
|
1252
|
-
return this.#extensionUiController.showHookCustom(factory);
|
|
1249
|
+
return this.#extensionUiController.showHookCustom(factory, options);
|
|
1253
1250
|
}
|
|
1254
1251
|
|
|
1255
1252
|
showExtensionError(extensionPath: string, error: string): void {
|
package/src/modes/types.ts
CHANGED
|
@@ -215,6 +215,7 @@ export interface InteractiveModeContext {
|
|
|
215
215
|
keybindings: KeybindingsManager,
|
|
216
216
|
done: (result: T) => void,
|
|
217
217
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
218
|
+
options?: { overlay?: boolean },
|
|
218
219
|
): Promise<T>;
|
|
219
220
|
showExtensionError(extensionPath: string, error: string): void;
|
|
220
221
|
showToolError(toolName: string, error: string): void;
|
|
@@ -278,6 +278,13 @@ export class UiHelpers {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
renderInitialMessages(): void {
|
|
281
|
+
// This path is used to rebuild the visible chat transcript (e.g. after custom/debug UI).
|
|
282
|
+
// Clear existing rendered chat first to avoid duplicating the full session in the container.
|
|
283
|
+
this.ctx.chatContainer.clear();
|
|
284
|
+
this.ctx.pendingMessagesContainer.clear();
|
|
285
|
+
this.ctx.pendingBashComponents = [];
|
|
286
|
+
this.ctx.pendingPythonComponents = [];
|
|
287
|
+
|
|
281
288
|
// Get aligned messages and entries from session context
|
|
282
289
|
const context = this.ctx.sessionManager.buildSessionContext();
|
|
283
290
|
this.ctx.renderSessionContext(context, {
|
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 =
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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 =
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
|
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 =
|
|
671
|
-
|
|
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
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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 =
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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 =
|
|
708
|
-
|
|
709
|
-
|
|
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,37 +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
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
});
|
|
814
|
-
time("discoverAndLoadMCPTools");
|
|
815
|
-
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
|
+
);
|
|
816
811
|
mcpManager = mcpResult.manager;
|
|
817
812
|
toolSession.mcpManager = mcpManager;
|
|
818
813
|
|
|
@@ -832,18 +827,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
832
827
|
}
|
|
833
828
|
}
|
|
834
829
|
|
|
835
|
-
debugStartup("sdk:geminiImageTools:start");
|
|
836
830
|
// Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
|
|
837
|
-
const geminiImageTools = await getGeminiImageTools
|
|
831
|
+
const geminiImageTools = await logger.timeAsync("getGeminiImageTools", getGeminiImageTools);
|
|
838
832
|
if (geminiImageTools.length > 0) {
|
|
839
833
|
customTools.push(...(geminiImageTools as unknown as CustomTool[]));
|
|
840
834
|
}
|
|
841
|
-
time("getGeminiImageTools");
|
|
842
835
|
|
|
843
836
|
// Add specialized Exa web search tools if EXA_API_KEY is available
|
|
844
837
|
const exaSettings = settings.getGroup("exa");
|
|
845
838
|
if (exaSettings.enabled && exaSettings.enableSearch) {
|
|
846
|
-
const exaSearchTools = await getSearchTools
|
|
839
|
+
const exaSearchTools = await logger.timeAsync("getSearchTools", getSearchTools, {
|
|
847
840
|
enableLinkedin: exaSettings.enableLinkedin as boolean,
|
|
848
841
|
enableCompany: exaSettings.enableCompany as boolean,
|
|
849
842
|
});
|
|
@@ -852,34 +845,34 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
852
845
|
if (specializedTools.length > 0) {
|
|
853
846
|
customTools.push(...specializedTools);
|
|
854
847
|
}
|
|
855
|
-
time("getSearchTools");
|
|
856
848
|
}
|
|
857
849
|
|
|
858
|
-
debugStartup("sdk:discoverCustomTools:start");
|
|
859
850
|
// Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
|
|
860
851
|
const builtInToolNames = builtinTools.map(t => t.name);
|
|
861
|
-
const discoveredCustomTools = await
|
|
852
|
+
const discoveredCustomTools = await logger.timeAsync(
|
|
853
|
+
"discoverAndLoadCustomTools",
|
|
854
|
+
discoverAndLoadCustomTools,
|
|
855
|
+
[],
|
|
856
|
+
cwd,
|
|
857
|
+
builtInToolNames,
|
|
858
|
+
);
|
|
862
859
|
for (const { path, error } of discoveredCustomTools.errors) {
|
|
863
860
|
logger.error("Custom tool load failed", { path, error });
|
|
864
861
|
}
|
|
865
862
|
if (discoveredCustomTools.tools.length > 0) {
|
|
866
863
|
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
|
|
867
864
|
}
|
|
868
|
-
time("discoverAndLoadCustomTools");
|
|
869
|
-
debugStartup("sdk:discoverCustomTools:done");
|
|
870
865
|
|
|
871
866
|
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
|
|
872
867
|
if (customTools.length > 0) {
|
|
873
868
|
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
874
869
|
}
|
|
875
870
|
|
|
876
|
-
debugStartup("sdk:loadExtensions:start");
|
|
877
871
|
// Load extensions (discovers from standard locations + configured paths)
|
|
878
872
|
let extensionsResult: LoadExtensionsResult;
|
|
879
873
|
if (options.disableExtensionDiscovery) {
|
|
880
874
|
const configuredPaths = options.additionalExtensionPaths ?? [];
|
|
881
|
-
extensionsResult = await loadExtensions
|
|
882
|
-
time("loadExtensions");
|
|
875
|
+
extensionsResult = await logger.timeAsync("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
|
|
883
876
|
for (const { path, error } of extensionsResult.errors) {
|
|
884
877
|
logger.error("Failed to load extension", { path, error });
|
|
885
878
|
}
|
|
@@ -891,14 +884,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
891
884
|
...(options.additionalExtensionPaths ?? []),
|
|
892
885
|
...((settings.get("extensions") as string[]) ?? []),
|
|
893
886
|
];
|
|
894
|
-
extensionsResult = await
|
|
887
|
+
extensionsResult = await logger.timeAsync(
|
|
888
|
+
"discoverAndLoadExtensions",
|
|
889
|
+
discoverAndLoadExtensions,
|
|
895
890
|
configuredPaths,
|
|
896
891
|
cwd,
|
|
897
892
|
eventBus,
|
|
898
|
-
(settings.get("disabledExtensions") as string[]) ?? [],
|
|
899
893
|
);
|
|
900
|
-
time("discoverAndLoadExtensions");
|
|
901
|
-
debugStartup("sdk:discoverAndLoadExtensions");
|
|
902
894
|
for (const { path, error } of extensionsResult.errors) {
|
|
903
895
|
logger.error("Failed to load extension", { path, error });
|
|
904
896
|
}
|
|
@@ -959,7 +951,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
959
951
|
break;
|
|
960
952
|
}
|
|
961
953
|
}
|
|
962
|
-
time("findAvailableModel");
|
|
963
954
|
if (model) {
|
|
964
955
|
if (modelFallbackMessage) {
|
|
965
956
|
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
@@ -970,13 +961,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
970
961
|
}
|
|
971
962
|
}
|
|
972
963
|
|
|
973
|
-
time("findModel");
|
|
974
964
|
// Discover custom commands (TypeScript slash commands)
|
|
975
965
|
const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
|
|
976
966
|
? { commands: [], errors: [] }
|
|
977
|
-
: await loadCustomCommandsInternal
|
|
967
|
+
: await logger.timeAsync("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
|
|
978
968
|
if (!options.disableExtensionDiscovery) {
|
|
979
|
-
time("discoverCustomCommands");
|
|
980
969
|
for (const { path, error } of customCommandsResult.errors) {
|
|
981
970
|
logger.error("Failed to load custom command", { path, error });
|
|
982
971
|
}
|
|
@@ -1050,7 +1039,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1050
1039
|
if (model?.provider === "cursor") {
|
|
1051
1040
|
toolRegistry.delete("edit");
|
|
1052
1041
|
}
|
|
1053
|
-
time("combineTools");
|
|
1054
1042
|
|
|
1055
1043
|
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1056
1044
|
const cursorExecHandlers = new CursorExecHandlers({
|
|
@@ -1117,16 +1105,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1117
1105
|
}
|
|
1118
1106
|
}
|
|
1119
1107
|
|
|
1120
|
-
const systemPrompt = await
|
|
1121
|
-
|
|
1122
|
-
|
|
1108
|
+
const systemPrompt = await logger.timeAsync(
|
|
1109
|
+
"buildSystemPrompt",
|
|
1110
|
+
rebuildSystemPrompt,
|
|
1111
|
+
initialToolNames,
|
|
1112
|
+
toolRegistry,
|
|
1113
|
+
);
|
|
1123
1114
|
|
|
1124
|
-
const promptTemplates =
|
|
1125
|
-
|
|
1115
|
+
const promptTemplates =
|
|
1116
|
+
options.promptTemplates ??
|
|
1117
|
+
(await logger.timeAsync("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir));
|
|
1126
1118
|
toolSession.promptTemplates = promptTemplates;
|
|
1127
1119
|
|
|
1128
|
-
const slashCommands =
|
|
1129
|
-
|
|
1120
|
+
const slashCommands =
|
|
1121
|
+
options.slashCommands ?? (await logger.timeAsync("discoverSlashCommands", discoverSlashCommands, cwd));
|
|
1130
1122
|
|
|
1131
1123
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
1132
1124
|
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
@@ -1166,13 +1158,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1166
1158
|
// Load and create secret obfuscator if secrets are enabled
|
|
1167
1159
|
let obfuscator: SecretObfuscator | undefined;
|
|
1168
1160
|
if (settings.get("secrets.enabled")) {
|
|
1169
|
-
const fileEntries = await loadSecrets
|
|
1161
|
+
const fileEntries = await logger.timeAsync("loadSecrets", loadSecrets, cwd, agentDir);
|
|
1170
1162
|
const envEntries = collectEnvSecrets();
|
|
1171
1163
|
const allEntries = [...envEntries, ...fileEntries];
|
|
1172
1164
|
if (allEntries.length > 0) {
|
|
1173
1165
|
obfuscator = new SecretObfuscator(allEntries);
|
|
1174
1166
|
}
|
|
1175
|
-
time("loadSecrets");
|
|
1176
1167
|
}
|
|
1177
1168
|
|
|
1178
1169
|
// Final convertToLlm: chain block-images filter with secret obfuscation
|
|
@@ -1230,8 +1221,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1230
1221
|
intentTracing: settings.get("tools.intentTracing") || $env.PI_INTENT_TRACING === "1",
|
|
1231
1222
|
});
|
|
1232
1223
|
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
1233
|
-
debugStartup("sdk:createAgent");
|
|
1234
|
-
time("createAgent");
|
|
1235
1224
|
|
|
1236
1225
|
// Restore messages if session has existing data
|
|
1237
1226
|
if (hasExistingSession) {
|
|
@@ -1266,20 +1255,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1266
1255
|
forceCopilotAgentInitiator,
|
|
1267
1256
|
obfuscator,
|
|
1268
1257
|
});
|
|
1269
|
-
debugStartup("sdk:createAgentSession");
|
|
1270
|
-
time("createAgentSession");
|
|
1271
1258
|
|
|
1272
1259
|
if (model?.api === "openai-codex-responses") {
|
|
1273
1260
|
try {
|
|
1274
|
-
|
|
1275
|
-
await prewarmOpenAICodexResponses(model, {
|
|
1261
|
+
await logger.timeAsync("prewarmCodexWebsocket", prewarmOpenAICodexResponses, model, {
|
|
1276
1262
|
apiKey: await modelRegistry.getApiKey(model, sessionId),
|
|
1277
1263
|
sessionId,
|
|
1278
1264
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1279
1265
|
providerSessionState: session.providerSessionState,
|
|
1280
1266
|
});
|
|
1281
|
-
debugStartup("sdk:prewarmCodexWebsocket:done");
|
|
1282
|
-
time("prewarmCodexWebsocket");
|
|
1283
1267
|
} catch (error) {
|
|
1284
1268
|
logger.debug("Codex websocket prewarm failed", {
|
|
1285
1269
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -1293,17 +1277,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1293
1277
|
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
1294
1278
|
if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
|
|
1295
1279
|
try {
|
|
1296
|
-
|
|
1297
|
-
const result = await warmupLspServers(cwd, {
|
|
1280
|
+
const result = await logger.timeAsync("warmupLspServers", warmupLspServers, cwd, {
|
|
1298
1281
|
onConnecting: serverNames => {
|
|
1299
1282
|
if (options.hasUI && serverNames.length > 0) {
|
|
1300
1283
|
process.stderr.write(chalk.gray(`Starting LSP servers: ${serverNames.join(", ")}…\n`));
|
|
1301
1284
|
}
|
|
1302
1285
|
},
|
|
1303
1286
|
});
|
|
1304
|
-
debugStartup("sdk:warmupLspServers:done");
|
|
1305
1287
|
lspServers = result.servers;
|
|
1306
|
-
time("warmupLspServers");
|
|
1307
1288
|
} catch (error) {
|
|
1308
1289
|
logger.warn("LSP server warmup failed", { cwd, error: String(error) });
|
|
1309
1290
|
}
|
|
@@ -1317,7 +1298,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1317
1298
|
taskDepth,
|
|
1318
1299
|
});
|
|
1319
1300
|
|
|
1320
|
-
debugStartup("sdk:return");
|
|
1321
1301
|
return {
|
|
1322
1302
|
session,
|
|
1323
1303
|
extensionsResult,
|