diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index e2592c1dc..463cfc99c 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Added + +- Added `loadMode` option to `AgentTool` to mark built-in tools as `essential` for initial loading or `discoverable` for search activation +- Added optional `summary` field to `AgentTool` definitions for one-line text used in tool discovery indexes ## [14.7.0] - 2026-05-04 ### Breaking Changes diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index 92ce50de0..34de7fa50 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -248,6 +248,10 @@ export interface AgentTool { readonly name = "edit"; readonly label = "Edit"; + readonly loadMode = "essential"; readonly nonAbortable = true; readonly concurrency = "exclusive"; readonly strict = true; diff --git a/packages/coding-agent/src/lsp/index.ts b/packages/coding-agent/src/lsp/index.ts index b598869ea..ba45ba1d5 100644 --- a/packages/coding-agent/src/lsp/index.ts +++ b/packages/coding-agent/src/lsp/index.ts @@ -1169,6 +1169,8 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions export class LspTool implements AgentTool { readonly name = "lsp"; readonly label = "LSP"; + readonly loadMode = "discoverable"; + readonly summary = "Query LSP (language server) for diagnostics, hover info, and references"; readonly description: string; readonly parameters = lspSchema; readonly renderCall = renderCall; diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 93f8df8b6..92c4c17e1 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -116,7 +116,6 @@ import { } from "./tool-discovery/tool-index"; import { BashTool, - BUILTIN_TOOL_METADATA, BUILTIN_TOOLS, computeEssentialBuiltinNames, createTools, @@ -284,7 +283,6 @@ export { // Individual tool classes (for custom usage) BashTool, // Tool classes and factories - BUILTIN_TOOL_METADATA, BUILTIN_TOOLS, createTools, EditTool, @@ -1358,19 +1356,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} toolContextStore.setToolNames(toolNames); const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : []; const activeToolNames = new Set(toolNames); - const builtinSummaryMap = new Map( - Object.entries(BUILTIN_TOOL_METADATA) - .filter(([, meta]) => typeof meta.summary === "string") - .map(([name, meta]) => [name, meta.summary!] as const), - ); const discoverableBuiltinTools: DiscoverableTool[] = effectiveDiscoveryMode === "all" ? collectDiscoverableTools( - Array.from(tools.values()).filter(tool => { - const meta = BUILTIN_TOOL_METADATA[tool.name]; - return meta?.loadMode === "discoverable" && !activeToolNames.has(tool.name); - }), - { source: "builtin", summaryMap: builtinSummaryMap }, + Array.from(tools.values()).filter( + tool => tool.loadMode === "discoverable" && !activeToolNames.has(tool.name), + ), + { source: "builtin" }, ) : []; const discoverableToolsForDesc: DiscoverableTool[] = [ @@ -1527,9 +1519,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} // activation persistence is a follow-up). MCP names won't collide with built-in names. const restoredDiscoveredNames = new Set(existingSession.selectedMCPToolNames); initialToolNames = initialToolNames.filter(name => { - const meta = BUILTIN_TOOL_METADATA[name]; - if (!meta) return true; // not a built-in — leave MCP/custom/extension to existing logic - if (meta.loadMode === "essential") return true; + const tool = toolRegistry.get(name); + if (!tool?.loadMode) return true; // not a built-in — leave MCP/custom/extension to existing logic + if (tool.loadMode === "essential") return true; if (essentialBuiltinNames.has(name)) return true; if (explicitlyRequestedToolNames.has(name)) return true; if (restoredDiscoveredNames.has(name)) return true; diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index ffa10ebf0..fa6189f88 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -137,7 +137,6 @@ import { } from "../tool-discovery/tool-index"; import { assertEditableFile } from "../tools/auto-generated-guard"; import type { CheckpointState } from "../tools/checkpoint"; -import { BUILTIN_TOOL_METADATA } from "../tools/index"; import { outputMeta } from "../tools/output-meta"; import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils"; import { isAutoQaEnabled } from "../tools/report-tool-issue"; @@ -2307,20 +2306,17 @@ export class AgentSession { return filter?.source ? allTools.filter(t => t.source === filter.source) : allTools; } - /** Collect built-in tools the model can discover via search_tool_bm25. Restricted to entries - * whose `BUILTIN_TOOL_METADATA[name].loadMode === "discoverable"`. This keeps hidden/internal - * tools (resolve, yield, exit_plan_mode, report_finding, report_tool_issue) out of the index + /** Collect built-in tools the model can discover via search_tool_bm25. Restricted to tool + * definitions whose `loadMode === "discoverable"`. This keeps hidden/internal tools + * (resolve, yield, exit_plan_mode, report_finding, report_tool_issue) out of the index * and avoids mislabeling extension/custom default-inactive tools as built-ins. */ #collectDiscoverableBuiltinTools(): DiscoverableTool[] { const activeNames = new Set(this.getActiveToolNames()); const result: DiscoverableTool[] = []; - for (const [name, meta] of Object.entries(BUILTIN_TOOL_METADATA)) { - if (meta.loadMode !== "discoverable") continue; - if (activeNames.has(name)) continue; - const tool = this.#toolRegistry.get(name); - if (!tool) continue; - const summaryMap = meta.summary ? new Map([[name, meta.summary]]) : undefined; - const collected = collectDiscoverableTools([tool], { source: "builtin", summaryMap }); + for (const tool of this.#toolRegistry.values()) { + if (tool.loadMode !== "discoverable") continue; + if (activeNames.has(tool.name)) continue; + const collected = collectDiscoverableTools([tool], { source: "builtin" }); result.push(...collected); } return result; diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index 66fae68b2..8aa4a3d45 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -195,7 +195,9 @@ function validateTaskModeParams(simpleMode: TaskSimpleMode, params: TaskParams): export class TaskTool implements AgentTool { readonly name = "task"; readonly label = "Task"; + readonly summary = "Spawn a subagent to complete a parallel task"; readonly strict = true; + readonly loadMode = "discoverable"; readonly renderResult = renderResult; readonly #discoveredAgents: AgentDefinition[]; readonly #blockedAgent: string | undefined; diff --git a/packages/coding-agent/src/tool-discovery/tool-index.ts b/packages/coding-agent/src/tool-discovery/tool-index.ts index ed408496a..b2ffdc5f8 100644 --- a/packages/coding-agent/src/tool-discovery/tool-index.ts +++ b/packages/coding-agent/src/tool-discovery/tool-index.ts @@ -156,12 +156,19 @@ export function getDiscoverableTool( label?: string; description?: string; mcpServerName?: string; + summary?: string; mcpToolName?: string; parameters?: unknown; }; const source: DiscoverableToolSource = overrides?.source ?? (isMCPToolName(tool.name) ? "mcp" : "builtin"); + const rawSummary = + typeof overrides?.summary === "string" + ? overrides.summary + : typeof toolRecord.summary === "string" + ? toolRecord.summary + : undefined; const rawDescription = typeof toolRecord.description === "string" ? toolRecord.description : ""; - const summary = overrides?.summary ?? rawDescription.slice(0, 200); + const summary = rawSummary ?? rawDescription.slice(0, 200); return { name: tool.name, label: typeof toolRecord.label === "string" ? toolRecord.label : tool.name, diff --git a/packages/coding-agent/src/tools/ask.ts b/packages/coding-agent/src/tools/ask.ts index 6f81d985f..2e716c3aa 100644 --- a/packages/coding-agent/src/tools/ask.ts +++ b/packages/coding-agent/src/tools/ask.ts @@ -379,9 +379,11 @@ type AskParams = AskToolInput; export class AskTool implements AgentTool { readonly name = "ask"; readonly label = "Ask"; + readonly summary = "Ask the user a clarifying question"; readonly description: string; readonly parameters = askSchema; readonly strict = true; + readonly loadMode = "discoverable"; constructor(private readonly session: ToolSession) { this.description = prompt.render(askDescription); diff --git a/packages/coding-agent/src/tools/ast-edit.ts b/packages/coding-agent/src/tools/ast-edit.ts index 7cd23d3a3..438218355 100644 --- a/packages/coding-agent/src/tools/ast-edit.ts +++ b/packages/coding-agent/src/tools/ast-edit.ts @@ -167,10 +167,12 @@ export interface AstEditToolDetails { export class AstEditTool implements AgentTool { readonly name = "ast_edit"; readonly label = "AST Edit"; + readonly summary = "Perform AST-aware code edits (structural refactoring)"; readonly description: string; readonly parameters = astEditSchema; readonly strict = true; readonly deferrable = true; + readonly loadMode = "discoverable"; constructor(private readonly session: ToolSession) { this.description = prompt.render(astEditDescription); } diff --git a/packages/coding-agent/src/tools/ast-grep.ts b/packages/coding-agent/src/tools/ast-grep.ts index 42a767e16..582f1e0cc 100644 --- a/packages/coding-agent/src/tools/ast-grep.ts +++ b/packages/coding-agent/src/tools/ast-grep.ts @@ -122,9 +122,11 @@ export interface AstGrepToolDetails { export class AstGrepTool implements AgentTool { readonly name = "ast_grep"; readonly label = "AST Grep"; + readonly summary = "Search code with AST patterns (structural grep)"; readonly description: string; readonly parameters = astGrepSchema; readonly strict = true; + readonly loadMode = "discoverable"; constructor(private readonly session: ToolSession) { this.description = prompt.render(astGrepDescription); diff --git a/packages/coding-agent/src/tools/bash.ts b/packages/coding-agent/src/tools/bash.ts index 9130c7d58..260fcc28f 100644 --- a/packages/coding-agent/src/tools/bash.ts +++ b/packages/coding-agent/src/tools/bash.ts @@ -236,6 +236,7 @@ function formatTimeoutClampNotice(requestedTimeoutSec: number, effectiveTimeoutS export class BashTool implements AgentTool { readonly name = "bash"; readonly label = "Bash"; + readonly loadMode = "essential"; readonly description: string; readonly parameters: BashToolSchema; readonly concurrency = "exclusive"; diff --git a/packages/coding-agent/src/tools/browser.ts b/packages/coding-agent/src/tools/browser.ts index 08506afdd..d043ab892 100644 --- a/packages/coding-agent/src/tools/browser.ts +++ b/packages/coding-agent/src/tools/browser.ts @@ -110,6 +110,8 @@ function resolveBrowserKind(params: BrowserParams, session: ToolSession): Browse export class BrowserTool implements AgentTool { readonly name = "browser"; readonly label = "Browser"; + readonly loadMode = "discoverable"; + readonly summary = "Control a headless browser to navigate and interact with web pages"; readonly parameters = browserSchema; readonly strict = true; diff --git a/packages/coding-agent/src/tools/calculator.ts b/packages/coding-agent/src/tools/calculator.ts index 492e13296..2cb5975ad 100644 --- a/packages/coding-agent/src/tools/calculator.ts +++ b/packages/coding-agent/src/tools/calculator.ts @@ -396,6 +396,8 @@ type CalculatorParams = Static; export class CalculatorTool implements AgentTool { readonly name = "calc"; readonly label = "Calc"; + readonly summary = "Evaluate a mathematical expression"; + readonly loadMode = "discoverable"; readonly description: string; readonly parameters = calculatorSchema; readonly strict = true; diff --git a/packages/coding-agent/src/tools/checkpoint.ts b/packages/coding-agent/src/tools/checkpoint.ts index 444079482..6ea27c8df 100644 --- a/packages/coding-agent/src/tools/checkpoint.ts +++ b/packages/coding-agent/src/tools/checkpoint.ts @@ -49,9 +49,11 @@ function isTopLevelSession(session: ToolSession): boolean { export class CheckpointTool implements AgentTool { readonly name = "checkpoint"; readonly label = "Checkpoint"; + readonly summary = "Create a git-based checkpoint to save and restore session state"; readonly description: string; readonly parameters = checkpointSchema; readonly strict = true; + readonly loadMode = "discoverable"; readonly intent = (args: Partial) => (args.goal ? `checkpointing: ${args.goal}` : "checkpointing"); constructor(private readonly session: ToolSession) { @@ -92,9 +94,11 @@ export class CheckpointTool implements AgentTool { readonly name = "rewind"; readonly label = "Rewind"; + readonly summary = "Rewind to a previously created checkpoint"; readonly description: string; readonly parameters = rewindSchema; readonly strict = true; + readonly loadMode = "discoverable"; readonly intent = (): string => "rewinding"; constructor(private readonly session: ToolSession) { diff --git a/packages/coding-agent/src/tools/debug.ts b/packages/coding-agent/src/tools/debug.ts index 73519202f..95a558a10 100644 --- a/packages/coding-agent/src/tools/debug.ts +++ b/packages/coding-agent/src/tools/debug.ts @@ -597,10 +597,12 @@ export const debugToolRenderer = { export class DebugTool implements AgentTool { readonly name = "debug"; readonly label = "Debug"; + readonly summary = "Debug a running process with DAP (debugger adapter protocol)"; readonly description: string; readonly parameters = debugSchema; readonly strict = true; readonly concurrency = "exclusive"; + readonly loadMode = "discoverable"; constructor(private readonly session: ToolSession) { this.description = prompt.render(debugDescription); diff --git a/packages/coding-agent/src/tools/eval.ts b/packages/coding-agent/src/tools/eval.ts index 500f962ea..bc58a539b 100644 --- a/packages/coding-agent/src/tools/eval.ts +++ b/packages/coding-agent/src/tools/eval.ts @@ -207,6 +207,8 @@ async function resolveBackend( export class EvalTool implements AgentTool { readonly name = "eval"; + readonly summary = "Execute Python or JavaScript code in an in-process eval backend"; + readonly loadMode = "discoverable"; readonly label = "Eval"; get description(): string { if (!this.session) return getEvalToolDescription(); diff --git a/packages/coding-agent/src/tools/find.ts b/packages/coding-agent/src/tools/find.ts index 0266b9340..36da5a682 100644 --- a/packages/coding-agent/src/tools/find.ts +++ b/packages/coding-agent/src/tools/find.ts @@ -88,6 +88,8 @@ export interface FindToolOptions { export class FindTool implements AgentTool { readonly name = "find"; + readonly summary = "Find files and directories matching a glob pattern"; + readonly loadMode = "discoverable"; readonly label = "Find"; readonly description: string; readonly parameters = findSchema; diff --git a/packages/coding-agent/src/tools/gh.ts b/packages/coding-agent/src/tools/gh.ts index 1959ed5c7..b7fe1a21e 100644 --- a/packages/coding-agent/src/tools/gh.ts +++ b/packages/coding-agent/src/tools/gh.ts @@ -2087,6 +2087,8 @@ function buildTextResult( export class GithubTool implements AgentTool { readonly name = "github"; + readonly summary = "Interact with GitHub issues, pull requests, and repositories"; + readonly loadMode = "discoverable"; readonly label = "GitHub"; readonly description = prompt.render(githubDescription); readonly parameters = githubSchema; diff --git a/packages/coding-agent/src/tools/hindsight-recall.ts b/packages/coding-agent/src/tools/hindsight-recall.ts index 5f5a60dbb..ca78b0573 100644 --- a/packages/coding-agent/src/tools/hindsight-recall.ts +++ b/packages/coding-agent/src/tools/hindsight-recall.ts @@ -19,6 +19,8 @@ export class HindsightRecallTool implements AgentTool Tool | null | Promise