@oh-my-pi/pi-coding-agent 14.0.5 → 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 +120 -0
- package/package.json +8 -8
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +43 -10
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/commit/model-selection.ts +16 -13
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +242 -45
- package/src/config/model-resolver.ts +282 -65
- package/src/config/settings-schema.ts +27 -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.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +614 -97
- 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 +4 -4
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +55 -1
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +6 -2
- package/src/memories/index.ts +7 -6
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/model-selector.ts +221 -64
- 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 +42 -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 +17 -6
- 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/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +16 -1
- package/src/prompts/tools/cancel-job.md +1 -1
- 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 +12 -3
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +758 -725
- package/src/session/agent-session.ts +187 -40
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +9 -5
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +240 -57
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
- package/src/tools/python.ts +293 -278
- package/src/tools/read.ts +218 -1
- package/src/tools/sqlite-reader.ts +623 -0
- 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/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/git.ts +24 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +16 -7
- 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,13 +18,13 @@ 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
|
-
import { AsyncJobManager } from "./async";
|
|
27
|
+
import { AsyncJobManager, isBackgroundJobSupportEnabled } from "./async";
|
|
29
28
|
import { createAutoresearchExtension } from "./autoresearch";
|
|
30
29
|
import { loadCapability } from "./capability";
|
|
31
30
|
import { type Rule, ruleCapability } from "./capability/rule";
|
|
@@ -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,
|
|
@@ -728,6 +726,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
728
726
|
resolveModelRoleValue(settings.getModelRole("default"), modelRegistry.getAvailable(), {
|
|
729
727
|
settings,
|
|
730
728
|
matchPreferences: modelMatchPreferences,
|
|
729
|
+
modelRegistry,
|
|
731
730
|
}),
|
|
732
731
|
);
|
|
733
732
|
let model = options.model;
|
|
@@ -834,10 +833,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
834
833
|
);
|
|
835
834
|
|
|
836
835
|
let agent: Agent;
|
|
837
|
-
let session
|
|
838
|
-
|
|
836
|
+
let session!: AgentSession;
|
|
837
|
+
let hasSession = false;
|
|
839
838
|
const enableLsp = options.enableLsp ?? true;
|
|
840
|
-
const
|
|
839
|
+
const backgroundJobsEnabled = isBackgroundJobSupportEnabled(settings);
|
|
841
840
|
const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
|
|
842
841
|
const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
|
|
843
842
|
const ASYNC_PREVIEW_MAX_CHARS = 4_000;
|
|
@@ -861,12 +860,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
861
860
|
|
|
862
861
|
return preview;
|
|
863
862
|
};
|
|
864
|
-
const asyncJobManager =
|
|
863
|
+
const asyncJobManager = backgroundJobsEnabled
|
|
865
864
|
? new AsyncJobManager({
|
|
866
865
|
maxRunningJobs: asyncMaxJobs,
|
|
867
866
|
onJobComplete: async (jobId, result, job) => {
|
|
868
|
-
if (!session) return;
|
|
867
|
+
if (!session || asyncJobManager!.isDeliverySuppressed(jobId)) return;
|
|
869
868
|
const formattedResult = await formatAsyncResultForFollowUp(result);
|
|
869
|
+
if (asyncJobManager!.isDeliverySuppressed(jobId)) return;
|
|
870
|
+
|
|
870
871
|
const message = prompt.render(asyncResultTemplate, { jobId, result: formattedResult });
|
|
871
872
|
const durationMs = job ? Math.max(0, Date.now() - job.startTime) : undefined;
|
|
872
873
|
await session.sendCustomMessage(
|
|
@@ -888,435 +889,423 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
888
889
|
})
|
|
889
890
|
: undefined;
|
|
890
891
|
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
enableLsp,
|
|
896
|
-
get hasEditTool() {
|
|
897
|
-
return !options.toolNames || options.toolNames.includes("edit");
|
|
898
|
-
},
|
|
899
|
-
skipPythonPreflight: options.skipPythonPreflight,
|
|
900
|
-
contextFiles,
|
|
901
|
-
skills,
|
|
902
|
-
eventBus,
|
|
903
|
-
outputSchema: options.outputSchema,
|
|
904
|
-
requireSubmitResultTool: options.requireSubmitResultTool,
|
|
905
|
-
taskDepth: options.taskDepth ?? 0,
|
|
906
|
-
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
907
|
-
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
908
|
-
getSessionSpawns: () => options.spawns ?? "*",
|
|
909
|
-
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
910
|
-
getActiveModelString: () => {
|
|
892
|
+
const pythonKernelOwnerId = `agent-session:${Snowflake.next()}`;
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
const getActiveModelString = (): string | undefined => {
|
|
911
896
|
const activeModel = agent?.state.model;
|
|
912
897
|
if (activeModel) return formatModelString(activeModel);
|
|
913
|
-
// Fall back to initial model during tool creation (before agent exists)
|
|
914
898
|
if (model) return formatModelString(model);
|
|
915
899
|
return undefined;
|
|
916
|
-
}
|
|
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
|
-
getSkills: () => skills,
|
|
977
|
-
}),
|
|
978
|
-
);
|
|
979
|
-
internalRouter.register(
|
|
980
|
-
new RuleProtocolHandler({
|
|
981
|
-
getRules: () => [...rulebookRules, ...alwaysApplyRules],
|
|
982
|
-
}),
|
|
983
|
-
);
|
|
984
|
-
internalRouter.register(new PiProtocolHandler());
|
|
985
|
-
internalRouter.register(new JobsProtocolHandler({ getAsyncJobManager: () => asyncJobManager }));
|
|
986
|
-
internalRouter.register(new McpProtocolHandler({ getMcpManager: () => mcpManager }));
|
|
987
|
-
toolSession.internalRouter = internalRouter;
|
|
988
|
-
toolSession.getArtifactsDir = getArtifactsDir;
|
|
989
|
-
toolSession.agentOutputManager = new AgentOutputManager(
|
|
990
|
-
getArtifactsDir,
|
|
991
|
-
options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
|
|
992
|
-
);
|
|
993
|
-
|
|
994
|
-
// Create built-in tools (already wrapped with meta notice formatting)
|
|
995
|
-
const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
|
|
996
|
-
|
|
997
|
-
// Discover MCP tools from .mcp.json files
|
|
998
|
-
let mcpManager: MCPManager | undefined;
|
|
999
|
-
const enableMCP = options.enableMCP ?? true;
|
|
1000
|
-
const customTools: CustomTool[] = [];
|
|
1001
|
-
if (enableMCP) {
|
|
1002
|
-
const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
|
|
1003
|
-
onConnecting: serverNames => {
|
|
1004
|
-
if (options.hasUI && serverNames.length > 0) {
|
|
1005
|
-
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 {};
|
|
1006
960
|
}
|
|
1007
961
|
},
|
|
1008
|
-
|
|
1009
|
-
// Always filter Exa - we have native integration
|
|
1010
|
-
filterExa: true,
|
|
1011
|
-
// Filter browser MCP servers when builtin browser tool is active
|
|
1012
|
-
filterBrowser: settings.get("browser.enabled") ?? false,
|
|
1013
|
-
cacheStorage: settings.getStorage(),
|
|
962
|
+
settings,
|
|
1014
963
|
authStorage,
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
if (settings.get("mcp.notifications")) {
|
|
1020
|
-
mcpManager.setNotificationsEnabled(true);
|
|
1021
|
-
}
|
|
1022
|
-
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
1023
|
-
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
|
|
1024
|
-
Bun.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
1025
|
-
}
|
|
964
|
+
modelRegistry,
|
|
965
|
+
asyncJobManager,
|
|
966
|
+
};
|
|
1026
967
|
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}
|
|
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
|
+
);
|
|
1031
1003
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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;
|
|
1037
1028
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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
|
+
}
|
|
1043
1036
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1037
|
+
// Log MCP errors
|
|
1038
|
+
for (const { path, error } of mcpResult.errors) {
|
|
1039
|
+
logger.error("MCP tool load failed", { path, error });
|
|
1040
|
+
}
|
|
1048
1041
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
[],
|
|
1055
|
-
cwd,
|
|
1056
|
-
builtInToolNames,
|
|
1057
|
-
action => queueResolveHandler(toolSession, action),
|
|
1058
|
-
);
|
|
1059
|
-
for (const { path, error } of discoveredCustomTools.errors) {
|
|
1060
|
-
logger.error("Custom tool load failed", { path, error });
|
|
1061
|
-
}
|
|
1062
|
-
if (discoveredCustomTools.tools.length > 0) {
|
|
1063
|
-
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
|
|
1064
|
-
}
|
|
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
|
+
}
|
|
1065
1047
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
+
}
|
|
1071
1053
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
const configuredPaths = options.additionalExtensionPaths ?? [];
|
|
1076
|
-
extensionsResult = await logger.time("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
|
|
1077
|
-
for (const { path, error } of extensionsResult.errors) {
|
|
1078
|
-
logger.error("Failed to load extension", { path, error });
|
|
1054
|
+
// Add web search tools
|
|
1055
|
+
if (options.toolNames?.includes("web_search")) {
|
|
1056
|
+
customTools.push(...getSearchTools());
|
|
1079
1057
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
"discoverAndLoadExtensions",
|
|
1088
|
-
discoverAndLoadExtensions,
|
|
1089
|
-
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
|
+
[],
|
|
1090
1065
|
cwd,
|
|
1091
|
-
|
|
1092
|
-
|
|
1066
|
+
builtInToolNames,
|
|
1067
|
+
action => queueResolveHandler(toolSession, action),
|
|
1093
1068
|
);
|
|
1094
|
-
for (const { path, error } of
|
|
1095
|
-
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));
|
|
1096
1074
|
}
|
|
1097
|
-
}
|
|
1098
1075
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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,
|
|
1105
1100
|
cwd,
|
|
1106
1101
|
eventBus,
|
|
1107
|
-
|
|
1108
|
-
`<inline-${i}>`,
|
|
1102
|
+
disabledExtensionIds,
|
|
1109
1103
|
);
|
|
1110
|
-
extensionsResult.
|
|
1104
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
1105
|
+
logger.error("Failed to load extension", { path, error });
|
|
1106
|
+
}
|
|
1111
1107
|
}
|
|
1112
|
-
}
|
|
1113
1108
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
+
}
|
|
1125
1122
|
}
|
|
1126
|
-
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
1127
|
-
}
|
|
1128
1123
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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 = [];
|
|
1141
1137
|
}
|
|
1142
|
-
}
|
|
1143
1138
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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`;
|
|
1152
1153
|
}
|
|
1153
1154
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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.";
|
|
1157
1173
|
}
|
|
1158
|
-
} else {
|
|
1159
|
-
modelFallbackMessage =
|
|
1160
|
-
"No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
1161
1174
|
}
|
|
1162
|
-
}
|
|
1163
1175
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
+
}
|
|
1171
1184
|
}
|
|
1172
|
-
}
|
|
1173
1185
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
+
}
|
|
1184
1196
|
|
|
1185
|
-
|
|
1186
|
-
sessionManager,
|
|
1187
|
-
modelRegistry,
|
|
1188
|
-
model: agent.state.model,
|
|
1189
|
-
isIdle: () => !session.isStreaming,
|
|
1190
|
-
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
1191
|
-
abort: () => {
|
|
1192
|
-
session.abort();
|
|
1193
|
-
},
|
|
1194
|
-
settings,
|
|
1195
|
-
});
|
|
1196
|
-
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
1197
|
-
|
|
1198
|
-
const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
|
|
1199
|
-
let wrappedExtensionTools: Tool[];
|
|
1200
|
-
|
|
1201
|
-
if (extensionRunner) {
|
|
1202
|
-
// With extension runner: convert CustomTools to ToolDefinitions and wrap all together
|
|
1203
|
-
const allCustomTools = [
|
|
1204
|
-
...registeredTools,
|
|
1205
|
-
...(options.customTools?.map(tool => {
|
|
1206
|
-
const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
|
|
1207
|
-
return { definition, extensionPath: "<sdk>" };
|
|
1208
|
-
}) ?? []),
|
|
1209
|
-
];
|
|
1210
|
-
wrappedExtensionTools = wrapRegisteredTools(allCustomTools, extensionRunner);
|
|
1211
|
-
} else {
|
|
1212
|
-
// Without extension runner: wrap CustomTools directly with CustomToolAdapter
|
|
1213
|
-
// ToolDefinition items require ExtensionContext and cannot be used without a runner
|
|
1214
|
-
const customToolContext = (): CustomToolContext => ({
|
|
1197
|
+
const getSessionContext = () => ({
|
|
1215
1198
|
sessionManager,
|
|
1216
1199
|
modelRegistry,
|
|
1217
|
-
model: agent
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1200
|
+
model: agent.state.model,
|
|
1201
|
+
isIdle: () => !session.isStreaming,
|
|
1202
|
+
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
1203
|
+
abort: () => {
|
|
1204
|
+
session.abort();
|
|
1205
|
+
},
|
|
1222
1206
|
settings,
|
|
1223
1207
|
});
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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));
|
|
1240
1238
|
}
|
|
1241
|
-
}
|
|
1242
|
-
if (model?.provider === "cursor") {
|
|
1243
|
-
toolRegistry.delete("edit");
|
|
1244
|
-
}
|
|
1245
1239
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
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");
|
|
1253
1255
|
}
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1257
|
-
const cursorExecHandlers = new CursorExecHandlers({
|
|
1258
|
-
cwd,
|
|
1259
|
-
tools: toolRegistry,
|
|
1260
|
-
getToolContext: () => toolContextStore.getContext(),
|
|
1261
|
-
emitEvent: event => cursorEventEmitter?.(event),
|
|
1262
|
-
});
|
|
1263
1256
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
const hasDiscoverableMCPTools =
|
|
1272
|
-
mcpDiscoveryEnabled && toolNames.includes("search_tool_bm25") && discoverableMCPTools.length > 0;
|
|
1273
|
-
const promptTools = buildSystemPromptToolMetadata(tools, {
|
|
1274
|
-
search_tool_bm25: { description: renderSearchToolBm25Description(discoverableMCPTools) },
|
|
1275
|
-
});
|
|
1276
|
-
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
1277
|
-
|
|
1278
|
-
// Build combined append prompt: memory instructions + MCP server instructions
|
|
1279
|
-
const serverInstructions = mcpManager?.getServerInstructions();
|
|
1280
|
-
let appendPrompt: string | undefined = memoryInstructions ?? undefined;
|
|
1281
|
-
if (serverInstructions && serverInstructions.size > 0) {
|
|
1282
|
-
const MAX_INSTRUCTIONS_LENGTH = 4000;
|
|
1283
|
-
const parts: string[] = [];
|
|
1284
|
-
if (appendPrompt) parts.push(appendPrompt);
|
|
1285
|
-
parts.push(
|
|
1286
|
-
"## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
|
|
1287
|
-
);
|
|
1288
|
-
for (const [srvName, srvInstructions] of serverInstructions) {
|
|
1289
|
-
const truncated =
|
|
1290
|
-
srvInstructions.length > MAX_INSTRUCTIONS_LENGTH
|
|
1291
|
-
? `${srvInstructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}\n[truncated]`
|
|
1292
|
-
: srvInstructions;
|
|
1293
|
-
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));
|
|
1294
1264
|
}
|
|
1295
|
-
appendPrompt = parts.join("\n\n");
|
|
1296
1265
|
}
|
|
1297
|
-
|
|
1266
|
+
|
|
1267
|
+
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1268
|
+
const cursorExecHandlers = new CursorExecHandlers({
|
|
1298
1269
|
cwd,
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
toolNames,
|
|
1303
|
-
rules: rulebookRules,
|
|
1304
|
-
alwaysApplyRules,
|
|
1305
|
-
skillsSettings: settings.getGroup("skills"),
|
|
1306
|
-
appendSystemPrompt: appendPrompt,
|
|
1307
|
-
repeatToolDescriptions,
|
|
1308
|
-
intentField,
|
|
1309
|
-
mcpDiscoveryMode: hasDiscoverableMCPTools,
|
|
1310
|
-
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1311
|
-
eagerTasks,
|
|
1312
|
-
secretsEnabled,
|
|
1270
|
+
tools: toolRegistry,
|
|
1271
|
+
getToolContext: () => toolContextStore.getContext(),
|
|
1272
|
+
emitEvent: event => cursorEventEmitter?.(event),
|
|
1313
1273
|
});
|
|
1314
1274
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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({
|
|
1320
1309
|
cwd,
|
|
1321
1310
|
skills,
|
|
1322
1311
|
contextFiles,
|
|
@@ -1325,7 +1314,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1325
1314
|
rules: rulebookRules,
|
|
1326
1315
|
alwaysApplyRules,
|
|
1327
1316
|
skillsSettings: settings.getGroup("skills"),
|
|
1328
|
-
customPrompt: options.systemPrompt,
|
|
1329
1317
|
appendSystemPrompt: appendPrompt,
|
|
1330
1318
|
repeatToolDescriptions,
|
|
1331
1319
|
intentField,
|
|
@@ -1334,362 +1322,407 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1334
1322
|
eagerTasks,
|
|
1335
1323
|
secretsEnabled,
|
|
1336
1324
|
});
|
|
1337
|
-
}
|
|
1338
|
-
return options.systemPrompt(defaultPrompt);
|
|
1339
|
-
};
|
|
1340
1325
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
: [];
|
|
1367
|
-
let initialSelectedMCPToolNames: string[] = [];
|
|
1368
|
-
let defaultSelectedMCPToolNames: string[] = [];
|
|
1369
|
-
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
1370
|
-
if (mcpDiscoveryEnabled) {
|
|
1371
|
-
const restoredSelectedMCPToolNames = existingSession.selectedMCPToolNames.filter(name => toolRegistry.has(name));
|
|
1372
|
-
defaultSelectedMCPToolNames = [
|
|
1373
|
-
...new Set([...discoveryDefaultServerToolNames, ...explicitlyRequestedMCPToolNames]),
|
|
1374
|
-
];
|
|
1375
|
-
initialSelectedMCPToolNames = existingSession.hasPersistedMCPToolSelection
|
|
1376
|
-
? restoredSelectedMCPToolNames
|
|
1377
|
-
: [...new Set([...restoredSelectedMCPToolNames, ...defaultSelectedMCPToolNames])];
|
|
1378
|
-
initialToolNames = [
|
|
1379
|
-
...new Set([
|
|
1380
|
-
...initialRequestedActiveToolNames.filter(name => !name.startsWith("mcp_")),
|
|
1381
|
-
...initialSelectedMCPToolNames,
|
|
1382
|
-
]),
|
|
1383
|
-
];
|
|
1384
|
-
}
|
|
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
|
+
};
|
|
1385
1351
|
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
+
];
|
|
1394
1399
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
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
|
+
}
|
|
1397
1413
|
}
|
|
1398
|
-
}
|
|
1399
1414
|
|
|
1400
|
-
|
|
1415
|
+
const systemPrompt = await logger.time("buildSystemPrompt", rebuildSystemPrompt, initialToolNames, toolRegistry);
|
|
1401
1416
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1417
|
+
const promptTemplates =
|
|
1418
|
+
options.promptTemplates ??
|
|
1419
|
+
(await logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir));
|
|
1420
|
+
toolSession.promptTemplates = promptTemplates;
|
|
1405
1421
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1422
|
+
const slashCommands =
|
|
1423
|
+
options.slashCommands ?? (await logger.time("discoverSlashCommands", discoverSlashCommands, cwd));
|
|
1408
1424
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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
|
+
}
|
|
1432
1451
|
}
|
|
1433
1452
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1437
|
-
};
|
|
1453
|
+
return msg;
|
|
1454
|
+
});
|
|
1455
|
+
};
|
|
1438
1456
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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;
|
|
1455
1473
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1474
|
+
const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
|
|
1475
|
+
toolContextStore.setUIContext(uiContext, hasUI);
|
|
1476
|
+
};
|
|
1459
1477
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
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
|
-
|
|
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
|
+
});
|
|
1524
1542
|
|
|
1525
|
-
|
|
1543
|
+
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
1526
1544
|
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
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);
|
|
1534
1554
|
}
|
|
1535
|
-
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
session = new AgentSession({
|
|
1539
|
-
agent,
|
|
1540
|
-
thinkingLevel,
|
|
1541
|
-
sessionManager,
|
|
1542
|
-
settings,
|
|
1543
|
-
scopedModels: options.scopedModels,
|
|
1544
|
-
promptTemplates,
|
|
1545
|
-
slashCommands,
|
|
1546
|
-
extensionRunner,
|
|
1547
|
-
customCommands: customCommandsResult.commands,
|
|
1548
|
-
skills,
|
|
1549
|
-
skillWarnings,
|
|
1550
|
-
skillsSettings: settings.getGroup("skills"),
|
|
1551
|
-
modelRegistry,
|
|
1552
|
-
toolRegistry,
|
|
1553
|
-
transformContext,
|
|
1554
|
-
onPayload,
|
|
1555
|
-
convertToLlm: convertToLlmFinal,
|
|
1556
|
-
rebuildSystemPrompt,
|
|
1557
|
-
mcpDiscoveryEnabled,
|
|
1558
|
-
initialSelectedMCPToolNames,
|
|
1559
|
-
defaultSelectedMCPToolNames,
|
|
1560
|
-
persistInitialMCPToolSelection: !hasExistingSession,
|
|
1561
|
-
defaultSelectedMCPServerNames: [...discoveryDefaultServers],
|
|
1562
|
-
ttsrManager,
|
|
1563
|
-
obfuscator,
|
|
1564
|
-
asyncJobManager,
|
|
1565
|
-
searchDb,
|
|
1566
|
-
});
|
|
1567
1555
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
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,
|
|
1575
1584
|
});
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
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
|
+
}
|
|
1596
1616
|
}
|
|
1597
|
-
}
|
|
1598
1617
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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);
|
|
1626
1651
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
error: errorMessage,
|
|
1630
|
-
};
|
|
1631
|
-
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
1632
|
-
}
|
|
1633
|
-
})();
|
|
1652
|
+
})();
|
|
1653
|
+
}
|
|
1634
1654
|
}
|
|
1635
|
-
}
|
|
1636
1655
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1656
|
+
logger.time("startMemoryStartupTask", () =>
|
|
1657
|
+
startMemoryStartupTask({
|
|
1658
|
+
session,
|
|
1659
|
+
settings,
|
|
1660
|
+
modelRegistry,
|
|
1661
|
+
agentDir,
|
|
1662
|
+
taskDepth,
|
|
1663
|
+
}),
|
|
1664
|
+
);
|
|
1646
1665
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
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,
|
|
1662
1713
|
};
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
if (!settings.get("mcp.notifications")) return;
|
|
1677
|
-
void session.followUp(
|
|
1678
|
-
`[MCP notification] Server "${serverName}" reports resource \`${uri}\` was updated. Use read(path="mcp://${uri}") to inspect if relevant.`,
|
|
1679
|
-
);
|
|
1680
|
-
}, debounceMs),
|
|
1681
|
-
);
|
|
1682
|
-
});
|
|
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;
|
|
1683
1727
|
}
|
|
1684
|
-
|
|
1685
|
-
logger.time("createAgentSession:return");
|
|
1686
|
-
return {
|
|
1687
|
-
session,
|
|
1688
|
-
extensionsResult,
|
|
1689
|
-
setToolUIContext,
|
|
1690
|
-
mcpManager,
|
|
1691
|
-
modelFallbackMessage,
|
|
1692
|
-
lspServers,
|
|
1693
|
-
eventBus,
|
|
1694
|
-
};
|
|
1695
1728
|
}
|