@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.13
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 +57 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-d8xh7keh.md} +57 -0
- package/dist/cli.js +3069 -3047
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +4 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +24 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +5 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +139 -8
- package/src/session/agent-session-types.ts +2 -0
- package/src/session/agent-session.ts +66 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +321 -184
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import type { Agent, AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import { isRecord, logger, prompt, stringProperty } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { isRecord, logger, prompt, stringProperty, untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { reset as resetCapabilities } from "../capability";
|
|
5
6
|
import type { ModelRegistry } from "../config/model-registry";
|
|
6
7
|
import { formatModelString } from "../config/model-resolver";
|
|
@@ -71,6 +72,8 @@ interface SessionToolsOptions {
|
|
|
71
72
|
createInspectImageTool?: () => Promise<AgentTool | null>;
|
|
72
73
|
builtInToolNames?: Iterable<string>;
|
|
73
74
|
presentationPinnedToolNames?: ReadonlySet<string>;
|
|
75
|
+
/** MCP tool names whose current registry entries came from the manager snapshot. */
|
|
76
|
+
mcpManagerToolNames?: Iterable<string>;
|
|
74
77
|
ensureWriteRegistered?: () => Promise<boolean>;
|
|
75
78
|
rebuildSystemPrompt?: (
|
|
76
79
|
toolNames: string[],
|
|
@@ -185,6 +188,8 @@ export class SessionTools {
|
|
|
185
188
|
#installedVibeToolNames = new Set<string>();
|
|
186
189
|
#builtInToolNames: Set<string>;
|
|
187
190
|
#rpcHostToolNames = new Set<string>();
|
|
191
|
+
#mcpManagerToolNames = new Set<string>();
|
|
192
|
+
#extensionMcpTools = new Map<string, AgentTool>();
|
|
188
193
|
#xdev: XdevState | undefined;
|
|
189
194
|
#pendingXdevMountDelta: { added: Set<string>; removed: Set<string> } | undefined;
|
|
190
195
|
/**
|
|
@@ -216,7 +221,8 @@ export class SessionTools {
|
|
|
216
221
|
* prompt carries no catalog (no mounts, or a custom prompt that omits the section).
|
|
217
222
|
*/
|
|
218
223
|
#basePromptXdevNames: ReadonlySet<string> = new Set();
|
|
219
|
-
#
|
|
224
|
+
#toolRegistryMutationScope = new AsyncLocalStorage<boolean>();
|
|
225
|
+
#toolRegistryMutationTail: Promise<void> = Promise.resolve();
|
|
220
226
|
#promptModelKey: string | undefined;
|
|
221
227
|
#rebuildSystemPrompt: SessionToolsOptions["rebuildSystemPrompt"];
|
|
222
228
|
#getLocalCalendarDate: () => string;
|
|
@@ -237,6 +243,17 @@ export class SessionTools {
|
|
|
237
243
|
this.#createComputerTool = options.createComputerTool;
|
|
238
244
|
this.#createInspectImageTool = options.createInspectImageTool;
|
|
239
245
|
this.#builtInToolNames = new Set(options.builtInToolNames ?? []);
|
|
246
|
+
this.#mcpManagerToolNames = new Set(options.mcpManagerToolNames ?? []);
|
|
247
|
+
if (options.mcpManagerToolNames === undefined) {
|
|
248
|
+
for (const name of this.#toolRegistry.keys()) {
|
|
249
|
+
if (isMCPToolName(name)) this.#mcpManagerToolNames.add(name);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const [name, tool] of this.#toolRegistry) {
|
|
253
|
+
if (isMCPToolName(name) && !this.#mcpManagerToolNames.has(name)) {
|
|
254
|
+
this.#extensionMcpTools.set(name, tool);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
240
257
|
this.#presentationPinnedToolNames = options.presentationPinnedToolNames;
|
|
241
258
|
this.#ensureWriteRegistered = options.ensureWriteRegistered;
|
|
242
259
|
this.#rebuildSystemPrompt = options.rebuildSystemPrompt;
|
|
@@ -362,6 +379,65 @@ export class SessionTools {
|
|
|
362
379
|
return this.#builtInToolNames.has(name);
|
|
363
380
|
}
|
|
364
381
|
|
|
382
|
+
/** Updates source provenance when a live registry entry is replaced or restored. */
|
|
383
|
+
setToolBuiltIn(name: string, builtIn: boolean): void {
|
|
384
|
+
if (builtIn) {
|
|
385
|
+
this.#builtInToolNames.add(name);
|
|
386
|
+
} else {
|
|
387
|
+
this.#builtInToolNames.delete(name);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Whether the live registry entry is owned by the RPC host. */
|
|
392
|
+
hasRpcHostTool(name: string): boolean {
|
|
393
|
+
return this.#rpcHostToolNames.has(name);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Whether the current MCP entry came from the manager snapshot. */
|
|
397
|
+
hasMCPManagerTool(name: string): boolean {
|
|
398
|
+
return this.#mcpManagerToolNames.has(name);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Restores manager ownership after a lifecycle registration rollback. */
|
|
402
|
+
setMCPManagerTool(name: string, managerOwned: boolean): void {
|
|
403
|
+
if (managerOwned) {
|
|
404
|
+
this.#mcpManagerToolNames.add(name);
|
|
405
|
+
} else {
|
|
406
|
+
this.#mcpManagerToolNames.delete(name);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Current extension-owned MCP entry retained across manager refreshes. */
|
|
411
|
+
getExtensionMCPTool(name: string): AgentTool | undefined {
|
|
412
|
+
return this.#extensionMcpTools.get(name);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Updates extension ownership when a lifecycle registration commits or rolls back. */
|
|
416
|
+
setExtensionMCPTool(name: string, tool: AgentTool | undefined): void {
|
|
417
|
+
if (!isMCPToolName(name)) return;
|
|
418
|
+
if (tool) {
|
|
419
|
+
this.#extensionMcpTools.set(name, tool);
|
|
420
|
+
this.#mcpManagerToolNames.delete(name);
|
|
421
|
+
} else {
|
|
422
|
+
this.#extensionMcpTools.delete(name);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Serializes every registry and presentation mutation for this session. */
|
|
427
|
+
runToolRegistryMutation<T>(mutation: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
428
|
+
if (this.#toolRegistryMutationScope.getStore()) return untilAborted(signal, mutation);
|
|
429
|
+
const serialized = this.#toolRegistryMutationTail.then(() => {
|
|
430
|
+
signal?.throwIfAborted();
|
|
431
|
+
return this.#toolRegistryMutationScope.run(true, mutation);
|
|
432
|
+
});
|
|
433
|
+
const operation = untilAborted(signal, serialized);
|
|
434
|
+
this.#toolRegistryMutationTail = serialized.then(
|
|
435
|
+
() => undefined,
|
|
436
|
+
() => undefined,
|
|
437
|
+
);
|
|
438
|
+
return operation;
|
|
439
|
+
}
|
|
440
|
+
|
|
365
441
|
/** Names of every registered tool. */
|
|
366
442
|
getAllToolNames(): string[] {
|
|
367
443
|
return Array.from(this.#toolRegistry.keys());
|
|
@@ -401,40 +477,46 @@ export class SessionTools {
|
|
|
401
477
|
}
|
|
402
478
|
|
|
403
479
|
/** Installs and activates the ephemeral vibe tool set. */
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
480
|
+
activateVibeTools(baseToolNames: string[]): Promise<void> {
|
|
481
|
+
return this.runToolRegistryMutation(async () => {
|
|
482
|
+
const createVibeTools = this.#createVibeTools;
|
|
483
|
+
if (!createVibeTools) {
|
|
484
|
+
throw new Error("Vibe tools are unavailable in this session.");
|
|
485
|
+
}
|
|
409
486
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
487
|
+
const tools = createVibeTools();
|
|
488
|
+
const vibeToolNames = tools.map(tool => tool.name);
|
|
489
|
+
if (new Set(vibeToolNames).size !== vibeToolNames.length) {
|
|
490
|
+
throw new Error("Vibe tool names must be unique.");
|
|
491
|
+
}
|
|
415
492
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
493
|
+
for (const tool of tools) {
|
|
494
|
+
if (this.#toolRegistry.has(tool.name)) continue;
|
|
495
|
+
this.#toolRegistry.set(tool.name, this.#wrapRuntimeTool(tool));
|
|
496
|
+
this.#builtInToolNames.add(tool.name);
|
|
497
|
+
this.#installedVibeToolNames.add(tool.name);
|
|
498
|
+
}
|
|
422
499
|
|
|
423
|
-
|
|
500
|
+
await this.#applyActiveToolsByName([...new Set([...baseToolNames, ...vibeToolNames])]);
|
|
501
|
+
});
|
|
424
502
|
}
|
|
425
503
|
|
|
426
504
|
/** Uninstalls vibe tools and activates the replacement set. */
|
|
427
|
-
|
|
428
|
-
this
|
|
429
|
-
|
|
505
|
+
deactivateVibeTools(nextToolNames: string[]): Promise<void> {
|
|
506
|
+
return this.runToolRegistryMutation(async () => {
|
|
507
|
+
this.#uninstallVibeTools();
|
|
508
|
+
await this.#applyActiveToolsByName(nextToolNames);
|
|
509
|
+
});
|
|
430
510
|
}
|
|
431
511
|
|
|
432
512
|
/** Removes vibe tools without restoring a source-session snapshot. */
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
513
|
+
removeVibeToolsPreservingActive(): Promise<void> {
|
|
514
|
+
return this.runToolRegistryMutation(async () => {
|
|
515
|
+
const removed = new Set(this.#installedVibeToolNames);
|
|
516
|
+
this.#uninstallVibeTools();
|
|
517
|
+
const nextActive = this.getActiveToolNames().filter(name => !removed.has(name));
|
|
518
|
+
await this.#applyActiveToolsByName(nextActive);
|
|
519
|
+
});
|
|
438
520
|
}
|
|
439
521
|
|
|
440
522
|
#uninstallVibeTools(): void {
|
|
@@ -633,11 +715,20 @@ export class SessionTools {
|
|
|
633
715
|
}
|
|
634
716
|
|
|
635
717
|
/** Applies an enabled tool set and reconciles its `xd://` partition. */
|
|
636
|
-
|
|
718
|
+
applyActiveToolsByName(toolNames: string[], forcePromptRefresh = false, signal?: AbortSignal): Promise<void> {
|
|
719
|
+
return this.runToolRegistryMutation(
|
|
720
|
+
() => this.#applyActiveToolsByName(toolNames, forcePromptRefresh, signal),
|
|
721
|
+
signal,
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async #applyActiveToolsByName(toolNames: string[], forcePromptRefresh = false, signal?: AbortSignal): Promise<void> {
|
|
726
|
+
signal?.throwIfAborted();
|
|
637
727
|
toolNames = normalizeToolNames(toolNames);
|
|
638
728
|
let builtInWriteAvailable = this.#builtInToolNames.has("write");
|
|
639
729
|
if (toolNames.includes("write") && !builtInWriteAvailable) {
|
|
640
|
-
|
|
730
|
+
const writeRegistration = this.#ensureWriteRegistered?.();
|
|
731
|
+
builtInWriteAvailable = writeRegistration ? (await untilAborted(signal, writeRegistration)) === true : false;
|
|
641
732
|
if (builtInWriteAvailable) this.#builtInToolNames.add("write");
|
|
642
733
|
}
|
|
643
734
|
const selectedTools = toolNames.flatMap(name => {
|
|
@@ -669,7 +760,8 @@ export class SessionTools {
|
|
|
669
760
|
const activeDeferrableTool = tools.some(tool => tool.deferrable === true);
|
|
670
761
|
const transportNeeded = mountNames.size > 0 || activeDeferrableTool || this.#host.planModeEnabled();
|
|
671
762
|
if (transportNeeded && !builtInWriteAvailable) {
|
|
672
|
-
|
|
763
|
+
const writeRegistration = this.#ensureWriteRegistered?.();
|
|
764
|
+
builtInWriteAvailable = writeRegistration ? (await untilAborted(signal, writeRegistration)) === true : false;
|
|
673
765
|
if (builtInWriteAvailable) this.#builtInToolNames.add("write");
|
|
674
766
|
}
|
|
675
767
|
if (transportNeeded && builtInWriteAvailable) {
|
|
@@ -699,13 +791,14 @@ export class SessionTools {
|
|
|
699
791
|
try {
|
|
700
792
|
if (this.#rebuildSystemPrompt) {
|
|
701
793
|
const signature = this.#computeAppliedToolSignature(validToolNames, tools);
|
|
702
|
-
if (signature !== this.#lastAppliedToolSignature) {
|
|
703
|
-
const built = await this.#rebuildSystemPrompt(validToolNames, this.#toolRegistry);
|
|
794
|
+
if (forcePromptRefresh || signature !== this.#lastAppliedToolSignature) {
|
|
795
|
+
const built = await untilAborted(signal, this.#rebuildSystemPrompt(validToolNames, this.#toolRegistry));
|
|
704
796
|
rebuiltSystemPrompt = built.systemPrompt;
|
|
705
797
|
rebuiltSignature = signature;
|
|
706
798
|
rebuiltXdevCatalogNames = built.xdevCatalogNames;
|
|
707
799
|
}
|
|
708
800
|
}
|
|
801
|
+
signal?.throwIfAborted();
|
|
709
802
|
} catch (error) {
|
|
710
803
|
this.#setMountedNames(previousMounted);
|
|
711
804
|
this.#setActiveToolNames?.(previousActiveToolNames);
|
|
@@ -917,15 +1010,17 @@ export class SessionTools {
|
|
|
917
1010
|
}
|
|
918
1011
|
|
|
919
1012
|
/** Selects enabled tools, ignoring names absent from the registry. */
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1013
|
+
setActiveToolsByName(toolNames: string[]): Promise<void> {
|
|
1014
|
+
return this.runToolRegistryMutation(async () => {
|
|
1015
|
+
const normalized = normalizeToolNames(toolNames);
|
|
1016
|
+
// Transport-write eligibility keys off the *current* active set: an ordinary
|
|
1017
|
+
// selection change should not demote `write` unless it is already active.
|
|
1018
|
+
await this.#applyToolPresentation(
|
|
1019
|
+
normalized,
|
|
1020
|
+
this.#xdev?.mountedNames ?? new Set(),
|
|
1021
|
+
this.getActiveToolNames().includes("write"),
|
|
1022
|
+
);
|
|
1023
|
+
});
|
|
929
1024
|
}
|
|
930
1025
|
|
|
931
1026
|
/**
|
|
@@ -938,19 +1033,31 @@ export class SessionTools {
|
|
|
938
1033
|
* `xd://` remain mount-eligible, even when the live mount set has drifted.
|
|
939
1034
|
*
|
|
940
1035
|
* Names outside `mountedToolNames` are pinned top-level for this application;
|
|
941
|
-
* names in the mounted subset remain eligible for xdev mounting.
|
|
942
|
-
*
|
|
943
|
-
*
|
|
1036
|
+
* names in the mounted subset remain eligible for xdev mounting. Set
|
|
1037
|
+
* `forcePromptRefresh` when an enabled tool's schema or prompt-visible metadata
|
|
1038
|
+
* changed without changing its name or presentation.
|
|
1039
|
+
*
|
|
1040
|
+
* Delegates the actual apply through {@link applyActiveToolsByName} and restores
|
|
1041
|
+
* the prior runtime selection if that apply throws.
|
|
944
1042
|
*/
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
normalized
|
|
953
|
-
|
|
1043
|
+
setActiveToolPresentation(
|
|
1044
|
+
toolNames: string[],
|
|
1045
|
+
mountedToolNames: string[],
|
|
1046
|
+
forcePromptRefresh = false,
|
|
1047
|
+
signal?: AbortSignal,
|
|
1048
|
+
): Promise<void> {
|
|
1049
|
+
return this.runToolRegistryMutation(async () => {
|
|
1050
|
+
const normalized = normalizeToolNames(toolNames);
|
|
1051
|
+
// Restoration targets a snapshot, so write eligibility comes from the
|
|
1052
|
+
// *target* set rather than whatever happens to be active mid-rollback.
|
|
1053
|
+
await this.#applyToolPresentation(
|
|
1054
|
+
normalized,
|
|
1055
|
+
new Set(normalizeToolNames(mountedToolNames)),
|
|
1056
|
+
normalized.includes("write"),
|
|
1057
|
+
forcePromptRefresh,
|
|
1058
|
+
signal,
|
|
1059
|
+
);
|
|
1060
|
+
}, signal);
|
|
954
1061
|
}
|
|
955
1062
|
|
|
956
1063
|
/**
|
|
@@ -962,6 +1069,8 @@ export class SessionTools {
|
|
|
962
1069
|
normalized: string[],
|
|
963
1070
|
mounted: ReadonlySet<string>,
|
|
964
1071
|
writeSelected: boolean,
|
|
1072
|
+
forcePromptRefresh = false,
|
|
1073
|
+
signal?: AbortSignal,
|
|
965
1074
|
): Promise<void> {
|
|
966
1075
|
const transportWriteActive =
|
|
967
1076
|
writeSelected &&
|
|
@@ -974,7 +1083,7 @@ export class SessionTools {
|
|
|
974
1083
|
normalized.filter(name => !mounted.has(name) && !(name === "write" && transportWriteActive)),
|
|
975
1084
|
);
|
|
976
1085
|
try {
|
|
977
|
-
await this
|
|
1086
|
+
await this.#applyActiveToolsByName(normalized, forcePromptRefresh, signal);
|
|
978
1087
|
} catch (error) {
|
|
979
1088
|
this.#runtimeSelectedToolNames = previousRuntimeSelectedToolNames;
|
|
980
1089
|
throw error;
|
|
@@ -982,24 +1091,26 @@ export class SessionTools {
|
|
|
982
1091
|
}
|
|
983
1092
|
|
|
984
1093
|
/** Replaces memory-backend tools while preserving unrelated selections. */
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1094
|
+
replaceMemoryTools(tools: AgentTool[]): Promise<void> {
|
|
1095
|
+
return this.runToolRegistryMutation(async () => {
|
|
1096
|
+
const removed = new Set<string>(MEMORY_BACKEND_TOOL_NAMES.filter(name => this.#builtInToolNames.has(name)));
|
|
1097
|
+
const nextActive = this.getEnabledToolNames().filter(name => !removed.has(name));
|
|
1098
|
+
for (const name of removed) {
|
|
1099
|
+
this.#toolRegistry.delete(name);
|
|
1100
|
+
this.#builtInToolNames.delete(name);
|
|
1101
|
+
}
|
|
992
1102
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1103
|
+
for (const tool of tools) {
|
|
1104
|
+
if (!MEMORY_BACKEND_TOOL_NAMES.some(name => name === tool.name) || this.#toolRegistry.has(tool.name)) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
const wrapped = this.#wrapRuntimeTool(tool);
|
|
1108
|
+
this.#toolRegistry.set(wrapped.name, wrapped);
|
|
1109
|
+
this.#builtInToolNames.add(wrapped.name);
|
|
1110
|
+
nextActive.push(wrapped.name);
|
|
996
1111
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
this.#builtInToolNames.add(wrapped.name);
|
|
1000
|
-
nextActive.push(wrapped.name);
|
|
1001
|
-
}
|
|
1002
|
-
await this.applyActiveToolsByName([...new Set(nextActive)]);
|
|
1112
|
+
await this.#applyActiveToolsByName([...new Set(nextActive)]);
|
|
1113
|
+
});
|
|
1003
1114
|
}
|
|
1004
1115
|
|
|
1005
1116
|
/**
|
|
@@ -1015,34 +1126,36 @@ export class SessionTools {
|
|
|
1015
1126
|
* @returns false when enabling was requested but this session cannot build the
|
|
1016
1127
|
* tool (e.g. restricted child sessions have no factory).
|
|
1017
1128
|
*/
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1129
|
+
setComputerToolEnabled(enabled: boolean): Promise<boolean> {
|
|
1130
|
+
return this.runToolRegistryMutation(async () => {
|
|
1131
|
+
const logState = (): void => this.#logComputerState("Computer tool state changed", enabled);
|
|
1132
|
+
const active = this.getEnabledToolNames();
|
|
1133
|
+
if (!enabled) {
|
|
1134
|
+
if (active.includes("computer")) {
|
|
1135
|
+
await this.#applyActiveToolsByName(active.filter(name => name !== "computer"));
|
|
1136
|
+
}
|
|
1137
|
+
logState();
|
|
1138
|
+
return true;
|
|
1139
|
+
}
|
|
1140
|
+
if (!this.#toolRegistry.has("computer")) {
|
|
1141
|
+
const tool = await this.#createComputerTool?.();
|
|
1142
|
+
if (tool?.name !== "computer") {
|
|
1143
|
+
const model = this.#host.model();
|
|
1144
|
+
logger.warn("Computer tool could not be created", {
|
|
1145
|
+
model: model ? formatModelString(model) : undefined,
|
|
1146
|
+
});
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
const wrapped = this.#wrapRuntimeTool(tool);
|
|
1150
|
+
this.#toolRegistry.set(wrapped.name, wrapped);
|
|
1151
|
+
this.#builtInToolNames.add(wrapped.name);
|
|
1152
|
+
}
|
|
1153
|
+
if (!active.includes("computer")) {
|
|
1154
|
+
await this.#applyActiveToolsByName([...active, "computer"]);
|
|
1024
1155
|
}
|
|
1025
1156
|
logState();
|
|
1026
1157
|
return true;
|
|
1027
|
-
}
|
|
1028
|
-
if (!this.#toolRegistry.has("computer")) {
|
|
1029
|
-
const tool = await this.#createComputerTool?.();
|
|
1030
|
-
if (tool?.name !== "computer") {
|
|
1031
|
-
const model = this.#host.model();
|
|
1032
|
-
logger.warn("Computer tool could not be created", {
|
|
1033
|
-
model: model ? formatModelString(model) : undefined,
|
|
1034
|
-
});
|
|
1035
|
-
return false;
|
|
1036
|
-
}
|
|
1037
|
-
const wrapped = this.#wrapRuntimeTool(tool);
|
|
1038
|
-
this.#toolRegistry.set(wrapped.name, wrapped);
|
|
1039
|
-
this.#builtInToolNames.add(wrapped.name);
|
|
1040
|
-
}
|
|
1041
|
-
if (!active.includes("computer")) {
|
|
1042
|
-
await this.applyActiveToolsByName([...active, "computer"]);
|
|
1043
|
-
}
|
|
1044
|
-
logState();
|
|
1045
|
-
return true;
|
|
1158
|
+
});
|
|
1046
1159
|
}
|
|
1047
1160
|
|
|
1048
1161
|
/** Current effective inspect_image state for `/vision status`. */
|
|
@@ -1065,48 +1178,50 @@ export class SessionTools {
|
|
|
1065
1178
|
* @returns false when the tool should be active but this session cannot
|
|
1066
1179
|
* build it (e.g. restricted child sessions have no factory).
|
|
1067
1180
|
*/
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
await this.applyActiveToolsByName(active.filter(name => name !== "inspect_image"));
|
|
1092
|
-
return true;
|
|
1093
|
-
}
|
|
1094
|
-
if (!this.#toolRegistry.has("inspect_image")) {
|
|
1095
|
-
const tool = await this.#createInspectImageTool?.();
|
|
1096
|
-
if (tool?.name !== "inspect_image") {
|
|
1097
|
-
logger.warn("inspect_image tool could not be created", {
|
|
1098
|
-
model: this.#host.model()?.id,
|
|
1099
|
-
});
|
|
1181
|
+
reconcileInspectImageTool(): Promise<boolean> {
|
|
1182
|
+
return this.runToolRegistryMutation(async () => {
|
|
1183
|
+
const expected = isInspectImageToolActive({
|
|
1184
|
+
settings: this.#host.settings,
|
|
1185
|
+
getActiveModel: () => this.#host.model(),
|
|
1186
|
+
getInspectImageModeOverride: () => this.#host.getInspectImageModeOverride(),
|
|
1187
|
+
});
|
|
1188
|
+
// Keep the read tool's advertised description in sync BEFORE any prompt
|
|
1189
|
+
// rebuild below, passing the post-change availability so the prompt never
|
|
1190
|
+
// lags a flip in either direction. Per-read lazy sync is the backstop.
|
|
1191
|
+
const syncReadDescription = (available: boolean): void => {
|
|
1192
|
+
const readTool = this.#toolRegistry.get("read") as
|
|
1193
|
+
| { syncInspectImageState?: (available?: boolean) => boolean }
|
|
1194
|
+
| undefined;
|
|
1195
|
+
readTool?.syncInspectImageState?.(available);
|
|
1196
|
+
};
|
|
1197
|
+
const active = this.getEnabledToolNames();
|
|
1198
|
+
const isActive = active.includes("inspect_image");
|
|
1199
|
+
if (expected === isActive) {
|
|
1200
|
+
syncReadDescription(isActive);
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1203
|
+
if (!expected) {
|
|
1100
1204
|
syncReadDescription(false);
|
|
1101
|
-
|
|
1205
|
+
await this.#applyActiveToolsByName(active.filter(name => name !== "inspect_image"));
|
|
1206
|
+
return true;
|
|
1102
1207
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1208
|
+
if (!this.#toolRegistry.has("inspect_image")) {
|
|
1209
|
+
const tool = await this.#createInspectImageTool?.();
|
|
1210
|
+
if (tool?.name !== "inspect_image") {
|
|
1211
|
+
logger.warn("inspect_image tool could not be created", {
|
|
1212
|
+
model: this.#host.model()?.id,
|
|
1213
|
+
});
|
|
1214
|
+
syncReadDescription(false);
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
const wrapped = this.#wrapRuntimeTool(tool);
|
|
1218
|
+
this.#toolRegistry.set(wrapped.name, wrapped);
|
|
1219
|
+
this.#builtInToolNames.add(wrapped.name);
|
|
1220
|
+
}
|
|
1221
|
+
syncReadDescription(true);
|
|
1222
|
+
await this.#applyActiveToolsByName([...active, "inspect_image"]);
|
|
1223
|
+
return true;
|
|
1224
|
+
});
|
|
1110
1225
|
}
|
|
1111
1226
|
|
|
1112
1227
|
/**
|
|
@@ -1115,20 +1230,22 @@ export class SessionTools {
|
|
|
1115
1230
|
* path — including retry-fallback switches that bypass
|
|
1116
1231
|
* {@link syncAfterModelChange}.
|
|
1117
1232
|
*/
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1233
|
+
reconcileInspectImageAfterModelChange(): Promise<void> {
|
|
1234
|
+
return this.runToolRegistryMutation(async () => {
|
|
1235
|
+
const before = this.getEnabledToolNames().includes("inspect_image");
|
|
1236
|
+
const reconciled = await this.reconcileInspectImageTool();
|
|
1237
|
+
const after = this.getEnabledToolNames().includes("inspect_image");
|
|
1238
|
+
if (!reconciled || before === after) return;
|
|
1239
|
+
const model = this.#host.model();
|
|
1240
|
+
const modelName = model ? formatModelString(model) : "the current model";
|
|
1241
|
+
this.#host.emitNotice(
|
|
1242
|
+
"info",
|
|
1243
|
+
after
|
|
1244
|
+
? `inspect_image is now available: ${modelName} has no native image input.`
|
|
1245
|
+
: `inspect_image is now hidden: ${modelName} supports image input natively. Override with /vision on.`,
|
|
1246
|
+
"vision",
|
|
1247
|
+
);
|
|
1248
|
+
});
|
|
1132
1249
|
}
|
|
1133
1250
|
|
|
1134
1251
|
/**
|
|
@@ -1139,16 +1256,22 @@ export class SessionTools {
|
|
|
1139
1256
|
*
|
|
1140
1257
|
* @returns false when `on` was requested but the tool cannot be built here.
|
|
1141
1258
|
*/
|
|
1142
|
-
|
|
1143
|
-
this
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1259
|
+
setInspectImageMode(mode: InspectImageMode): Promise<boolean> {
|
|
1260
|
+
return this.runToolRegistryMutation(async () => {
|
|
1261
|
+
this.#host.setInspectImageModeOverride(mode === "auto" ? undefined : mode);
|
|
1262
|
+
const applied = await this.reconcileInspectImageTool();
|
|
1263
|
+
const { active, model } = this.inspectImageState();
|
|
1264
|
+
logger.debug("inspect_image mode changed", { mode, active, model });
|
|
1265
|
+
return applied;
|
|
1266
|
+
});
|
|
1148
1267
|
}
|
|
1149
1268
|
|
|
1150
1269
|
/** Rebuilds the stable base prompt for the current tools and model. */
|
|
1151
|
-
|
|
1270
|
+
refreshBaseSystemPrompt(): Promise<void> {
|
|
1271
|
+
return this.runToolRegistryMutation(() => this.#refreshBaseSystemPrompt());
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
async #refreshBaseSystemPrompt(): Promise<void> {
|
|
1152
1275
|
if (this.#host.isDisposed() || !this.#rebuildSystemPrompt) return;
|
|
1153
1276
|
const activeToolNames = this.getActiveToolNames();
|
|
1154
1277
|
this.#setActiveToolNames?.(activeToolNames);
|
|
@@ -1299,32 +1422,25 @@ export class SessionTools {
|
|
|
1299
1422
|
*/
|
|
1300
1423
|
refreshMCPTools(mcpTools: CustomTool[]): Promise<void> {
|
|
1301
1424
|
const snapshot = [...mcpTools];
|
|
1302
|
-
|
|
1303
|
-
this.#host.isDisposed() ?
|
|
1425
|
+
return this.runToolRegistryMutation(() =>
|
|
1426
|
+
this.#host.isDisposed() ? Promise.resolve() : this.#applyMCPToolRefresh(snapshot),
|
|
1304
1427
|
);
|
|
1305
|
-
this.#mcpRefreshTail = refresh.catch(() => {});
|
|
1306
|
-
return refresh;
|
|
1307
1428
|
}
|
|
1308
1429
|
|
|
1309
1430
|
async #applyMCPToolRefresh(mcpTools: CustomTool[]): Promise<void> {
|
|
1310
|
-
const
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
);
|
|
1431
|
+
const previousMcpTools = new Map<string, AgentTool>();
|
|
1432
|
+
for (const [name, tool] of this.#toolRegistry) {
|
|
1433
|
+
if (isMCPToolName(name)) previousMcpTools.set(name, tool);
|
|
1434
|
+
}
|
|
1435
|
+
const previousMcpManagerToolNames = new Set(this.#mcpManagerToolNames);
|
|
1436
|
+
const previousActiveMcpToolNames = this.getEnabledToolNames().filter(isMCPToolName);
|
|
1317
1437
|
const restorePreviousMcpTools = () => {
|
|
1318
1438
|
for (const name of this.#toolRegistry.keys()) {
|
|
1319
1439
|
if (isMCPToolName(name)) this.#toolRegistry.delete(name);
|
|
1320
1440
|
}
|
|
1321
1441
|
for (const [name, tool] of previousMcpTools) this.#toolRegistry.set(name, tool);
|
|
1442
|
+
this.#mcpManagerToolNames = previousMcpManagerToolNames;
|
|
1322
1443
|
};
|
|
1323
|
-
for (const name of existingNames) {
|
|
1324
|
-
if (isMCPToolName(name)) {
|
|
1325
|
-
this.#toolRegistry.delete(name);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
1444
|
|
|
1329
1445
|
const getCustomToolContext = (): CustomToolContext => ({
|
|
1330
1446
|
sessionManager: this.#host.sessionManager,
|
|
@@ -1340,20 +1456,36 @@ export class SessionTools {
|
|
|
1340
1456
|
});
|
|
1341
1457
|
|
|
1342
1458
|
const extensionRunner = this.#host.extensionRunner();
|
|
1343
|
-
const
|
|
1344
|
-
for (const customTool of uniqueMcpTools) {
|
|
1459
|
+
const managerTools = deduplicateMCPToolsByName(mcpTools).map(customTool => {
|
|
1345
1460
|
const wrapped = wrapToolWithMetaNotice(CustomToolAdapter.wrap(customTool, getCustomToolContext) as AgentTool);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1461
|
+
return (extensionRunner ? new ExtensionToolWrapper(wrapped, extensionRunner) : wrapped) as AgentTool;
|
|
1462
|
+
});
|
|
1463
|
+
const managerToolSet = new Set(managerTools);
|
|
1464
|
+
const reconciledTools = deduplicateMCPToolsByName([...this.#extensionMcpTools.values(), ...managerTools]);
|
|
1465
|
+
|
|
1466
|
+
for (const name of this.#toolRegistry.keys()) {
|
|
1467
|
+
if (isMCPToolName(name)) this.#toolRegistry.delete(name);
|
|
1468
|
+
}
|
|
1469
|
+
this.#mcpManagerToolNames.clear();
|
|
1470
|
+
for (const tool of reconciledTools) {
|
|
1471
|
+
this.#toolRegistry.set(tool.name, tool);
|
|
1472
|
+
if (managerToolSet.has(tool)) this.#mcpManagerToolNames.add(tool.name);
|
|
1350
1473
|
}
|
|
1351
1474
|
|
|
1352
|
-
//
|
|
1353
|
-
//
|
|
1354
|
-
const
|
|
1475
|
+
// Connected manager tools become active immediately. Extension-owned MCP
|
|
1476
|
+
// tools retain their prior selection while both sets share one registry.
|
|
1477
|
+
const retainedActiveExtensionToolNames = previousActiveMcpToolNames.filter(
|
|
1478
|
+
name => this.#extensionMcpTools.has(name) && this.#toolRegistry.has(name),
|
|
1479
|
+
);
|
|
1480
|
+
const nextActive = [
|
|
1481
|
+
...new Set([
|
|
1482
|
+
...this.#getActiveNonMCPToolNames(),
|
|
1483
|
+
...this.#mcpManagerToolNames,
|
|
1484
|
+
...retainedActiveExtensionToolNames,
|
|
1485
|
+
]),
|
|
1486
|
+
];
|
|
1355
1487
|
try {
|
|
1356
|
-
await this
|
|
1488
|
+
await this.#applyActiveToolsByName(nextActive);
|
|
1357
1489
|
if (this.#host.isDisposed()) restorePreviousMcpTools();
|
|
1358
1490
|
} catch (error) {
|
|
1359
1491
|
restorePreviousMcpTools();
|
|
@@ -1362,7 +1494,12 @@ export class SessionTools {
|
|
|
1362
1494
|
}
|
|
1363
1495
|
|
|
1364
1496
|
/** Replaces RPC host-owned tools and refreshes the active set before the next model call. */
|
|
1365
|
-
|
|
1497
|
+
refreshRpcHostTools(rpcTools: AgentTool[]): Promise<void> {
|
|
1498
|
+
const snapshot = [...rpcTools];
|
|
1499
|
+
return this.runToolRegistryMutation(() => this.#applyRpcHostToolRefresh(snapshot));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
async #applyRpcHostToolRefresh(rpcTools: AgentTool[]): Promise<void> {
|
|
1366
1503
|
const nextToolNames = rpcTools.map(tool => tool.name);
|
|
1367
1504
|
const uniqueToolNames = new Set(nextToolNames);
|
|
1368
1505
|
if (uniqueToolNames.size !== nextToolNames.length) {
|
|
@@ -1406,7 +1543,7 @@ export class SessionTools {
|
|
|
1406
1543
|
.filter(tool => !tool.hidden && !previousRpcHostToolNames.has(tool.name))
|
|
1407
1544
|
.map(tool => tool.name);
|
|
1408
1545
|
try {
|
|
1409
|
-
await this
|
|
1546
|
+
await this.#applyActiveToolsByName(
|
|
1410
1547
|
Array.from(new Set([...activeNonRpcToolNames, ...preservedRpcToolNames, ...autoActivatedRpcToolNames])),
|
|
1411
1548
|
);
|
|
1412
1549
|
} catch (error) {
|