@oh-my-pi/pi-coding-agent 14.1.0 → 14.1.1
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 +79 -0
- package/package.json +8 -8
- package/src/async/job-manager.ts +43 -10
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +1 -0
- package/src/config/model-registry.ts +63 -34
- package/src/config/model-resolver.ts +111 -15
- package/src/config/settings-schema.ts +4 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +6 -4
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +54 -0
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +0 -1
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +3 -3
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/sdk.ts +754 -724
- package/src/session/agent-session.ts +164 -34
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +26 -8
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/{await-tool.ts → poll-tool.ts} +36 -30
- package/src/tools/python.ts +293 -278
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +15 -6
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
package/src/sdk.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
getOpenAICodexTransportDetails,
|
|
12
12
|
prewarmOpenAICodexResponses,
|
|
13
13
|
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
14
|
-
import { SearchDb } from "@oh-my-pi/pi-natives";
|
|
15
14
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
16
15
|
import {
|
|
17
16
|
$env,
|
|
@@ -19,10 +18,10 @@ import {
|
|
|
19
18
|
getAgentDbPath,
|
|
20
19
|
getAgentDir,
|
|
21
20
|
getProjectDir,
|
|
22
|
-
getSearchDbDir,
|
|
23
21
|
logger,
|
|
24
22
|
postmortem,
|
|
25
23
|
prompt,
|
|
24
|
+
Snowflake,
|
|
26
25
|
} from "@oh-my-pi/pi-utils";
|
|
27
26
|
import chalk from "chalk";
|
|
28
27
|
import { AsyncJobManager, isBackgroundJobSupportEnabled } from "./async";
|
|
@@ -73,7 +72,7 @@ import {
|
|
|
73
72
|
RuleProtocolHandler,
|
|
74
73
|
SkillProtocolHandler,
|
|
75
74
|
} from "./internal-urls";
|
|
76
|
-
import { disposeAllKernelSessions } from "./ipy/executor";
|
|
75
|
+
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./ipy/executor";
|
|
77
76
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
78
77
|
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp";
|
|
79
78
|
import {
|
|
@@ -148,8 +147,6 @@ export interface CreateAgentSessionOptions {
|
|
|
148
147
|
authStorage?: AuthStorage;
|
|
149
148
|
/** Model registry. Default: discoverModels(authStorage, agentDir) */
|
|
150
149
|
modelRegistry?: ModelRegistry;
|
|
151
|
-
/** Shared native search DB for grep/glob/fuzzyFind-backed workflows. */
|
|
152
|
-
searchDb?: SearchDb;
|
|
153
150
|
|
|
154
151
|
/** Model to use. Default: from settings, else first available */
|
|
155
152
|
model?: Model;
|
|
@@ -202,6 +199,8 @@ export interface CreateAgentSessionOptions {
|
|
|
202
199
|
enableLsp?: boolean;
|
|
203
200
|
/** Skip Python kernel availability check and prelude warmup */
|
|
204
201
|
skipPythonPreflight?: boolean;
|
|
202
|
+
/** Force Python prelude warmup even when test env would normally skip it */
|
|
203
|
+
forcePythonWarmup?: boolean;
|
|
205
204
|
|
|
206
205
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
207
206
|
toolNames?: string[];
|
|
@@ -402,7 +401,6 @@ function createCustomToolContext(ctx: ExtensionContext): CustomToolContext {
|
|
|
402
401
|
sessionManager: ctx.sessionManager,
|
|
403
402
|
modelRegistry: ctx.modelRegistry,
|
|
404
403
|
model: ctx.model,
|
|
405
|
-
searchDb: ctx.searchDb,
|
|
406
404
|
isIdle: ctx.isIdle,
|
|
407
405
|
hasQueuedMessages: ctx.hasPendingMessages,
|
|
408
406
|
abort: ctx.abort,
|
|
@@ -835,8 +833,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
835
833
|
);
|
|
836
834
|
|
|
837
835
|
let agent: Agent;
|
|
838
|
-
let session
|
|
839
|
-
|
|
836
|
+
let session!: AgentSession;
|
|
837
|
+
let hasSession = false;
|
|
840
838
|
const enableLsp = options.enableLsp ?? true;
|
|
841
839
|
const backgroundJobsEnabled = isBackgroundJobSupportEnabled(settings);
|
|
842
840
|
const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
|
|
@@ -866,8 +864,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
866
864
|
? new AsyncJobManager({
|
|
867
865
|
maxRunningJobs: asyncMaxJobs,
|
|
868
866
|
onJobComplete: async (jobId, result, job) => {
|
|
869
|
-
if (!session) return;
|
|
867
|
+
if (!session || asyncJobManager!.isDeliverySuppressed(jobId)) return;
|
|
870
868
|
const formattedResult = await formatAsyncResultForFollowUp(result);
|
|
869
|
+
if (asyncJobManager!.isDeliverySuppressed(jobId)) return;
|
|
870
|
+
|
|
871
871
|
const message = prompt.render(asyncResultTemplate, { jobId, result: formattedResult });
|
|
872
872
|
const durationMs = job ? Math.max(0, Date.now() - job.startTime) : undefined;
|
|
873
873
|
await session.sendCustomMessage(
|
|
@@ -889,437 +889,423 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
889
889
|
})
|
|
890
890
|
: undefined;
|
|
891
891
|
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
enableLsp,
|
|
897
|
-
get hasEditTool() {
|
|
898
|
-
return !options.toolNames || options.toolNames.includes("edit");
|
|
899
|
-
},
|
|
900
|
-
skipPythonPreflight: options.skipPythonPreflight,
|
|
901
|
-
contextFiles,
|
|
902
|
-
skills,
|
|
903
|
-
eventBus,
|
|
904
|
-
outputSchema: options.outputSchema,
|
|
905
|
-
requireSubmitResultTool: options.requireSubmitResultTool,
|
|
906
|
-
taskDepth: options.taskDepth ?? 0,
|
|
907
|
-
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
908
|
-
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
909
|
-
getSessionSpawns: () => options.spawns ?? "*",
|
|
910
|
-
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
911
|
-
getActiveModelString: () => {
|
|
892
|
+
const pythonKernelOwnerId = `agent-session:${Snowflake.next()}`;
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
const getActiveModelString = (): string | undefined => {
|
|
912
896
|
const activeModel = agent?.state.model;
|
|
913
897
|
if (activeModel) return formatModelString(activeModel);
|
|
914
|
-
// Fall back to initial model during tool creation (before agent exists)
|
|
915
898
|
if (model) return formatModelString(model);
|
|
916
899
|
return undefined;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
getSkills: () => skills,
|
|
978
|
-
}),
|
|
979
|
-
);
|
|
980
|
-
internalRouter.register(
|
|
981
|
-
new RuleProtocolHandler({
|
|
982
|
-
getRules: () => [...rulebookRules, ...alwaysApplyRules],
|
|
983
|
-
}),
|
|
984
|
-
);
|
|
985
|
-
internalRouter.register(new PiProtocolHandler());
|
|
986
|
-
internalRouter.register(new JobsProtocolHandler({ getAsyncJobManager: () => asyncJobManager }));
|
|
987
|
-
internalRouter.register(new McpProtocolHandler({ getMcpManager: () => mcpManager }));
|
|
988
|
-
toolSession.internalRouter = internalRouter;
|
|
989
|
-
toolSession.getArtifactsDir = getArtifactsDir;
|
|
990
|
-
toolSession.agentOutputManager = new AgentOutputManager(
|
|
991
|
-
getArtifactsDir,
|
|
992
|
-
options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
|
|
993
|
-
);
|
|
994
|
-
|
|
995
|
-
// Create built-in tools (already wrapped with meta notice formatting)
|
|
996
|
-
const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
|
|
997
|
-
|
|
998
|
-
// Discover MCP tools from .mcp.json files
|
|
999
|
-
let mcpManager: MCPManager | undefined;
|
|
1000
|
-
const enableMCP = options.enableMCP ?? true;
|
|
1001
|
-
const customTools: CustomTool[] = [];
|
|
1002
|
-
if (enableMCP) {
|
|
1003
|
-
const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
|
|
1004
|
-
onConnecting: serverNames => {
|
|
1005
|
-
if (options.hasUI && serverNames.length > 0) {
|
|
1006
|
-
process.stderr.write(`${chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}…`)}\n`);
|
|
900
|
+
};
|
|
901
|
+
const toolSession: ToolSession = {
|
|
902
|
+
cwd,
|
|
903
|
+
hasUI: options.hasUI ?? false,
|
|
904
|
+
enableLsp,
|
|
905
|
+
get hasEditTool() {
|
|
906
|
+
const requestedToolNames = options.toolNames
|
|
907
|
+
? [...new Set(options.toolNames.map(name => name.toLowerCase()))]
|
|
908
|
+
: undefined;
|
|
909
|
+
return !requestedToolNames || requestedToolNames.includes("edit");
|
|
910
|
+
},
|
|
911
|
+
skipPythonPreflight: options.skipPythonPreflight,
|
|
912
|
+
forcePythonWarmup: options.forcePythonWarmup,
|
|
913
|
+
contextFiles,
|
|
914
|
+
skills,
|
|
915
|
+
eventBus,
|
|
916
|
+
outputSchema: options.outputSchema,
|
|
917
|
+
requireSubmitResultTool: options.requireSubmitResultTool,
|
|
918
|
+
taskDepth: options.taskDepth ?? 0,
|
|
919
|
+
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
920
|
+
getPythonKernelOwnerId: () => pythonKernelOwnerId,
|
|
921
|
+
assertPythonExecutionAllowed: () => session?.assertPythonExecutionAllowed(),
|
|
922
|
+
trackPythonExecution: (execution, abortController) =>
|
|
923
|
+
session ? session.trackPythonExecution(execution, abortController) : execution,
|
|
924
|
+
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
925
|
+
getSessionSpawns: () => options.spawns ?? "*",
|
|
926
|
+
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
927
|
+
getActiveModelString,
|
|
928
|
+
getPlanModeState: () => session.getPlanModeState(),
|
|
929
|
+
getCompactContext: () => session.formatCompactContext(),
|
|
930
|
+
getTodoPhases: () => session.getTodoPhases(),
|
|
931
|
+
setTodoPhases: phases => session.setTodoPhases(phases),
|
|
932
|
+
isMCPDiscoveryEnabled: () => session.isMCPDiscoveryEnabled(),
|
|
933
|
+
getDiscoverableMCPTools: () => session.getDiscoverableMCPTools(),
|
|
934
|
+
getDiscoverableMCPSearchIndex: () => session.getDiscoverableMCPSearchIndex(),
|
|
935
|
+
getSelectedMCPToolNames: () => session.getSelectedMCPToolNames(),
|
|
936
|
+
activateDiscoveredMCPTools: toolNames => session.activateDiscoveredMCPTools(toolNames),
|
|
937
|
+
getCheckpointState: () => session.getCheckpointState(),
|
|
938
|
+
setCheckpointState: state => session.setCheckpointState(state ?? undefined),
|
|
939
|
+
getToolChoiceQueue: () => session.toolChoiceQueue,
|
|
940
|
+
buildToolChoice: name => {
|
|
941
|
+
const m = session.model;
|
|
942
|
+
return m ? buildNamedToolChoice(name, m) : undefined;
|
|
943
|
+
},
|
|
944
|
+
steer: msg =>
|
|
945
|
+
session.agent.steer({
|
|
946
|
+
role: "custom",
|
|
947
|
+
customType: msg.customType,
|
|
948
|
+
content: msg.content,
|
|
949
|
+
display: false,
|
|
950
|
+
details: msg.details,
|
|
951
|
+
attribution: "agent",
|
|
952
|
+
timestamp: Date.now(),
|
|
953
|
+
}),
|
|
954
|
+
peekQueueInvoker: () => session.peekQueueInvoker(),
|
|
955
|
+
allocateOutputArtifact: async toolType => {
|
|
956
|
+
try {
|
|
957
|
+
return await sessionManager.allocateArtifactPath(toolType);
|
|
958
|
+
} catch {
|
|
959
|
+
return {};
|
|
1007
960
|
}
|
|
1008
961
|
},
|
|
1009
|
-
|
|
1010
|
-
// Always filter Exa - we have native integration
|
|
1011
|
-
filterExa: true,
|
|
1012
|
-
// Filter browser MCP servers when builtin browser tool is active
|
|
1013
|
-
filterBrowser: settings.get("browser.enabled") ?? false,
|
|
1014
|
-
cacheStorage: settings.getStorage(),
|
|
962
|
+
settings,
|
|
1015
963
|
authStorage,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
if (settings.get("mcp.notifications")) {
|
|
1021
|
-
mcpManager.setNotificationsEnabled(true);
|
|
1022
|
-
}
|
|
1023
|
-
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
1024
|
-
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
|
|
1025
|
-
Bun.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
1026
|
-
}
|
|
964
|
+
modelRegistry,
|
|
965
|
+
asyncJobManager,
|
|
966
|
+
};
|
|
1027
967
|
|
|
1028
|
-
//
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
}
|
|
968
|
+
// Initialize internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, mcp://, local://)
|
|
969
|
+
const internalRouter = new InternalUrlRouter();
|
|
970
|
+
const getArtifactsDir = () => sessionManager.getArtifactsDir();
|
|
971
|
+
internalRouter.register(new AgentProtocolHandler({ getArtifactsDir }));
|
|
972
|
+
internalRouter.register(new ArtifactProtocolHandler({ getArtifactsDir }));
|
|
973
|
+
internalRouter.register(
|
|
974
|
+
new MemoryProtocolHandler({
|
|
975
|
+
getMemoryRoot: () => getMemoryRoot(agentDir, settings.getCwd()),
|
|
976
|
+
}),
|
|
977
|
+
);
|
|
978
|
+
internalRouter.register(
|
|
979
|
+
new LocalProtocolHandler({
|
|
980
|
+
getArtifactsDir,
|
|
981
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
982
|
+
}),
|
|
983
|
+
);
|
|
984
|
+
internalRouter.register(
|
|
985
|
+
new SkillProtocolHandler({
|
|
986
|
+
getSkills: () => skills,
|
|
987
|
+
}),
|
|
988
|
+
);
|
|
989
|
+
internalRouter.register(
|
|
990
|
+
new RuleProtocolHandler({
|
|
991
|
+
getRules: () => [...rulebookRules, ...alwaysApplyRules],
|
|
992
|
+
}),
|
|
993
|
+
);
|
|
994
|
+
internalRouter.register(new PiProtocolHandler());
|
|
995
|
+
internalRouter.register(new JobsProtocolHandler({ getAsyncJobManager: () => asyncJobManager }));
|
|
996
|
+
internalRouter.register(new McpProtocolHandler({ getMcpManager: () => mcpManager }));
|
|
997
|
+
toolSession.internalRouter = internalRouter;
|
|
998
|
+
toolSession.getArtifactsDir = getArtifactsDir;
|
|
999
|
+
toolSession.agentOutputManager = new AgentOutputManager(
|
|
1000
|
+
getArtifactsDir,
|
|
1001
|
+
options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
|
|
1002
|
+
);
|
|
1032
1003
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1004
|
+
// Create built-in tools (already wrapped with meta notice formatting)
|
|
1005
|
+
const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
|
|
1006
|
+
|
|
1007
|
+
// Discover MCP tools from .mcp.json files
|
|
1008
|
+
let mcpManager: MCPManager | undefined;
|
|
1009
|
+
const enableMCP = options.enableMCP ?? true;
|
|
1010
|
+
const customTools: CustomTool[] = [];
|
|
1011
|
+
if (enableMCP) {
|
|
1012
|
+
const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
|
|
1013
|
+
onConnecting: serverNames => {
|
|
1014
|
+
if (options.hasUI && serverNames.length > 0) {
|
|
1015
|
+
process.stderr.write(`${chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}…`)}\n`);
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
|
|
1019
|
+
// Always filter Exa - we have native integration
|
|
1020
|
+
filterExa: true,
|
|
1021
|
+
// Filter browser MCP servers when builtin browser tool is active
|
|
1022
|
+
filterBrowser: settings.get("browser.enabled") ?? false,
|
|
1023
|
+
cacheStorage: settings.getStorage(),
|
|
1024
|
+
authStorage,
|
|
1025
|
+
});
|
|
1026
|
+
mcpManager = mcpResult.manager;
|
|
1027
|
+
toolSession.mcpManager = mcpManager;
|
|
1038
1028
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1029
|
+
if (settings.get("mcp.notifications")) {
|
|
1030
|
+
mcpManager.setNotificationsEnabled(true);
|
|
1031
|
+
}
|
|
1032
|
+
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
1033
|
+
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
|
|
1034
|
+
Bun.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
1035
|
+
}
|
|
1044
1036
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1037
|
+
// Log MCP errors
|
|
1038
|
+
for (const { path, error } of mcpResult.errors) {
|
|
1039
|
+
logger.error("MCP tool load failed", { path, error });
|
|
1040
|
+
}
|
|
1049
1041
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
[],
|
|
1056
|
-
cwd,
|
|
1057
|
-
builtInToolNames,
|
|
1058
|
-
action => queueResolveHandler(toolSession, action),
|
|
1059
|
-
);
|
|
1060
|
-
for (const { path, error } of discoveredCustomTools.errors) {
|
|
1061
|
-
logger.error("Custom tool load failed", { path, error });
|
|
1062
|
-
}
|
|
1063
|
-
if (discoveredCustomTools.tools.length > 0) {
|
|
1064
|
-
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
|
|
1065
|
-
}
|
|
1042
|
+
if (mcpResult.tools.length > 0) {
|
|
1043
|
+
// MCP tools are LoadedCustomTool, extract the tool property
|
|
1044
|
+
customTools.push(...mcpResult.tools.map(loaded => loaded.tool));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1066
1047
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1048
|
+
// Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
|
|
1049
|
+
const geminiImageTools = await logger.time("getGeminiImageTools", getGeminiImageTools);
|
|
1050
|
+
if (geminiImageTools.length > 0) {
|
|
1051
|
+
customTools.push(...(geminiImageTools as unknown as CustomTool[]));
|
|
1052
|
+
}
|
|
1072
1053
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const configuredPaths = options.additionalExtensionPaths ?? [];
|
|
1077
|
-
extensionsResult = await logger.time("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
|
|
1078
|
-
for (const { path, error } of extensionsResult.errors) {
|
|
1079
|
-
logger.error("Failed to load extension", { path, error });
|
|
1054
|
+
// Add web search tools
|
|
1055
|
+
if (options.toolNames?.includes("web_search")) {
|
|
1056
|
+
customTools.push(...getSearchTools());
|
|
1080
1057
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
"discoverAndLoadExtensions",
|
|
1089
|
-
discoverAndLoadExtensions,
|
|
1090
|
-
configuredPaths,
|
|
1058
|
+
|
|
1059
|
+
// Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
|
|
1060
|
+
const builtInToolNames = builtinTools.map(t => t.name);
|
|
1061
|
+
const discoveredCustomTools = await logger.time(
|
|
1062
|
+
"discoverAndLoadCustomTools",
|
|
1063
|
+
discoverAndLoadCustomTools,
|
|
1064
|
+
[],
|
|
1091
1065
|
cwd,
|
|
1092
|
-
|
|
1093
|
-
|
|
1066
|
+
builtInToolNames,
|
|
1067
|
+
action => queueResolveHandler(toolSession, action),
|
|
1094
1068
|
);
|
|
1095
|
-
for (const { path, error } of
|
|
1096
|
-
logger.error("
|
|
1069
|
+
for (const { path, error } of discoveredCustomTools.errors) {
|
|
1070
|
+
logger.error("Custom tool load failed", { path, error });
|
|
1071
|
+
}
|
|
1072
|
+
if (discoveredCustomTools.tools.length > 0) {
|
|
1073
|
+
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
|
|
1097
1074
|
}
|
|
1098
|
-
}
|
|
1099
1075
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1076
|
+
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
|
|
1077
|
+
inlineExtensions.push(createAutoresearchExtension);
|
|
1078
|
+
if (customTools.length > 0) {
|
|
1079
|
+
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Load extensions (discovers from standard locations + configured paths)
|
|
1083
|
+
let extensionsResult: LoadExtensionsResult;
|
|
1084
|
+
if (options.disableExtensionDiscovery) {
|
|
1085
|
+
const configuredPaths = options.additionalExtensionPaths ?? [];
|
|
1086
|
+
extensionsResult = await logger.time("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
|
|
1087
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
1088
|
+
logger.error("Failed to load extension", { path, error });
|
|
1089
|
+
}
|
|
1090
|
+
} else if (options.preloadedExtensions) {
|
|
1091
|
+
extensionsResult = options.preloadedExtensions;
|
|
1092
|
+
} else {
|
|
1093
|
+
// Merge CLI extension paths with settings extension paths
|
|
1094
|
+
const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
|
|
1095
|
+
const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
|
|
1096
|
+
extensionsResult = await logger.time(
|
|
1097
|
+
"discoverAndLoadExtensions",
|
|
1098
|
+
discoverAndLoadExtensions,
|
|
1099
|
+
configuredPaths,
|
|
1106
1100
|
cwd,
|
|
1107
1101
|
eventBus,
|
|
1108
|
-
|
|
1109
|
-
`<inline-${i}>`,
|
|
1102
|
+
disabledExtensionIds,
|
|
1110
1103
|
);
|
|
1111
|
-
extensionsResult.
|
|
1104
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
1105
|
+
logger.error("Failed to load extension", { path, error });
|
|
1106
|
+
}
|
|
1112
1107
|
}
|
|
1113
|
-
}
|
|
1114
1108
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1109
|
+
// Load inline extensions from factories
|
|
1110
|
+
if (inlineExtensions.length > 0) {
|
|
1111
|
+
for (let i = 0; i < inlineExtensions.length; i++) {
|
|
1112
|
+
const factory = inlineExtensions[i];
|
|
1113
|
+
const loaded = await loadExtensionFromFactory(
|
|
1114
|
+
factory,
|
|
1115
|
+
cwd,
|
|
1116
|
+
eventBus,
|
|
1117
|
+
extensionsResult.runtime,
|
|
1118
|
+
`<inline-${i}>`,
|
|
1119
|
+
);
|
|
1120
|
+
extensionsResult.extensions.push(loaded);
|
|
1121
|
+
}
|
|
1126
1122
|
}
|
|
1127
|
-
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
1128
|
-
}
|
|
1129
1123
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
modelFallbackMessage = `Model "${options.modelPattern}" not found`;
|
|
1124
|
+
// Process provider registrations queued during extension loading.
|
|
1125
|
+
// This must happen before the runner is created so that models registered by
|
|
1126
|
+
// extensions are available for model selection on session resume / fallback.
|
|
1127
|
+
const activeExtensionSources = extensionsResult.extensions.map(extension => extension.path);
|
|
1128
|
+
modelRegistry.syncExtensionSources(activeExtensionSources);
|
|
1129
|
+
for (const sourceId of new Set(activeExtensionSources)) {
|
|
1130
|
+
modelRegistry.clearSourceRegistrations(sourceId);
|
|
1131
|
+
}
|
|
1132
|
+
if (extensionsResult.runtime.pendingProviderRegistrations.length > 0) {
|
|
1133
|
+
for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
|
|
1134
|
+
modelRegistry.registerProvider(name, config, sourceId);
|
|
1135
|
+
}
|
|
1136
|
+
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
1144
1137
|
}
|
|
1145
|
-
}
|
|
1146
1138
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1139
|
+
// Resolve deferred --model pattern now that extension models are registered.
|
|
1140
|
+
if (!model && options.modelPattern) {
|
|
1141
|
+
const availableModels = modelRegistry.getAll();
|
|
1142
|
+
const matchPreferences = {
|
|
1143
|
+
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
1144
|
+
};
|
|
1145
|
+
const { model: resolved } = parseModelPattern(options.modelPattern, availableModels, matchPreferences, {
|
|
1146
|
+
modelRegistry,
|
|
1147
|
+
});
|
|
1148
|
+
if (resolved) {
|
|
1149
|
+
model = resolved;
|
|
1150
|
+
modelFallbackMessage = undefined;
|
|
1151
|
+
} else {
|
|
1152
|
+
modelFallbackMessage = `Model "${options.modelPattern}" not found`;
|
|
1155
1153
|
}
|
|
1156
1154
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1155
|
+
|
|
1156
|
+
// Fall back to first available model with a valid API key.
|
|
1157
|
+
// Skip fallback if the user explicitly requested a model via --model that wasn't found.
|
|
1158
|
+
if (!model && !options.modelPattern) {
|
|
1159
|
+
const allModels = modelRegistry.getAll();
|
|
1160
|
+
for (const candidate of allModels) {
|
|
1161
|
+
if (await hasModelApiKey(candidate)) {
|
|
1162
|
+
model = candidate;
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (model) {
|
|
1167
|
+
if (modelFallbackMessage) {
|
|
1168
|
+
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
1169
|
+
}
|
|
1170
|
+
} else {
|
|
1171
|
+
modelFallbackMessage =
|
|
1172
|
+
"No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
1160
1173
|
}
|
|
1161
|
-
} else {
|
|
1162
|
-
modelFallbackMessage =
|
|
1163
|
-
"No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
1164
1174
|
}
|
|
1165
|
-
}
|
|
1166
1175
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1176
|
+
// Discover custom commands (TypeScript slash commands)
|
|
1177
|
+
const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
|
|
1178
|
+
? { commands: [], errors: [] }
|
|
1179
|
+
: await logger.time("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
|
|
1180
|
+
if (!options.disableExtensionDiscovery) {
|
|
1181
|
+
for (const { path, error } of customCommandsResult.errors) {
|
|
1182
|
+
logger.error("Failed to load custom command", { path, error });
|
|
1183
|
+
}
|
|
1174
1184
|
}
|
|
1175
|
-
}
|
|
1176
1185
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1186
|
+
let extensionRunner: ExtensionRunner | undefined;
|
|
1187
|
+
if (extensionsResult.extensions.length > 0) {
|
|
1188
|
+
extensionRunner = new ExtensionRunner(
|
|
1189
|
+
extensionsResult.extensions,
|
|
1190
|
+
extensionsResult.runtime,
|
|
1191
|
+
cwd,
|
|
1192
|
+
sessionManager,
|
|
1193
|
+
modelRegistry,
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1187
1196
|
|
|
1188
|
-
|
|
1189
|
-
sessionManager,
|
|
1190
|
-
modelRegistry,
|
|
1191
|
-
model: agent.state.model,
|
|
1192
|
-
isIdle: () => !session.isStreaming,
|
|
1193
|
-
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
1194
|
-
abort: () => {
|
|
1195
|
-
session.abort();
|
|
1196
|
-
},
|
|
1197
|
-
settings,
|
|
1198
|
-
});
|
|
1199
|
-
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
1200
|
-
|
|
1201
|
-
const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
|
|
1202
|
-
let wrappedExtensionTools: Tool[];
|
|
1203
|
-
|
|
1204
|
-
if (extensionRunner) {
|
|
1205
|
-
// With extension runner: convert CustomTools to ToolDefinitions and wrap all together
|
|
1206
|
-
const allCustomTools = [
|
|
1207
|
-
...registeredTools,
|
|
1208
|
-
...(options.customTools?.map(tool => {
|
|
1209
|
-
const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
|
|
1210
|
-
return { definition, extensionPath: "<sdk>" };
|
|
1211
|
-
}) ?? []),
|
|
1212
|
-
];
|
|
1213
|
-
wrappedExtensionTools = wrapRegisteredTools(allCustomTools, extensionRunner);
|
|
1214
|
-
} else {
|
|
1215
|
-
// Without extension runner: wrap CustomTools directly with CustomToolAdapter
|
|
1216
|
-
// ToolDefinition items require ExtensionContext and cannot be used without a runner
|
|
1217
|
-
const customToolContext = (): CustomToolContext => ({
|
|
1197
|
+
const getSessionContext = () => ({
|
|
1218
1198
|
sessionManager,
|
|
1219
1199
|
modelRegistry,
|
|
1220
|
-
model: agent
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1200
|
+
model: agent.state.model,
|
|
1201
|
+
isIdle: () => !session.isStreaming,
|
|
1202
|
+
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
1203
|
+
abort: () => {
|
|
1204
|
+
session.abort();
|
|
1205
|
+
},
|
|
1225
1206
|
settings,
|
|
1226
1207
|
});
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1208
|
+
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
1209
|
+
|
|
1210
|
+
const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
|
|
1211
|
+
let wrappedExtensionTools: Tool[];
|
|
1212
|
+
|
|
1213
|
+
if (extensionRunner) {
|
|
1214
|
+
// With extension runner: convert CustomTools to ToolDefinitions and wrap all together
|
|
1215
|
+
const allCustomTools = [
|
|
1216
|
+
...registeredTools,
|
|
1217
|
+
...(options.customTools?.map(tool => {
|
|
1218
|
+
const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
|
|
1219
|
+
return { definition, extensionPath: "<sdk>" };
|
|
1220
|
+
}) ?? []),
|
|
1221
|
+
];
|
|
1222
|
+
wrappedExtensionTools = wrapRegisteredTools(allCustomTools, extensionRunner);
|
|
1223
|
+
} else {
|
|
1224
|
+
// Without extension runner: wrap CustomTools directly with CustomToolAdapter
|
|
1225
|
+
// ToolDefinition items require ExtensionContext and cannot be used without a runner
|
|
1226
|
+
const customToolContext = (): CustomToolContext => ({
|
|
1227
|
+
sessionManager,
|
|
1228
|
+
modelRegistry,
|
|
1229
|
+
model: agent?.state.model,
|
|
1230
|
+
isIdle: () => !session?.isStreaming,
|
|
1231
|
+
hasQueuedMessages: () => (session?.queuedMessageCount ?? 0) > 0,
|
|
1232
|
+
abort: () => session?.abort(),
|
|
1233
|
+
settings,
|
|
1234
|
+
});
|
|
1235
|
+
wrappedExtensionTools = (options.customTools ?? [])
|
|
1236
|
+
.filter(isCustomTool)
|
|
1237
|
+
.map(tool => CustomToolAdapter.wrap(tool, customToolContext));
|
|
1243
1238
|
}
|
|
1244
|
-
}
|
|
1245
|
-
if (model?.provider === "cursor") {
|
|
1246
|
-
toolRegistry.delete("edit");
|
|
1247
|
-
}
|
|
1248
1239
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
toolRegistry.set(
|
|
1240
|
+
// All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
|
|
1241
|
+
const toolRegistry = new Map<string, Tool>();
|
|
1242
|
+
for (const tool of builtinTools) {
|
|
1243
|
+
toolRegistry.set(tool.name, tool);
|
|
1244
|
+
}
|
|
1245
|
+
for (const tool of wrappedExtensionTools) {
|
|
1246
|
+
toolRegistry.set(tool.name, tool);
|
|
1247
|
+
}
|
|
1248
|
+
if (extensionRunner) {
|
|
1249
|
+
for (const tool of toolRegistry.values()) {
|
|
1250
|
+
toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (model?.provider === "cursor") {
|
|
1254
|
+
toolRegistry.delete("edit");
|
|
1256
1255
|
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1260
|
-
const cursorExecHandlers = new CursorExecHandlers({
|
|
1261
|
-
cwd,
|
|
1262
|
-
tools: toolRegistry,
|
|
1263
|
-
getToolContext: () => toolContextStore.getContext(),
|
|
1264
|
-
emitEvent: event => cursorEventEmitter?.(event),
|
|
1265
|
-
});
|
|
1266
1256
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
const hasDiscoverableMCPTools =
|
|
1275
|
-
mcpDiscoveryEnabled && toolNames.includes("search_tool_bm25") && discoverableMCPTools.length > 0;
|
|
1276
|
-
const promptTools = buildSystemPromptToolMetadata(tools, {
|
|
1277
|
-
search_tool_bm25: { description: renderSearchToolBm25Description(discoverableMCPTools) },
|
|
1278
|
-
});
|
|
1279
|
-
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
1280
|
-
|
|
1281
|
-
// Build combined append prompt: memory instructions + MCP server instructions
|
|
1282
|
-
const serverInstructions = mcpManager?.getServerInstructions();
|
|
1283
|
-
let appendPrompt: string | undefined = memoryInstructions ?? undefined;
|
|
1284
|
-
if (serverInstructions && serverInstructions.size > 0) {
|
|
1285
|
-
const MAX_INSTRUCTIONS_LENGTH = 4000;
|
|
1286
|
-
const parts: string[] = [];
|
|
1287
|
-
if (appendPrompt) parts.push(appendPrompt);
|
|
1288
|
-
parts.push(
|
|
1289
|
-
"## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
|
|
1290
|
-
);
|
|
1291
|
-
for (const [srvName, srvInstructions] of serverInstructions) {
|
|
1292
|
-
const truncated =
|
|
1293
|
-
srvInstructions.length > MAX_INSTRUCTIONS_LENGTH
|
|
1294
|
-
? `${srvInstructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}\n[truncated]`
|
|
1295
|
-
: srvInstructions;
|
|
1296
|
-
parts.push(`### ${srvName}\n${truncated}`);
|
|
1257
|
+
const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
|
|
1258
|
+
if (!hasDeferrableTools) {
|
|
1259
|
+
toolRegistry.delete("resolve");
|
|
1260
|
+
} else if (!toolRegistry.has("resolve")) {
|
|
1261
|
+
const resolveTool = await logger.time("createTools:resolve:session", HIDDEN_TOOLS.resolve, toolSession);
|
|
1262
|
+
if (resolveTool) {
|
|
1263
|
+
toolRegistry.set(resolveTool.name, wrapToolWithMetaNotice(resolveTool));
|
|
1297
1264
|
}
|
|
1298
|
-
appendPrompt = parts.join("\n\n");
|
|
1299
1265
|
}
|
|
1300
|
-
|
|
1266
|
+
|
|
1267
|
+
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1268
|
+
const cursorExecHandlers = new CursorExecHandlers({
|
|
1301
1269
|
cwd,
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
toolNames,
|
|
1306
|
-
rules: rulebookRules,
|
|
1307
|
-
alwaysApplyRules,
|
|
1308
|
-
skillsSettings: settings.getGroup("skills"),
|
|
1309
|
-
appendSystemPrompt: appendPrompt,
|
|
1310
|
-
repeatToolDescriptions,
|
|
1311
|
-
intentField,
|
|
1312
|
-
mcpDiscoveryMode: hasDiscoverableMCPTools,
|
|
1313
|
-
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1314
|
-
eagerTasks,
|
|
1315
|
-
secretsEnabled,
|
|
1270
|
+
tools: toolRegistry,
|
|
1271
|
+
getToolContext: () => toolContextStore.getContext(),
|
|
1272
|
+
emitEvent: event => cursorEventEmitter?.(event),
|
|
1316
1273
|
});
|
|
1317
1274
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1275
|
+
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
1276
|
+
const eagerTasks = settings.get("task.eager");
|
|
1277
|
+
const intentField = settings.get("tools.intentTracing") || $flag("PI_INTENT_TRACING") ? INTENT_FIELD : undefined;
|
|
1278
|
+
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
1279
|
+
toolContextStore.setToolNames(toolNames);
|
|
1280
|
+
const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
|
|
1281
|
+
const discoverableMCPSummary = summarizeDiscoverableMCPTools(discoverableMCPTools);
|
|
1282
|
+
const hasDiscoverableMCPTools =
|
|
1283
|
+
mcpDiscoveryEnabled && toolNames.includes("search_tool_bm25") && discoverableMCPTools.length > 0;
|
|
1284
|
+
const promptTools = buildSystemPromptToolMetadata(tools, {
|
|
1285
|
+
search_tool_bm25: { description: renderSearchToolBm25Description(discoverableMCPTools) },
|
|
1286
|
+
});
|
|
1287
|
+
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
1288
|
+
|
|
1289
|
+
// Build combined append prompt: memory instructions + MCP server instructions
|
|
1290
|
+
const serverInstructions = mcpManager?.getServerInstructions();
|
|
1291
|
+
let appendPrompt: string | undefined = memoryInstructions ?? undefined;
|
|
1292
|
+
if (serverInstructions && serverInstructions.size > 0) {
|
|
1293
|
+
const MAX_INSTRUCTIONS_LENGTH = 4000;
|
|
1294
|
+
const parts: string[] = [];
|
|
1295
|
+
if (appendPrompt) parts.push(appendPrompt);
|
|
1296
|
+
parts.push(
|
|
1297
|
+
"## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
|
|
1298
|
+
);
|
|
1299
|
+
for (const [srvName, srvInstructions] of serverInstructions) {
|
|
1300
|
+
const truncated =
|
|
1301
|
+
srvInstructions.length > MAX_INSTRUCTIONS_LENGTH
|
|
1302
|
+
? `${srvInstructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}\n[truncated]`
|
|
1303
|
+
: srvInstructions;
|
|
1304
|
+
parts.push(`### ${srvName}\n${truncated}`);
|
|
1305
|
+
}
|
|
1306
|
+
appendPrompt = parts.join("\n\n");
|
|
1307
|
+
}
|
|
1308
|
+
const defaultPrompt = await buildSystemPromptInternal({
|
|
1323
1309
|
cwd,
|
|
1324
1310
|
skills,
|
|
1325
1311
|
contextFiles,
|
|
@@ -1328,7 +1314,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1328
1314
|
rules: rulebookRules,
|
|
1329
1315
|
alwaysApplyRules,
|
|
1330
1316
|
skillsSettings: settings.getGroup("skills"),
|
|
1331
|
-
customPrompt: options.systemPrompt,
|
|
1332
1317
|
appendSystemPrompt: appendPrompt,
|
|
1333
1318
|
repeatToolDescriptions,
|
|
1334
1319
|
intentField,
|
|
@@ -1337,362 +1322,407 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1337
1322
|
eagerTasks,
|
|
1338
1323
|
secretsEnabled,
|
|
1339
1324
|
});
|
|
1340
|
-
}
|
|
1341
|
-
return options.systemPrompt(defaultPrompt);
|
|
1342
|
-
};
|
|
1343
1325
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
: [];
|
|
1370
|
-
let initialSelectedMCPToolNames: string[] = [];
|
|
1371
|
-
let defaultSelectedMCPToolNames: string[] = [];
|
|
1372
|
-
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
1373
|
-
if (mcpDiscoveryEnabled) {
|
|
1374
|
-
const restoredSelectedMCPToolNames = existingSession.selectedMCPToolNames.filter(name => toolRegistry.has(name));
|
|
1375
|
-
defaultSelectedMCPToolNames = [
|
|
1376
|
-
...new Set([...discoveryDefaultServerToolNames, ...explicitlyRequestedMCPToolNames]),
|
|
1377
|
-
];
|
|
1378
|
-
initialSelectedMCPToolNames = existingSession.hasPersistedMCPToolSelection
|
|
1379
|
-
? restoredSelectedMCPToolNames
|
|
1380
|
-
: [...new Set([...restoredSelectedMCPToolNames, ...defaultSelectedMCPToolNames])];
|
|
1381
|
-
initialToolNames = [
|
|
1382
|
-
...new Set([
|
|
1383
|
-
...initialRequestedActiveToolNames.filter(name => !name.startsWith("mcp_")),
|
|
1384
|
-
...initialSelectedMCPToolNames,
|
|
1385
|
-
]),
|
|
1386
|
-
];
|
|
1387
|
-
}
|
|
1326
|
+
if (options.systemPrompt === undefined) {
|
|
1327
|
+
return defaultPrompt;
|
|
1328
|
+
}
|
|
1329
|
+
if (typeof options.systemPrompt === "string") {
|
|
1330
|
+
return await buildSystemPromptInternal({
|
|
1331
|
+
cwd,
|
|
1332
|
+
skills,
|
|
1333
|
+
contextFiles,
|
|
1334
|
+
tools: promptTools,
|
|
1335
|
+
toolNames,
|
|
1336
|
+
rules: rulebookRules,
|
|
1337
|
+
alwaysApplyRules,
|
|
1338
|
+
skillsSettings: settings.getGroup("skills"),
|
|
1339
|
+
customPrompt: options.systemPrompt,
|
|
1340
|
+
appendSystemPrompt: appendPrompt,
|
|
1341
|
+
repeatToolDescriptions,
|
|
1342
|
+
intentField,
|
|
1343
|
+
mcpDiscoveryMode: hasDiscoverableMCPTools,
|
|
1344
|
+
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1345
|
+
eagerTasks,
|
|
1346
|
+
secretsEnabled,
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
return options.systemPrompt(defaultPrompt);
|
|
1350
|
+
};
|
|
1388
1351
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1352
|
+
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
1353
|
+
const requestedToolNames =
|
|
1354
|
+
(options.toolNames ? [...new Set(options.toolNames.map(name => name.toLowerCase()))] : undefined) ??
|
|
1355
|
+
toolNamesFromRegistry;
|
|
1356
|
+
const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
|
|
1357
|
+
const includeExitPlanMode = requestedToolNames.includes("exit_plan_mode");
|
|
1358
|
+
const mcpDiscoveryEnabled = settings.get("mcp.discoveryMode") ?? false;
|
|
1359
|
+
const defaultInactiveToolNames = new Set(
|
|
1360
|
+
registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
|
|
1361
|
+
);
|
|
1362
|
+
const requestedActiveToolNames = includeExitPlanMode
|
|
1363
|
+
? normalizedRequested
|
|
1364
|
+
: normalizedRequested.filter(name => name !== "exit_plan_mode");
|
|
1365
|
+
const initialRequestedActiveToolNames = options.toolNames
|
|
1366
|
+
? requestedActiveToolNames
|
|
1367
|
+
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
1368
|
+
const explicitlyRequestedMCPToolNames = options.toolNames
|
|
1369
|
+
? requestedActiveToolNames.filter(name => name.startsWith("mcp_"))
|
|
1370
|
+
: [];
|
|
1371
|
+
const discoveryDefaultServers = new Set(
|
|
1372
|
+
(settings.get("mcp.discoveryDefaultServers") ?? []).map(serverName => serverName.trim()).filter(Boolean),
|
|
1373
|
+
);
|
|
1374
|
+
const discoveryDefaultServerToolNames = mcpDiscoveryEnabled
|
|
1375
|
+
? selectDiscoverableMCPToolNamesByServer(
|
|
1376
|
+
collectDiscoverableMCPTools(toolRegistry.values()),
|
|
1377
|
+
discoveryDefaultServers,
|
|
1378
|
+
)
|
|
1379
|
+
: [];
|
|
1380
|
+
let initialSelectedMCPToolNames: string[] = [];
|
|
1381
|
+
let defaultSelectedMCPToolNames: string[] = [];
|
|
1382
|
+
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
1383
|
+
if (mcpDiscoveryEnabled) {
|
|
1384
|
+
const restoredSelectedMCPToolNames = existingSession.selectedMCPToolNames.filter(name =>
|
|
1385
|
+
toolRegistry.has(name),
|
|
1386
|
+
);
|
|
1387
|
+
defaultSelectedMCPToolNames = [
|
|
1388
|
+
...new Set([...discoveryDefaultServerToolNames, ...explicitlyRequestedMCPToolNames]),
|
|
1389
|
+
];
|
|
1390
|
+
initialSelectedMCPToolNames = existingSession.hasPersistedMCPToolSelection
|
|
1391
|
+
? restoredSelectedMCPToolNames
|
|
1392
|
+
: [...new Set([...restoredSelectedMCPToolNames, ...defaultSelectedMCPToolNames])];
|
|
1393
|
+
initialToolNames = [
|
|
1394
|
+
...new Set([
|
|
1395
|
+
...initialRequestedActiveToolNames.filter(name => !name.startsWith("mcp_")),
|
|
1396
|
+
...initialSelectedMCPToolNames,
|
|
1397
|
+
]),
|
|
1398
|
+
];
|
|
1397
1399
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
+
|
|
1401
|
+
// Custom tools and extension-registered tools are always included regardless of toolNames filter
|
|
1402
|
+
const alwaysInclude: string[] = [
|
|
1403
|
+
...(options.customTools?.map(t => (isCustomTool(t) ? t.name : t.name)) ?? []),
|
|
1404
|
+
...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
|
|
1405
|
+
];
|
|
1406
|
+
for (const name of alwaysInclude) {
|
|
1407
|
+
if (mcpDiscoveryEnabled && name.startsWith("mcp_")) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
|
|
1411
|
+
initialToolNames.push(name);
|
|
1412
|
+
}
|
|
1400
1413
|
}
|
|
1401
|
-
}
|
|
1402
1414
|
|
|
1403
|
-
|
|
1415
|
+
const systemPrompt = await logger.time("buildSystemPrompt", rebuildSystemPrompt, initialToolNames, toolRegistry);
|
|
1404
1416
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1417
|
+
const promptTemplates =
|
|
1418
|
+
options.promptTemplates ??
|
|
1419
|
+
(await logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir));
|
|
1420
|
+
toolSession.promptTemplates = promptTemplates;
|
|
1408
1421
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1422
|
+
const slashCommands =
|
|
1423
|
+
options.slashCommands ?? (await logger.time("discoverSlashCommands", discoverSlashCommands, cwd));
|
|
1411
1424
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1425
|
+
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
1426
|
+
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
1427
|
+
const converted = convertToLlm(messages);
|
|
1428
|
+
// Check setting dynamically so mid-session changes take effect
|
|
1429
|
+
if (!settings.get("images.blockImages")) {
|
|
1430
|
+
return converted;
|
|
1431
|
+
}
|
|
1432
|
+
// Filter out ImageContent from all messages, replacing with text placeholder
|
|
1433
|
+
return converted.map(msg => {
|
|
1434
|
+
if (msg.role === "user" || msg.role === "toolResult") {
|
|
1435
|
+
const content = msg.content;
|
|
1436
|
+
if (Array.isArray(content)) {
|
|
1437
|
+
const hasImages = content.some(c => c.type === "image");
|
|
1438
|
+
if (hasImages) {
|
|
1439
|
+
const filteredContent = content
|
|
1440
|
+
.map(c =>
|
|
1441
|
+
c.type === "image" ? { type: "text" as const, text: "Image reading is disabled." } : c,
|
|
1442
|
+
)
|
|
1443
|
+
.filter((c, i, arr) => {
|
|
1444
|
+
// Dedupe consecutive "Image reading is disabled." texts
|
|
1445
|
+
if (!(c.type === "text" && c.text === "Image reading is disabled." && i > 0)) return true;
|
|
1446
|
+
const prev = arr[i - 1];
|
|
1447
|
+
return !(prev.type === "text" && prev.text === "Image reading is disabled.");
|
|
1448
|
+
});
|
|
1449
|
+
return { ...msg, content: filteredContent };
|
|
1450
|
+
}
|
|
1435
1451
|
}
|
|
1436
1452
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
}
|
|
1440
|
-
};
|
|
1453
|
+
return msg;
|
|
1454
|
+
});
|
|
1455
|
+
};
|
|
1441
1456
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1457
|
+
// Final convertToLlm: chain block-images filter with secret obfuscation
|
|
1458
|
+
const convertToLlmFinal = (messages: AgentMessage[]): Message[] => {
|
|
1459
|
+
const converted = convertToLlmWithBlockImages(messages);
|
|
1460
|
+
if (!obfuscator?.hasSecrets()) return converted;
|
|
1461
|
+
return obfuscateMessages(obfuscator, converted);
|
|
1462
|
+
};
|
|
1463
|
+
const transformContext = extensionRunner
|
|
1464
|
+
? async (messages: AgentMessage[], _signal?: AbortSignal) => {
|
|
1465
|
+
return await extensionRunner.emitContext(messages);
|
|
1466
|
+
}
|
|
1467
|
+
: undefined;
|
|
1468
|
+
const onPayload = extensionRunner
|
|
1469
|
+
? async (payload: unknown, _model?: Model) => {
|
|
1470
|
+
return await extensionRunner.emitBeforeProviderRequest(payload);
|
|
1471
|
+
}
|
|
1472
|
+
: undefined;
|
|
1458
1473
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1474
|
+
const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
|
|
1475
|
+
toolContextStore.setUIContext(uiContext, hasUI);
|
|
1476
|
+
};
|
|
1462
1477
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1478
|
+
const initialTools = initialToolNames
|
|
1479
|
+
.map(name => toolRegistry.get(name))
|
|
1480
|
+
.filter((tool): tool is AgentTool => tool !== undefined);
|
|
1481
|
+
|
|
1482
|
+
const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
|
|
1483
|
+
const preferOpenAICodexWebsockets =
|
|
1484
|
+
openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
|
|
1485
|
+
const serviceTierSetting = settings.get("serviceTier");
|
|
1486
|
+
|
|
1487
|
+
const initialServiceTier = hasServiceTierEntry
|
|
1488
|
+
? existingSession.serviceTier
|
|
1489
|
+
: serviceTierSetting === "none"
|
|
1490
|
+
? undefined
|
|
1491
|
+
: serviceTierSetting;
|
|
1492
|
+
|
|
1493
|
+
agent = new Agent({
|
|
1494
|
+
initialState: {
|
|
1495
|
+
systemPrompt,
|
|
1496
|
+
model,
|
|
1497
|
+
thinkingLevel: toReasoningEffort(thinkingLevel),
|
|
1498
|
+
tools: initialTools,
|
|
1499
|
+
},
|
|
1500
|
+
convertToLlm: convertToLlmFinal,
|
|
1501
|
+
onPayload,
|
|
1502
|
+
sessionId: providerSessionId,
|
|
1503
|
+
transformContext,
|
|
1504
|
+
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
1505
|
+
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
1506
|
+
interruptMode: settings.get("interruptMode") ?? "immediate",
|
|
1507
|
+
thinkingBudgets: settings.getGroup("thinkingBudgets"),
|
|
1508
|
+
temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
|
|
1509
|
+
topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
|
|
1510
|
+
topK: settings.get("topK") >= 0 ? settings.get("topK") : undefined,
|
|
1511
|
+
minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
|
|
1512
|
+
presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
|
|
1513
|
+
repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
|
|
1514
|
+
serviceTier: initialServiceTier,
|
|
1515
|
+
kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
|
|
1516
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1517
|
+
getToolContext: tc => toolContextStore.getContext(tc),
|
|
1518
|
+
getApiKey: async provider => {
|
|
1519
|
+
// Use the provider-facing session id for sticky credential selection so cache keys
|
|
1520
|
+
// and provider auth affinity stay aligned across fresh benchmark sessions.
|
|
1521
|
+
const key = await modelRegistry.getApiKeyForProvider(provider, providerSessionId);
|
|
1522
|
+
if (!key) {
|
|
1523
|
+
throw new Error(`No API key found for provider "${provider}"`);
|
|
1524
|
+
}
|
|
1525
|
+
return key;
|
|
1526
|
+
},
|
|
1527
|
+
cursorExecHandlers,
|
|
1528
|
+
transformToolCallArguments: (args, _toolName) => {
|
|
1529
|
+
let result = args;
|
|
1530
|
+
const maxTimeout = settings.get("tools.maxTimeout");
|
|
1531
|
+
if (maxTimeout > 0 && typeof result.timeout === "number") {
|
|
1532
|
+
result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
|
|
1533
|
+
}
|
|
1534
|
+
if (obfuscator?.hasSecrets()) {
|
|
1535
|
+
result = obfuscator.deobfuscateObject(result);
|
|
1536
|
+
}
|
|
1537
|
+
return result;
|
|
1538
|
+
},
|
|
1539
|
+
intentTracing: !!intentField,
|
|
1540
|
+
getToolChoice: () => session?.nextToolChoice(),
|
|
1541
|
+
});
|
|
1527
1542
|
|
|
1528
|
-
|
|
1543
|
+
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
1529
1544
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1545
|
+
// Restore messages if session has existing data
|
|
1546
|
+
if (hasExistingSession) {
|
|
1547
|
+
agent.replaceMessages(existingSession.messages);
|
|
1548
|
+
} else {
|
|
1549
|
+
// Save initial model and thinking level for new sessions so they can be restored on resume
|
|
1550
|
+
if (model) {
|
|
1551
|
+
sessionManager.appendModelChange(`${model.provider}/${model.id}`);
|
|
1552
|
+
}
|
|
1553
|
+
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
1537
1554
|
}
|
|
1538
|
-
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
session = new AgentSession({
|
|
1542
|
-
agent,
|
|
1543
|
-
thinkingLevel,
|
|
1544
|
-
sessionManager,
|
|
1545
|
-
settings,
|
|
1546
|
-
scopedModels: options.scopedModels,
|
|
1547
|
-
promptTemplates,
|
|
1548
|
-
slashCommands,
|
|
1549
|
-
extensionRunner,
|
|
1550
|
-
customCommands: customCommandsResult.commands,
|
|
1551
|
-
skills,
|
|
1552
|
-
skillWarnings,
|
|
1553
|
-
skillsSettings: settings.getGroup("skills"),
|
|
1554
|
-
modelRegistry,
|
|
1555
|
-
toolRegistry,
|
|
1556
|
-
transformContext,
|
|
1557
|
-
onPayload,
|
|
1558
|
-
convertToLlm: convertToLlmFinal,
|
|
1559
|
-
rebuildSystemPrompt,
|
|
1560
|
-
mcpDiscoveryEnabled,
|
|
1561
|
-
initialSelectedMCPToolNames,
|
|
1562
|
-
defaultSelectedMCPToolNames,
|
|
1563
|
-
persistInitialMCPToolSelection: !hasExistingSession,
|
|
1564
|
-
defaultSelectedMCPServerNames: [...discoveryDefaultServers],
|
|
1565
|
-
ttsrManager,
|
|
1566
|
-
obfuscator,
|
|
1567
|
-
asyncJobManager,
|
|
1568
|
-
searchDb,
|
|
1569
|
-
});
|
|
1570
1555
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1556
|
+
session = new AgentSession({
|
|
1557
|
+
agent,
|
|
1558
|
+
thinkingLevel,
|
|
1559
|
+
sessionManager,
|
|
1560
|
+
settings,
|
|
1561
|
+
pythonKernelOwnerId,
|
|
1562
|
+
scopedModels: options.scopedModels,
|
|
1563
|
+
promptTemplates,
|
|
1564
|
+
slashCommands,
|
|
1565
|
+
extensionRunner,
|
|
1566
|
+
customCommands: customCommandsResult.commands,
|
|
1567
|
+
skills,
|
|
1568
|
+
skillWarnings,
|
|
1569
|
+
skillsSettings: settings.getGroup("skills"),
|
|
1570
|
+
modelRegistry,
|
|
1571
|
+
toolRegistry,
|
|
1572
|
+
transformContext,
|
|
1573
|
+
onPayload,
|
|
1574
|
+
convertToLlm: convertToLlmFinal,
|
|
1575
|
+
rebuildSystemPrompt,
|
|
1576
|
+
mcpDiscoveryEnabled,
|
|
1577
|
+
initialSelectedMCPToolNames,
|
|
1578
|
+
defaultSelectedMCPToolNames,
|
|
1579
|
+
persistInitialMCPToolSelection: !hasExistingSession,
|
|
1580
|
+
defaultSelectedMCPServerNames: [...discoveryDefaultServers],
|
|
1581
|
+
ttsrManager,
|
|
1582
|
+
obfuscator,
|
|
1583
|
+
asyncJobManager,
|
|
1578
1584
|
});
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1585
|
+
hasSession = true;
|
|
1586
|
+
|
|
1587
|
+
if (model?.api === "openai-codex-responses") {
|
|
1588
|
+
const codexModel = model;
|
|
1589
|
+
const codexTransport = getOpenAICodexTransportDetails(codexModel, {
|
|
1590
|
+
sessionId: providerSessionId,
|
|
1591
|
+
baseUrl: codexModel.baseUrl,
|
|
1592
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1593
|
+
providerSessionState: session.providerSessionState,
|
|
1594
|
+
});
|
|
1595
|
+
if (codexTransport.websocketPreferred) {
|
|
1596
|
+
void (async () => {
|
|
1597
|
+
try {
|
|
1598
|
+
const codexPrewarmApiKey = await modelRegistry.getApiKey(codexModel, providerSessionId);
|
|
1599
|
+
if (!codexPrewarmApiKey) return;
|
|
1600
|
+
await logger.time("prewarmOpenAICodexResponses", prewarmOpenAICodexResponses, codexModel, {
|
|
1601
|
+
apiKey: codexPrewarmApiKey,
|
|
1602
|
+
sessionId: providerSessionId,
|
|
1603
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1604
|
+
providerSessionState: session.providerSessionState,
|
|
1605
|
+
});
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1608
|
+
logger.debug("Codex websocket prewarm failed", {
|
|
1609
|
+
error: errorMessage,
|
|
1610
|
+
provider: codexModel.provider,
|
|
1611
|
+
model: codexModel.id,
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
})();
|
|
1615
|
+
}
|
|
1599
1616
|
}
|
|
1600
|
-
}
|
|
1601
1617
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1618
|
+
// Start LSP warmup in the background so startup does not block on language server initialization.
|
|
1619
|
+
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
1620
|
+
if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
|
|
1621
|
+
lspServers = discoverStartupLspServers(cwd);
|
|
1622
|
+
if (lspServers.length > 0) {
|
|
1623
|
+
void (async () => {
|
|
1624
|
+
try {
|
|
1625
|
+
const result = await logger.time("warmupLspServers", warmupLspServers, cwd);
|
|
1626
|
+
const serversByName = new Map(result.servers.map(server => [server.name, server] as const));
|
|
1627
|
+
for (const server of lspServers ?? []) {
|
|
1628
|
+
const next = serversByName.get(server.name);
|
|
1629
|
+
if (!next) continue;
|
|
1630
|
+
server.status = next.status;
|
|
1631
|
+
server.fileTypes = next.fileTypes;
|
|
1632
|
+
server.error = next.error;
|
|
1633
|
+
}
|
|
1634
|
+
const event: LspStartupEvent = {
|
|
1635
|
+
type: "completed",
|
|
1636
|
+
servers: result.servers,
|
|
1637
|
+
};
|
|
1638
|
+
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1641
|
+
logger.warn("LSP server warmup failed", { cwd, error: errorMessage });
|
|
1642
|
+
for (const server of lspServers ?? []) {
|
|
1643
|
+
server.status = "error";
|
|
1644
|
+
server.error = errorMessage;
|
|
1645
|
+
}
|
|
1646
|
+
const event: LspStartupEvent = {
|
|
1647
|
+
type: "failed",
|
|
1648
|
+
error: errorMessage,
|
|
1649
|
+
};
|
|
1650
|
+
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
1629
1651
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
error: errorMessage,
|
|
1633
|
-
};
|
|
1634
|
-
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
1635
|
-
}
|
|
1636
|
-
})();
|
|
1652
|
+
})();
|
|
1653
|
+
}
|
|
1637
1654
|
}
|
|
1638
|
-
}
|
|
1639
1655
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1656
|
+
logger.time("startMemoryStartupTask", () =>
|
|
1657
|
+
startMemoryStartupTask({
|
|
1658
|
+
session,
|
|
1659
|
+
settings,
|
|
1660
|
+
modelRegistry,
|
|
1661
|
+
agentDir,
|
|
1662
|
+
taskDepth,
|
|
1663
|
+
}),
|
|
1664
|
+
);
|
|
1649
1665
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1666
|
+
// Wire MCP manager callbacks to session for reactive tool updates
|
|
1667
|
+
if (mcpManager) {
|
|
1668
|
+
mcpManager.setOnToolsChanged(tools => {
|
|
1669
|
+
void session.refreshMCPTools(tools);
|
|
1670
|
+
});
|
|
1671
|
+
// Wire prompt refresh → rebuild MCP prompt slash commands
|
|
1672
|
+
mcpManager.setOnPromptsChanged(serverName => {
|
|
1673
|
+
const promptCommands = buildMCPPromptCommands(mcpManager);
|
|
1674
|
+
session.setMCPPromptCommands(promptCommands);
|
|
1675
|
+
logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
|
|
1676
|
+
});
|
|
1677
|
+
const notificationDebounceTimers = new Map<string, Timer>();
|
|
1678
|
+
const clearDebounceTimers = () => {
|
|
1679
|
+
for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
|
|
1680
|
+
notificationDebounceTimers.clear();
|
|
1681
|
+
};
|
|
1682
|
+
postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
|
|
1683
|
+
mcpManager.setOnResourcesChanged((serverName, uri) => {
|
|
1684
|
+
logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
|
|
1685
|
+
if (!settings.get("mcp.notifications")) return;
|
|
1686
|
+
const debounceMs = settings.get("mcp.notificationDebounceMs");
|
|
1687
|
+
const key = `${serverName}:${uri}`;
|
|
1688
|
+
const existing = notificationDebounceTimers.get(key);
|
|
1689
|
+
if (existing) clearTimeout(existing);
|
|
1690
|
+
notificationDebounceTimers.set(
|
|
1691
|
+
key,
|
|
1692
|
+
setTimeout(() => {
|
|
1693
|
+
notificationDebounceTimers.delete(key);
|
|
1694
|
+
// Re-check: user may have disabled notifications during the debounce window
|
|
1695
|
+
if (!settings.get("mcp.notifications")) return;
|
|
1696
|
+
void session.followUp(
|
|
1697
|
+
`[MCP notification] Server "${serverName}" reports resource \`${uri}\` was updated. Use read(path="mcp://${uri}") to inspect if relevant.`,
|
|
1698
|
+
);
|
|
1699
|
+
}, debounceMs),
|
|
1700
|
+
);
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
logger.time("createAgentSession:return");
|
|
1705
|
+
return {
|
|
1706
|
+
session,
|
|
1707
|
+
extensionsResult,
|
|
1708
|
+
setToolUIContext,
|
|
1709
|
+
mcpManager,
|
|
1710
|
+
modelFallbackMessage,
|
|
1711
|
+
lspServers,
|
|
1712
|
+
eventBus,
|
|
1665
1713
|
};
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
if (!settings.get("mcp.notifications")) return;
|
|
1680
|
-
void session.followUp(
|
|
1681
|
-
`[MCP notification] Server "${serverName}" reports resource \`${uri}\` was updated. Use read(path="mcp://${uri}") to inspect if relevant.`,
|
|
1682
|
-
);
|
|
1683
|
-
}, debounceMs),
|
|
1684
|
-
);
|
|
1685
|
-
});
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
try {
|
|
1716
|
+
if (hasSession) {
|
|
1717
|
+
await session.dispose();
|
|
1718
|
+
} else {
|
|
1719
|
+
await disposeKernelSessionsByOwner(pythonKernelOwnerId);
|
|
1720
|
+
}
|
|
1721
|
+
} catch (cleanupError) {
|
|
1722
|
+
logger.warn("Failed to clean up createAgentSession resources after startup error", {
|
|
1723
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
throw error;
|
|
1686
1727
|
}
|
|
1687
|
-
|
|
1688
|
-
logger.time("createAgentSession:return");
|
|
1689
|
-
return {
|
|
1690
|
-
session,
|
|
1691
|
-
extensionsResult,
|
|
1692
|
-
setToolUIContext,
|
|
1693
|
-
mcpManager,
|
|
1694
|
-
modelFallbackMessage,
|
|
1695
|
-
lspServers,
|
|
1696
|
-
eventBus,
|
|
1697
|
-
};
|
|
1698
1728
|
}
|