@oh-my-pi/pi-coding-agent 17.1.7 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/dist/{CHANGELOG-5k4dq4g6.md → CHANGELOG-be1f2t8h.md} +127 -0
- package/dist/cli.js +9554 -12379
- package/dist/template-8vdv6xb4.js +1671 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/dist/types/advisor/advise-tool.d.ts +0 -7
- package/dist/types/advisor/runtime.d.ts +8 -11
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/async/job-manager.d.ts +11 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cleanse/agent.d.ts +19 -0
- package/dist/types/cleanse/balance.d.ts +7 -0
- package/dist/types/cleanse/checkers.d.ts +13 -0
- package/dist/types/cleanse/index.d.ts +16 -0
- package/dist/types/cleanse/loop.d.ts +16 -0
- package/dist/types/cleanse/parsers.d.ts +13 -0
- package/dist/types/cleanse/progress.d.ts +14 -0
- package/dist/types/cleanse/types.d.ts +64 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/cleanse.d.ts +23 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/export/html/index.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +8 -5
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/async-job-delivery.d.ts +8 -0
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/model-controls.d.ts +3 -3
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/isolation-ownership.d.ts +34 -0
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
- package/dist/types/tools/browser/tab-worker.d.ts +1 -4
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/tui/output-block.d.ts +5 -5
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +16 -12
- package/scripts/bundle-dist.ts +3 -1
- package/src/advisor/advise-tool.ts +0 -11
- package/src/advisor/runtime.ts +138 -24
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/async/job-manager.ts +30 -7
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli/update-cli.ts +3 -0
- package/src/cli/usage-cli.ts +29 -4
- package/src/cli/worktree-cli.ts +28 -11
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +11 -1
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/discovery/claude-plugins.ts +144 -34
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/index.ts +17 -10
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/broker.ts +14 -4
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +27 -18
- package/src/modes/acp/acp-event-mapper.ts +38 -4
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/settings-selector.ts +9 -1
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +67 -64
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/goals/guided-goal-interview.md +41 -6
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/vibe-mode-active.md +4 -1
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +330 -179
- package/src/session/async-job-delivery.ts +8 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +15 -7
- package/src/session/session-advisors.ts +142 -23
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-history-format.ts +31 -6
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +136 -5
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/render.ts +20 -4
- package/src/task/worktree.ts +8 -0
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/ask.ts +3 -3
- package/src/tools/ast-edit.ts +9 -2
- package/src/tools/browser/cmux/cmux-tab.ts +9 -14
- package/src/tools/browser/tab-worker.ts +12 -35
- package/src/tools/browser.ts +7 -2
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/gh-renderer.ts +3 -3
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +38 -5
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +44 -3
- package/src/tools/xdev.ts +39 -14
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/output-block.ts +25 -8
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/utils/shell-snapshot-fn-env.sh +5 -2
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/render.ts +2 -2
- package/src/web/search/types.ts +1 -1
- package/dist/types/goals/guided-setup.d.ts +0 -30
- package/src/goals/guided-setup.ts +0 -171
- package/src/prompts/goals/guided-goal-system.md +0 -33
package/src/lsp/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { formatPathRelativeToCwd, resolveToCwd } from "../tools/path-utils";
|
|
|
17
17
|
import { ToolAbortError, ToolError, throwIfAborted } from "../tools/tool-errors";
|
|
18
18
|
import { clampTimeout } from "../tools/tool-timeouts";
|
|
19
19
|
import {
|
|
20
|
+
clearInitializationFailure,
|
|
20
21
|
ensureFileOpen,
|
|
21
22
|
FileChangeType,
|
|
22
23
|
getActiveClients,
|
|
@@ -2345,6 +2346,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2345
2346
|
const outputs: string[] = [];
|
|
2346
2347
|
for (const [workspaceServerName, workspaceServerConfig] of servers) {
|
|
2347
2348
|
throwIfAborted(signal);
|
|
2349
|
+
clearInitializationFailure(workspaceServerConfig, this.session.cwd);
|
|
2348
2350
|
try {
|
|
2349
2351
|
const workspaceClient = await getOrCreateClient(
|
|
2350
2352
|
workspaceServerConfig,
|
|
@@ -2377,6 +2379,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2377
2379
|
|
|
2378
2380
|
const [serverName, serverConfig] = serverInfo;
|
|
2379
2381
|
|
|
2382
|
+
if (action === "reload") clearInitializationFailure(serverConfig, this.session.cwd);
|
|
2383
|
+
|
|
2380
2384
|
try {
|
|
2381
2385
|
const client = await getOrCreateClient(serverConfig, this.session.cwd, undefined, signal);
|
|
2382
2386
|
const targetFile = resolvedFile;
|
package/src/lsp/render.ts
CHANGED
|
@@ -582,7 +582,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
582
582
|
for (let i = 0; i < lines.length; i++) {
|
|
583
583
|
const isLast = i === lines.length - 1;
|
|
584
584
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
585
|
-
output += `\n ${theme.fg("dim", branch)} ${lines[i]}`;
|
|
585
|
+
output += `\n ${theme.fg("dim", branch)} ${truncateToWidth(replaceTabs(lines[i]), TRUNCATE_LENGTHS.CONTENT)}`;
|
|
586
586
|
}
|
|
587
587
|
return output.split("\n");
|
|
588
588
|
}
|
package/src/main.ts
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
type ScopedModel,
|
|
42
42
|
} from "./config/model-resolver";
|
|
43
43
|
import { ModelsConfigFile } from "./config/models-config";
|
|
44
|
-
import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
|
|
44
|
+
import { getDefault, type SettingPath, Settings, type SettingValue, settings } from "./config/settings";
|
|
45
45
|
import { initializeWithSettings } from "./discovery";
|
|
46
46
|
import {
|
|
47
47
|
clearPluginRootsAndCaches,
|
|
@@ -74,6 +74,13 @@ import {
|
|
|
74
74
|
import type { AgentSession } from "./session/agent-session";
|
|
75
75
|
import type { AuthStorage } from "./session/auth-storage";
|
|
76
76
|
import { describePendingToolCalls } from "./session/exit-diagnostics";
|
|
77
|
+
import {
|
|
78
|
+
createForeignSessionStore,
|
|
79
|
+
foreignSessionInfoToSessionInfo,
|
|
80
|
+
foreignSessionSourceName,
|
|
81
|
+
persistForeignSession,
|
|
82
|
+
} from "./session/foreign-session-import";
|
|
83
|
+
import type { ForeignSessionInfo, ForeignSessionSource, ForeignSessionStore } from "./session/foreign-session-store";
|
|
77
84
|
import { resolveResumableSession, type SessionInfo } from "./session/session-listing";
|
|
78
85
|
import { SessionManager } from "./session/session-manager";
|
|
79
86
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
@@ -83,14 +90,7 @@ import { createPersistedSubagentReviverFactory } from "./task/persisted-revive";
|
|
|
83
90
|
import { createTelemetryExportConfig, initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
|
|
84
91
|
import { concreteThinkingLevel, parseConfiguredThinkingLevel } from "./thinking";
|
|
85
92
|
import type { LspStartupServerInfo } from "./tools";
|
|
86
|
-
import {
|
|
87
|
-
getChangelogPath,
|
|
88
|
-
parseChangelog,
|
|
89
|
-
parseChangelogVersion,
|
|
90
|
-
readLastChangelogVersion,
|
|
91
|
-
selectStartupChangelog,
|
|
92
|
-
writeLastChangelogVersion,
|
|
93
|
-
} from "./utils/changelog";
|
|
93
|
+
import { getChangelogPath, resolveStartupChangelogForDisplay, type StartupChangelogSelection } from "./utils/changelog";
|
|
94
94
|
import { EventBus } from "./utils/event-bus";
|
|
95
95
|
import { withTimeoutSignal } from "./utils/fetch-timeout";
|
|
96
96
|
|
|
@@ -408,7 +408,7 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
|
|
|
408
408
|
async function runInteractiveMode(
|
|
409
409
|
session: AgentSession,
|
|
410
410
|
version: string,
|
|
411
|
-
|
|
411
|
+
startupChangelog: StartupChangelogSelection | undefined,
|
|
412
412
|
notifs: (InteractiveModeNotify | null)[],
|
|
413
413
|
versionCheckPromise: Promise<string | undefined>,
|
|
414
414
|
initialMessages: string[],
|
|
@@ -426,7 +426,7 @@ async function runInteractiveMode(
|
|
|
426
426
|
const mode = new InteractiveMode(
|
|
427
427
|
session,
|
|
428
428
|
version,
|
|
429
|
-
|
|
429
|
+
startupChangelog,
|
|
430
430
|
setExtensionUIContext,
|
|
431
431
|
lspServers,
|
|
432
432
|
mcpManager,
|
|
@@ -565,6 +565,27 @@ export class SessionResolutionError extends Error {
|
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
+
function resolveForeignSessionSource(
|
|
569
|
+
parsed: Pick<Args, "continue" | "fork" | "fromClaude" | "fromCodex" | "noSession" | "resume">,
|
|
570
|
+
): ForeignSessionSource | undefined {
|
|
571
|
+
if (parsed.fromClaude && parsed.fromCodex) {
|
|
572
|
+
throw new SessionResolutionError("--from-claude and --from-codex cannot be used together");
|
|
573
|
+
}
|
|
574
|
+
const source = parsed.fromClaude ? "claude" : parsed.fromCodex ? "codex" : undefined;
|
|
575
|
+
if (!source) return undefined;
|
|
576
|
+
if (parsed.noSession) {
|
|
577
|
+
throw new SessionResolutionError(`--from-${source} requires session persistence`);
|
|
578
|
+
}
|
|
579
|
+
if (parsed.continue || parsed.resume || parsed.fork) {
|
|
580
|
+
throw new SessionResolutionError(`--from-${source} cannot be combined with --continue, --resume, or --fork`);
|
|
581
|
+
}
|
|
582
|
+
return source;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function isForeignSessionImport(parsed: Pick<Args, "fromClaude" | "fromCodex">): boolean {
|
|
586
|
+
return parsed.fromClaude === true || parsed.fromCodex === true;
|
|
587
|
+
}
|
|
588
|
+
|
|
568
589
|
type MissingCwdMoveResult =
|
|
569
590
|
| { status: "not-needed" }
|
|
570
591
|
| { status: "declined" }
|
|
@@ -651,33 +672,19 @@ async function resolveScopedModels(
|
|
|
651
672
|
);
|
|
652
673
|
}
|
|
653
674
|
|
|
654
|
-
async function getChangelogForDisplay(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const lastVersion = await readLastChangelogVersion();
|
|
660
|
-
const parsedLastVersion = parseChangelogVersion(lastVersion);
|
|
661
|
-
if (!parsedLastVersion) {
|
|
662
|
-
await writeLastChangelogVersion(VERSION);
|
|
663
|
-
return undefined;
|
|
664
|
-
}
|
|
665
|
-
if (lastVersion === VERSION) {
|
|
666
|
-
// Steady state: user already saw the current version's changelog. Skip the file read + parse.
|
|
675
|
+
async function getChangelogForDisplay(
|
|
676
|
+
parsed: Args,
|
|
677
|
+
mode: SettingValue<"startup.changelogMode">,
|
|
678
|
+
): Promise<StartupChangelogSelection | undefined> {
|
|
679
|
+
if (parsed.continue || parsed.resume || isForeignSessionImport(parsed)) {
|
|
667
680
|
return undefined;
|
|
668
681
|
}
|
|
669
682
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
if (startupChangelog.markdown) {
|
|
677
|
-
return startupChangelog.markdown;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return undefined;
|
|
683
|
+
return resolveStartupChangelogForDisplay({
|
|
684
|
+
mode,
|
|
685
|
+
currentVersion: VERSION,
|
|
686
|
+
changelogPath: getChangelogPath(),
|
|
687
|
+
});
|
|
681
688
|
}
|
|
682
689
|
|
|
683
690
|
const SESSION_ID_ARG_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -852,6 +859,7 @@ export async function buildSessionOptions(
|
|
|
852
859
|
cwd: parsed.cwd ?? getProjectDir(),
|
|
853
860
|
autoApprove: parsed.autoApprove ?? false,
|
|
854
861
|
};
|
|
862
|
+
const restoringSession = Boolean(parsed.continue || parsed.resume || isForeignSessionImport(parsed));
|
|
855
863
|
const cliDirs = parsed.addDir ?? [];
|
|
856
864
|
const settingsDirs = activeSettings.get("workspace.additionalDirectories");
|
|
857
865
|
if (cliDirs.length > 0 || settingsDirs.length > 0) {
|
|
@@ -882,7 +890,7 @@ export async function buildSessionOptions(
|
|
|
882
890
|
options.providerPromptCacheKeySource = "explicit";
|
|
883
891
|
} else {
|
|
884
892
|
const header = sessionManager?.getHeader();
|
|
885
|
-
const scopedModelOverride = scopedModels.length > 0 && !
|
|
893
|
+
const scopedModelOverride = scopedModels.length > 0 && !restoringSession;
|
|
886
894
|
const forkCacheShapeChanged =
|
|
887
895
|
scopedModelOverride ||
|
|
888
896
|
parsed.model !== undefined ||
|
|
@@ -939,7 +947,7 @@ export async function buildSessionOptions(
|
|
|
939
947
|
options.thinkingLevel = resolved.thinkingLevel;
|
|
940
948
|
}
|
|
941
949
|
}
|
|
942
|
-
} else if (scopedModels.length > 0 && !
|
|
950
|
+
} else if (scopedModels.length > 0 && !restoringSession) {
|
|
943
951
|
const remembered = activeSettings.getModelRole("default");
|
|
944
952
|
if (remembered) {
|
|
945
953
|
const rememberedSpec = resolveModelRoleValue(
|
|
@@ -1042,8 +1050,7 @@ export async function buildSessionOptions(
|
|
|
1042
1050
|
// thinking suffix) after extensions register; seeding the fallback
|
|
1043
1051
|
// scoped model's level here would override it in createAgentSession.
|
|
1044
1052
|
!deferredDefaultRole &&
|
|
1045
|
-
!
|
|
1046
|
-
!parsed.resume
|
|
1053
|
+
!restoringSession
|
|
1047
1054
|
) {
|
|
1048
1055
|
options.thinkingLevel = scopedModels[0].thinkingLevel;
|
|
1049
1056
|
}
|
|
@@ -1119,6 +1126,7 @@ interface RunRootCommandDependencies {
|
|
|
1119
1126
|
discoverAuthStorage?: typeof discoverAuthStorage;
|
|
1120
1127
|
selectSession?: typeof selectSession;
|
|
1121
1128
|
runAcpMode?: RunAcpMode;
|
|
1129
|
+
createForeignSessionStore?: (source: ForeignSessionSource) => ForeignSessionStore;
|
|
1122
1130
|
settings?: Settings;
|
|
1123
1131
|
forceSetupWizard?: boolean;
|
|
1124
1132
|
}
|
|
@@ -1282,19 +1290,77 @@ export async function runRootCommand(
|
|
|
1282
1290
|
// id from UUID-shaped values owned by later extension flags.
|
|
1283
1291
|
normalizeContinueSessionArgs(parsedArgs, rawArgs);
|
|
1284
1292
|
|
|
1285
|
-
//
|
|
1286
|
-
//
|
|
1287
|
-
// prompt, --fork with --no-session): print + exit cleanly instead of letting
|
|
1288
|
-
// it surface as `[Uncaught Exception]` (see issue #2084).
|
|
1293
|
+
// Resolve native resume/fork flags or import one foreign transcript into a
|
|
1294
|
+
// fresh persisted OMP session before constructing the AgentSession.
|
|
1289
1295
|
let sessionManager: SessionManager | undefined;
|
|
1296
|
+
let foreignSource: ForeignSessionSource | undefined;
|
|
1290
1297
|
try {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
+
foreignSource = resolveForeignSessionSource(parsedArgs);
|
|
1299
|
+
if (foreignSource) {
|
|
1300
|
+
if (isProtocolMode) {
|
|
1301
|
+
throw new SessionResolutionError(`--from-${foreignSource} is not supported in ${mode} mode`);
|
|
1302
|
+
}
|
|
1303
|
+
const sourceName = foreignSessionSourceName(foreignSource);
|
|
1304
|
+
const store = (deps.createForeignSessionStore ?? createForeignSessionStore)(foreignSource);
|
|
1305
|
+
let foreignSessions: ForeignSessionInfo[];
|
|
1306
|
+
try {
|
|
1307
|
+
foreignSessions = await logger.time(`list${sourceName}Sessions`, () => store.list());
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1310
|
+
throw new SessionResolutionError(`Failed to list ${sourceName} sessions: ${message}`);
|
|
1311
|
+
}
|
|
1312
|
+
if (foreignSessions.length === 0) {
|
|
1313
|
+
writeStartupNotice(parsedArgs, `${chalk.dim(`No ${sourceName} sessions found`)}\n`);
|
|
1314
|
+
stopStartupWatchdog();
|
|
1315
|
+
process.exit(0);
|
|
1316
|
+
}
|
|
1317
|
+
const choices = foreignSessions.map(foreignSessionInfoToSessionInfo);
|
|
1318
|
+
pauseStartupWatchdog();
|
|
1319
|
+
let selected: SessionInfo | null;
|
|
1320
|
+
try {
|
|
1321
|
+
selected = await logger.time(`select${sourceName}Session`, deps.selectSession ?? selectSession, choices, {
|
|
1322
|
+
title: `Import ${sourceName} Session`,
|
|
1323
|
+
scopeLabel: false,
|
|
1324
|
+
showCwd: true,
|
|
1325
|
+
allowDelete: false,
|
|
1326
|
+
allowGlobalScope: false,
|
|
1327
|
+
historySearch: false,
|
|
1328
|
+
});
|
|
1329
|
+
} finally {
|
|
1330
|
+
resumeStartupWatchdog();
|
|
1331
|
+
}
|
|
1332
|
+
if (!selected) {
|
|
1333
|
+
writeStartupNotice(parsedArgs, `${chalk.dim(`No ${sourceName} session selected`)}\n`);
|
|
1334
|
+
stopStartupWatchdog();
|
|
1335
|
+
process.exit(0);
|
|
1336
|
+
}
|
|
1337
|
+
const foreignSession = foreignSessions.find(
|
|
1338
|
+
session => session.id === selected.id && session.path === selected.path,
|
|
1339
|
+
);
|
|
1340
|
+
if (!foreignSession) {
|
|
1341
|
+
throw new SessionResolutionError(`Selected ${sourceName} session is no longer available`);
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
sessionManager = await logger.time(
|
|
1345
|
+
`import${sourceName}Session`,
|
|
1346
|
+
persistForeignSession,
|
|
1347
|
+
store,
|
|
1348
|
+
foreignSession,
|
|
1349
|
+
{ fallbackCwd: cwd, sessionDir: parsedArgs.sessionDir },
|
|
1350
|
+
);
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1353
|
+
throw new SessionResolutionError(`Failed to import ${sourceName} session: ${message}`);
|
|
1354
|
+
}
|
|
1355
|
+
} else {
|
|
1356
|
+
sessionManager = await logger.time(
|
|
1357
|
+
"createSessionManager",
|
|
1358
|
+
createSessionManager,
|
|
1359
|
+
parsedArgs,
|
|
1360
|
+
cwd,
|
|
1361
|
+
settingsInstance,
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1298
1364
|
} catch (error: unknown) {
|
|
1299
1365
|
if (error instanceof SessionResolutionError) {
|
|
1300
1366
|
process.stderr.write(`${chalk.red(`Error: ${error.message}`)}\n`);
|
|
@@ -1306,7 +1372,7 @@ export async function runRootCommand(
|
|
|
1306
1372
|
throw error;
|
|
1307
1373
|
}
|
|
1308
1374
|
|
|
1309
|
-
if (typeof parsedArgs.resume === "string" && sessionManager) {
|
|
1375
|
+
if ((typeof parsedArgs.resume === "string" || foreignSource) && sessionManager) {
|
|
1310
1376
|
const previousCwd = cwd;
|
|
1311
1377
|
cwd = await switchToResumedProject(sessionManager.getCwd(), settingsInstance, pluginPreloadPromise);
|
|
1312
1378
|
if (cwd !== previousCwd) {
|
|
@@ -1372,7 +1438,7 @@ export async function runRootCommand(
|
|
|
1372
1438
|
sessionManager = await SessionManager.open(selected.path);
|
|
1373
1439
|
}
|
|
1374
1440
|
|
|
1375
|
-
if (sessionManager && (parsedArgs.continue || parsedArgs.resume || parsedArgs.fork)) {
|
|
1441
|
+
if (sessionManager && (parsedArgs.continue || parsedArgs.resume || parsedArgs.fork || foreignSource)) {
|
|
1376
1442
|
const pendingToolWarning = describePendingToolCalls(sessionManager.getBranch());
|
|
1377
1443
|
if (pendingToolWarning) {
|
|
1378
1444
|
logger.warn("Resumed session has pending tool calls", {
|
|
@@ -1515,7 +1581,7 @@ export async function runRootCommand(
|
|
|
1515
1581
|
const showStartupSplash = shouldShowStartupSplash({
|
|
1516
1582
|
configured: settingsInstance.get("startup.showSplash"),
|
|
1517
1583
|
isInteractive,
|
|
1518
|
-
resuming: Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork),
|
|
1584
|
+
resuming: Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork || foreignSource),
|
|
1519
1585
|
quiet: settingsInstance.get("startup.quiet"),
|
|
1520
1586
|
timing: Boolean($env.PI_TIMING),
|
|
1521
1587
|
stdinIsTTY: process.stdin.isTTY,
|
|
@@ -1580,7 +1646,12 @@ export async function runRootCommand(
|
|
|
1580
1646
|
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus, rpcInput);
|
|
1581
1647
|
} else if (isInteractive) {
|
|
1582
1648
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
1583
|
-
const
|
|
1649
|
+
const startupChangelog = await logger.time(
|
|
1650
|
+
"main:getChangelogForDisplay",
|
|
1651
|
+
getChangelogForDisplay,
|
|
1652
|
+
parsedArgs,
|
|
1653
|
+
settingsInstance.get("startup.changelogMode"),
|
|
1654
|
+
);
|
|
1584
1655
|
|
|
1585
1656
|
const modelScopeNotification = buildModelScopeNotification(
|
|
1586
1657
|
scopedModels,
|
|
@@ -1605,14 +1676,14 @@ export async function runRootCommand(
|
|
|
1605
1676
|
await runInteractiveMode(
|
|
1606
1677
|
session,
|
|
1607
1678
|
VERSION,
|
|
1608
|
-
|
|
1679
|
+
startupChangelog,
|
|
1609
1680
|
notifs,
|
|
1610
1681
|
versionCheckPromise,
|
|
1611
1682
|
initialArgs.messages,
|
|
1612
1683
|
setToolUIContext,
|
|
1613
1684
|
lspServers,
|
|
1614
1685
|
mcpManager,
|
|
1615
|
-
Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork),
|
|
1686
|
+
Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork || foreignSource),
|
|
1616
1687
|
deps.forceSetupWizard === true,
|
|
1617
1688
|
showStartupSplash,
|
|
1618
1689
|
eventBus,
|
package/src/mcp/manager.ts
CHANGED
|
@@ -94,6 +94,14 @@ const STARTUP_TIMEOUT_MS = 250;
|
|
|
94
94
|
const RECONNECT_BURST_WINDOW_MS = 30_000;
|
|
95
95
|
const RECONNECT_BURST_LIMIT = 5;
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Bounded buffer for notifications received before any listener attaches.
|
|
99
|
+
* Mirrors {@link IrcBus}'s `MAILBOX_CAP` — drop-oldest on overflow. Drained
|
|
100
|
+
* into the first {@link MCPManager.addNotificationListener} subscriber, then
|
|
101
|
+
* cleared; subsequent frames deliver directly to attached listeners.
|
|
102
|
+
*/
|
|
103
|
+
const NOTIFICATION_BUFFER_CAP = 100;
|
|
104
|
+
|
|
97
105
|
function trackPromise<T>(promise: Promise<T>): TrackedPromise<T> {
|
|
98
106
|
const tracked: TrackedPromise<T> = { promise, status: "pending" };
|
|
99
107
|
promise.then(
|
|
@@ -194,8 +202,14 @@ export class MCPManager {
|
|
|
194
202
|
#sources = new Map<string, SourceMeta>();
|
|
195
203
|
#authStorage: AuthStorage | null = null;
|
|
196
204
|
#authHandler?: MCPAuthHandler;
|
|
197
|
-
#
|
|
198
|
-
|
|
205
|
+
#notificationListeners = new Set<(serverName: string, method: string, params: unknown) => void>();
|
|
206
|
+
/**
|
|
207
|
+
* Notifications received before any listener attached, to be drained on
|
|
208
|
+
* the first {@link addNotificationListener} call. Bounded by
|
|
209
|
+
* {@link NOTIFICATION_BUFFER_CAP}, drop-oldest on overflow.
|
|
210
|
+
*/
|
|
211
|
+
#pendingNotifications: Array<{ server: string; method: string; params: unknown }> = [];
|
|
212
|
+
#onToolsChanged?: (tools: CustomTool<TSchema, MCPToolDetails>[]) => void | Promise<void>;
|
|
199
213
|
#onResourcesChanged?: (serverName: string, uri: string) => void;
|
|
200
214
|
#onPromptsChanged?: (serverName: string) => void;
|
|
201
215
|
#notificationsEnabled = false;
|
|
@@ -219,16 +233,66 @@ export class MCPManager {
|
|
|
219
233
|
) {}
|
|
220
234
|
|
|
221
235
|
/**
|
|
222
|
-
*
|
|
236
|
+
* Register a listener for server-initiated MCP notifications.
|
|
237
|
+
*
|
|
238
|
+
* The listener is called for every JSON-RPC notification received from any
|
|
239
|
+
* connected server, AFTER the manager's own handling of known methods
|
|
240
|
+
* (`notifications/tools/list_changed`, `notifications/resources/list_changed`,
|
|
241
|
+
* `notifications/resources/updated`, `notifications/prompts/list_changed`).
|
|
242
|
+
* For list-change methods the internal refresh promise is awaited before
|
|
243
|
+
* fanout, so listeners observe up-to-date manager and tool state. Unknown
|
|
244
|
+
* or server-custom methods are also delivered, letting consumers bridge
|
|
245
|
+
* server-initiated events into session-level behavior (e.g. an extension
|
|
246
|
+
* injecting a steer via `pi.sendMessage`).
|
|
247
|
+
*
|
|
248
|
+
* Notifications received before any listener attached are buffered
|
|
249
|
+
* (bounded FIFO, cap {@link NOTIFICATION_BUFFER_CAP}, drop-oldest) and
|
|
250
|
+
* drained into the first subscriber — matches {@link setOnPromptsChanged}'s
|
|
251
|
+
* replay-on-attach and {@link IrcBus}'s mailbox semantics.
|
|
252
|
+
*
|
|
253
|
+
* Returns an unsubscribe function; call it to remove the listener.
|
|
254
|
+
*
|
|
255
|
+
* Multiple listeners are allowed; each is invoked with independent error
|
|
256
|
+
* isolation — a listener that throws does not prevent other listeners from
|
|
257
|
+
* firing.
|
|
223
258
|
*/
|
|
224
|
-
|
|
225
|
-
this.#
|
|
259
|
+
addNotificationListener(listener: (serverName: string, method: string, params: unknown) => void): () => void {
|
|
260
|
+
const wasEmpty = this.#notificationListeners.size === 0;
|
|
261
|
+
this.#notificationListeners.add(listener);
|
|
262
|
+
|
|
263
|
+
// Drain startup-buffered notifications into the first attaching listener.
|
|
264
|
+
if (wasEmpty && this.#pendingNotifications.length > 0) {
|
|
265
|
+
const pending = this.#pendingNotifications.splice(0);
|
|
266
|
+
for (const frame of pending) {
|
|
267
|
+
try {
|
|
268
|
+
listener(frame.server, frame.method, frame.params);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logger.debug("MCP notification listener threw during buffered drain", {
|
|
271
|
+
path: `mcp:${frame.server}`,
|
|
272
|
+
method: frame.method,
|
|
273
|
+
error,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return () => {
|
|
280
|
+
this.#notificationListeners.delete(listener);
|
|
281
|
+
};
|
|
226
282
|
}
|
|
227
283
|
|
|
228
284
|
/**
|
|
229
285
|
* Set a callback to fire when any server's tools change.
|
|
286
|
+
*
|
|
287
|
+
* May return a Promise; if so, {@link refreshServerTools} awaits it so that
|
|
288
|
+
* downstream consumers (e.g. `mcp_notification` listeners for
|
|
289
|
+
* `notifications/tools/list_changed`) observe not just the manager's
|
|
290
|
+
* refreshed tool set but also any session-level rebind driven by the
|
|
291
|
+
* handler (`session.refreshMCPTools`). Other callsites (initial connect,
|
|
292
|
+
* disconnect, reconnect) invoke the handler synchronously — their downstream
|
|
293
|
+
* chains don't need to serialize on the rebind.
|
|
230
294
|
*/
|
|
231
|
-
setOnToolsChanged(handler: (tools: CustomTool<TSchema, MCPToolDetails>[]) => void): void {
|
|
295
|
+
setOnToolsChanged(handler: (tools: CustomTool<TSchema, MCPToolDetails>[]) => void | Promise<void>): void {
|
|
232
296
|
this.#onToolsChanged = handler;
|
|
233
297
|
}
|
|
234
298
|
|
|
@@ -488,7 +552,7 @@ export class MCPManager {
|
|
|
488
552
|
this.reconnectServer(name, options);
|
|
489
553
|
const customTools = MCPTool.fromTools(connection, serverTools, reconnect);
|
|
490
554
|
this.#replaceServerTools(name, customTools);
|
|
491
|
-
this.#onToolsChanged?.(this.#tools);
|
|
555
|
+
void this.#onToolsChanged?.(this.#tools);
|
|
492
556
|
void this.toolCache?.set(name, config, serverTools);
|
|
493
557
|
|
|
494
558
|
onStatus?.({ type: "connected", serverName: name });
|
|
@@ -597,7 +661,7 @@ export class MCPManager {
|
|
|
597
661
|
sortMCPToolsByName(this.#tools);
|
|
598
662
|
}
|
|
599
663
|
|
|
600
|
-
#triggerNotificationRefresh(serverName: string, kind: "tools" | "resources" | "prompts"): void {
|
|
664
|
+
#triggerNotificationRefresh(serverName: string, kind: "tools" | "resources" | "prompts"): Promise<void> {
|
|
601
665
|
const refresh = (() => {
|
|
602
666
|
switch (kind) {
|
|
603
667
|
case "tools":
|
|
@@ -608,22 +672,33 @@ export class MCPManager {
|
|
|
608
672
|
return this.refreshServerPrompts(serverName);
|
|
609
673
|
}
|
|
610
674
|
})();
|
|
611
|
-
|
|
675
|
+
return refresh.catch(error => {
|
|
612
676
|
logger.debug("Failed MCP notification refresh", { path: `mcp:${serverName}`, kind, error });
|
|
613
677
|
});
|
|
614
678
|
}
|
|
615
|
-
#handleServerNotification(serverName: string, method: string, params: unknown): void {
|
|
679
|
+
async #handleServerNotification(serverName: string, method: string, params: unknown): Promise<void> {
|
|
616
680
|
logger.debug("MCP notification received", { path: `mcp:${serverName}`, method });
|
|
617
681
|
|
|
682
|
+
// Only trigger refresh if the connection is already stored — during the
|
|
683
|
+
// initial connect handshake, notifications may arrive before
|
|
684
|
+
// `#connections.set()` completes, and `refreshServer*` would no-op
|
|
685
|
+
// anyway. Skipping the await in that case preserves arrival order
|
|
686
|
+
// across concurrently-dispatched notifications (an awaited refresh,
|
|
687
|
+
// even a no-op, yields a microtask that lets later frames overtake).
|
|
688
|
+
const connectionKnown = this.#connections.has(serverName);
|
|
689
|
+
let refreshPromise: Promise<void> | undefined;
|
|
618
690
|
switch (method) {
|
|
619
691
|
case MCPNotificationMethods.TOOLS_LIST_CHANGED:
|
|
620
|
-
this.#triggerNotificationRefresh(serverName, "tools");
|
|
692
|
+
if (connectionKnown) refreshPromise = this.#triggerNotificationRefresh(serverName, "tools");
|
|
621
693
|
break;
|
|
622
694
|
case MCPNotificationMethods.RESOURCES_LIST_CHANGED:
|
|
623
|
-
this.#triggerNotificationRefresh(serverName, "resources");
|
|
695
|
+
if (connectionKnown) refreshPromise = this.#triggerNotificationRefresh(serverName, "resources");
|
|
624
696
|
break;
|
|
625
697
|
case MCPNotificationMethods.RESOURCES_UPDATED: {
|
|
626
|
-
const uri =
|
|
698
|
+
const uri =
|
|
699
|
+
params && typeof params === "object" && "uri" in params && typeof params.uri === "string"
|
|
700
|
+
? params.uri
|
|
701
|
+
: undefined;
|
|
627
702
|
const subscribed = this.#subscribedResources.get(serverName);
|
|
628
703
|
if (uri && subscribed?.has(uri)) {
|
|
629
704
|
this.#onResourcesChanged?.(serverName, uri);
|
|
@@ -631,13 +706,40 @@ export class MCPManager {
|
|
|
631
706
|
break;
|
|
632
707
|
}
|
|
633
708
|
case MCPNotificationMethods.PROMPTS_LIST_CHANGED:
|
|
634
|
-
this.#triggerNotificationRefresh(serverName, "prompts");
|
|
709
|
+
if (connectionKnown) refreshPromise = this.#triggerNotificationRefresh(serverName, "prompts");
|
|
635
710
|
break;
|
|
636
711
|
default:
|
|
637
712
|
break;
|
|
638
713
|
}
|
|
639
714
|
|
|
640
|
-
|
|
715
|
+
// Await internal refresh so listeners see the manager's post-refresh
|
|
716
|
+
// state (satisfies the documented "AFTER the manager's own handling"
|
|
717
|
+
// contract on `addNotificationListener` — otherwise an extension acting
|
|
718
|
+
// on `tools/list_changed` could hit stale `getTools()`).
|
|
719
|
+
if (refreshPromise) {
|
|
720
|
+
await refreshPromise;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Buffer for late-attaching subscribers when no listener exists yet.
|
|
724
|
+
if (this.#notificationListeners.size === 0) {
|
|
725
|
+
this.#pendingNotifications.push({ server: serverName, method, params });
|
|
726
|
+
if (this.#pendingNotifications.length > NOTIFICATION_BUFFER_CAP) {
|
|
727
|
+
this.#pendingNotifications.shift();
|
|
728
|
+
}
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
for (const listener of this.#notificationListeners) {
|
|
733
|
+
try {
|
|
734
|
+
listener(serverName, method, params);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger.debug("MCP notification listener threw", {
|
|
737
|
+
path: `mcp:${serverName}`,
|
|
738
|
+
method,
|
|
739
|
+
error,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
641
743
|
}
|
|
642
744
|
|
|
643
745
|
/** Handle server-to-client JSON-RPC requests (e.g. ping, roots/list). */
|
|
@@ -781,7 +883,7 @@ export class MCPManager {
|
|
|
781
883
|
// Remove tools from this server and notify consumers
|
|
782
884
|
const hadTools = this.#tools.some(t => t.mcpServerName === name);
|
|
783
885
|
this.#tools = this.#tools.filter(t => t.mcpServerName !== name);
|
|
784
|
-
if (hadTools) this.#onToolsChanged?.(this.#tools);
|
|
886
|
+
if (hadTools) void this.#onToolsChanged?.(this.#tools);
|
|
785
887
|
|
|
786
888
|
// Notify prompt consumers so stale commands are cleared
|
|
787
889
|
if (connection?.prompts?.length) this.#onPromptsChanged?.(name);
|
|
@@ -1022,7 +1124,7 @@ export class MCPManager {
|
|
|
1022
1124
|
const customTools = MCPTool.fromTools(connection, serverTools, reconnect);
|
|
1023
1125
|
void this.toolCache?.set(name, config, serverTools);
|
|
1024
1126
|
this.#replaceServerTools(name, customTools);
|
|
1025
|
-
this.#onToolsChanged?.(this.#tools);
|
|
1127
|
+
void this.#onToolsChanged?.(this.#tools);
|
|
1026
1128
|
void this.#loadServerResourcesAndPrompts(name, connection);
|
|
1027
1129
|
return connection;
|
|
1028
1130
|
} catch (error) {
|
|
@@ -1075,7 +1177,7 @@ export class MCPManager {
|
|
|
1075
1177
|
|
|
1076
1178
|
// Replace tools from this server
|
|
1077
1179
|
this.#replaceServerTools(name, customTools);
|
|
1078
|
-
this.#onToolsChanged?.(this.#tools);
|
|
1180
|
+
await this.#onToolsChanged?.(this.#tools);
|
|
1079
1181
|
}
|
|
1080
1182
|
|
|
1081
1183
|
/**
|
|
@@ -82,7 +82,6 @@ import {
|
|
|
82
82
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
83
83
|
import { createAcpClientBridge } from "./acp-client-bridge";
|
|
84
84
|
import {
|
|
85
|
-
buildToolCallStartUpdate,
|
|
86
85
|
extractAssistantMessageText,
|
|
87
86
|
mapAgentSessionEventToAcpSessionUpdates,
|
|
88
87
|
normalizeReplayToolArguments,
|
|
@@ -652,12 +651,17 @@ export class AcpAgent implements Agent {
|
|
|
652
651
|
});
|
|
653
652
|
}
|
|
654
653
|
|
|
655
|
-
// For `thinking`
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
654
|
+
// For `model`/`thinking`, `#setModelById`/`#setThinkingLevelById` change
|
|
655
|
+
// the session model/thinking level through AgentSession, which now emits
|
|
656
|
+
// a lifetime event (`model_changed`/`thinking_level_changed`) that
|
|
657
|
+
// `#handleLifetimeEvent` turns into a push once the subscription is
|
|
658
|
+
// installed. Only push here when that subscription is not yet
|
|
659
|
+
// installed, so pre-bootstrap callers still see the change without a
|
|
660
|
+
// post-bootstrap duplicate.
|
|
661
|
+
const handledBySubscription =
|
|
662
|
+
(params.configId === THINKING_CONFIG_ID || params.configId === MODEL_CONFIG_ID) &&
|
|
663
|
+
record.lifetimeUnsubscribe !== undefined;
|
|
664
|
+
if (!handledBySubscription) {
|
|
661
665
|
await this.#pushConfigOptionUpdate(record);
|
|
662
666
|
}
|
|
663
667
|
return { configOptions: this.#buildConfigOptions(record.session) };
|
|
@@ -1151,14 +1155,15 @@ export class AcpAgent implements Agent {
|
|
|
1151
1155
|
}
|
|
1152
1156
|
|
|
1153
1157
|
async #handleLifetimeEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
|
|
1154
|
-
if (event.type !== "thinking_level_changed") {
|
|
1158
|
+
if (event.type !== "thinking_level_changed" && event.type !== "model_changed") {
|
|
1155
1159
|
return;
|
|
1156
1160
|
}
|
|
1157
1161
|
try {
|
|
1158
1162
|
await this.#pushConfigOptionUpdate(record);
|
|
1159
1163
|
} catch (error) {
|
|
1160
|
-
logger.warn("Failed to push
|
|
1164
|
+
logger.warn("Failed to push config_option_update after a lifetime event", {
|
|
1161
1165
|
sessionId: record.session.sessionId,
|
|
1166
|
+
eventType: event.type,
|
|
1162
1167
|
error,
|
|
1163
1168
|
});
|
|
1164
1169
|
}
|
|
@@ -1576,7 +1581,7 @@ export class AcpAgent implements Agent {
|
|
|
1576
1581
|
#buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
|
|
1577
1582
|
return [
|
|
1578
1583
|
{ value: THINKING_OFF, name: "Off" },
|
|
1579
|
-
{ value: AUTO_THINKING, name: "Auto", description: "Auto-detect per prompt
|
|
1584
|
+
{ value: AUTO_THINKING, name: "Auto", description: "Auto-detect per prompt" },
|
|
1580
1585
|
...session.getAvailableThinkingLevels().map(level => ({
|
|
1581
1586
|
value: level,
|
|
1582
1587
|
name: level,
|
|
@@ -2158,14 +2163,18 @@ export class AcpAgent implements Agent {
|
|
|
2158
2163
|
typeof toolItem.name === "string"
|
|
2159
2164
|
) {
|
|
2160
2165
|
const args = this.#buildReplayAssistantToolArgs(toolItem);
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2166
|
+
notifications.push(
|
|
2167
|
+
...mapAgentSessionEventToAcpSessionUpdates(
|
|
2168
|
+
{
|
|
2169
|
+
type: "tool_execution_start",
|
|
2170
|
+
toolCallId: toolItem.id,
|
|
2171
|
+
toolName: toolItem.name,
|
|
2172
|
+
args,
|
|
2173
|
+
},
|
|
2174
|
+
sessionId,
|
|
2175
|
+
{ cwd },
|
|
2176
|
+
),
|
|
2177
|
+
);
|
|
2169
2178
|
replayedToolCallIds.add(toolItem.id);
|
|
2170
2179
|
replayedToolCallArgs.set(toolItem.id, args);
|
|
2171
2180
|
}
|