@oh-my-pi/pi-coding-agent 17.2.1 → 17.2.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 +47 -0
- package/dist/{CHANGELOG-dj46zzrm.md → CHANGELOG-xfpkakrn.md} +47 -0
- package/dist/cli.js +3891 -3885
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +16 -5
- package/dist/types/edit/hashline/block-resolver.d.ts +5 -5
- package/dist/types/edit/hashline/diff.d.ts +4 -4
- package/dist/types/eval/executor-base.d.ts +27 -0
- package/dist/types/eval/js/context-manager.d.ts +7 -0
- package/dist/types/eval/js/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +64 -5
- package/dist/types/extensibility/extensions/types.d.ts +15 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +1 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
- package/dist/types/live/visualizer.d.ts +3 -1
- package/dist/types/modes/acp/acp-agent.d.ts +3 -3
- package/dist/types/modes/components/keybinding-hints.d.ts +1 -1
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +13 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/secrets/index.d.ts +22 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/session/codex-auto-reset.d.ts +182 -64
- package/dist/types/session/session-storage.d.ts +5 -2
- package/dist/types/session/session-tools.d.ts +2 -1
- package/dist/types/session/turn-recovery.d.ts +7 -1
- package/dist/types/system-prompt.d.ts +8 -0
- package/dist/types/task/executor.d.ts +27 -0
- package/dist/types/task/persisted-revive.d.ts +7 -0
- package/dist/types/tools/bash-interceptor.d.ts +1 -1
- package/dist/types/tools/browser/attach.d.ts +9 -1
- package/dist/types/tools/browser/launch.d.ts +20 -1
- package/dist/types/tools/browser/registry.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +8 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/shell-tokenize.d.ts +11 -0
- package/dist/types/utils/image-loading.d.ts +2 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/src/commit/agentic/index.ts +2 -1
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +6 -1
- package/src/config/settings-schema.ts +16 -3
- package/src/edit/hashline/block-resolver.ts +5 -5
- package/src/edit/hashline/diff.ts +22 -15
- package/src/edit/hashline/execute.ts +14 -13
- package/src/edit/renderer.ts +3 -3
- package/src/eval/executor-base.ts +39 -0
- package/src/eval/jl/executor.ts +13 -2
- package/src/eval/js/context-manager.ts +97 -14
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/index.ts +1 -0
- package/src/eval/py/executor.ts +55 -15
- package/src/eval/rb/executor.ts +8 -1
- package/src/extensibility/extensions/runner.ts +121 -4
- package/src/extensibility/extensions/types.ts +15 -0
- package/src/extensibility/extensions/wrapper.ts +19 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +5 -5
- package/src/live/visualizer.ts +8 -1
- package/src/lsp/index.ts +51 -11
- package/src/main.ts +3 -0
- package/src/modes/acp/acp-agent.ts +16 -5
- package/src/modes/components/assistant-message.ts +5 -10
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/keybinding-hints.ts +4 -13
- package/src/modes/components/status-line/segments.ts +4 -5
- package/src/modes/components/tool-execution.ts +4 -5
- package/src/modes/controllers/input-controller.ts +18 -40
- package/src/modes/controllers/live-command-controller.ts +1 -0
- package/src/modes/controllers/mcp-command-controller.ts +28 -11
- package/src/modes/interactive-mode.ts +59 -2
- package/src/modes/theme/theme.ts +13 -6
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/system/plan-mode-active.md +5 -5
- package/src/sdk.ts +47 -16
- package/src/secrets/index.ts +83 -3
- package/src/secrets/obfuscator.ts +70 -15
- package/src/session/agent-session-types.ts +7 -1
- package/src/session/agent-session.ts +393 -112
- package/src/session/codex-auto-reset.ts +594 -123
- package/src/session/eval-runner.ts +2 -0
- package/src/session/messages.ts +1 -1
- package/src/session/session-maintenance.ts +48 -1
- package/src/session/session-manager.ts +4 -3
- package/src/session/session-stats.ts +14 -3
- package/src/session/session-storage.ts +47 -15
- package/src/session/session-tools.ts +30 -2
- package/src/session/turn-recovery.ts +15 -2
- package/src/slash-commands/builtin-registry.ts +21 -15
- package/src/slash-commands/helpers/reset-usage.ts +2 -0
- package/src/system-prompt.ts +13 -1
- package/src/task/executor.ts +168 -15
- package/src/task/persisted-revive.ts +26 -1
- package/src/telemetry-export.ts +8 -4
- package/src/tools/bash-interceptor.ts +85 -8
- package/src/tools/bash.ts +1 -1
- package/src/tools/browser/attach.ts +35 -10
- package/src/tools/browser/launch.ts +60 -11
- package/src/tools/browser/registry.ts +18 -2
- package/src/tools/browser/tab-protocol.ts +8 -0
- package/src/tools/browser/tab-supervisor.ts +31 -4
- package/src/tools/browser/tab-worker.ts +32 -2
- package/src/tools/output-meta.ts +16 -1
- package/src/tools/shell-tokenize.ts +101 -0
- package/src/utils/image-loading.ts +8 -3
- package/src/utils/shell-snapshot.ts +31 -21
- package/src/web/search/providers/duckduckgo.ts +183 -15
- package/src/web/search/types.ts +6 -1
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utilities for formatting keybinding hints in the UI.
|
|
3
3
|
*/
|
|
4
|
-
import { getKeybindings, type Keybinding
|
|
5
|
-
import type
|
|
4
|
+
import { getKeybindings, type Keybinding } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { type AppKeybinding, formatKeyHints, type KeybindingsManager } from "../../config/keybindings";
|
|
6
6
|
import { theme } from "../../modes/theme/theme";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Format keys array as display string (e.g., ["ctrl+c", "escape"] -> "ctrl+c/escape").
|
|
10
|
-
*/
|
|
11
|
-
function formatKeys(keys: KeyId[]): string {
|
|
12
|
-
if (keys.length === 0) return "";
|
|
13
|
-
if (keys.length === 1) return keys[0]!;
|
|
14
|
-
return keys.join("/");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
8
|
/**
|
|
18
9
|
* Get display string for an editor action.
|
|
19
10
|
*/
|
|
20
11
|
export function editorKey(action: Keybinding): string {
|
|
21
|
-
return
|
|
12
|
+
return formatKeyHints(getKeybindings().getKeys(action));
|
|
22
13
|
}
|
|
23
14
|
|
|
24
15
|
/**
|
|
25
16
|
* Get display string for an app action.
|
|
26
17
|
*/
|
|
27
18
|
export function appKey(keybindings: KeybindingsManager, action: AppKeybinding): string {
|
|
28
|
-
return
|
|
19
|
+
return formatKeyHints(keybindings.getKeys(action));
|
|
29
20
|
}
|
|
30
21
|
|
|
31
22
|
/**
|
|
@@ -5,6 +5,7 @@ import { TERMINAL } from "@oh-my-pi/pi-tui";
|
|
|
5
5
|
import { formatDuration, formatNumber, getProjectDir, pathIsWithin, relativePathWithinRoot } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { type ThemeColor, theme } from "../../../modes/theme/theme";
|
|
7
7
|
import { shortenPath, TRUNCATE_LENGTHS, truncateToWidth } from "../../../tools/render-utils";
|
|
8
|
+
import { fileHyperlink } from "../../../tui/hyperlink";
|
|
8
9
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/session-color";
|
|
9
10
|
import { sanitizeStatusText } from "../../shared";
|
|
10
11
|
import { formatContextUsage, getContextUsageLevel, getContextUsageThemeColor } from "./context-thresholds";
|
|
@@ -280,7 +281,8 @@ const pathSegment: StatusLineSegment = {
|
|
|
280
281
|
if (stripPrefix && ctx.worktree) {
|
|
281
282
|
const { projectName, worktreeName } = ctx.worktree;
|
|
282
283
|
const label = ctx.git.branch === worktreeName ? projectName : `${projectName}/${worktreeName}`;
|
|
283
|
-
const
|
|
284
|
+
const text = fileHyperlink(getProjectDir(), clampPathLength(label, opts.maxLength ?? 40));
|
|
285
|
+
const content = withIcon(theme.icon.worktree, text);
|
|
284
286
|
return { content: theme.fg("statusLinePath", content), visible: true };
|
|
285
287
|
}
|
|
286
288
|
|
|
@@ -301,13 +303,10 @@ const pathSegment: StatusLineSegment = {
|
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
pwd = clampPathLength(pwd, opts.maxLength ?? 40);
|
|
304
|
-
if (repoSuffix) {
|
|
305
|
-
pwd = `${pwd}${repoSuffix}`;
|
|
306
|
-
}
|
|
307
306
|
|
|
308
307
|
const showScratchIcon = scratch && stripPrefix;
|
|
309
308
|
const icon = showScratchIcon ? theme.icon.scratchFolder : theme.icon.folder;
|
|
310
|
-
const content = withIcon(icon, pwd);
|
|
309
|
+
const content = withIcon(icon, `${fileHyperlink(projectDir, pwd)}${repoSuffix}`);
|
|
311
310
|
return { content: theme.fg("statusLinePath", content), visible: true };
|
|
312
311
|
},
|
|
313
312
|
};
|
|
@@ -27,6 +27,7 @@ import { type FirstResultViewportRepaint, toolRenderers } from "../../tools/rend
|
|
|
27
27
|
import { TODO_STRIKE_TOTAL_FRAMES, type TodoToolDetails } from "../../tools/todo";
|
|
28
28
|
import type { XdevState } from "../../tools/xdev";
|
|
29
29
|
import { isFramedBlockComponent, markFramedBlockComponent, renderStatusLine, WidthAwareText } from "../../tui";
|
|
30
|
+
import { convertImageToPng } from "../../utils/image-loading";
|
|
30
31
|
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
31
32
|
import { renderDiff } from "./diff";
|
|
32
33
|
|
|
@@ -651,11 +652,9 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
651
652
|
|
|
652
653
|
// Convert async - catch errors from processing
|
|
653
654
|
const index = i;
|
|
654
|
-
|
|
655
|
-
.
|
|
656
|
-
|
|
657
|
-
.then(data => {
|
|
658
|
-
this.#convertedImages.set(index, { data, mimeType: "image/png" });
|
|
655
|
+
convertImageToPng({ type: "image", data: img.data, mimeType: img.mimeType })
|
|
656
|
+
.then(converted => {
|
|
657
|
+
this.#convertedImages.set(index, converted);
|
|
659
658
|
this.#displayInputVersion++;
|
|
660
659
|
this.#updateDisplay();
|
|
661
660
|
this.#ui.requestRender();
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import {
|
|
6
|
+
import { isEnoent, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
8
8
|
import { resolveLocalRoot } from "../../internal-urls";
|
|
9
9
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
@@ -20,7 +20,6 @@ import manualContinuePrompt from "../../prompts/system/manual-continue.md" with
|
|
|
20
20
|
import { USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
21
21
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
22
22
|
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
23
|
-
import { isLowSignalTitleInput } from "../../tiny/text";
|
|
24
23
|
import { tinyTitleClient } from "../../tiny/title-client";
|
|
25
24
|
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
26
25
|
import { shortenPath, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -404,14 +403,13 @@ export class InputController {
|
|
|
404
403
|
this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
|
|
405
404
|
this.ctx.editor.setActionKeys("app.display.reset", this.ctx.keybindings.getKeys("app.display.reset"));
|
|
406
405
|
this.ctx.editor.onDisplayReset = () => {
|
|
407
|
-
// Explicit user gesture (
|
|
408
|
-
// so a mid-session light/dark switch is picked
|
|
409
|
-
// without an end-to-end Mode 2031 notification
|
|
410
|
-
// appearance callback re-evaluates the auto theme; the
|
|
411
|
-
// then renders the resolved palette. Bounded to one OSC 11
|
|
412
|
-
// gesture — no timers, no periodic polling.
|
|
413
|
-
this.ctx.
|
|
414
|
-
this.ctx.ui.resetDisplay();
|
|
406
|
+
// Explicit user gesture (display reset, Alt+L by default): re-query the
|
|
407
|
+
// terminal background once so a mid-session light/dark switch is picked
|
|
408
|
+
// up even on terminals without an end-to-end Mode 2031 notification
|
|
409
|
+
// path (#5352). The appearance callback re-evaluates the auto theme; the
|
|
410
|
+
// repaint below then renders the resolved palette. Bounded to one OSC 11
|
|
411
|
+
// probe per gesture — no timers, no periodic polling.
|
|
412
|
+
this.ctx.resetDisplayAfterAppearanceRefresh();
|
|
415
413
|
};
|
|
416
414
|
this.ctx.editor.onExit = () => this.handleCtrlD();
|
|
417
415
|
this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
|
|
@@ -487,6 +485,9 @@ export class InputController {
|
|
|
487
485
|
for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
|
|
488
486
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
|
|
489
487
|
}
|
|
488
|
+
for (const key of this.ctx.keybindings.getKeys("app.live.toggle")) {
|
|
489
|
+
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleLiveCommand());
|
|
490
|
+
}
|
|
490
491
|
// Hold the space bar to push-to-talk: the editor recognizes the auto-repeat burst, tracks
|
|
491
492
|
// the spam back out, and toggles STT on hold start / release. Gated on `stt.enabled` so a
|
|
492
493
|
// disabled STT leaves the space bar typing normally.
|
|
@@ -901,12 +902,9 @@ export class InputController {
|
|
|
901
902
|
}
|
|
902
903
|
|
|
903
904
|
/**
|
|
904
|
-
* Kick off session-title generation
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
* Skips slash extension commands (consumed locally by AgentSession.prompt()),
|
|
908
|
-
* already-named sessions, PI_NO_TITLE, and low-signal greetings — no model or
|
|
909
|
-
* download UI for those.
|
|
905
|
+
* Kick off session-title generation after the optimistic user row paints.
|
|
906
|
+
* Local extension commands are consumed before reaching the shared session
|
|
907
|
+
* title gate and must not name the conversation.
|
|
910
908
|
*/
|
|
911
909
|
#maybeStartTitleGeneration(text: string): void {
|
|
912
910
|
const runner = this.ctx.session.extensionRunner;
|
|
@@ -915,32 +913,12 @@ export class InputController {
|
|
|
915
913
|
text.startsWith("/") &&
|
|
916
914
|
runner?.getCommand(extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace)) !==
|
|
917
915
|
undefined;
|
|
918
|
-
if (
|
|
919
|
-
isLocalExtensionCommand ||
|
|
920
|
-
this.ctx.sessionManager.getSessionName() ||
|
|
921
|
-
$env.PI_NO_TITLE ||
|
|
922
|
-
isLowSignalTitleInput(text)
|
|
923
|
-
) {
|
|
916
|
+
if (isLocalExtensionCommand) {
|
|
924
917
|
return;
|
|
925
918
|
}
|
|
926
|
-
this
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
.then(async title => {
|
|
930
|
-
// Re-check: a concurrent attempt for an earlier message may have
|
|
931
|
-
// already named the session. Don't clobber it. Terminal title and
|
|
932
|
-
// accent updates fire from the onSessionNameChanged listener.
|
|
933
|
-
if (title && !this.ctx.sessionManager.getSessionName()) {
|
|
934
|
-
await this.ctx.sessionManager.setSessionName(title, "auto");
|
|
935
|
-
}
|
|
936
|
-
})
|
|
937
|
-
.catch(err => {
|
|
938
|
-
logger.warn("title-generator: uncaught auto-title error", {
|
|
939
|
-
sessionId: this.ctx.session.sessionId,
|
|
940
|
-
reason: "uncaught-auto-title-error",
|
|
941
|
-
error: err instanceof Error ? err.message : String(err),
|
|
942
|
-
});
|
|
943
|
-
});
|
|
919
|
+
this.ctx.session.maybeStartTitleGeneration(text, () => {
|
|
920
|
+
this.#showTinyTitleDownloadProgress(this.ctx.settings.get("providers.tinyModel"));
|
|
921
|
+
});
|
|
944
922
|
}
|
|
945
923
|
|
|
946
924
|
/** Submit editor text to the focused subagent session (chat-only focus policy). */
|
|
@@ -99,6 +99,7 @@ export class LiveCommandController {
|
|
|
99
99
|
void this.stop().catch(cause => this.#ctx.showError(errorFrom(cause).message));
|
|
100
100
|
},
|
|
101
101
|
onToggleMute: () => this.#session?.toggleMute(),
|
|
102
|
+
stopKeys: this.#ctx.keybindings.getKeys("app.live.toggle"),
|
|
102
103
|
});
|
|
103
104
|
this.#mountVisualizer(visualizer);
|
|
104
105
|
|
|
@@ -1228,7 +1228,7 @@ export class MCPCommandController {
|
|
|
1228
1228
|
await addMCPServer(filePath, name, config);
|
|
1229
1229
|
|
|
1230
1230
|
// Reload MCP manager
|
|
1231
|
-
await this
|
|
1231
|
+
await this.reloadServers();
|
|
1232
1232
|
const state =
|
|
1233
1233
|
config.enabled === false
|
|
1234
1234
|
? "disconnected"
|
|
@@ -1486,7 +1486,7 @@ export class MCPCommandController {
|
|
|
1486
1486
|
await removeMCPServer(filePath, name);
|
|
1487
1487
|
|
|
1488
1488
|
// Reload MCP manager
|
|
1489
|
-
await this
|
|
1489
|
+
await this.reloadServers();
|
|
1490
1490
|
|
|
1491
1491
|
this.#showMessage(["", theme.fg("success", `- Removed server "${name}" from ${scope} config`), ""].join("\n"));
|
|
1492
1492
|
} catch (error) {
|
|
@@ -1736,7 +1736,7 @@ export class MCPCommandController {
|
|
|
1736
1736
|
);
|
|
1737
1737
|
return;
|
|
1738
1738
|
}
|
|
1739
|
-
await this
|
|
1739
|
+
await this.reloadServers();
|
|
1740
1740
|
this.#showMessage(
|
|
1741
1741
|
["", theme.fg("success", `- Cleared auth for "${name}" (${found.scope} config)`), ""].join("\n"),
|
|
1742
1742
|
);
|
|
@@ -1745,7 +1745,7 @@ export class MCPCommandController {
|
|
|
1745
1745
|
|
|
1746
1746
|
const updated = this.#stripOAuthAuth(found.config);
|
|
1747
1747
|
await updateMCPServer(found.filePath, name, updated);
|
|
1748
|
-
await this
|
|
1748
|
+
await this.reloadServers();
|
|
1749
1749
|
|
|
1750
1750
|
this.#showMessage(
|
|
1751
1751
|
["", theme.fg("success", `- Cleared auth for "${name}" (${found.scope} config)`), ""].join("\n"),
|
|
@@ -1856,7 +1856,7 @@ export class MCPCommandController {
|
|
|
1856
1856
|
await updateMCPServer(found.filePath, name, updatedConfig);
|
|
1857
1857
|
}
|
|
1858
1858
|
if (options.reload !== false) {
|
|
1859
|
-
await this
|
|
1859
|
+
await this.reloadServers();
|
|
1860
1860
|
const state = await this.#waitForServerConnectionWithAnimation(name);
|
|
1861
1861
|
|
|
1862
1862
|
const lines = [
|
|
@@ -1891,7 +1891,7 @@ export class MCPCommandController {
|
|
|
1891
1891
|
async #handleReload(): Promise<void> {
|
|
1892
1892
|
try {
|
|
1893
1893
|
this.#showMessage(["", theme.fg("muted", "Reloading MCP servers and runtime tools..."), ""].join("\n"));
|
|
1894
|
-
await this
|
|
1894
|
+
await this.reloadServers();
|
|
1895
1895
|
const connectedCount = this.ctx.mcpManager?.getConnectedServers().length ?? 0;
|
|
1896
1896
|
this.#showMessage(
|
|
1897
1897
|
[
|
|
@@ -1979,18 +1979,35 @@ export class MCPCommandController {
|
|
|
1979
1979
|
}
|
|
1980
1980
|
|
|
1981
1981
|
/**
|
|
1982
|
-
*
|
|
1982
|
+
* Reconnect every configured MCP server and rebind the session's MCP tools.
|
|
1983
|
+
*
|
|
1984
|
+
* Disconnects all live connections, rediscovers `.mcp.json` configs, and
|
|
1985
|
+
* calls `session.refreshMCPTools(...)` so config edits take effect without a
|
|
1986
|
+
* restart. Public because `/reload-plugins` reuses it alongside `/mcp reload`
|
|
1987
|
+
* and the config-mutation flows in this controller.
|
|
1988
|
+
*
|
|
1989
|
+
* Discovery options are derived from settings so the reload honors the same
|
|
1990
|
+
* opt-outs as startup — notably `mcp.enableProjectConfig: false`, which must
|
|
1991
|
+
* keep project `.mcp.json` servers from being started on reload.
|
|
1983
1992
|
*/
|
|
1984
|
-
async
|
|
1993
|
+
async reloadServers(): Promise<void> {
|
|
1985
1994
|
if (!this.ctx.mcpManager) {
|
|
1986
1995
|
return;
|
|
1987
1996
|
}
|
|
1988
1997
|
|
|
1989
1998
|
// Disconnect all existing servers
|
|
1990
1999
|
await this.ctx.mcpManager.disconnectAll();
|
|
1991
|
-
|
|
1992
|
-
//
|
|
1993
|
-
|
|
2000
|
+
// Prompt enrichment is asynchronous. Clear commands before rediscovery so
|
|
2001
|
+
// removed/disabled servers cannot leave stale `/server:prompt` entries;
|
|
2002
|
+
// newly loaded prompts repopulate them through the manager callback.
|
|
2003
|
+
this.ctx.session.setMCPPromptCommands([]);
|
|
2004
|
+
|
|
2005
|
+
// Rediscover and connect, mirroring startup's discovery filters.
|
|
2006
|
+
const result = await this.ctx.mcpManager.discoverAndConnect({
|
|
2007
|
+
enableProjectConfig: this.ctx.settings.get("mcp.enableProjectConfig") ?? true,
|
|
2008
|
+
filterExa: true,
|
|
2009
|
+
filterBrowser: this.ctx.settings.get("browser.enabled") ?? false,
|
|
2010
|
+
});
|
|
1994
2011
|
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1995
2012
|
|
|
1996
2013
|
this.#showMCPConnectionErrors(result.errors);
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
TUI,
|
|
38
38
|
visibleWidth,
|
|
39
39
|
} from "@oh-my-pi/pi-tui";
|
|
40
|
+
import type { TerminalAppearanceRequestToken } from "@oh-my-pi/pi-tui/terminal";
|
|
40
41
|
import { isInsideTerminalMultiplexer } from "@oh-my-pi/pi-tui/terminal-capabilities";
|
|
41
42
|
import {
|
|
42
43
|
$env,
|
|
@@ -429,6 +430,8 @@ export function renderSubagentHudLines(sessions: ObservableSession[], columns: n
|
|
|
429
430
|
return ["", theme.bold(theme.fg("accent", "Subagents")), ...rows.map(line => ` ${line}`)];
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
const CTRL_L_APPEARANCE_RESPONSE_DEADLINE_MS = 2000;
|
|
434
|
+
|
|
432
435
|
export class InteractiveMode implements InteractiveModeContext {
|
|
433
436
|
session: AgentSession;
|
|
434
437
|
sessionManager: SessionManager;
|
|
@@ -471,6 +474,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
471
474
|
#loopAutoSubmitTimer: NodeJS.Timeout | undefined;
|
|
472
475
|
#todoAutoClearTimer: NodeJS.Timeout | undefined;
|
|
473
476
|
#modelCycleClearTimer: NodeJS.Timeout | undefined;
|
|
477
|
+
#nextAppearanceRequestToken = 1;
|
|
478
|
+
#appearanceRefreshRequest: { token: TerminalAppearanceRequestToken; deadline: number } | undefined;
|
|
474
479
|
todoPhases: TodoPhase[] = [];
|
|
475
480
|
hideThinkingBlock = false;
|
|
476
481
|
#sessionsWithDisplayableThinkingContent = new WeakSet<AgentSession>();
|
|
@@ -1141,8 +1146,38 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1141
1146
|
// Subscribe to terminal dark/light appearance changes.
|
|
1142
1147
|
// The terminal queries background color via OSC 11 at startup and on
|
|
1143
1148
|
// Mode 2031 notifications, computing luminance to detect dark/light.
|
|
1144
|
-
this.ui.terminal.
|
|
1145
|
-
|
|
1149
|
+
const unsubscribeAppearanceReport = this.ui.terminal.onAppearanceReport?.((_mode, requestToken) => {
|
|
1150
|
+
const request = this.#appearanceRefreshRequest;
|
|
1151
|
+
if (request === undefined || requestToken !== request.token) return;
|
|
1152
|
+
// ProcessTerminal dispatches report callbacks first, then synchronously
|
|
1153
|
+
// dispatches onAppearanceChange when the reported appearance changed.
|
|
1154
|
+
// That change callback consumes the request below before this microtask
|
|
1155
|
+
// runs; an unchanged matching report has no change callback, so it
|
|
1156
|
+
// consumes the one-shot here. Comparing the captured request prevents a
|
|
1157
|
+
// newer Ctrl+L request from being cleared by this report's microtask.
|
|
1158
|
+
queueMicrotask(() => {
|
|
1159
|
+
if (this.#appearanceRefreshRequest === request) {
|
|
1160
|
+
this.#appearanceRefreshRequest = undefined;
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
});
|
|
1164
|
+
if (unsubscribeAppearanceReport) {
|
|
1165
|
+
this.#eventBusUnsubscribers.push(unsubscribeAppearanceReport);
|
|
1166
|
+
}
|
|
1167
|
+
this.ui.terminal.onAppearanceChange((mode, requestToken) => {
|
|
1168
|
+
const request = this.#appearanceRefreshRequest;
|
|
1169
|
+
const appearanceRefreshWasRequested =
|
|
1170
|
+
request !== undefined &&
|
|
1171
|
+
Date.now() <= request.deadline &&
|
|
1172
|
+
(requestToken === request.token || requestToken === undefined);
|
|
1173
|
+
if (request !== undefined && requestToken === request.token) {
|
|
1174
|
+
this.#appearanceRefreshRequest = undefined;
|
|
1175
|
+
}
|
|
1176
|
+
// Ctrl+L already replays immediately below. If either its asynchronous
|
|
1177
|
+
// OSC 11 response or an automatic query ahead of it reveals a theme
|
|
1178
|
+
// change, commit that change so theme loading performs a second full
|
|
1179
|
+
// replay with the newly detected palette.
|
|
1180
|
+
onTerminalAppearanceChange(mode, appearanceRefreshWasRequested ? {} : undefined);
|
|
1146
1181
|
});
|
|
1147
1182
|
|
|
1148
1183
|
// A branch change (checkout, worktree switch, `git switch`) invalidates
|
|
@@ -3932,6 +3967,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3932
3967
|
}
|
|
3933
3968
|
|
|
3934
3969
|
stop(): void {
|
|
3970
|
+
this.#appearanceRefreshRequest = undefined;
|
|
3935
3971
|
if (this.loadingAnimation) {
|
|
3936
3972
|
this.#stopLoadingAnimation(false);
|
|
3937
3973
|
}
|
|
@@ -4752,6 +4788,27 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4752
4788
|
this.#inputController.handleCtrlZ();
|
|
4753
4789
|
}
|
|
4754
4790
|
|
|
4791
|
+
resetDisplayAfterAppearanceRefresh(): void {
|
|
4792
|
+
const refreshAppearance = this.ui.terminal.refreshAppearance;
|
|
4793
|
+
if (refreshAppearance) {
|
|
4794
|
+
const token = this.#nextAppearanceRequestToken++;
|
|
4795
|
+
const request = {
|
|
4796
|
+
token,
|
|
4797
|
+
deadline: Date.now() + CTRL_L_APPEARANCE_RESPONSE_DEADLINE_MS,
|
|
4798
|
+
};
|
|
4799
|
+
this.#appearanceRefreshRequest = request;
|
|
4800
|
+
const acceptedToken = refreshAppearance.call(this.ui.terminal, token);
|
|
4801
|
+
if (acceptedToken !== token && this.#appearanceRefreshRequest === request) {
|
|
4802
|
+
this.#appearanceRefreshRequest = undefined;
|
|
4803
|
+
}
|
|
4804
|
+
} else {
|
|
4805
|
+
this.#appearanceRefreshRequest = undefined;
|
|
4806
|
+
}
|
|
4807
|
+
// Preserve Ctrl+L's immediate full replay when the probe is unsupported,
|
|
4808
|
+
// receives no response, or reports an unchanged appearance.
|
|
4809
|
+
this.ui.resetDisplay();
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4755
4812
|
handleDequeue(): void {
|
|
4756
4813
|
this.#inputController.handleDequeue();
|
|
4757
4814
|
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -2323,10 +2323,13 @@ export function setAutoThemeMapping(mode: "dark" | "light", themeName: string):
|
|
|
2323
2323
|
* The terminal layer queries OSC 11 (background color) and computes luminance;
|
|
2324
2324
|
* Mode 2031 notifications trigger re-queries rather than providing the value directly.
|
|
2325
2325
|
*/
|
|
2326
|
-
export function onTerminalAppearanceChange(
|
|
2326
|
+
export function onTerminalAppearanceChange(
|
|
2327
|
+
mode: "dark" | "light",
|
|
2328
|
+
event: ThemeChangeEvent = { ephemeral: true },
|
|
2329
|
+
): void {
|
|
2327
2330
|
if (terminalReportedAppearance === mode) return;
|
|
2328
2331
|
terminalReportedAppearance = mode;
|
|
2329
|
-
reevaluateAutoTheme("terminal appearance");
|
|
2332
|
+
reevaluateAutoTheme("terminal appearance", event);
|
|
2330
2333
|
}
|
|
2331
2334
|
|
|
2332
2335
|
export function setThemeInstance(themeInstance: Theme): void {
|
|
@@ -2530,6 +2533,10 @@ function reevaluateAutoTheme(debugLabel: string, event: ThemeChangeEvent = {}, a
|
|
|
2530
2533
|
applyResolvedAutoTheme(resolved, debugLabel, event);
|
|
2531
2534
|
}
|
|
2532
2535
|
|
|
2536
|
+
function reevaluateAutoThemeForAppearance(debugLabel: string, appearance?: "dark" | "light"): void {
|
|
2537
|
+
reevaluateAutoTheme(debugLabel, { ephemeral: true }, appearance);
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2533
2540
|
// ============================================================================
|
|
2534
2541
|
// macOS Appearance Fallback Observer
|
|
2535
2542
|
// ============================================================================
|
|
@@ -2606,7 +2613,7 @@ export function startMacOSAppearanceReprobeFallback(terminal: MacOSAppearanceRep
|
|
|
2606
2613
|
const appearance = probeResponseConfirmed ? terminal.appearance : undefined;
|
|
2607
2614
|
cancelProbeSequence();
|
|
2608
2615
|
if (!autoDetectedTheme || !appearance) return;
|
|
2609
|
-
|
|
2616
|
+
reevaluateAutoThemeForAppearance("macOS appearance reconciliation", appearance);
|
|
2610
2617
|
}, MACOS_APPEARANCE_RECONCILE_DELAY_MS);
|
|
2611
2618
|
reconciliationTimer.unref?.();
|
|
2612
2619
|
};
|
|
@@ -2635,7 +2642,7 @@ export function startMacOSAppearanceReprobeFallback(terminal: MacOSAppearanceRep
|
|
|
2635
2642
|
return;
|
|
2636
2643
|
}
|
|
2637
2644
|
if (appearance === "dark" || appearance === "light") {
|
|
2638
|
-
|
|
2645
|
+
reevaluateAutoThemeForAppearance("macOS provisional appearance", appearance);
|
|
2639
2646
|
}
|
|
2640
2647
|
scheduleProbeSequence();
|
|
2641
2648
|
});
|
|
@@ -2670,7 +2677,7 @@ function startMacAppearanceObserver(): void {
|
|
|
2670
2677
|
macObserver = MacAppearanceObserver.start((err, appearance) => {
|
|
2671
2678
|
if (!err && (appearance === "dark" || appearance === "light")) {
|
|
2672
2679
|
macOSReportedAppearance = appearance;
|
|
2673
|
-
|
|
2680
|
+
reevaluateAutoThemeForAppearance("macOS fallback");
|
|
2674
2681
|
}
|
|
2675
2682
|
});
|
|
2676
2683
|
} catch (err) {
|
|
@@ -2694,7 +2701,7 @@ function stopMacAppearanceObserver(): void {
|
|
|
2694
2701
|
function startSigwinchListener(): void {
|
|
2695
2702
|
stopSigwinchListener();
|
|
2696
2703
|
sigwinchHandler = () => {
|
|
2697
|
-
|
|
2704
|
+
reevaluateAutoThemeForAppearance("SIGWINCH");
|
|
2698
2705
|
};
|
|
2699
2706
|
process.on("SIGWINCH", sigwinchHandler);
|
|
2700
2707
|
startMacAppearanceObserver();
|
package/src/modes/types.ts
CHANGED
|
@@ -401,6 +401,8 @@ export interface InteractiveModeContext {
|
|
|
401
401
|
handleCtrlC(): void;
|
|
402
402
|
handleCtrlD(): void;
|
|
403
403
|
handleCtrlZ(): void;
|
|
404
|
+
/** Re-query terminal appearance for an explicit display reset, then immediately replay the display. */
|
|
405
|
+
resetDisplayAfterAppearanceRefresh(): void;
|
|
404
406
|
handleDequeue(): void;
|
|
405
407
|
handleImagePaste(): Promise<boolean>;
|
|
406
408
|
/** Queue a message for delivery only after the active agent turn would stop. */
|
|
@@ -51,6 +51,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
51
51
|
`| \`${appKey(bindings, "app.retry")}\` | Retry last failed assistant turn |`,
|
|
52
52
|
`| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image or text from clipboard |`,
|
|
53
53
|
"| Hold `Space` | Speech-to-text (push-to-talk): hold to record, release to transcribe |",
|
|
54
|
+
`| \`${appKey(bindings, "app.live.toggle")}\` | Start/stop live voice mode (/live) |`,
|
|
54
55
|
`| \`${appKey(bindings, "app.agents.hub")}\` / \`${appKey(bindings, "app.session.observe")}\` / double-tap \`←\` (empty editor) | Open the agent hub |`,
|
|
55
56
|
"| `#<number>` | GitHub issue/PR reference (e.g. `#3164` → `pr://`/`issue://`) |",
|
|
56
57
|
"| `#` / `#<text>` | Prompt actions (copy / undo / move cursor) |",
|
|
@@ -28,12 +28,12 @@ Choose a short kebab-case `<slug>` naming this task and write the plan to `local
|
|
|
28
28
|
Use `{{editToolName}}` for incremental edits and `{{writeToolName}}` only to create or fully replace the file. You MUST write findings into the plan as you learn them — you NEVER batch all writing to the end.
|
|
29
29
|
|
|
30
30
|
{{#if isHashlineEditMode}}
|
|
31
|
-
Structure the plan as `##`/`###` markdown sections so you can revise it section-by-section: with `{{editToolName}}`, a heading
|
|
32
|
-
- `
|
|
33
|
-
- `CUT
|
|
34
|
-
- `
|
|
31
|
+
Structure the plan as `##`/`###` markdown sections so you can revise it section-by-section: with `{{editToolName}}`, the `N*` locator targets a heading's WHOLE section (through every nested deeper heading, up to the next same-or-higher heading). Use composable locators to grow the plan without rewriting the file:
|
|
32
|
+
- `PUT N*:` on a heading line — rewrite that entire section in place.
|
|
33
|
+
- `CUT N*` on a heading line — drop the whole section.
|
|
34
|
+
- `PUT >N*:` on a heading line — add a new section AFTER that one (end the inserted body with a blank line so the next heading stays separated).
|
|
35
35
|
|
|
36
|
-
Write each section together with its body —
|
|
36
|
+
Write each section together with its body — `N*` needs a multi-line section; a bare heading with no body falls back to plain `PUT >N:`/`CUT N`/`PUT N:`.
|
|
37
37
|
{{/if}}
|
|
38
38
|
|
|
39
39
|
## Ground every claim
|
package/src/sdk.ts
CHANGED
|
@@ -67,6 +67,7 @@ import { createBridgeEditTool, createBridgeGrepFactory } from "./cursor-bridge-t
|
|
|
67
67
|
import "./discovery";
|
|
68
68
|
import { initializeWithSettings } from "./discovery";
|
|
69
69
|
import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor";
|
|
70
|
+
import { disposeVmContextsByOwner } from "./eval/js/context-manager";
|
|
70
71
|
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
|
|
71
72
|
import { disposeAllRubyKernelSessions, disposeRubyKernelSessionsByOwner } from "./eval/rb/executor";
|
|
72
73
|
import { defaultEvalSessionId } from "./eval/session-id";
|
|
@@ -119,11 +120,13 @@ import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with
|
|
|
119
120
|
import { AgentLifecycleManager } from "./registry/agent-lifecycle";
|
|
120
121
|
import { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
121
122
|
import {
|
|
123
|
+
builtinCredentialSecretEntries,
|
|
122
124
|
collectEnvSecrets,
|
|
123
125
|
deobfuscateSessionContext,
|
|
124
126
|
deobfuscateToolArguments,
|
|
125
127
|
getExistingSecretPlaceholderKey,
|
|
126
128
|
getSecretPlaceholderKey,
|
|
129
|
+
getSecretPlaceholderKeySync,
|
|
127
130
|
loadSecrets,
|
|
128
131
|
obfuscateMessages,
|
|
129
132
|
obfuscateProviderContext,
|
|
@@ -1359,21 +1362,26 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1359
1362
|
if (settings.get("secrets.enabled")) {
|
|
1360
1363
|
const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
|
|
1361
1364
|
const envEntries = collectEnvSecrets();
|
|
1362
|
-
|
|
1363
|
-
|
|
1365
|
+
// Built-in credential-pattern entries come last so user-configured entries
|
|
1366
|
+
// (plain literals, custom regexes) take precedence in the scan order.
|
|
1367
|
+
const allEntries = [...envEntries, ...fileEntries, ...builtinCredentialSecretEntries()];
|
|
1368
|
+
// Only CONFIGURED entries force startup key creation: a configured
|
|
1369
|
+
// obfuscate-mode secret — or a default (no custom `replacement`)
|
|
1370
|
+
// replace-mode regex whose key-derived idempotent fallback marker needs a
|
|
1371
|
+
// stable key across restarts (see `secretEntryNeedsPlaceholderKey`) —
|
|
1372
|
+
// mints placeholders as soon as the obfuscator is built. The built-in
|
|
1373
|
+
// credential-pattern entry matches dynamically, so it resolves the
|
|
1374
|
+
// persisted key lazily on first match instead of creating the key file
|
|
1375
|
+
// for every secrets.enabled session; a session whose content never
|
|
1376
|
+
// contains a credential-shaped token must not require the key, otherwise a
|
|
1377
|
+
// headless run on an unwritable default config root pays for a feature it
|
|
1378
|
+
// does not use.
|
|
1379
|
+
const needsPlaceholderKey = secretEntriesNeedPlaceholderKey([...envEntries, ...fileEntries]);
|
|
1364
1380
|
const placeholderKey = needsPlaceholderKey
|
|
1365
1381
|
? await getSecretPlaceholderKey(agentDir)
|
|
1366
1382
|
: await getExistingSecretPlaceholderKey(agentDir);
|
|
1367
1383
|
if (allEntries.length > 0) {
|
|
1368
|
-
|
|
1369
|
-
// configured agentDir — is only needed for reversible obfuscate-mode
|
|
1370
|
-
// placeholders, or for a default (no custom `replacement`) replace-mode
|
|
1371
|
-
// regex whose key-derived idempotent fallback marker needs a stable key
|
|
1372
|
-
// across restarts (see `secretEntryNeedsPlaceholderKey`). A replace-only
|
|
1373
|
-
// secrets set with no such regex must not require the key; otherwise a
|
|
1374
|
-
// headless run with an unwritable default config root fails startup for a
|
|
1375
|
-
// feature it does not use.
|
|
1376
|
-
obfuscator = new SecretObfuscator(allEntries, placeholderKey);
|
|
1384
|
+
obfuscator = new SecretObfuscator(allEntries, placeholderKey ?? (() => getSecretPlaceholderKeySync(agentDir)));
|
|
1377
1385
|
}
|
|
1378
1386
|
if (obfuscator?.hasSecrets() !== true && placeholderKey !== undefined) {
|
|
1379
1387
|
// No configured entry produced an active secret (e.g. only ignored short
|
|
@@ -2564,6 +2572,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2564
2572
|
autoApprove: options.autoApprove ?? false,
|
|
2565
2573
|
});
|
|
2566
2574
|
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
2575
|
+
// Native built-in implementations backing same-tool `ctx.invokeTool`, so a tool that
|
|
2576
|
+
// re-registers a built-in (e.g. wrapping `write`) can delegate to the original — reaching the
|
|
2577
|
+
// unwrapped native execute, which inherits the caller's already-granted approval rather than
|
|
2578
|
+
// re-running the gate. Seeded from the xdev registry when present (it retains discoverable
|
|
2579
|
+
// built-ins like `browser` that xdev partitioning removes from the active tool array), else
|
|
2580
|
+
// from the built-in registry; captured before the ExtensionToolWrapper pass so the natives
|
|
2581
|
+
// stay unwrapped. The extension runner exposes it to re-registered tools via createContext.
|
|
2582
|
+
const nativeToolsByName = new Map<string, Tool>(toolSession.xdev?.tools ?? undefined);
|
|
2567
2583
|
|
|
2568
2584
|
const registeredTools = restrictToolNames ? [] : extensionRunner.getAllRegisteredTools();
|
|
2569
2585
|
const sdkCustomTools =
|
|
@@ -2587,17 +2603,32 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2587
2603
|
|
|
2588
2604
|
// All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
|
|
2589
2605
|
const builtInRegistryToolNames = toolSession.xdev?.builtInNames ?? new Set(toolRegistry.keys());
|
|
2606
|
+
// Capture the native built-in implementations before extension re-registration replaces registry
|
|
2607
|
+
// entries and before the ExtensionToolWrapper pass below, so `ctx.invokeTool` reaches the
|
|
2608
|
+
// unwrapped native execute (inheriting the caller's already-granted approval, not re-gating).
|
|
2609
|
+
for (const [name, tool] of toolRegistry) {
|
|
2610
|
+
nativeToolsByName.set(name, tool);
|
|
2611
|
+
}
|
|
2590
2612
|
if (!restrictToolNames && !toolRegistry.has("goal") && settings.get("goal.enabled")) {
|
|
2591
2613
|
const goalTool = await logger.time("createTools:goal:session", HIDDEN_TOOLS.goal, toolSession);
|
|
2592
2614
|
if (goalTool) {
|
|
2593
|
-
|
|
2615
|
+
const wrapped = wrapToolWithMetaNotice(goalTool);
|
|
2616
|
+
toolRegistry.set(goalTool.name, wrapped);
|
|
2594
2617
|
builtInRegistryToolNames.add(goalTool.name);
|
|
2618
|
+
nativeToolsByName.set(goalTool.name, wrapped);
|
|
2595
2619
|
}
|
|
2596
2620
|
}
|
|
2597
2621
|
for (const tool of wrappedExtensionTools) {
|
|
2598
2622
|
toolRegistry.set(tool.name, tool);
|
|
2599
2623
|
builtInRegistryToolNames.delete(tool.name);
|
|
2600
2624
|
}
|
|
2625
|
+
// Expose the native built-ins to same-tool `ctx.invokeTool` on re-registered tools. Set after
|
|
2626
|
+
// the override loop so the map holds the natives, not the extension replacements. The context
|
|
2627
|
+
// factory is the loop's own tool context, so a delegated native call sees ordinary session state.
|
|
2628
|
+
extensionRunner.setNativeToolResolver(name => {
|
|
2629
|
+
const tool = nativeToolsByName.get(name);
|
|
2630
|
+
return tool ? { tool, makeContext: () => toolContextStore.getContext() } : undefined;
|
|
2631
|
+
});
|
|
2601
2632
|
if (deferMCPDiscoveryForUI && mcpManager) {
|
|
2602
2633
|
for (const name of collectPendingMCPToolNames(options.toolNames)) {
|
|
2603
2634
|
if (!toolRegistry.has(name)) {
|
|
@@ -2660,11 +2691,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2660
2691
|
writeRegistration ??= (async () => {
|
|
2661
2692
|
const writeTool = await logger.time("createTools:write:session", BUILTIN_TOOLS.write, toolSession);
|
|
2662
2693
|
if (!writeTool || toolRegistry.has("write")) return builtInRegistryToolNames.has("write");
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
new ExtensionToolWrapper(wrapToolWithMetaNotice(writeTool), extensionRunner) as Tool,
|
|
2666
|
-
);
|
|
2694
|
+
const nativeWrite = wrapToolWithMetaNotice(writeTool);
|
|
2695
|
+
toolRegistry.set(writeTool.name, new ExtensionToolWrapper(nativeWrite, extensionRunner) as Tool);
|
|
2667
2696
|
builtInRegistryToolNames.add(writeTool.name);
|
|
2697
|
+
nativeToolsByName.set(writeTool.name, nativeWrite);
|
|
2668
2698
|
return true;
|
|
2669
2699
|
})().finally(() => {
|
|
2670
2700
|
writeRegistration = undefined;
|
|
@@ -3715,6 +3745,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3715
3745
|
await disposeKernelSessionsByOwner(evalKernelOwnerId);
|
|
3716
3746
|
await disposeRubyKernelSessionsByOwner(evalKernelOwnerId);
|
|
3717
3747
|
await disposeJuliaKernelSessionsByOwner(evalKernelOwnerId);
|
|
3748
|
+
await disposeVmContextsByOwner(evalKernelOwnerId);
|
|
3718
3749
|
if (ownsAuthStorage) authStorage.close();
|
|
3719
3750
|
}
|
|
3720
3751
|
} catch (cleanupError) {
|