@oh-my-pi/pi-coding-agent 3.15.0 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -1
- package/docs/extensions.md +1055 -0
- package/docs/rpc.md +69 -13
- package/docs/session-tree-plan.md +1 -1
- package/examples/extensions/README.md +141 -0
- package/examples/extensions/api-demo.ts +87 -0
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/extensions/hello.ts +33 -0
- package/examples/extensions/pirate.ts +44 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/05-tools.ts +7 -3
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/06-hooks.ts +14 -13
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/08-slash-commands.ts +17 -12
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-full-control.ts +6 -6
- package/package.json +11 -7
- package/src/capability/extension-module.ts +34 -0
- package/src/cli/args.ts +22 -7
- package/src/cli/file-processor.ts +38 -67
- package/src/cli/list-models.ts +1 -1
- package/src/config.ts +25 -14
- package/src/core/agent-session.ts +505 -242
- package/src/core/auth-storage.ts +33 -21
- package/src/core/compaction/branch-summarization.ts +4 -4
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +430 -0
- package/src/core/custom-commands/loader.ts +9 -0
- package/src/core/custom-tools/wrapper.ts +5 -0
- package/src/core/event-bus.ts +59 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/extensions/index.ts +100 -0
- package/src/core/extensions/loader.ts +501 -0
- package/src/core/extensions/runner.ts +477 -0
- package/src/core/extensions/types.ts +712 -0
- package/src/core/extensions/wrapper.ts +147 -0
- package/src/core/hooks/types.ts +2 -2
- package/src/core/index.ts +10 -21
- package/src/core/keybindings.ts +199 -0
- package/src/core/messages.ts +26 -7
- package/src/core/model-registry.ts +123 -46
- package/src/core/model-resolver.ts +7 -5
- package/src/core/prompt-templates.ts +242 -0
- package/src/core/sdk.ts +378 -295
- package/src/core/session-manager.ts +72 -58
- package/src/core/settings-manager.ts +118 -22
- package/src/core/system-prompt.ts +24 -1
- package/src/core/terminal-notify.ts +37 -0
- package/src/core/tools/context.ts +4 -4
- package/src/core/tools/exa/mcp-client.ts +5 -4
- package/src/core/tools/exa/render.ts +176 -131
- package/src/core/tools/gemini-image.ts +361 -0
- package/src/core/tools/git.ts +216 -0
- package/src/core/tools/index.ts +28 -15
- package/src/core/tools/lsp/config.ts +5 -4
- package/src/core/tools/lsp/index.ts +17 -12
- package/src/core/tools/lsp/render.ts +39 -47
- package/src/core/tools/read.ts +66 -29
- package/src/core/tools/render-utils.ts +268 -0
- package/src/core/tools/renderers.ts +243 -225
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +66 -58
- package/src/core/tools/task/index.ts +29 -10
- package/src/core/tools/task/model-resolver.ts +8 -13
- package/src/core/tools/task/omp-command.ts +24 -0
- package/src/core/tools/task/render.ts +35 -60
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/web-fetch.ts +29 -28
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/core/tools/web-search/providers/exa.ts +6 -5
- package/src/core/tools/web-search/render.ts +66 -111
- package/src/core/voice-controller.ts +135 -0
- package/src/core/voice-supervisor.ts +1003 -0
- package/src/core/voice.ts +308 -0
- package/src/discovery/builtin.ts +75 -1
- package/src/discovery/claude.ts +47 -1
- package/src/discovery/codex.ts +54 -2
- package/src/discovery/gemini.ts +55 -2
- package/src/discovery/helpers.ts +100 -1
- package/src/discovery/index.ts +2 -0
- package/src/index.ts +14 -9
- package/src/lib/worktree/collapse.ts +179 -0
- package/src/lib/worktree/constants.ts +14 -0
- package/src/lib/worktree/errors.ts +23 -0
- package/src/lib/worktree/git.ts +110 -0
- package/src/lib/worktree/index.ts +23 -0
- package/src/lib/worktree/operations.ts +216 -0
- package/src/lib/worktree/session.ts +114 -0
- package/src/lib/worktree/stats.ts +67 -0
- package/src/main.ts +61 -37
- package/src/migrations.ts +37 -7
- package/src/modes/interactive/components/bash-execution.ts +6 -4
- package/src/modes/interactive/components/custom-editor.ts +55 -0
- package/src/modes/interactive/components/custom-message.ts +95 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/extensions/types.ts +1 -0
- package/src/modes/interactive/components/footer.ts +324 -0
- package/src/modes/interactive/components/hook-editor.ts +1 -0
- package/src/modes/interactive/components/hook-selector.ts +3 -3
- package/src/modes/interactive/components/model-selector.ts +7 -6
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/settings-defs.ts +55 -6
- package/src/modes/interactive/components/status-line/separators.ts +4 -4
- package/src/modes/interactive/components/status-line.ts +45 -35
- package/src/modes/interactive/components/tool-execution.ts +95 -23
- package/src/modes/interactive/interactive-mode.ts +644 -113
- package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
- package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
- package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
- package/src/modes/interactive/theme/defaults/basalt.json +90 -0
- package/src/modes/interactive/theme/defaults/birch.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
- package/src/modes/interactive/theme/defaults/graphite.json +99 -0
- package/src/modes/interactive/theme/defaults/index.ts +128 -0
- package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
- package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
- package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
- package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
- package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
- package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
- package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
- package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
- package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
- package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
- package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
- package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
- package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
- package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
- package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
- package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
- package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
- package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
- package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
- package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
- package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
- package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
- package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
- package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
- package/src/modes/interactive/theme/defaults/limestone.json +100 -0
- package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
- package/src/modes/interactive/theme/defaults/marble.json +99 -0
- package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
- package/src/modes/interactive/theme/defaults/onyx.json +90 -0
- package/src/modes/interactive/theme/defaults/pearl.json +99 -0
- package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
- package/src/modes/interactive/theme/defaults/quartz.json +102 -0
- package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
- package/src/modes/interactive/theme/defaults/titanium.json +89 -0
- package/src/modes/print-mode.ts +14 -72
- package/src/modes/rpc/rpc-client.ts +23 -9
- package/src/modes/rpc/rpc-mode.ts +137 -125
- package/src/modes/rpc/rpc-types.ts +46 -24
- package/src/prompts/task.md +1 -0
- package/src/prompts/tools/gemini-image.md +4 -0
- package/src/prompts/tools/git.md +9 -0
- package/src/prompts/voice-summary.md +12 -0
- package/src/utils/image-convert.ts +26 -0
- package/src/utils/image-resize.ts +215 -0
- package/src/utils/shell-snapshot.ts +22 -20
package/src/core/sdk.ts
CHANGED
|
@@ -9,12 +9,9 @@
|
|
|
9
9
|
* // Minimal - everything auto-discovered
|
|
10
10
|
* const session = await createAgentSession();
|
|
11
11
|
*
|
|
12
|
-
* // With custom
|
|
12
|
+
* // With custom extensions
|
|
13
13
|
* const session = await createAgentSession({
|
|
14
|
-
*
|
|
15
|
-
* ...await discoverHooks(),
|
|
16
|
-
* { factory: myHookFactory },
|
|
17
|
-
* ],
|
|
14
|
+
* extensions: [myExtensionFactory],
|
|
18
15
|
* });
|
|
19
16
|
*
|
|
20
17
|
* // Full control
|
|
@@ -22,7 +19,7 @@
|
|
|
22
19
|
* model: myModel,
|
|
23
20
|
* getApiKey: async () => process.env.MY_KEY,
|
|
24
21
|
* tools: [readTool, bashTool],
|
|
25
|
-
*
|
|
22
|
+
* extensions: [],
|
|
26
23
|
* skills: [],
|
|
27
24
|
* sessionFile: false,
|
|
28
25
|
* });
|
|
@@ -30,52 +27,64 @@
|
|
|
30
27
|
*/
|
|
31
28
|
|
|
32
29
|
import { join } from "node:path";
|
|
33
|
-
import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
30
|
+
import { Agent, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
34
31
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
32
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
35
33
|
import chalk from "chalk";
|
|
36
34
|
// Import discovery to register all providers on startup
|
|
37
35
|
import "../discovery";
|
|
38
36
|
import { loadSync as loadCapability } from "../capability/index";
|
|
39
37
|
import { type Rule, ruleCapability } from "../capability/rule";
|
|
40
38
|
import { getAgentDir, getConfigDirPaths } from "../config";
|
|
39
|
+
import { initializeWithSettings } from "../discovery";
|
|
41
40
|
import { AgentSession } from "./agent-session";
|
|
42
41
|
import { AuthStorage } from "./auth-storage";
|
|
43
42
|
import {
|
|
44
43
|
type CustomCommandsLoadResult,
|
|
45
44
|
loadCustomCommands as loadCustomCommandsInternal,
|
|
46
45
|
} from "./custom-commands/index";
|
|
46
|
+
import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "./custom-tools/types";
|
|
47
|
+
import { createEventBus, type EventBus } from "./event-bus";
|
|
47
48
|
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
type
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
discoverAndLoadExtensions,
|
|
50
|
+
type ExtensionContext,
|
|
51
|
+
type ExtensionFactory,
|
|
52
|
+
ExtensionRunner,
|
|
53
|
+
type LoadExtensionsResult,
|
|
54
|
+
type LoadedExtension,
|
|
55
|
+
loadExtensionFromFactory,
|
|
56
|
+
type ToolDefinition,
|
|
57
|
+
wrapRegisteredTools,
|
|
58
|
+
wrapToolsWithExtensions,
|
|
59
|
+
} from "./extensions/index";
|
|
56
60
|
import { logger } from "./logger";
|
|
57
61
|
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
|
|
58
62
|
import { convertToLlm } from "./messages";
|
|
59
63
|
import { ModelRegistry } from "./model-registry";
|
|
64
|
+
import { parseModelString } from "./model-resolver";
|
|
65
|
+
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./prompt-templates";
|
|
60
66
|
import { SessionManager } from "./session-manager";
|
|
61
|
-
import { type
|
|
67
|
+
import { type Settings, SettingsManager, type SkillsSettings } from "./settings-manager";
|
|
62
68
|
import { loadSkills as loadSkillsInternal, type Skill } from "./skills";
|
|
63
|
-
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands";
|
|
64
69
|
import {
|
|
65
70
|
buildSystemPrompt as buildSystemPromptInternal,
|
|
66
71
|
loadProjectContextFiles as loadContextFilesInternal,
|
|
67
72
|
} from "./system-prompt";
|
|
68
73
|
import { time } from "./timings";
|
|
69
74
|
import { createToolContextStore } from "./tools/context";
|
|
75
|
+
import { getGeminiImageTools } from "./tools/gemini-image";
|
|
70
76
|
import {
|
|
71
77
|
allTools,
|
|
72
78
|
applyBashInterception,
|
|
79
|
+
baseCodingToolNames,
|
|
73
80
|
bashTool,
|
|
74
81
|
codingTools,
|
|
82
|
+
createAllTools,
|
|
75
83
|
createBashTool,
|
|
76
84
|
createCodingTools,
|
|
77
85
|
createEditTool,
|
|
78
86
|
createFindTool,
|
|
87
|
+
createGitTool,
|
|
79
88
|
createGrepTool,
|
|
80
89
|
createLsTool,
|
|
81
90
|
createReadOnlyTools,
|
|
@@ -85,11 +94,14 @@ import {
|
|
|
85
94
|
editTool,
|
|
86
95
|
filterRulebookRules,
|
|
87
96
|
findTool,
|
|
97
|
+
getWebSearchTools,
|
|
98
|
+
gitTool,
|
|
88
99
|
grepTool,
|
|
89
100
|
lsTool,
|
|
90
101
|
readOnlyTools,
|
|
91
102
|
readTool,
|
|
92
103
|
type Tool,
|
|
104
|
+
type ToolName,
|
|
93
105
|
warmupLspServers,
|
|
94
106
|
writeTool,
|
|
95
107
|
} from "./tools/index";
|
|
@@ -118,24 +130,29 @@ export interface CreateAgentSessionOptions {
|
|
|
118
130
|
/** System prompt. String replaces default, function receives default and returns final. */
|
|
119
131
|
systemPrompt?: string | ((defaultPrompt: string) => string);
|
|
120
132
|
|
|
121
|
-
/** Built-in tools to use. Default:
|
|
133
|
+
/** Built-in tools to use. Default: all coding tools (read, bash, edit, write, grep, find, ls, lsp, notebook, output, task, web_fetch, web_search) */
|
|
122
134
|
tools?: Tool[];
|
|
123
|
-
/** Custom tools (
|
|
124
|
-
customTools?:
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
136
|
+
customTools?: (CustomTool | ToolDefinition)[];
|
|
137
|
+
/** Inline extensions (merged with discovery). */
|
|
138
|
+
extensions?: ExtensionFactory[];
|
|
139
|
+
/** Additional extension paths to load (merged with discovery). */
|
|
140
|
+
additionalExtensionPaths?: string[];
|
|
141
|
+
/**
|
|
142
|
+
* Pre-loaded extensions (skips file discovery).
|
|
143
|
+
* @internal Used by CLI when extensions are loaded early to parse custom flags.
|
|
144
|
+
*/
|
|
145
|
+
preloadedExtensions?: LoadedExtension[];
|
|
146
|
+
|
|
147
|
+
/** Shared event bus for tool/extension communication. Default: creates new bus. */
|
|
148
|
+
eventBus?: EventBus;
|
|
132
149
|
|
|
133
150
|
/** Skills. Default: discovered from multiple locations */
|
|
134
151
|
skills?: Skill[];
|
|
135
152
|
/** Context files (AGENTS.md content). Default: discovered walking up from cwd */
|
|
136
153
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
137
|
-
/**
|
|
138
|
-
|
|
154
|
+
/** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
|
|
155
|
+
promptTemplates?: PromptTemplate[];
|
|
139
156
|
|
|
140
157
|
/** Enable MCP server discovery from .mcp.json files. Default: true */
|
|
141
158
|
enableMCP?: boolean;
|
|
@@ -157,8 +174,8 @@ export interface CreateAgentSessionOptions {
|
|
|
157
174
|
export interface CreateAgentSessionResult {
|
|
158
175
|
/** The created session */
|
|
159
176
|
session: AgentSession;
|
|
160
|
-
/**
|
|
161
|
-
|
|
177
|
+
/** Extensions result (for UI context setup in interactive mode) */
|
|
178
|
+
extensionsResult: LoadExtensionsResult;
|
|
162
179
|
/** MCP manager for server lifecycle management (undefined if MCP disabled) */
|
|
163
180
|
mcpManager?: MCPManager;
|
|
164
181
|
/** Warning if session was restored with a different model than saved */
|
|
@@ -170,12 +187,18 @@ export interface CreateAgentSessionResult {
|
|
|
170
187
|
// Re-exports
|
|
171
188
|
|
|
172
189
|
export type { CustomCommand, CustomCommandFactory } from "./custom-commands/types";
|
|
173
|
-
export type { CustomTool } from "./custom-tools/types";
|
|
174
|
-
export type {
|
|
190
|
+
export type { CustomTool, CustomToolFactory } from "./custom-tools/types";
|
|
191
|
+
export type {
|
|
192
|
+
ExtensionAPI,
|
|
193
|
+
ExtensionCommandContext,
|
|
194
|
+
ExtensionContext,
|
|
195
|
+
ExtensionFactory,
|
|
196
|
+
ToolDefinition,
|
|
197
|
+
} from "./extensions/index";
|
|
175
198
|
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index";
|
|
199
|
+
export type { PromptTemplate } from "./prompt-templates";
|
|
176
200
|
export type { Settings, SkillsSettings } from "./settings-manager";
|
|
177
201
|
export type { Skill } from "./skills";
|
|
178
|
-
export type { FileSlashCommand } from "./slash-commands";
|
|
179
202
|
export type { Tool } from "./tools/index";
|
|
180
203
|
|
|
181
204
|
export {
|
|
@@ -186,6 +209,7 @@ export {
|
|
|
186
209
|
writeTool,
|
|
187
210
|
grepTool,
|
|
188
211
|
findTool,
|
|
212
|
+
gitTool,
|
|
189
213
|
lsTool,
|
|
190
214
|
codingTools,
|
|
191
215
|
readOnlyTools,
|
|
@@ -199,6 +223,7 @@ export {
|
|
|
199
223
|
createWriteTool,
|
|
200
224
|
createGrepTool,
|
|
201
225
|
createFindTool,
|
|
226
|
+
createGitTool,
|
|
202
227
|
createLsTool,
|
|
203
228
|
};
|
|
204
229
|
|
|
@@ -214,7 +239,7 @@ function getDefaultAgentDir(): string {
|
|
|
214
239
|
* Create an AuthStorage instance with fallback support.
|
|
215
240
|
* Reads from primary path first, then falls back to legacy paths (.pi, .claude).
|
|
216
241
|
*/
|
|
217
|
-
export function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): AuthStorage {
|
|
242
|
+
export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
|
|
218
243
|
const primaryPath = join(agentDir, "auth.json");
|
|
219
244
|
// Get all auth.json paths (user-level only), excluding the primary
|
|
220
245
|
const allPaths = getConfigDirPaths("auth.json", { project: false });
|
|
@@ -222,14 +247,19 @@ export function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Au
|
|
|
222
247
|
|
|
223
248
|
logger.debug("discoverAuthStorage", { agentDir, primaryPath, allPaths, fallbackPaths });
|
|
224
249
|
|
|
225
|
-
|
|
250
|
+
const storage = new AuthStorage(primaryPath, fallbackPaths);
|
|
251
|
+
await storage.reload();
|
|
252
|
+
return storage;
|
|
226
253
|
}
|
|
227
254
|
|
|
228
255
|
/**
|
|
229
256
|
* Create a ModelRegistry with fallback support.
|
|
230
257
|
* Reads from primary path first, then falls back to legacy paths (.pi, .claude).
|
|
231
258
|
*/
|
|
232
|
-
export function discoverModels(
|
|
259
|
+
export async function discoverModels(
|
|
260
|
+
authStorage: AuthStorage,
|
|
261
|
+
agentDir: string = getDefaultAgentDir(),
|
|
262
|
+
): Promise<ModelRegistry> {
|
|
233
263
|
const primaryPath = join(agentDir, "models.json");
|
|
234
264
|
// Get all models.json paths (user-level only), excluding the primary
|
|
235
265
|
const allPaths = getConfigDirPaths("models.json", { project: false });
|
|
@@ -237,51 +267,18 @@ export function discoverModels(authStorage: AuthStorage, agentDir: string = getD
|
|
|
237
267
|
|
|
238
268
|
logger.debug("discoverModels", { primaryPath, fallbackPaths });
|
|
239
269
|
|
|
240
|
-
|
|
270
|
+
const registry = new ModelRegistry(authStorage, primaryPath, fallbackPaths);
|
|
271
|
+
await registry.refresh();
|
|
272
|
+
return registry;
|
|
241
273
|
}
|
|
242
274
|
|
|
243
275
|
/**
|
|
244
|
-
* Discover
|
|
276
|
+
* Discover extensions from cwd.
|
|
245
277
|
*/
|
|
246
|
-
export async function
|
|
247
|
-
cwd?: string,
|
|
248
|
-
_agentDir?: string,
|
|
249
|
-
): Promise<Array<{ path: string; factory: HookFactory }>> {
|
|
278
|
+
export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult> {
|
|
250
279
|
const resolvedCwd = cwd ?? process.cwd();
|
|
251
280
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// Log errors but don't fail
|
|
255
|
-
for (const { path, error } of errors) {
|
|
256
|
-
console.error(`Failed to load hook "${path}": ${error}`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return hooks.map((h) => ({
|
|
260
|
-
path: h.path,
|
|
261
|
-
factory: createFactoryFromLoadedHook(h),
|
|
262
|
-
}));
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Discover custom tools from cwd and agentDir.
|
|
267
|
-
*/
|
|
268
|
-
export async function discoverCustomTools(
|
|
269
|
-
cwd?: string,
|
|
270
|
-
_agentDir?: string,
|
|
271
|
-
): Promise<Array<{ path: string; tool: CustomTool }>> {
|
|
272
|
-
const resolvedCwd = cwd ?? process.cwd();
|
|
273
|
-
|
|
274
|
-
const { tools, errors } = await discoverAndLoadCustomTools([], resolvedCwd, Object.keys(allTools));
|
|
275
|
-
|
|
276
|
-
// Log errors but don't fail
|
|
277
|
-
for (const { path, error } of errors) {
|
|
278
|
-
console.error(`Failed to load custom tool "${path}": ${error}`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return tools.map((t) => ({
|
|
282
|
-
path: t.path,
|
|
283
|
-
tool: t.tool,
|
|
284
|
-
}));
|
|
281
|
+
return discoverAndLoadExtensions([], resolvedCwd);
|
|
285
282
|
}
|
|
286
283
|
|
|
287
284
|
/**
|
|
@@ -309,15 +306,12 @@ export function discoverContextFiles(
|
|
|
309
306
|
}
|
|
310
307
|
|
|
311
308
|
/**
|
|
312
|
-
* Discover
|
|
309
|
+
* Discover prompt templates from cwd and agentDir.
|
|
313
310
|
*/
|
|
314
|
-
export function
|
|
315
|
-
|
|
316
|
-
_agentDir?: string,
|
|
317
|
-
_settings?: CommandsSettings,
|
|
318
|
-
): FileSlashCommand[] {
|
|
319
|
-
return loadSlashCommandsInternal({
|
|
311
|
+
export async function discoverPromptTemplates(cwd?: string, agentDir?: string): Promise<PromptTemplate[]> {
|
|
312
|
+
return await loadPromptTemplatesInternal({
|
|
320
313
|
cwd: cwd ?? process.cwd(),
|
|
314
|
+
agentDir: agentDir ?? getDefaultAgentDir(),
|
|
321
315
|
});
|
|
322
316
|
}
|
|
323
317
|
|
|
@@ -377,15 +371,16 @@ export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|
|
377
371
|
return {
|
|
378
372
|
modelRoles: manager.getModelRoles(),
|
|
379
373
|
defaultThinkingLevel: manager.getDefaultThinkingLevel(),
|
|
380
|
-
|
|
374
|
+
steeringMode: manager.getSteeringMode(),
|
|
375
|
+
followUpMode: manager.getFollowUpMode(),
|
|
376
|
+
interruptMode: manager.getInterruptMode(),
|
|
381
377
|
theme: manager.getTheme(),
|
|
382
378
|
compaction: manager.getCompactionSettings(),
|
|
383
379
|
retry: manager.getRetrySettings(),
|
|
384
380
|
hideThinkingBlock: manager.getHideThinkingBlock(),
|
|
385
381
|
shellPath: manager.getShellPath(),
|
|
386
382
|
collapseChangelog: manager.getCollapseChangelog(),
|
|
387
|
-
|
|
388
|
-
customTools: manager.getCustomToolPaths(),
|
|
383
|
+
extensions: manager.getExtensionPaths(),
|
|
389
384
|
skills: manager.getSkillsSettings(),
|
|
390
385
|
terminal: { showImages: manager.getShowImages() },
|
|
391
386
|
};
|
|
@@ -393,84 +388,85 @@ export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|
|
393
388
|
|
|
394
389
|
// Internal Helpers
|
|
395
390
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
api.on(eventType as any, handler as any);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
391
|
+
function createCustomToolContext(ctx: ExtensionContext): CustomToolContext {
|
|
392
|
+
return {
|
|
393
|
+
sessionManager: ctx.sessionManager,
|
|
394
|
+
modelRegistry: ctx.modelRegistry,
|
|
395
|
+
model: ctx.model,
|
|
396
|
+
isIdle: ctx.isIdle,
|
|
397
|
+
hasQueuedMessages: ctx.hasPendingMessages,
|
|
398
|
+
abort: ctx.abort,
|
|
407
399
|
};
|
|
408
400
|
}
|
|
409
401
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const handlers = new Map<string, Array<(...args: unknown[]) => Promise<unknown>>>();
|
|
416
|
-
const messageRenderers = new Map<string, any>();
|
|
417
|
-
const commands = new Map<string, any>();
|
|
418
|
-
let sendMessageHandler: (message: any, triggerTurn?: boolean) => void = () => {};
|
|
419
|
-
let appendEntryHandler: (customType: string, data?: any) => void = () => {};
|
|
420
|
-
let newSessionHandler: (options?: any) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
|
421
|
-
let branchHandler: (entryId: string) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
|
422
|
-
let navigateTreeHandler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }> = async () => ({
|
|
423
|
-
cancelled: false,
|
|
424
|
-
});
|
|
402
|
+
function isCustomTool(tool: CustomTool | ToolDefinition): tool is CustomTool {
|
|
403
|
+
// To distinguish, we mark converted tools with a hidden symbol property.
|
|
404
|
+
// If the tool doesn't have this marker, it's a CustomTool that needs conversion.
|
|
405
|
+
return !(tool as any).__isToolDefinition;
|
|
406
|
+
}
|
|
425
407
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
408
|
+
const TOOL_DEFINITION_MARKER = Symbol("__isToolDefinition");
|
|
409
|
+
|
|
410
|
+
function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
411
|
+
const definition: ToolDefinition & { [TOOL_DEFINITION_MARKER]: true } = {
|
|
412
|
+
name: tool.name,
|
|
413
|
+
label: tool.label,
|
|
414
|
+
description: tool.description,
|
|
415
|
+
parameters: tool.parameters,
|
|
416
|
+
hidden: tool.hidden,
|
|
417
|
+
execute: (toolCallId, params, onUpdate, ctx, signal) =>
|
|
418
|
+
tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
|
|
419
|
+
onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
|
|
420
|
+
renderCall: tool.renderCall,
|
|
421
|
+
renderResult: tool.renderResult
|
|
422
|
+
? (result, options, theme): Component => {
|
|
423
|
+
const component = tool.renderResult?.(
|
|
424
|
+
result,
|
|
425
|
+
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
|
426
|
+
theme,
|
|
427
|
+
);
|
|
428
|
+
// Return empty component if undefined to match Component type requirement
|
|
429
|
+
return component ?? ({ render: () => [] } as unknown as Component);
|
|
430
|
+
}
|
|
431
|
+
: undefined,
|
|
432
|
+
[TOOL_DEFINITION_MARKER]: true,
|
|
433
|
+
};
|
|
434
|
+
return definition;
|
|
435
|
+
}
|
|
448
436
|
|
|
449
|
-
|
|
437
|
+
function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
438
|
+
return (api) => {
|
|
439
|
+
for (const tool of tools) {
|
|
440
|
+
api.registerTool(customToolToDefinition(tool));
|
|
441
|
+
}
|
|
450
442
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
setAppendEntryHandler: (handler: (customType: string, data?: any) => void) => {
|
|
461
|
-
appendEntryHandler = handler;
|
|
462
|
-
},
|
|
463
|
-
setNewSessionHandler: (handler: (options?: any) => Promise<{ cancelled: boolean }>) => {
|
|
464
|
-
newSessionHandler = handler;
|
|
465
|
-
},
|
|
466
|
-
setBranchHandler: (handler: (entryId: string) => Promise<{ cancelled: boolean }>) => {
|
|
467
|
-
branchHandler = handler;
|
|
468
|
-
},
|
|
469
|
-
setNavigateTreeHandler: (handler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }>) => {
|
|
470
|
-
navigateTreeHandler = handler;
|
|
471
|
-
},
|
|
443
|
+
const runOnSession = async (event: CustomToolSessionEvent, ctx: ExtensionContext) => {
|
|
444
|
+
for (const tool of tools) {
|
|
445
|
+
if (!tool.onSession) continue;
|
|
446
|
+
try {
|
|
447
|
+
await tool.onSession(event, createCustomToolContext(ctx));
|
|
448
|
+
} catch (err) {
|
|
449
|
+
logger.warn("Custom tool onSession error", { tool: tool.name, error: String(err) });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
472
452
|
};
|
|
473
|
-
|
|
453
|
+
|
|
454
|
+
api.on("session_start", async (_event, ctx) =>
|
|
455
|
+
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx),
|
|
456
|
+
);
|
|
457
|
+
api.on("session_switch", async (event, ctx) =>
|
|
458
|
+
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx),
|
|
459
|
+
);
|
|
460
|
+
api.on("session_branch", async (event, ctx) =>
|
|
461
|
+
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx),
|
|
462
|
+
);
|
|
463
|
+
api.on("session_tree", async (_event, ctx) =>
|
|
464
|
+
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx),
|
|
465
|
+
);
|
|
466
|
+
api.on("session_shutdown", async (_event, ctx) =>
|
|
467
|
+
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
|
|
468
|
+
);
|
|
469
|
+
};
|
|
474
470
|
}
|
|
475
471
|
|
|
476
472
|
// Factory
|
|
@@ -501,7 +497,6 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa
|
|
|
501
497
|
* getApiKey: async () => process.env.MY_KEY,
|
|
502
498
|
* systemPrompt: 'You are helpful.',
|
|
503
499
|
* tools: [readTool, bashTool],
|
|
504
|
-
* hooks: [],
|
|
505
500
|
* skills: [],
|
|
506
501
|
* sessionManager: SessionManager.inMemory(),
|
|
507
502
|
* });
|
|
@@ -510,17 +505,15 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa
|
|
|
510
505
|
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
511
506
|
const cwd = options.cwd ?? process.cwd();
|
|
512
507
|
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
|
508
|
+
const eventBus = options.eventBus ?? createEventBus();
|
|
513
509
|
|
|
514
510
|
// Use provided or create AuthStorage and ModelRegistry
|
|
515
|
-
const authStorage = options.authStorage ?? discoverAuthStorage(agentDir);
|
|
516
|
-
const modelRegistry = options.modelRegistry ?? discoverModels(authStorage, agentDir);
|
|
511
|
+
const authStorage = options.authStorage ?? (await discoverAuthStorage(agentDir));
|
|
512
|
+
const modelRegistry = options.modelRegistry ?? (await discoverModels(authStorage, agentDir));
|
|
517
513
|
time("discoverModels");
|
|
518
514
|
|
|
519
515
|
const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
|
|
520
516
|
time("settingsManager");
|
|
521
|
-
|
|
522
|
-
// Initialize discovery system with settings for provider persistence
|
|
523
|
-
const { initializeWithSettings } = await import("../discovery");
|
|
524
517
|
initializeWithSettings(settingsManager);
|
|
525
518
|
time("initializeWithSettings");
|
|
526
519
|
|
|
@@ -538,17 +531,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
538
531
|
// If session has data, try to restore model from it
|
|
539
532
|
const defaultModelStr = existingSession.models.default;
|
|
540
533
|
if (!model && hasExistingSession && defaultModelStr) {
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
543
|
-
const
|
|
544
|
-
const modelId = defaultModelStr.slice(slashIdx + 1);
|
|
545
|
-
const restoredModel = modelRegistry.find(provider, modelId);
|
|
534
|
+
const parsedModel = parseModelString(defaultModelStr);
|
|
535
|
+
if (parsedModel) {
|
|
536
|
+
const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
546
537
|
if (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {
|
|
547
538
|
model = restoredModel;
|
|
548
539
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
540
|
+
}
|
|
541
|
+
if (!model) {
|
|
542
|
+
modelFallbackMessage = `Could not restore model ${defaultModelStr}`;
|
|
552
543
|
}
|
|
553
544
|
}
|
|
554
545
|
|
|
@@ -556,11 +547,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
556
547
|
if (!model) {
|
|
557
548
|
const settingsDefaultModel = settingsManager.getModelRole("default");
|
|
558
549
|
if (settingsDefaultModel) {
|
|
559
|
-
const
|
|
560
|
-
if (
|
|
561
|
-
const
|
|
562
|
-
const modelId = settingsDefaultModel.slice(slashIdx + 1);
|
|
563
|
-
const settingsModel = modelRegistry.find(provider, modelId);
|
|
550
|
+
const parsedModel = parseModelString(settingsDefaultModel);
|
|
551
|
+
if (parsedModel) {
|
|
552
|
+
const settingsModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
564
553
|
if (settingsModel && (await modelRegistry.getApiKey(settingsModel))) {
|
|
565
554
|
model = settingsModel;
|
|
566
555
|
}
|
|
@@ -624,68 +613,35 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
624
613
|
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
|
625
614
|
time("discoverContextFiles");
|
|
626
615
|
|
|
627
|
-
// Hook runner - always created (needed for custom command context even without hooks)
|
|
628
|
-
let loadedHooks: LoadedHook[] = [];
|
|
629
|
-
if (options.hooks !== undefined) {
|
|
630
|
-
if (options.hooks.length > 0) {
|
|
631
|
-
loadedHooks = createLoadedHooksFromDefinitions(options.hooks);
|
|
632
|
-
}
|
|
633
|
-
} else {
|
|
634
|
-
// Discover hooks, merging with additional paths
|
|
635
|
-
const configuredPaths = [...settingsManager.getHookPaths(), ...(options.additionalHookPaths ?? [])];
|
|
636
|
-
const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd);
|
|
637
|
-
time("discoverAndLoadHooks");
|
|
638
|
-
for (const { path, error } of errors) {
|
|
639
|
-
console.error(`Failed to load hook "${path}": ${error}`);
|
|
640
|
-
}
|
|
641
|
-
loadedHooks = hooks;
|
|
642
|
-
}
|
|
643
|
-
const hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
|
|
644
|
-
|
|
645
616
|
const sessionContext = {
|
|
646
617
|
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
647
618
|
};
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const loadedTools: LoadedCustomTool[] = options.customTools.map((ct) => ({
|
|
662
|
-
path: ct.path ?? "<inline>",
|
|
663
|
-
resolvedPath: ct.path ?? "<inline>",
|
|
664
|
-
tool: ct.tool,
|
|
665
|
-
}));
|
|
666
|
-
customToolsResult = {
|
|
667
|
-
tools: loadedTools,
|
|
668
|
-
errors: [],
|
|
669
|
-
setUIContext: () => {},
|
|
670
|
-
};
|
|
671
|
-
} else {
|
|
672
|
-
// Discover custom tools, merging with additional paths
|
|
673
|
-
const configuredPaths = [...settingsManager.getCustomToolPaths(), ...(options.additionalCustomToolPaths ?? [])];
|
|
674
|
-
customToolsResult = await discoverAndLoadCustomTools(configuredPaths, cwd, Object.keys(allTools));
|
|
675
|
-
time("discoverAndLoadCustomTools");
|
|
676
|
-
for (const { path, error } of customToolsResult.errors) {
|
|
677
|
-
console.error(`Failed to load custom tool "${path}": ${error}`);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
619
|
+
const allBuiltInToolsMap = await createAllTools(cwd, sessionContext, {
|
|
620
|
+
lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
|
|
621
|
+
lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
|
|
622
|
+
lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
|
|
623
|
+
editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
|
|
624
|
+
readAutoResizeImages: settingsManager.getImageAutoResize(),
|
|
625
|
+
});
|
|
626
|
+
time("createAllTools");
|
|
627
|
+
|
|
628
|
+
const initialActiveToolNames: ToolName[] = options.tools
|
|
629
|
+
? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allBuiltInToolsMap)
|
|
630
|
+
: baseCodingToolNames;
|
|
631
|
+
const initialActiveBuiltInTools = initialActiveToolNames.map((name) => allBuiltInToolsMap[name]);
|
|
680
632
|
|
|
681
633
|
// Discover MCP tools from .mcp.json files
|
|
682
634
|
let mcpManager: MCPManager | undefined;
|
|
683
635
|
const enableMCP = options.enableMCP ?? true;
|
|
636
|
+
const customTools: CustomTool[] = [];
|
|
684
637
|
if (enableMCP) {
|
|
685
638
|
const mcpResult = await discoverAndLoadMCPTools(cwd, {
|
|
686
639
|
onConnecting: (serverNames) => {
|
|
687
640
|
if (options.hasUI && serverNames.length > 0) {
|
|
688
|
-
process.stderr.write(
|
|
641
|
+
process.stderr.write(
|
|
642
|
+
chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}...
|
|
643
|
+
`),
|
|
644
|
+
);
|
|
689
645
|
}
|
|
690
646
|
},
|
|
691
647
|
enableProjectConfig: settingsManager.getMCPProjectConfigEnabled(),
|
|
@@ -705,19 +661,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
705
661
|
console.error(`MCP "${path}": ${error}`);
|
|
706
662
|
}
|
|
707
663
|
|
|
708
|
-
// Merge MCP tools into custom tools result
|
|
709
664
|
if (mcpResult.tools.length > 0) {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
tools: [...customToolsResult.tools, ...mcpResult.tools],
|
|
713
|
-
};
|
|
665
|
+
// MCP tools are LoadedCustomTool, extract the tool property
|
|
666
|
+
customTools.push(...mcpResult.tools.map((loaded) => loaded.tool));
|
|
714
667
|
}
|
|
715
668
|
}
|
|
716
669
|
|
|
670
|
+
// Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
|
|
671
|
+
const geminiImageTools = await getGeminiImageTools();
|
|
672
|
+
if (geminiImageTools.length > 0) {
|
|
673
|
+
customTools.push(...(geminiImageTools as unknown as CustomTool[]));
|
|
674
|
+
}
|
|
675
|
+
time("getGeminiImageTools");
|
|
676
|
+
|
|
717
677
|
// Add specialized Exa web search tools if EXA_API_KEY is available
|
|
718
678
|
const exaSettings = settingsManager.getExaSettings();
|
|
719
679
|
if (exaSettings.enabled && exaSettings.enableSearch) {
|
|
720
|
-
const { getWebSearchTools } = await import("./tools/web-search/index.js");
|
|
721
680
|
const exaWebSearchTools = await getWebSearchTools({
|
|
722
681
|
enableLinkedin: exaSettings.enableLinkedin,
|
|
723
682
|
enableCompany: exaSettings.enableCompany,
|
|
@@ -725,20 +684,85 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
725
684
|
// Filter out the base web_search (already in built-in tools), add specialized Exa tools
|
|
726
685
|
const specializedTools = exaWebSearchTools.filter((t) => t.name !== "web_search");
|
|
727
686
|
if (specializedTools.length > 0) {
|
|
728
|
-
|
|
729
|
-
path: "<exa>",
|
|
730
|
-
resolvedPath: "<exa>",
|
|
731
|
-
tool,
|
|
732
|
-
source: { provider: "builtin", providerName: "builtin", level: "user" },
|
|
733
|
-
}));
|
|
734
|
-
customToolsResult = {
|
|
735
|
-
...customToolsResult,
|
|
736
|
-
tools: [...customToolsResult.tools, ...loadedExaTools],
|
|
737
|
-
};
|
|
687
|
+
customTools.push(...specializedTools);
|
|
738
688
|
}
|
|
739
689
|
time("getWebSearchTools");
|
|
740
690
|
}
|
|
741
691
|
|
|
692
|
+
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
|
|
693
|
+
if (customTools.length > 0) {
|
|
694
|
+
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Load extensions (discovers from standard locations + configured paths)
|
|
698
|
+
let extensionsResult: LoadExtensionsResult;
|
|
699
|
+
if (options.preloadedExtensions !== undefined && options.preloadedExtensions.length > 0) {
|
|
700
|
+
extensionsResult = {
|
|
701
|
+
extensions: options.preloadedExtensions,
|
|
702
|
+
errors: [],
|
|
703
|
+
setUIContext: () => {},
|
|
704
|
+
};
|
|
705
|
+
} else {
|
|
706
|
+
// Merge CLI extension paths with settings extension paths
|
|
707
|
+
const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...settingsManager.getExtensionPaths()];
|
|
708
|
+
extensionsResult = await discoverAndLoadExtensions(
|
|
709
|
+
configuredPaths,
|
|
710
|
+
cwd,
|
|
711
|
+
eventBus,
|
|
712
|
+
settingsManager.getDisabledExtensions(),
|
|
713
|
+
);
|
|
714
|
+
time("discoverAndLoadExtensions");
|
|
715
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
716
|
+
console.error(`Failed to load extension "${path}": ${error}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Load inline extensions from factories
|
|
721
|
+
if (inlineExtensions.length > 0) {
|
|
722
|
+
const uiHolder: { ui: any; hasUI: boolean } = {
|
|
723
|
+
ui: {
|
|
724
|
+
select: async () => undefined,
|
|
725
|
+
confirm: async () => false,
|
|
726
|
+
input: async () => undefined,
|
|
727
|
+
notify: () => {},
|
|
728
|
+
setStatus: () => {},
|
|
729
|
+
setWidget: () => {},
|
|
730
|
+
setTitle: () => {},
|
|
731
|
+
custom: async () => undefined as never,
|
|
732
|
+
setEditorText: () => {},
|
|
733
|
+
getEditorText: () => "",
|
|
734
|
+
editor: async () => undefined,
|
|
735
|
+
get theme() {
|
|
736
|
+
return {} as any;
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
hasUI: false,
|
|
740
|
+
};
|
|
741
|
+
for (let i = 0; i < inlineExtensions.length; i++) {
|
|
742
|
+
const factory = inlineExtensions[i];
|
|
743
|
+
const loaded = loadExtensionFromFactory(factory, cwd, eventBus, uiHolder, `<inline-${i}>`);
|
|
744
|
+
extensionsResult.extensions.push(loaded);
|
|
745
|
+
}
|
|
746
|
+
const originalSetUIContext = extensionsResult.setUIContext;
|
|
747
|
+
extensionsResult.setUIContext = (uiContext, hasUI) => {
|
|
748
|
+
originalSetUIContext(uiContext, hasUI);
|
|
749
|
+
uiHolder.ui = uiContext;
|
|
750
|
+
uiHolder.hasUI = hasUI;
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Discover custom commands (TypeScript slash commands)
|
|
755
|
+
const customCommandsResult = await loadCustomCommandsInternal({ cwd, agentDir });
|
|
756
|
+
time("discoverCustomCommands");
|
|
757
|
+
for (const { path, error } of customCommandsResult.errors) {
|
|
758
|
+
console.error(`Failed to load custom command "${path}": ${error}`);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
let extensionRunner: ExtensionRunner | undefined;
|
|
762
|
+
if (extensionsResult.extensions.length > 0) {
|
|
763
|
+
extensionRunner = new ExtensionRunner(extensionsResult.extensions, cwd, sessionManager, modelRegistry);
|
|
764
|
+
}
|
|
765
|
+
|
|
742
766
|
let agent: Agent;
|
|
743
767
|
let session: AgentSession;
|
|
744
768
|
const getSessionContext = () => ({
|
|
@@ -752,90 +776,148 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
752
776
|
},
|
|
753
777
|
});
|
|
754
778
|
const toolContextStore = createToolContextStore(getSessionContext);
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
...
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
779
|
+
|
|
780
|
+
const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
|
|
781
|
+
const allCustomTools = [
|
|
782
|
+
...registeredTools,
|
|
783
|
+
...(options.customTools?.map((tool) => {
|
|
784
|
+
const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
|
|
785
|
+
return { definition, extensionPath: "<sdk>" };
|
|
786
|
+
}) ?? []),
|
|
787
|
+
];
|
|
788
|
+
const wrappedExtensionTools = wrapRegisteredTools(allCustomTools, () => ({
|
|
789
|
+
ui: extensionRunner?.getUIContext() ?? {
|
|
790
|
+
select: async () => undefined,
|
|
791
|
+
confirm: async () => false,
|
|
792
|
+
input: async () => undefined,
|
|
793
|
+
notify: () => {},
|
|
794
|
+
setStatus: () => {},
|
|
795
|
+
setWidget: () => {},
|
|
796
|
+
setTitle: () => {},
|
|
797
|
+
custom: async () => undefined as never,
|
|
798
|
+
setEditorText: () => {},
|
|
799
|
+
getEditorText: () => "",
|
|
800
|
+
editor: async () => undefined,
|
|
801
|
+
get theme() {
|
|
802
|
+
return {} as any;
|
|
803
|
+
},
|
|
762
804
|
},
|
|
763
|
-
|
|
805
|
+
hasUI: extensionRunner?.getHasUI() ?? false,
|
|
806
|
+
cwd,
|
|
807
|
+
sessionManager,
|
|
808
|
+
modelRegistry,
|
|
809
|
+
model: agent.state.model,
|
|
810
|
+
isIdle: () => !session.isStreaming,
|
|
811
|
+
abort: () => {
|
|
812
|
+
session.abort();
|
|
813
|
+
},
|
|
814
|
+
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
815
|
+
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
816
|
+
}));
|
|
817
|
+
|
|
818
|
+
const toolRegistry = new Map<string, AgentTool>();
|
|
819
|
+
for (const [name, tool] of Object.entries(allBuiltInToolsMap)) {
|
|
820
|
+
toolRegistry.set(name, tool as AgentTool);
|
|
821
|
+
}
|
|
822
|
+
for (const tool of wrappedExtensionTools as AgentTool[]) {
|
|
823
|
+
toolRegistry.set(tool.name, tool);
|
|
824
|
+
}
|
|
764
825
|
|
|
765
|
-
let
|
|
826
|
+
let activeToolsArray: Tool[] = [...initialActiveBuiltInTools, ...wrappedExtensionTools];
|
|
766
827
|
|
|
767
|
-
// Add rulebook tool if there are rules with descriptions (always enabled, regardless of --tools)
|
|
768
828
|
if (rulebookRules.length > 0) {
|
|
769
|
-
|
|
829
|
+
activeToolsArray.push(createRulebookTool(rulebookRules));
|
|
770
830
|
}
|
|
771
831
|
|
|
772
|
-
// Filter out hidden tools unless explicitly requested
|
|
773
832
|
if (options.explicitTools) {
|
|
774
833
|
const explicitSet = new Set(options.explicitTools);
|
|
775
|
-
|
|
834
|
+
activeToolsArray = activeToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
|
|
776
835
|
} else {
|
|
777
|
-
|
|
836
|
+
activeToolsArray = activeToolsArray.filter((tool) => !tool.hidden);
|
|
778
837
|
}
|
|
779
838
|
time("combineTools");
|
|
780
839
|
|
|
781
|
-
// Apply bash interception to redirect common shell patterns to proper tools (if enabled)
|
|
782
840
|
if (settingsManager.getBashInterceptorEnabled()) {
|
|
783
|
-
|
|
841
|
+
activeToolsArray = applyBashInterception(activeToolsArray);
|
|
784
842
|
}
|
|
785
843
|
time("applyBashInterception");
|
|
786
844
|
|
|
787
|
-
|
|
788
|
-
|
|
845
|
+
let wrappedToolRegistry: Map<string, AgentTool> | undefined;
|
|
846
|
+
if (extensionRunner) {
|
|
847
|
+
activeToolsArray = wrapToolsWithExtensions(activeToolsArray as AgentTool[], extensionRunner);
|
|
848
|
+
const allRegistryTools = Array.from(toolRegistry.values());
|
|
849
|
+
const wrappedAllTools = wrapToolsWithExtensions(allRegistryTools, extensionRunner);
|
|
850
|
+
wrappedToolRegistry = new Map<string, AgentTool>();
|
|
851
|
+
for (const tool of wrappedAllTools) {
|
|
852
|
+
wrappedToolRegistry.set(tool.name, tool);
|
|
853
|
+
}
|
|
789
854
|
}
|
|
790
855
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
} else if (typeof options.systemPrompt === "string") {
|
|
803
|
-
systemPrompt = buildSystemPromptInternal({
|
|
856
|
+
const rebuildSystemPrompt = (toolNames: string[]): string => {
|
|
857
|
+
const validToolNames = toolNames.filter((n): n is ToolName => n in allBuiltInToolsMap);
|
|
858
|
+
const extraToolDescriptions = toolNames
|
|
859
|
+
.filter((name) => !(name in allBuiltInToolsMap))
|
|
860
|
+
.map((name) => {
|
|
861
|
+
const tool = toolRegistry.get(name);
|
|
862
|
+
if (!tool) return null;
|
|
863
|
+
return { name, description: tool.description || tool.label || "Custom tool" };
|
|
864
|
+
})
|
|
865
|
+
.filter((tool): tool is { name: string; description: string } => tool !== null);
|
|
866
|
+
const defaultPrompt = buildSystemPromptInternal({
|
|
804
867
|
cwd,
|
|
805
868
|
skills,
|
|
806
869
|
contextFiles,
|
|
807
870
|
rulebookRules,
|
|
808
|
-
|
|
871
|
+
selectedTools: validToolNames,
|
|
872
|
+
extraToolDescriptions,
|
|
873
|
+
skillsSettings: settingsManager.getSkillsSettings(),
|
|
809
874
|
});
|
|
810
|
-
} else {
|
|
811
|
-
systemPrompt = options.systemPrompt(defaultPrompt);
|
|
812
|
-
}
|
|
813
875
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
876
|
+
if (options.systemPrompt === undefined) {
|
|
877
|
+
return defaultPrompt;
|
|
878
|
+
}
|
|
879
|
+
if (typeof options.systemPrompt === "string") {
|
|
880
|
+
return buildSystemPromptInternal({
|
|
881
|
+
cwd,
|
|
882
|
+
skills,
|
|
883
|
+
contextFiles,
|
|
884
|
+
rulebookRules,
|
|
885
|
+
selectedTools: validToolNames,
|
|
886
|
+
extraToolDescriptions,
|
|
887
|
+
skillsSettings: settingsManager.getSkillsSettings(),
|
|
888
|
+
customPrompt: options.systemPrompt,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
return options.systemPrompt(defaultPrompt);
|
|
892
|
+
};
|
|
817
893
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
894
|
+
const systemPrompt = rebuildSystemPrompt(initialActiveToolNames);
|
|
895
|
+
time("buildSystemPrompt");
|
|
896
|
+
|
|
897
|
+
const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
|
|
898
|
+
time("discoverPromptTemplates");
|
|
899
|
+
|
|
900
|
+
const baseSetUIContext = extensionsResult.setUIContext;
|
|
901
|
+
extensionsResult.setUIContext = (uiContext, hasUI) => {
|
|
902
|
+
baseSetUIContext(uiContext, hasUI);
|
|
903
|
+
toolContextStore.setUIContext(uiContext, hasUI);
|
|
904
|
+
};
|
|
824
905
|
|
|
825
906
|
agent = new Agent({
|
|
826
907
|
initialState: {
|
|
827
908
|
systemPrompt,
|
|
828
909
|
model,
|
|
829
910
|
thinkingLevel,
|
|
830
|
-
tools:
|
|
911
|
+
tools: activeToolsArray,
|
|
831
912
|
},
|
|
832
913
|
convertToLlm,
|
|
833
|
-
transformContext:
|
|
914
|
+
transformContext: extensionRunner
|
|
834
915
|
? async (messages) => {
|
|
835
|
-
return
|
|
916
|
+
return extensionRunner.emitContext(messages);
|
|
836
917
|
}
|
|
837
918
|
: undefined,
|
|
838
|
-
|
|
919
|
+
steeringMode: settingsManager.getSteeringMode(),
|
|
920
|
+
followUpMode: settingsManager.getFollowUpMode(),
|
|
839
921
|
interruptMode: settingsManager.getInterruptMode(),
|
|
840
922
|
getToolContext: toolContextStore.getContext,
|
|
841
923
|
getApiKey: async () => {
|
|
@@ -868,12 +950,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
868
950
|
sessionManager,
|
|
869
951
|
settingsManager,
|
|
870
952
|
scopedModels: options.scopedModels,
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
customTools: customToolsResult.tools,
|
|
953
|
+
promptTemplates,
|
|
954
|
+
extensionRunner,
|
|
874
955
|
customCommands: customCommandsResult.commands,
|
|
875
956
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
876
957
|
modelRegistry,
|
|
958
|
+
toolRegistry: wrappedToolRegistry ?? toolRegistry,
|
|
959
|
+
rebuildSystemPrompt,
|
|
877
960
|
ttsrManager,
|
|
878
961
|
});
|
|
879
962
|
time("createAgentSession");
|
|
@@ -892,7 +975,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
892
975
|
|
|
893
976
|
return {
|
|
894
977
|
session,
|
|
895
|
-
|
|
978
|
+
extensionsResult,
|
|
896
979
|
mcpManager,
|
|
897
980
|
modelFallbackMessage,
|
|
898
981
|
lspServers,
|