@oh-my-pi/pi-coding-agent 14.7.0 → 14.7.2
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 +24 -0
- package/package.json +12 -12
- package/src/cli/grep-cli.ts +1 -1
- package/src/config/model-equivalence.ts +1 -0
- package/src/config/model-registry.ts +108 -22
- package/src/config/settings-schema.ts +46 -1
- package/src/config/settings.ts +71 -1
- package/src/dap/client.ts +1 -0
- package/src/discovery/builtin.ts +34 -9
- package/src/discovery/helpers.ts +4 -3
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/hashline.ts +212 -63
- package/src/eval/py/gateway-coordinator.ts +2 -3
- package/src/eval/py/runtime.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/lsp/index.ts +2 -0
- package/src/main.ts +10 -15
- package/src/mcp/discoverable-tool-metadata.ts +24 -202
- package/src/modes/components/extensions/extension-dashboard.ts +26 -2
- package/src/modes/components/extensions/state-manager.ts +41 -0
- package/src/modes/controllers/selector-controller.ts +3 -0
- package/src/modes/interactive-mode.ts +45 -13
- package/src/prompts/system/plan-mode-active.md +7 -3
- package/src/prompts/system/plan-mode-approved.md +5 -0
- package/src/prompts/tools/search-tool-bm25.md +14 -14
- package/src/prompts/tools/todo-write.md +1 -0
- package/src/sdk.ts +69 -8
- package/src/session/agent-session.ts +177 -1
- package/src/slash-commands/builtin-registry.ts +13 -2
- package/src/task/index.ts +2 -0
- package/src/task/isolation-backend.ts +22 -0
- package/src/tool-discovery/tool-index.ts +377 -0
- package/src/tools/ask.ts +2 -0
- package/src/tools/ast-edit.ts +2 -0
- package/src/tools/ast-grep.ts +2 -0
- package/src/tools/bash.ts +1 -0
- package/src/tools/browser.ts +2 -0
- package/src/tools/calculator.ts +2 -0
- package/src/tools/checkpoint.ts +4 -0
- package/src/tools/debug.ts +2 -0
- package/src/tools/eval.ts +2 -0
- package/src/tools/find.ts +2 -0
- package/src/tools/gh.ts +2 -0
- package/src/tools/hindsight-recall.ts +2 -0
- package/src/tools/hindsight-reflect.ts +2 -0
- package/src/tools/hindsight-retain.ts +2 -0
- package/src/tools/index.ts +74 -14
- package/src/tools/inspect-image.ts +2 -0
- package/src/tools/irc.ts +2 -1
- package/src/tools/job.ts +2 -1
- package/src/tools/notebook.ts +2 -0
- package/src/tools/read.ts +7 -1
- package/src/tools/recipe/index.ts +2 -0
- package/src/tools/render-mermaid.ts +2 -0
- package/src/tools/search-tool-bm25.ts +128 -42
- package/src/tools/search.ts +2 -0
- package/src/tools/ssh.ts +2 -0
- package/src/tools/todo-write.ts +2 -1
- package/src/tools/write.ts +2 -0
- package/src/web/search/index.ts +2 -0
- package/src/web/search/providers/searxng.ts +8 -0
package/src/sdk.ts
CHANGED
|
@@ -81,7 +81,6 @@ import {
|
|
|
81
81
|
collectDiscoverableMCPTools,
|
|
82
82
|
formatDiscoverableMCPToolServerSummary,
|
|
83
83
|
selectDiscoverableMCPToolNamesByServer,
|
|
84
|
-
summarizeDiscoverableMCPTools,
|
|
85
84
|
} from "./mcp/discoverable-tool-metadata";
|
|
86
85
|
import { getMemoryRoot } from "./memories";
|
|
87
86
|
import { resolveMemoryBackend } from "./memory-backend";
|
|
@@ -110,9 +109,15 @@ import {
|
|
|
110
109
|
} from "./system-prompt";
|
|
111
110
|
import { AgentOutputManager } from "./task/output-manager";
|
|
112
111
|
import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
|
|
112
|
+
import {
|
|
113
|
+
collectDiscoverableTools,
|
|
114
|
+
type DiscoverableTool,
|
|
115
|
+
summarizeDiscoverableTools,
|
|
116
|
+
} from "./tool-discovery/tool-index";
|
|
113
117
|
import {
|
|
114
118
|
BashTool,
|
|
115
119
|
BUILTIN_TOOLS,
|
|
120
|
+
computeEssentialBuiltinNames,
|
|
116
121
|
createTools,
|
|
117
122
|
discoverStartupLspServers,
|
|
118
123
|
EditTool,
|
|
@@ -995,6 +1000,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
995
1000
|
getDiscoverableMCPSearchIndex: () => session.getDiscoverableMCPSearchIndex(),
|
|
996
1001
|
getSelectedMCPToolNames: () => session.getSelectedMCPToolNames(),
|
|
997
1002
|
activateDiscoveredMCPTools: toolNames => session.activateDiscoveredMCPTools(toolNames),
|
|
1003
|
+
// Generic tool discovery (unified — covers built-in + MCP + extension)
|
|
1004
|
+
isToolDiscoveryEnabled: () => session.isToolDiscoveryEnabled(),
|
|
1005
|
+
getDiscoverableTools: filter => session.getDiscoverableTools(filter),
|
|
1006
|
+
getDiscoverableToolSearchIndex: () => session.getDiscoverableToolSearchIndex(),
|
|
1007
|
+
getSelectedDiscoveredToolNames: () => session.getSelectedDiscoveredToolNames(),
|
|
1008
|
+
activateDiscoveredTools: toolNames => session.activateDiscoveredTools(toolNames),
|
|
998
1009
|
getCheckpointState: () => session.getCheckpointState(),
|
|
999
1010
|
setCheckpointState: state => session.setCheckpointState(state ?? undefined),
|
|
1000
1011
|
getToolChoiceQueue: () => session.toolChoiceQueue,
|
|
@@ -1344,11 +1355,33 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1344
1355
|
): Promise<BuildSystemPromptResult> => {
|
|
1345
1356
|
toolContextStore.setToolNames(toolNames);
|
|
1346
1357
|
const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
|
|
1347
|
-
const
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1358
|
+
const activeToolNames = new Set(toolNames);
|
|
1359
|
+
const discoverableBuiltinTools: DiscoverableTool[] =
|
|
1360
|
+
effectiveDiscoveryMode === "all"
|
|
1361
|
+
? collectDiscoverableTools(
|
|
1362
|
+
Array.from(tools.values()).filter(
|
|
1363
|
+
tool => tool.loadMode === "discoverable" && !activeToolNames.has(tool.name),
|
|
1364
|
+
),
|
|
1365
|
+
{ source: "builtin" },
|
|
1366
|
+
)
|
|
1367
|
+
: [];
|
|
1368
|
+
const discoverableToolsForDesc: DiscoverableTool[] = [
|
|
1369
|
+
...discoverableBuiltinTools,
|
|
1370
|
+
...discoverableMCPTools.map(t => ({
|
|
1371
|
+
name: t.name,
|
|
1372
|
+
label: t.label,
|
|
1373
|
+
summary: t.description,
|
|
1374
|
+
source: "mcp" as const,
|
|
1375
|
+
serverName: t.serverName,
|
|
1376
|
+
mcpToolName: t.mcpToolName,
|
|
1377
|
+
schemaKeys: t.schemaKeys,
|
|
1378
|
+
})),
|
|
1379
|
+
];
|
|
1380
|
+
const discoverableToolSummary = summarizeDiscoverableTools(discoverableToolsForDesc);
|
|
1381
|
+
const hasDiscoverableTools =
|
|
1382
|
+
mcpDiscoveryEnabled && toolNames.includes("search_tool_bm25") && discoverableToolsForDesc.length > 0;
|
|
1350
1383
|
const promptTools = buildSystemPromptToolMetadata(tools, {
|
|
1351
|
-
search_tool_bm25: { description: renderSearchToolBm25Description(
|
|
1384
|
+
search_tool_bm25: { description: renderSearchToolBm25Description(discoverableToolsForDesc) },
|
|
1352
1385
|
});
|
|
1353
1386
|
const memoryInstructions = await resolveMemoryBackend(settings).buildDeveloperInstructions(
|
|
1354
1387
|
agentDir,
|
|
@@ -1386,8 +1419,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1386
1419
|
appendSystemPrompt: appendPrompt,
|
|
1387
1420
|
repeatToolDescriptions,
|
|
1388
1421
|
intentField,
|
|
1389
|
-
mcpDiscoveryMode:
|
|
1390
|
-
mcpDiscoveryServerSummaries:
|
|
1422
|
+
mcpDiscoveryMode: hasDiscoverableTools,
|
|
1423
|
+
mcpDiscoveryServerSummaries: discoverableToolSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1391
1424
|
eagerTasks,
|
|
1392
1425
|
secretsEnabled,
|
|
1393
1426
|
agentsMdSearch: agentsMdSearchPromise,
|
|
@@ -1411,7 +1444,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1411
1444
|
toolNamesFromRegistry;
|
|
1412
1445
|
const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
|
|
1413
1446
|
const includeExitPlanMode = requestedToolNames.includes("exit_plan_mode");
|
|
1414
|
-
|
|
1447
|
+
// Effective discovery mode: tools.discoveryMode takes precedence; mcp.discoveryMode is back-compat alias.
|
|
1448
|
+
const toolsDiscoveryModeSetting = settings.get("tools.discoveryMode");
|
|
1449
|
+
const effectiveDiscoveryMode: "off" | "mcp-only" | "all" =
|
|
1450
|
+
toolsDiscoveryModeSetting !== "off"
|
|
1451
|
+
? (toolsDiscoveryModeSetting as "off" | "mcp-only" | "all")
|
|
1452
|
+
: settings.get("mcp.discoveryMode")
|
|
1453
|
+
? "mcp-only"
|
|
1454
|
+
: "off";
|
|
1455
|
+
const mcpDiscoveryEnabled = effectiveDiscoveryMode !== "off"; // back-compat: true when any discovery active
|
|
1415
1456
|
const defaultInactiveToolNames = new Set(
|
|
1416
1457
|
registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
|
|
1417
1458
|
);
|
|
@@ -1468,6 +1509,26 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1468
1509
|
}
|
|
1469
1510
|
}
|
|
1470
1511
|
|
|
1512
|
+
// When tools.discoveryMode === "all", hide non-essential built-in discoverable tools
|
|
1513
|
+
// from the initial set unless they were explicitly requested or restored from persistence.
|
|
1514
|
+
// The model finds them via search_tool_bm25 and activates them on demand.
|
|
1515
|
+
if (effectiveDiscoveryMode === "all") {
|
|
1516
|
+
const essentialBuiltinNames = new Set(computeEssentialBuiltinNames(settings));
|
|
1517
|
+
const explicitlyRequestedToolNames = new Set(options.toolNames?.map(name => name.toLowerCase()) ?? []);
|
|
1518
|
+
// Back-compat: persisted activations live under selectedMCPToolNames today (built-in
|
|
1519
|
+
// activation persistence is a follow-up). MCP names won't collide with built-in names.
|
|
1520
|
+
const restoredDiscoveredNames = new Set(existingSession.selectedMCPToolNames);
|
|
1521
|
+
initialToolNames = initialToolNames.filter(name => {
|
|
1522
|
+
const tool = toolRegistry.get(name);
|
|
1523
|
+
if (!tool?.loadMode) return true; // not a built-in — leave MCP/custom/extension to existing logic
|
|
1524
|
+
if (tool.loadMode === "essential") return true;
|
|
1525
|
+
if (essentialBuiltinNames.has(name)) return true;
|
|
1526
|
+
if (explicitlyRequestedToolNames.has(name)) return true;
|
|
1527
|
+
if (restoredDiscoveredNames.has(name)) return true;
|
|
1528
|
+
return false;
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1471
1532
|
const { systemPrompt } = await logger.time(
|
|
1472
1533
|
"buildSystemPrompt",
|
|
1473
1534
|
rebuildSystemPrompt,
|
|
@@ -129,6 +129,12 @@ import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { t
|
|
|
129
129
|
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
130
130
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
131
131
|
import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
|
|
132
|
+
import {
|
|
133
|
+
buildDiscoverableToolSearchIndex,
|
|
134
|
+
collectDiscoverableTools,
|
|
135
|
+
type DiscoverableTool,
|
|
136
|
+
type DiscoverableToolSearchIndex,
|
|
137
|
+
} from "../tool-discovery/tool-index";
|
|
132
138
|
import { assertEditableFile } from "../tools/auto-generated-guard";
|
|
133
139
|
import type { CheckpointState } from "../tools/checkpoint";
|
|
134
140
|
import { outputMeta } from "../tools/output-meta";
|
|
@@ -536,6 +542,9 @@ export class AgentSession {
|
|
|
536
542
|
#discoverableMCPTools = new Map<string, DiscoverableMCPTool>();
|
|
537
543
|
#discoverableMCPSearchIndex: DiscoverableMCPSearchIndex | null = null;
|
|
538
544
|
#selectedMCPToolNames = new Set<string>();
|
|
545
|
+
// Generic tool discovery (covers built-in + MCP + extension when tools.discoveryMode === "all")
|
|
546
|
+
#discoverableToolSearchIndex: DiscoverableToolSearchIndex | null = null;
|
|
547
|
+
#selectedDiscoveredToolNames = new Set<string>();
|
|
539
548
|
#rpcHostToolNames = new Set<string>();
|
|
540
549
|
#defaultSelectedMCPServerNames = new Set<string>();
|
|
541
550
|
#defaultSelectedMCPToolNames = new Set<string>();
|
|
@@ -2101,7 +2110,15 @@ export class AgentSession {
|
|
|
2101
2110
|
|
|
2102
2111
|
#setDiscoverableMCPTools(discoverableMCPTools: Map<string, DiscoverableMCPTool>): void {
|
|
2103
2112
|
this.#discoverableMCPTools = discoverableMCPTools;
|
|
2113
|
+
this.#invalidateDiscoveryCaches();
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/** Single point for invalidating cached discovery indices. Call after any change that can
|
|
2117
|
+
* affect which tools should be discoverable: registry mutations (refreshMCPTools,
|
|
2118
|
+
* refreshRpcHostTools) or active-tool mutations (#applyActiveToolsByName). */
|
|
2119
|
+
#invalidateDiscoveryCaches(): void {
|
|
2104
2120
|
this.#discoverableMCPSearchIndex = null;
|
|
2121
|
+
this.#discoverableToolSearchIndex = null;
|
|
2105
2122
|
}
|
|
2106
2123
|
|
|
2107
2124
|
#filterSelectableMCPToolNames(toolNames: Iterable<string>): string[] {
|
|
@@ -2204,10 +2221,21 @@ export class AgentSession {
|
|
|
2204
2221
|
return this.#mcpDiscoveryEnabled;
|
|
2205
2222
|
}
|
|
2206
2223
|
|
|
2224
|
+
/** @deprecated Use {@link getDiscoverableTools} with `{ source: "mcp" }` instead.
|
|
2225
|
+
* Preserves the legacy `description`-bearing MCP shape for back-compat callers. */
|
|
2207
2226
|
getDiscoverableMCPTools(): DiscoverableMCPTool[] {
|
|
2208
|
-
return Array.from(this.#discoverableMCPTools.values())
|
|
2227
|
+
return Array.from(this.#discoverableMCPTools.values()).map(t => ({
|
|
2228
|
+
name: t.name,
|
|
2229
|
+
label: t.label,
|
|
2230
|
+
description: t.description,
|
|
2231
|
+
serverName: t.serverName,
|
|
2232
|
+
mcpToolName: t.mcpToolName,
|
|
2233
|
+
schemaKeys: t.schemaKeys,
|
|
2234
|
+
}));
|
|
2209
2235
|
}
|
|
2210
2236
|
|
|
2237
|
+
/** @deprecated Use {@link getDiscoverableToolSearchIndex} instead.
|
|
2238
|
+
* Returns the legacy MCP search index whose documents expose `tool.description`. */
|
|
2211
2239
|
getDiscoverableMCPSearchIndex(): DiscoverableMCPSearchIndex {
|
|
2212
2240
|
if (!this.#discoverableMCPSearchIndex) {
|
|
2213
2241
|
this.#discoverableMCPSearchIndex = buildDiscoverableMCPSearchIndex(this.#discoverableMCPTools.values());
|
|
@@ -2243,6 +2271,113 @@ export class AgentSession {
|
|
|
2243
2271
|
return [...new Set(activated)];
|
|
2244
2272
|
}
|
|
2245
2273
|
|
|
2274
|
+
// ── Generic tool discovery (covers built-in + MCP + extension) ────────────
|
|
2275
|
+
|
|
2276
|
+
/** Resolve effective discovery mode: tools.discoveryMode wins; mcp.discoveryMode is back-compat alias. */
|
|
2277
|
+
#resolveEffectiveDiscoveryMode(): "off" | "mcp-only" | "all" {
|
|
2278
|
+
const toolsMode = this.settings.get("tools.discoveryMode");
|
|
2279
|
+
if (toolsMode !== "off") return toolsMode as "off" | "mcp-only" | "all";
|
|
2280
|
+
if (this.settings.get("mcp.discoveryMode")) return "mcp-only";
|
|
2281
|
+
return "off";
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
isToolDiscoveryEnabled(): boolean {
|
|
2285
|
+
return this.#resolveEffectiveDiscoveryMode() !== "off";
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
getDiscoverableTools(filter?: { source?: DiscoverableTool["source"] }): DiscoverableTool[] {
|
|
2289
|
+
// For "all" mode we combine built-in registry entries + MCP tools.
|
|
2290
|
+
// For "mcp-only" mode we only return MCP tools.
|
|
2291
|
+
const mode = this.#resolveEffectiveDiscoveryMode();
|
|
2292
|
+
const activeNames = new Set(this.getActiveToolNames());
|
|
2293
|
+
const mcpTools: DiscoverableTool[] = Array.from(this.#discoverableMCPTools.values())
|
|
2294
|
+
.filter(t => !activeNames.has(t.name))
|
|
2295
|
+
.map(t => ({
|
|
2296
|
+
name: t.name,
|
|
2297
|
+
label: t.label,
|
|
2298
|
+
summary: t.description,
|
|
2299
|
+
source: "mcp" as const,
|
|
2300
|
+
serverName: t.serverName,
|
|
2301
|
+
mcpToolName: t.mcpToolName,
|
|
2302
|
+
schemaKeys: t.schemaKeys,
|
|
2303
|
+
}));
|
|
2304
|
+
const builtinTools: DiscoverableTool[] = mode === "all" ? this.#collectDiscoverableBuiltinTools() : [];
|
|
2305
|
+
const allTools = [...builtinTools, ...mcpTools];
|
|
2306
|
+
return filter?.source ? allTools.filter(t => t.source === filter.source) : allTools;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/** Collect built-in tools the model can discover via search_tool_bm25. Restricted to tool
|
|
2310
|
+
* definitions whose `loadMode === "discoverable"`. This keeps hidden/internal tools
|
|
2311
|
+
* (resolve, yield, exit_plan_mode, report_finding, report_tool_issue) out of the index
|
|
2312
|
+
* and avoids mislabeling extension/custom default-inactive tools as built-ins. */
|
|
2313
|
+
#collectDiscoverableBuiltinTools(): DiscoverableTool[] {
|
|
2314
|
+
const activeNames = new Set(this.getActiveToolNames());
|
|
2315
|
+
const result: DiscoverableTool[] = [];
|
|
2316
|
+
for (const tool of this.#toolRegistry.values()) {
|
|
2317
|
+
if (tool.loadMode !== "discoverable") continue;
|
|
2318
|
+
if (activeNames.has(tool.name)) continue;
|
|
2319
|
+
const collected = collectDiscoverableTools([tool], { source: "builtin" });
|
|
2320
|
+
result.push(...collected);
|
|
2321
|
+
}
|
|
2322
|
+
return result;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
getDiscoverableToolSearchIndex(): DiscoverableToolSearchIndex {
|
|
2326
|
+
if (!this.#discoverableToolSearchIndex) {
|
|
2327
|
+
this.#discoverableToolSearchIndex = buildDiscoverableToolSearchIndex(this.getDiscoverableTools());
|
|
2328
|
+
}
|
|
2329
|
+
return this.#discoverableToolSearchIndex;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
/** Invalidate the generic search index cache (call after tool set changes).
|
|
2333
|
+
* Delegates to {@link #invalidateDiscoveryCaches} so all discovery-related caches stay in sync. */
|
|
2334
|
+
#invalidateDiscoverableToolSearchIndex(): void {
|
|
2335
|
+
this.#invalidateDiscoveryCaches();
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
getSelectedDiscoveredToolNames(): string[] {
|
|
2339
|
+
// Union of MCP-selected and generic non-MCP selected. Non-MCP selections are only
|
|
2340
|
+
// selected while they are still active; otherwise BM25 must be able to rediscover them.
|
|
2341
|
+
const activeNames = new Set(this.getActiveToolNames());
|
|
2342
|
+
const mcpSelected = this.getSelectedMCPToolNames();
|
|
2343
|
+
const nonMcpSelected = Array.from(this.#selectedDiscoveredToolNames).filter(
|
|
2344
|
+
name => activeNames.has(name) && this.#toolRegistry.has(name) && !isMCPToolName(name),
|
|
2345
|
+
);
|
|
2346
|
+
return [...new Set([...mcpSelected, ...nonMcpSelected])];
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
async activateDiscoveredTools(toolNames: string[]): Promise<string[]> {
|
|
2350
|
+
const mcpNames = toolNames.filter(isMCPToolName);
|
|
2351
|
+
const nonMcpNames = toolNames.filter(name => !isMCPToolName(name));
|
|
2352
|
+
const activated: string[] = [];
|
|
2353
|
+
|
|
2354
|
+
// Activate MCP tools via existing path
|
|
2355
|
+
if (mcpNames.length > 0) {
|
|
2356
|
+
const activatedMcp = await this.activateDiscoveredMCPTools(mcpNames);
|
|
2357
|
+
activated.push(...activatedMcp);
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// Activate non-MCP tools (built-ins that are in the registry but not currently active)
|
|
2361
|
+
if (nonMcpNames.length > 0) {
|
|
2362
|
+
const currentActiveNames = new Set(this.getActiveToolNames());
|
|
2363
|
+
const newlyAdded: string[] = [];
|
|
2364
|
+
for (const name of nonMcpNames) {
|
|
2365
|
+
if (this.#toolRegistry.has(name) && !currentActiveNames.has(name)) {
|
|
2366
|
+
newlyAdded.push(name);
|
|
2367
|
+
this.#selectedDiscoveredToolNames.add(name);
|
|
2368
|
+
activated.push(name);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (newlyAdded.length > 0) {
|
|
2372
|
+
const nextActive = [...this.getActiveToolNames(), ...newlyAdded];
|
|
2373
|
+
await this.setActiveToolsByName(nextActive);
|
|
2374
|
+
this.#invalidateDiscoverableToolSearchIndex();
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
return [...new Set(activated)];
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2246
2381
|
async #applyActiveToolsByName(
|
|
2247
2382
|
toolNames: string[],
|
|
2248
2383
|
options?: { persistMCPSelection?: boolean; previousSelectedMCPToolNames?: string[] },
|
|
@@ -2273,8 +2408,18 @@ export class AgentSession {
|
|
|
2273
2408
|
),
|
|
2274
2409
|
);
|
|
2275
2410
|
}
|
|
2411
|
+
const activeNameSet = new Set(validToolNames);
|
|
2412
|
+
for (const name of Array.from(this.#selectedDiscoveredToolNames)) {
|
|
2413
|
+
if (!activeNameSet.has(name) || isMCPToolName(name) || !this.#toolRegistry.has(name)) {
|
|
2414
|
+
this.#selectedDiscoveredToolNames.delete(name);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2276
2417
|
this.agent.setTools(tools);
|
|
2277
2418
|
|
|
2419
|
+
// Active tool set changed → discoverable tool list (which excludes already-active tools)
|
|
2420
|
+
// is now stale. Invalidate before any prompt-template hook reads the discovery list.
|
|
2421
|
+
this.#invalidateDiscoveryCaches();
|
|
2422
|
+
|
|
2278
2423
|
// Rebuild base system prompt with new tool set, but only when the tool set
|
|
2279
2424
|
// actually changed. MCP servers can reconnect at arbitrary times and call
|
|
2280
2425
|
// `refreshMCPTools` -> `#applyActiveToolsByName` even though the resulting
|
|
@@ -2509,6 +2654,11 @@ export class AgentSession {
|
|
|
2509
2654
|
this.#rpcHostToolNames.add(finalTool.name);
|
|
2510
2655
|
}
|
|
2511
2656
|
|
|
2657
|
+
// Registry contents changed — invalidate discovery caches so the next BM25 lookup sees
|
|
2658
|
+
// the new RPC-host tool set. (#applyActiveToolsByName below also invalidates, but doing
|
|
2659
|
+
// it here too keeps the contract local to "registry mutated".)
|
|
2660
|
+
this.#invalidateDiscoveryCaches();
|
|
2661
|
+
|
|
2512
2662
|
const activeNonRpcToolNames = previousActiveToolNames.filter(name => !previousRpcHostToolNames.has(name));
|
|
2513
2663
|
const preservedRpcToolNames = previousActiveToolNames.filter(
|
|
2514
2664
|
name => previousRpcHostToolNames.has(name) && this.#rpcHostToolNames.has(name),
|
|
@@ -5983,6 +6133,32 @@ export class AgentSession {
|
|
|
5983
6133
|
setAutoRetryEnabled(enabled: boolean): void {
|
|
5984
6134
|
this.settings.set("retry.enabled", enabled);
|
|
5985
6135
|
}
|
|
6136
|
+
/**
|
|
6137
|
+
* Manually retry the last failed assistant turn.
|
|
6138
|
+
* Removes the error message from agent state and re-attempts with a fresh retry budget.
|
|
6139
|
+
* @returns true if retry was initiated, false if no failed turn to retry or agent is busy
|
|
6140
|
+
*/
|
|
6141
|
+
async retry(): Promise<boolean> {
|
|
6142
|
+
if (this.isStreaming || this.isCompacting || this.isRetrying) return false;
|
|
6143
|
+
|
|
6144
|
+
const messages = this.agent.state.messages;
|
|
6145
|
+
const lastMsg = messages[messages.length - 1];
|
|
6146
|
+
if (lastMsg?.role !== "assistant") return false;
|
|
6147
|
+
|
|
6148
|
+
const assistantMsg = lastMsg as AssistantMessage;
|
|
6149
|
+
if (assistantMsg.stopReason !== "error" && assistantMsg.stopReason !== "aborted") return false;
|
|
6150
|
+
|
|
6151
|
+
// Remove the failed/aborted assistant message (same as auto-retry does before re-attempting)
|
|
6152
|
+
this.agent.replaceMessages(messages.slice(0, -1));
|
|
6153
|
+
|
|
6154
|
+
// Reset retry budget for a fresh attempt
|
|
6155
|
+
this.#retryAttempt = 0;
|
|
6156
|
+
|
|
6157
|
+
// Re-attempt the turn
|
|
6158
|
+
this.#scheduleAgentContinue({ delayMs: 1 });
|
|
6159
|
+
|
|
6160
|
+
return true;
|
|
6161
|
+
}
|
|
5986
6162
|
|
|
5987
6163
|
// =========================================================================
|
|
5988
6164
|
// Bash Execution
|
|
@@ -57,8 +57,8 @@ interface BuiltinSlashCommandSpec extends BuiltinSlashCommand {
|
|
|
57
57
|
handle: (
|
|
58
58
|
command: ParsedBuiltinSlashCommand,
|
|
59
59
|
runtime: BuiltinSlashCommandRuntime,
|
|
60
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: void needed so handlers returning nothing are assignable
|
|
61
|
-
) => Promise<string |
|
|
60
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: void needed so async handlers returning nothing are assignable
|
|
61
|
+
) => Promise<string | void> | string | void;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export interface BuiltinSlashCommandRuntime {
|
|
@@ -572,6 +572,17 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
572
572
|
await runtime.ctx.handleBtwCommand(question);
|
|
573
573
|
},
|
|
574
574
|
},
|
|
575
|
+
{
|
|
576
|
+
name: "retry",
|
|
577
|
+
description: "Retry the last failed agent turn",
|
|
578
|
+
handle: async (_command, runtime) => {
|
|
579
|
+
const didRetry = await runtime.ctx.session.retry();
|
|
580
|
+
if (!didRetry) {
|
|
581
|
+
runtime.ctx.showStatus("Nothing to retry");
|
|
582
|
+
}
|
|
583
|
+
runtime.ctx.editor.setText("");
|
|
584
|
+
},
|
|
585
|
+
},
|
|
575
586
|
{
|
|
576
587
|
name: "background",
|
|
577
588
|
aliases: ["bg"],
|
package/src/task/index.ts
CHANGED
|
@@ -195,7 +195,9 @@ function validateTaskModeParams(simpleMode: TaskSimpleMode, params: TaskParams):
|
|
|
195
195
|
export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
196
196
|
readonly name = "task";
|
|
197
197
|
readonly label = "Task";
|
|
198
|
+
readonly summary = "Spawn a subagent to complete a parallel task";
|
|
198
199
|
readonly strict = true;
|
|
200
|
+
readonly loadMode = "discoverable";
|
|
199
201
|
readonly renderResult = renderResult;
|
|
200
202
|
readonly #discoveredAgents: AgentDefinition[];
|
|
201
203
|
readonly #blockedAgent: string | undefined;
|
|
@@ -9,11 +9,26 @@ export interface IsolationBackendResolution {
|
|
|
9
9
|
warning: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
type ProcessorEnv = Partial<Pick<NodeJS.ProcessEnv, "PROCESSOR_ARCHITECTURE" | "PROCESSOR_ARCHITEW6432">>;
|
|
13
|
+
|
|
14
|
+
function isWindowsArm64HostUnderX64Emulation(
|
|
15
|
+
platform: NodeJS.Platform,
|
|
16
|
+
arch: NodeJS.Architecture,
|
|
17
|
+
env: ProcessorEnv,
|
|
18
|
+
): boolean {
|
|
19
|
+
if (platform !== "win32" || arch !== "x64") return false;
|
|
20
|
+
return (
|
|
21
|
+
env.PROCESSOR_ARCHITECTURE?.toUpperCase() === "ARM64" || env.PROCESSOR_ARCHITEW6432?.toUpperCase() === "ARM64"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
export async function resolveIsolationBackendForTaskExecution(
|
|
13
26
|
requestedMode: TaskIsolationMode,
|
|
14
27
|
isIsolated: boolean,
|
|
15
28
|
repoRoot: string | null,
|
|
16
29
|
platform: NodeJS.Platform = process.platform,
|
|
30
|
+
arch: NodeJS.Architecture = process.arch,
|
|
31
|
+
env: ProcessorEnv = process.env as ProcessorEnv,
|
|
17
32
|
): Promise<IsolationBackendResolution> {
|
|
18
33
|
let effectiveIsolationMode = requestedMode;
|
|
19
34
|
let warning = "";
|
|
@@ -39,6 +54,13 @@ export async function resolveIsolationBackendForTaskExecution(
|
|
|
39
54
|
return { effectiveIsolationMode, warning };
|
|
40
55
|
}
|
|
41
56
|
|
|
57
|
+
if (isWindowsArm64HostUnderX64Emulation(platform, arch, env)) {
|
|
58
|
+
effectiveIsolationMode = "worktree";
|
|
59
|
+
warning =
|
|
60
|
+
"<system-notification>ProjFS isolation is disabled on Windows ARM64 x64 emulation. Falling back to worktree isolation.</system-notification>";
|
|
61
|
+
return { effectiveIsolationMode, warning };
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
const probe = projfsOverlayProbe();
|
|
43
65
|
if (!probe.available) {
|
|
44
66
|
effectiveIsolationMode = "worktree";
|