@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.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 +87 -1
- package/README.md +1 -1
- package/dist/cli.js +5495 -5021
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +350 -50
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +18 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +8 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +120 -750
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +95 -1
- package/package.json +13 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +11 -2
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +50 -2
- package/src/config/model-resolver.ts +0 -1
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +250 -54
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +52 -9
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +44 -11
- package/src/main.ts +9 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/render.ts +94 -35
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +27 -10
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +28 -3
- package/src/modes/components/settings-selector.ts +178 -6
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +70 -22
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +80 -41
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +160 -38
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/task.md +12 -2
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +255 -139
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1910 -12213
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +47 -5
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +10 -51
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +25 -4
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +288 -70
- package/src/task/index.ts +148 -111
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +4 -1
- package/src/task/types.ts +16 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +8 -1
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -22,10 +22,17 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
22
22
|
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
23
23
|
import { callTool } from "./client";
|
|
24
24
|
import { renderMCPCall, renderMCPResult } from "./render";
|
|
25
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
MCPAuthChallenge,
|
|
27
|
+
MCPContent,
|
|
28
|
+
MCPServerConnection,
|
|
29
|
+
MCPToolCallParams,
|
|
30
|
+
MCPToolCallResult,
|
|
31
|
+
MCPToolDefinition,
|
|
32
|
+
} from "./types";
|
|
26
33
|
|
|
27
|
-
/** Reconnect callback: tears down stale connection,
|
|
28
|
-
export type MCPReconnect = () => Promise<MCPServerConnection | null>;
|
|
34
|
+
/** Reconnect callback: tears down a stale connection, optionally authorizing first. */
|
|
35
|
+
export type MCPReconnect = (options?: { authChallenge?: MCPAuthChallenge }) => Promise<MCPServerConnection | null>;
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
38
|
* Network-level and stale-session errors that warrant a reconnect + single retry.
|
|
@@ -174,6 +181,8 @@ export interface MCPToolDetails {
|
|
|
174
181
|
isError?: boolean;
|
|
175
182
|
/** Raw content from MCP response */
|
|
176
183
|
rawContent?: MCPContent[];
|
|
184
|
+
/** Structured metadata from the MCP response */
|
|
185
|
+
mcpMeta?: Record<string, unknown>;
|
|
177
186
|
/** Provider ID (e.g., "claude", "mcp-json") */
|
|
178
187
|
provider?: string;
|
|
179
188
|
/** Provider display name (e.g., "Claude Code", "MCP Config") */
|
|
@@ -222,6 +231,7 @@ function buildResult(
|
|
|
222
231
|
mcpToolName,
|
|
223
232
|
isError: result.isError,
|
|
224
233
|
rawContent: result.content,
|
|
234
|
+
mcpMeta: result._meta,
|
|
225
235
|
provider,
|
|
226
236
|
providerName,
|
|
227
237
|
};
|
|
@@ -249,6 +259,51 @@ function buildErrorResult(
|
|
|
249
259
|
};
|
|
250
260
|
}
|
|
251
261
|
|
|
262
|
+
type MCPToolCallAttempt = {
|
|
263
|
+
connection: MCPServerConnection;
|
|
264
|
+
result?: MCPToolCallResult;
|
|
265
|
+
error?: unknown;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function getMcpAuthChallenge(result: MCPToolCallResult): MCPAuthChallenge | undefined {
|
|
269
|
+
if (!result.isError) return undefined;
|
|
270
|
+
const values = result._meta?.["mcp/www_authenticate"];
|
|
271
|
+
if (!Array.isArray(values)) return undefined;
|
|
272
|
+
const wwwAuthenticate = values.filter((value): value is string => typeof value === "string" && value.trim() !== "");
|
|
273
|
+
return wwwAuthenticate.length > 0 ? { wwwAuthenticate } : undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function callToolWithAuthRetry(
|
|
277
|
+
connection: MCPServerConnection,
|
|
278
|
+
toolName: string,
|
|
279
|
+
args: MCPToolArgs,
|
|
280
|
+
reconnect: MCPReconnect | undefined,
|
|
281
|
+
signal?: AbortSignal,
|
|
282
|
+
): Promise<MCPToolCallAttempt> {
|
|
283
|
+
const result = await callTool(connection, toolName, args, { signal });
|
|
284
|
+
const authChallenge = getMcpAuthChallenge(result);
|
|
285
|
+
if (!authChallenge || !reconnect) return { connection, result };
|
|
286
|
+
|
|
287
|
+
let newConnection: MCPServerConnection | null;
|
|
288
|
+
try {
|
|
289
|
+
newConnection = await reconnectWithAbort(reconnect, signal, { authChallenge });
|
|
290
|
+
} catch (error) {
|
|
291
|
+
rethrowIfAborted(error, signal);
|
|
292
|
+
return { connection, error };
|
|
293
|
+
}
|
|
294
|
+
if (!newConnection) return { connection, result };
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
return {
|
|
298
|
+
connection: newConnection,
|
|
299
|
+
result: await callTool(newConnection, toolName, args, { signal }),
|
|
300
|
+
};
|
|
301
|
+
} catch (error) {
|
|
302
|
+
rethrowIfAborted(error, signal);
|
|
303
|
+
return { connection: newConnection, error };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
252
307
|
/** Re-throw abort-related errors so they bypass error-result handling. */
|
|
253
308
|
function rethrowIfAborted(error: unknown, signal?: AbortSignal): void {
|
|
254
309
|
if (error instanceof ToolAbortError) throw error;
|
|
@@ -256,9 +311,13 @@ function rethrowIfAborted(error: unknown, signal?: AbortSignal): void {
|
|
|
256
311
|
if (signal?.aborted) throw new ToolAbortError();
|
|
257
312
|
}
|
|
258
313
|
|
|
259
|
-
async function reconnectWithAbort(
|
|
314
|
+
async function reconnectWithAbort(
|
|
315
|
+
reconnect: MCPReconnect,
|
|
316
|
+
signal?: AbortSignal,
|
|
317
|
+
options?: { authChallenge?: MCPAuthChallenge },
|
|
318
|
+
): Promise<MCPServerConnection | null> {
|
|
260
319
|
try {
|
|
261
|
-
return await untilAborted(signal, reconnect);
|
|
320
|
+
return await untilAborted(signal, () => reconnect(options));
|
|
262
321
|
} catch (error) {
|
|
263
322
|
rethrowIfAborted(error, signal);
|
|
264
323
|
return null;
|
|
@@ -379,8 +438,27 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
379
438
|
const providerName = this.connection._source?.providerName;
|
|
380
439
|
|
|
381
440
|
try {
|
|
382
|
-
const
|
|
383
|
-
|
|
441
|
+
const attempt = await callToolWithAuthRetry(this.connection, this.tool.name, args, this.reconnect, signal);
|
|
442
|
+
if (attempt.error !== undefined) {
|
|
443
|
+
return buildErrorResult(attempt.error, this.connection.name, this.tool.name, provider, providerName);
|
|
444
|
+
}
|
|
445
|
+
if (!attempt.result) {
|
|
446
|
+
return buildErrorResult(
|
|
447
|
+
new Error("MCP tool call returned no result"),
|
|
448
|
+
this.connection.name,
|
|
449
|
+
this.tool.name,
|
|
450
|
+
provider,
|
|
451
|
+
providerName,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
this.connection = attempt.connection;
|
|
455
|
+
return buildResult(
|
|
456
|
+
attempt.result,
|
|
457
|
+
attempt.connection.name,
|
|
458
|
+
this.tool.name,
|
|
459
|
+
attempt.connection._source?.provider ?? provider,
|
|
460
|
+
attempt.connection._source?.providerName ?? providerName,
|
|
461
|
+
);
|
|
384
462
|
} catch (error) {
|
|
385
463
|
rethrowIfAborted(error, signal);
|
|
386
464
|
if (this.reconnect && isRetriableConnectionError(error)) {
|
|
@@ -483,13 +561,31 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
483
561
|
const connection = await untilAborted(signal, () => this.getConnection());
|
|
484
562
|
throwIfAborted(signal);
|
|
485
563
|
try {
|
|
486
|
-
const
|
|
564
|
+
const attempt = await callToolWithAuthRetry(connection, this.tool.name, args, this.reconnect, signal);
|
|
565
|
+
if (attempt.error !== undefined) {
|
|
566
|
+
return buildErrorResult(
|
|
567
|
+
attempt.error,
|
|
568
|
+
this.serverName,
|
|
569
|
+
this.tool.name,
|
|
570
|
+
attempt.connection._source?.provider ?? provider,
|
|
571
|
+
attempt.connection._source?.providerName ?? providerName,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
if (!attempt.result) {
|
|
575
|
+
return buildErrorResult(
|
|
576
|
+
new Error("MCP tool call returned no result"),
|
|
577
|
+
this.serverName,
|
|
578
|
+
this.tool.name,
|
|
579
|
+
provider,
|
|
580
|
+
providerName,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
487
583
|
return buildResult(
|
|
488
|
-
result,
|
|
584
|
+
attempt.result,
|
|
489
585
|
this.serverName,
|
|
490
586
|
this.tool.name,
|
|
491
|
-
connection._source?.provider ?? provider,
|
|
492
|
-
connection._source?.providerName ?? providerName,
|
|
587
|
+
attempt.connection._source?.provider ?? provider,
|
|
588
|
+
attempt.connection._source?.providerName ?? providerName,
|
|
493
589
|
);
|
|
494
590
|
} catch (callError) {
|
|
495
591
|
rethrowIfAborted(callError, signal);
|
package/src/mcp/types.ts
CHANGED
|
@@ -206,10 +206,17 @@ export interface MCPResourceContent {
|
|
|
206
206
|
|
|
207
207
|
export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
|
|
208
208
|
|
|
209
|
+
/** Structured authentication challenge returned in a tool result. */
|
|
210
|
+
export interface MCPAuthChallenge {
|
|
211
|
+
/** Values from `_meta["mcp/www_authenticate"]`. */
|
|
212
|
+
readonly wwwAuthenticate: readonly string[];
|
|
213
|
+
}
|
|
214
|
+
|
|
209
215
|
/** tools/call response */
|
|
210
216
|
export interface MCPToolCallResult {
|
|
211
217
|
content: MCPContent[];
|
|
212
218
|
isError?: boolean;
|
|
219
|
+
_meta?: Record<string, unknown>;
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
// =============================================================================
|
package/src/memories/index.ts
CHANGED
|
@@ -142,9 +142,12 @@ export function startMemoryStartupTask(options: {
|
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const signal = session.beginLocalMemoryStartup?.() ?? new AbortController().signal;
|
|
146
|
+
void runMemoryStartup({ session, settings, modelRegistry, agentDir, config: cfg, signal })
|
|
147
|
+
.catch(error => {
|
|
148
|
+
if (!signal.aborted) logger.warn("Memory startup failed", { error: String(error) });
|
|
149
|
+
})
|
|
150
|
+
.finally(() => session.endLocalMemoryStartup?.(signal));
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
interface MemoryInstructionSession {
|
|
@@ -315,26 +318,32 @@ export function enqueueMemoryConsolidation(agentDir: string, cwd: string, source
|
|
|
315
318
|
}
|
|
316
319
|
}
|
|
317
320
|
|
|
318
|
-
|
|
321
|
+
interface MemoryStartupOptions {
|
|
319
322
|
session: AgentSession;
|
|
320
323
|
settings: Settings;
|
|
321
324
|
modelRegistry: ModelRegistry;
|
|
322
325
|
agentDir: string;
|
|
323
326
|
config: MemoryRuntimeConfig;
|
|
324
|
-
|
|
327
|
+
signal: AbortSignal;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function isMemoryStartupActive(options: MemoryStartupOptions): boolean {
|
|
331
|
+
return !options.signal.aborted && !options.session.isDisposed && options.settings.get("memory.backend") === "local";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function runMemoryStartup(options: MemoryStartupOptions): Promise<void> {
|
|
335
|
+
if (!isMemoryStartupActive(options)) return;
|
|
325
336
|
await runPhase1(options);
|
|
337
|
+
if (!isMemoryStartupActive(options)) return;
|
|
326
338
|
await runPhase2(options);
|
|
339
|
+
if (!isMemoryStartupActive(options)) return;
|
|
327
340
|
await refreshMemoryToolDeveloperInstructionsCacheAfterStartup(options.session, options.agentDir, options.settings);
|
|
341
|
+
if (!isMemoryStartupActive(options)) return;
|
|
328
342
|
await options.session.refreshBaseSystemPrompt?.();
|
|
329
343
|
}
|
|
330
344
|
|
|
331
|
-
async function runPhase1(options: {
|
|
332
|
-
|
|
333
|
-
settings: Settings;
|
|
334
|
-
modelRegistry: ModelRegistry;
|
|
335
|
-
agentDir: string;
|
|
336
|
-
config: MemoryRuntimeConfig;
|
|
337
|
-
}): Promise<void> {
|
|
345
|
+
async function runPhase1(options: MemoryStartupOptions): Promise<void> {
|
|
346
|
+
if (!isMemoryStartupActive(options)) return;
|
|
338
347
|
const { session, modelRegistry, agentDir, config } = options;
|
|
339
348
|
const db = openMemoryDb(getAgentDbPath(agentDir));
|
|
340
349
|
const nowSec = unixNow();
|
|
@@ -344,6 +353,7 @@ async function runPhase1(options: {
|
|
|
344
353
|
|
|
345
354
|
try {
|
|
346
355
|
const threads = await collectThreads(session, currentThreadId);
|
|
356
|
+
if (!isMemoryStartupActive(options)) return;
|
|
347
357
|
upsertThreads(db, threads);
|
|
348
358
|
|
|
349
359
|
const phase1Model = await resolveMemoryModel({
|
|
@@ -364,6 +374,7 @@ async function runPhase1(options: {
|
|
|
364
374
|
return;
|
|
365
375
|
}
|
|
366
376
|
|
|
377
|
+
if (!isMemoryStartupActive(options)) return;
|
|
367
378
|
const claims = claimStage1Jobs(db, {
|
|
368
379
|
nowSec,
|
|
369
380
|
threadScanLimit: config.threadScanLimit,
|
|
@@ -387,6 +398,7 @@ async function runPhase1(options: {
|
|
|
387
398
|
};
|
|
388
399
|
|
|
389
400
|
await runWithConcurrency(claims, config.stage1Concurrency, async claim => {
|
|
401
|
+
if (!isMemoryStartupActive(options)) return;
|
|
390
402
|
const result = await runStage1Job({
|
|
391
403
|
claim,
|
|
392
404
|
model: phase1Model,
|
|
@@ -395,6 +407,7 @@ async function runPhase1(options: {
|
|
|
395
407
|
config,
|
|
396
408
|
metadata: session.agent?.metadataForProvider(phase1Model.provider),
|
|
397
409
|
});
|
|
410
|
+
if (!isMemoryStartupActive(options)) return;
|
|
398
411
|
|
|
399
412
|
if (result.kind === "failed") {
|
|
400
413
|
logger.error("Memory phase1 stage1 job failed", {
|
|
@@ -460,13 +473,8 @@ async function runPhase1(options: {
|
|
|
460
473
|
}
|
|
461
474
|
}
|
|
462
475
|
|
|
463
|
-
async function runPhase2(options: {
|
|
464
|
-
|
|
465
|
-
settings: Settings;
|
|
466
|
-
modelRegistry: ModelRegistry;
|
|
467
|
-
agentDir: string;
|
|
468
|
-
config: MemoryRuntimeConfig;
|
|
469
|
-
}): Promise<void> {
|
|
476
|
+
async function runPhase2(options: MemoryStartupOptions): Promise<void> {
|
|
477
|
+
if (!isMemoryStartupActive(options)) return;
|
|
470
478
|
const { session, modelRegistry, agentDir, config } = options;
|
|
471
479
|
const cwd = session.sessionManager.getCwd();
|
|
472
480
|
const db = openMemoryDb(getAgentDbPath(agentDir));
|
|
@@ -488,8 +496,10 @@ async function runPhase2(options: {
|
|
|
488
496
|
const newWatermark = computeCompletionWatermark(claim.inputWatermark, outputs);
|
|
489
497
|
|
|
490
498
|
await syncPhase2Artifacts(memoryRoot, outputs);
|
|
499
|
+
if (!isMemoryStartupActive(options)) return;
|
|
491
500
|
if (outputs.length === 0) {
|
|
492
501
|
await cleanupConsolidatedArtifacts(memoryRoot);
|
|
502
|
+
if (!isMemoryStartupActive(options)) return;
|
|
493
503
|
const marked = markGlobalPhase2Succeeded(db, {
|
|
494
504
|
ownershipToken: claim.ownershipToken,
|
|
495
505
|
newWatermark,
|
|
@@ -502,6 +512,7 @@ async function runPhase2(options: {
|
|
|
502
512
|
return;
|
|
503
513
|
}
|
|
504
514
|
|
|
515
|
+
if (!isMemoryStartupActive(options)) return;
|
|
505
516
|
const phase2Model = await resolveMemoryModel({
|
|
506
517
|
modelRegistry,
|
|
507
518
|
session,
|
|
@@ -529,8 +540,13 @@ async function runPhase2(options: {
|
|
|
529
540
|
return;
|
|
530
541
|
}
|
|
531
542
|
|
|
543
|
+
if (!isMemoryStartupActive(options)) return;
|
|
532
544
|
let heartbeatLostOwnership = false;
|
|
533
545
|
const heartbeat = setInterval(() => {
|
|
546
|
+
if (!isMemoryStartupActive(options)) {
|
|
547
|
+
clearInterval(heartbeat);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
534
550
|
const ok = heartbeatGlobalJob(db, {
|
|
535
551
|
ownershipToken: claim.ownershipToken,
|
|
536
552
|
leaseSeconds: config.phase2LeaseSeconds,
|
|
@@ -544,13 +560,16 @@ async function runPhase2(options: {
|
|
|
544
560
|
}, config.phase2HeartbeatSeconds * 1000);
|
|
545
561
|
|
|
546
562
|
try {
|
|
563
|
+
if (!isMemoryStartupActive(options)) return;
|
|
547
564
|
const consolidated = await runConsolidationModel({
|
|
548
565
|
memoryRoot,
|
|
549
566
|
model: phase2Model,
|
|
550
567
|
apiKey: modelRegistry.resolver(phase2Model, session.sessionId),
|
|
551
568
|
metadata: session.agent?.metadataForProvider(phase2Model.provider),
|
|
552
569
|
});
|
|
570
|
+
if (!isMemoryStartupActive(options)) return;
|
|
553
571
|
await applyConsolidation(memoryRoot, consolidated);
|
|
572
|
+
if (!isMemoryStartupActive(options)) return;
|
|
554
573
|
if (heartbeatLostOwnership) {
|
|
555
574
|
throw new Error("Phase2 lease ownership lost before completion");
|
|
556
575
|
}
|
|
@@ -564,6 +583,7 @@ async function runPhase2(options: {
|
|
|
564
583
|
throw new Error("Phase2 could not mark success: ownership lost");
|
|
565
584
|
}
|
|
566
585
|
} catch (error) {
|
|
586
|
+
if (!isMemoryStartupActive(options)) return;
|
|
567
587
|
markPhase2FailureWithFallback(db, {
|
|
568
588
|
claim,
|
|
569
589
|
retryDelaySeconds: config.phase2RetryDelaySeconds,
|
|
@@ -1227,7 +1247,7 @@ async function resolveMemoryModel(options: {
|
|
|
1227
1247
|
|
|
1228
1248
|
function loadMemoryConfig(settings: Settings): MemoryRuntimeConfig {
|
|
1229
1249
|
return {
|
|
1230
|
-
enabled: settings.get("memory.backend") === "local"
|
|
1250
|
+
enabled: settings.get("memory.backend") === "local",
|
|
1231
1251
|
maxRolloutsPerStartup: settings.get("memories.maxRolloutsPerStartup") ?? DEFAULTS.maxRolloutsPerStartup,
|
|
1232
1252
|
maxRolloutAgeDays: settings.get("memories.maxRolloutAgeDays") ?? DEFAULTS.maxRolloutAgeDays,
|
|
1233
1253
|
minRolloutIdleHours: settings.get("memories.minRolloutIdleHours") ?? DEFAULTS.minRolloutIdleHours,
|
package/src/mnemopi/backend.ts
CHANGED
|
@@ -62,6 +62,20 @@ const STATIC_INSTRUCTIONS = [
|
|
|
62
62
|
"",
|
|
63
63
|
].join("\n");
|
|
64
64
|
|
|
65
|
+
async function installMnemopiState(session: AgentSession, config: MnemopiBackendConfig): Promise<MnemopiSessionState> {
|
|
66
|
+
const state = new MnemopiSessionState({ sessionId: session.sessionId, config, session });
|
|
67
|
+
const previous = setMnemopiSessionState(session, state);
|
|
68
|
+
await previous?.dispose();
|
|
69
|
+
try {
|
|
70
|
+
state.attachSessionListeners();
|
|
71
|
+
return state;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
setMnemopiSessionState(session, undefined);
|
|
74
|
+
await state.dispose({ consolidate: false });
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
export const mnemopiBackend: MemoryBackend = {
|
|
66
80
|
id: "mnemopi",
|
|
67
81
|
|
|
@@ -90,10 +104,7 @@ export const mnemopiBackend: MemoryBackend = {
|
|
|
90
104
|
try {
|
|
91
105
|
const config = await loadMnemopiConfigWithProviders(settings, agentDir, modelRegistry, sessionId);
|
|
92
106
|
await Promise.all([loadMnemopi(), loadMnemopiCore()]);
|
|
93
|
-
|
|
94
|
-
const previous = setMnemopiSessionState(session, state);
|
|
95
|
-
await previous?.dispose();
|
|
96
|
-
state.attachSessionListeners();
|
|
107
|
+
await installMnemopiState(session, config);
|
|
97
108
|
} catch (error) {
|
|
98
109
|
logger.warn("Mnemopi: backend startup failed; memory backend inert.", { error: String(error) });
|
|
99
110
|
}
|
|
@@ -129,12 +140,19 @@ export const mnemopiBackend: MemoryBackend = {
|
|
|
129
140
|
requireMnemopiCore().resetMemoryForTests();
|
|
130
141
|
await Bun.sleep(0);
|
|
131
142
|
await removeDbFiles(getMnemopiScopedDbPaths(config));
|
|
143
|
+
if (!session?.sessionId || previous?.aliasOf || session.settings.get("memory.backend") !== "mnemopi") return;
|
|
144
|
+
try {
|
|
145
|
+
await Promise.all([loadMnemopi(), loadMnemopiCore()]);
|
|
146
|
+
await installMnemopiState(session, config);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.warn("Mnemopi: clear rehydrate failed; memory backend inert.", { error: String(error) });
|
|
149
|
+
}
|
|
132
150
|
},
|
|
133
151
|
|
|
134
152
|
async enqueue(agentDir, _cwd, session): Promise<void> {
|
|
135
153
|
try {
|
|
136
154
|
let state = getMnemopiSessionState(session);
|
|
137
|
-
if (!state && session) {
|
|
155
|
+
if (!state && session?.sessionId) {
|
|
138
156
|
const config = await loadMnemopiConfigWithProviders(
|
|
139
157
|
session.settings,
|
|
140
158
|
agentDir,
|
|
@@ -142,8 +160,7 @@ export const mnemopiBackend: MemoryBackend = {
|
|
|
142
160
|
session.sessionId,
|
|
143
161
|
);
|
|
144
162
|
await Promise.all([loadMnemopi(), loadMnemopiCore()]);
|
|
145
|
-
state =
|
|
146
|
-
setMnemopiSessionState(session, state);
|
|
163
|
+
state = await installMnemopiState(session, config);
|
|
147
164
|
}
|
|
148
165
|
await state?.consolidate({ full: true });
|
|
149
166
|
} catch (error) {
|
|
@@ -164,6 +164,10 @@ function isPromptTurnInFlight(turn: PromptTurnState | undefined): turn is Prompt
|
|
|
164
164
|
type ManagedSessionRecord = {
|
|
165
165
|
session: AgentSession;
|
|
166
166
|
mcpManager: MCPManager | undefined;
|
|
167
|
+
// Ordered queue of MCP tool refreshes for this record. Rebuilt per
|
|
168
|
+
// `#configureMcpServers` call; drained on reconfigure so a stale in-flight
|
|
169
|
+
// refresh can never land after a newer configuration's tools.
|
|
170
|
+
mcpRefreshChain: Promise<void> | undefined;
|
|
167
171
|
promptTurn: PromptTurnState | undefined;
|
|
168
172
|
promptQueue: PromptQueueState;
|
|
169
173
|
liveMessageId: string | undefined;
|
|
@@ -1132,6 +1136,7 @@ export class AcpAgent implements Agent {
|
|
|
1132
1136
|
return {
|
|
1133
1137
|
session,
|
|
1134
1138
|
mcpManager: undefined,
|
|
1139
|
+
mcpRefreshChain: undefined,
|
|
1135
1140
|
promptTurn: undefined,
|
|
1136
1141
|
promptQueue: { promise: Promise.resolve(), release: undefined },
|
|
1137
1142
|
liveMessageId: undefined,
|
|
@@ -2275,6 +2280,24 @@ export class AcpAgent implements Agent {
|
|
|
2275
2280
|
return;
|
|
2276
2281
|
}
|
|
2277
2282
|
|
|
2283
|
+
const uiContext = createAcpExtensionUiContext(
|
|
2284
|
+
this.#connection,
|
|
2285
|
+
() => record.session.sessionId,
|
|
2286
|
+
this.#clientCapabilities,
|
|
2287
|
+
);
|
|
2288
|
+
if (this.#clientCapabilities?.elicitation?.form != null) {
|
|
2289
|
+
record.session.setUsageFallbackConfirmer(confirmation => {
|
|
2290
|
+
const reserve =
|
|
2291
|
+
confirmation.remainingPercent === undefined
|
|
2292
|
+
? "inside the configured reserve margin"
|
|
2293
|
+
: `${confirmation.remainingPercent.toFixed(1)}% remaining`;
|
|
2294
|
+
return uiContext.confirm(
|
|
2295
|
+
"Coding-plan reserve reached",
|
|
2296
|
+
`${confirmation.from} has ${reserve}. Switch to ${confirmation.to}? Choose No to keep using the current plan.`,
|
|
2297
|
+
);
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2278
2301
|
const extensionRunner = record.session.extensionRunner;
|
|
2279
2302
|
if (!extensionRunner) {
|
|
2280
2303
|
record.extensionsConfigured = true;
|
|
@@ -2311,6 +2334,8 @@ export class AcpAgent implements Agent {
|
|
|
2311
2334
|
},
|
|
2312
2335
|
getThinkingLevel: () => record.session.thinkingLevel,
|
|
2313
2336
|
setThinkingLevel: level => record.session.setThinkingLevel(level),
|
|
2337
|
+
getServiceTiers: () => record.session.serviceTierByFamily,
|
|
2338
|
+
setServiceTier: (family, tier) => record.session.setServiceTierFamily(family, tier),
|
|
2314
2339
|
getSessionName: () => record.session.sessionManager.getSessionName(),
|
|
2315
2340
|
setSessionName: async name => {
|
|
2316
2341
|
await record.session.sessionManager.setSessionName(name, "user");
|
|
@@ -2355,15 +2380,7 @@ export class AcpAgent implements Agent {
|
|
|
2355
2380
|
},
|
|
2356
2381
|
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
2357
2382
|
},
|
|
2358
|
-
|
|
2359
|
-
// `sessionManager.getSessionId()` (it's a getter, not a field), so an
|
|
2360
|
-
// extension command that calls `ctx.newSession` / `ctx.switchSession`
|
|
2361
|
-
// — both exposed in the block just above — mutates the underlying id
|
|
2362
|
-
// mid-flight. Reading lazily on each elicitation matches every other
|
|
2363
|
-
// `sessionUpdate` call in this file. Hoisting the factory to an
|
|
2364
|
-
// `AcpAgent` field would still be wrong because it would also lose
|
|
2365
|
-
// the per-`record` binding.
|
|
2366
|
-
createAcpExtensionUiContext(this.#connection, () => record.session.sessionId, this.#clientCapabilities),
|
|
2383
|
+
uiContext,
|
|
2367
2384
|
);
|
|
2368
2385
|
await extensionRunner.emit({ type: "session_start" });
|
|
2369
2386
|
record.extensionsConfigured = true;
|
|
@@ -2373,6 +2390,11 @@ export class AcpAgent implements Agent {
|
|
|
2373
2390
|
if (record.mcpManager) {
|
|
2374
2391
|
await record.mcpManager.disconnectAll();
|
|
2375
2392
|
}
|
|
2393
|
+
// Drain any in-flight refresh queued by a previous configuration: a refresh
|
|
2394
|
+
// that already passed its manager guard could otherwise finish applying a
|
|
2395
|
+
// stale tool set after this reconfiguration installs the new one.
|
|
2396
|
+
await record.mcpRefreshChain;
|
|
2397
|
+
record.mcpRefreshChain = undefined;
|
|
2376
2398
|
if (servers.length === 0) {
|
|
2377
2399
|
record.mcpManager = undefined;
|
|
2378
2400
|
await record.session.refreshMCPTools([]);
|
|
@@ -2380,6 +2402,30 @@ export class AcpAgent implements Agent {
|
|
|
2380
2402
|
}
|
|
2381
2403
|
|
|
2382
2404
|
const manager = new MCPManager(record.session.sessionManager.getCwd());
|
|
2405
|
+
// MCP servers connect and reconnect independently, so `onToolsChanged` can fire
|
|
2406
|
+
// several times back to back. Each firing is chained onto `record.mcpRefreshChain`
|
|
2407
|
+
// so refreshes apply in order, and each one re-reads `manager.getTools()` at the
|
|
2408
|
+
// time it actually runs rather than the snapshot from when it was queued — so a
|
|
2409
|
+
// refresh can never apply a stale, smaller tool set after a newer one already landed.
|
|
2410
|
+
// The returned promise propagates failures (the initial awaited refresh below must
|
|
2411
|
+
// fail session setup, as the pre-queue code did); the stored chain swallows them
|
|
2412
|
+
// after logging so background firings only warn and the chain never rejects.
|
|
2413
|
+
const enqueueMcpToolsRefresh = (): Promise<void> => {
|
|
2414
|
+
const run = (record.mcpRefreshChain ?? Promise.resolve()).then(async () => {
|
|
2415
|
+
if (record.mcpManager !== manager) return;
|
|
2416
|
+
await record.session.refreshMCPTools(manager.getTools());
|
|
2417
|
+
});
|
|
2418
|
+
record.mcpRefreshChain = run.catch(error => {
|
|
2419
|
+
logger.warn("ACP MCP tool refresh failed", {
|
|
2420
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2421
|
+
});
|
|
2422
|
+
});
|
|
2423
|
+
return run;
|
|
2424
|
+
};
|
|
2425
|
+
manager.setOnToolsChanged(() => {
|
|
2426
|
+
// Failures are logged once via the stored chain's catch above.
|
|
2427
|
+
enqueueMcpToolsRefresh().catch(() => {});
|
|
2428
|
+
});
|
|
2383
2429
|
const configs: MCPConfigMap = {};
|
|
2384
2430
|
const sources: MCPSourceMap = {};
|
|
2385
2431
|
for (const server of servers) {
|
|
@@ -2402,7 +2448,7 @@ export class AcpAgent implements Agent {
|
|
|
2402
2448
|
}
|
|
2403
2449
|
|
|
2404
2450
|
record.mcpManager = manager;
|
|
2405
|
-
await
|
|
2451
|
+
await enqueueMcpToolsRefresh();
|
|
2406
2452
|
}
|
|
2407
2453
|
|
|
2408
2454
|
#toMcpConfig(server: McpServer): MCPServerConfig {
|
|
@@ -369,6 +369,7 @@ const todoStatusMap: Record<TodoStatus, "pending" | "in_progress" | "completed">
|
|
|
369
369
|
in_progress: "in_progress",
|
|
370
370
|
completed: "completed",
|
|
371
371
|
abandoned: "completed",
|
|
372
|
+
blocked: "pending",
|
|
372
373
|
};
|
|
373
374
|
|
|
374
375
|
function mapTodoStatus(status: TodoStatus): "pending" | "in_progress" | "completed" {
|
|
@@ -432,7 +433,13 @@ function extractTodoEntries(phases: unknown[]): Array<{ content: string; status:
|
|
|
432
433
|
}
|
|
433
434
|
|
|
434
435
|
function isTodoStatus(status: unknown): status is TodoStatus {
|
|
435
|
-
return
|
|
436
|
+
return (
|
|
437
|
+
status === "pending" ||
|
|
438
|
+
status === "in_progress" ||
|
|
439
|
+
status === "completed" ||
|
|
440
|
+
status === "abandoned" ||
|
|
441
|
+
status === "blocked"
|
|
442
|
+
);
|
|
436
443
|
}
|
|
437
444
|
export function buildToolCallStartUpdate(input: {
|
|
438
445
|
toolCallId: string;
|
|
@@ -76,26 +76,43 @@ function formatModelBadge(modelId: string, level: ThinkingLevel | undefined): st
|
|
|
76
76
|
return `${model} ${theme.getThinkingBorderColor(level)(display)}`;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Format a resolved selector, preserving provider identity when requested. */
|
|
80
|
+
function formatResolvedModelBadge(resolved: string, preserveProvider = false): string {
|
|
81
|
+
// Model ids may themselves contain colons (`qwen3:14b`), so only treat the
|
|
82
|
+
// suffix as a thinking level when it parses as one.
|
|
83
|
+
const colon = resolved.lastIndexOf(":");
|
|
84
|
+
const level = colon >= 0 ? parseThinkingLevel(resolved.slice(colon + 1)) : undefined;
|
|
85
|
+
const selector = level !== undefined ? resolved.slice(0, colon) : resolved;
|
|
86
|
+
const label = preserveProvider ? selector : selector.slice(selector.indexOf("/") + 1);
|
|
87
|
+
return formatModelBadge(label, level);
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
/**
|
|
80
91
|
* Active model + reasoning level for a hub row: live session state when the
|
|
81
92
|
* agent is attached, else the executor-reported `resolvedModel` selector
|
|
82
|
-
* (`provider/id`, optionally `:<level>`).
|
|
93
|
+
* (`provider/id`, optionally `:<level>`). Active retry fallbacks retain their
|
|
94
|
+
* provider and carry an explicit marker. Undefined when no model is known
|
|
83
95
|
* (e.g. a parked historical agent restored from disk).
|
|
84
96
|
*/
|
|
85
97
|
function modelBadge(ref: AgentRef, observed: ObservableSession | undefined): string | undefined {
|
|
98
|
+
const progress = observed?.progress;
|
|
99
|
+
// Prefer the live session's own resolved fallback selector; else honor the
|
|
100
|
+
// executor-reported fallback flag. The latter covers observer-only rows (no
|
|
101
|
+
// live session) AND live rows whose fallback armed no session retry state —
|
|
102
|
+
// e.g. the Fireworks Fast → base degrade, which emits `retry_fallback_applied`
|
|
103
|
+
// without populating `#activeRetryFallback`, so `retryFallbackModel` is undefined.
|
|
104
|
+
const fallbackSelector =
|
|
105
|
+
ref.session?.retryFallbackModel ?? (progress?.resolvedModelIsFallback ? progress.resolvedModel : undefined);
|
|
106
|
+
if (fallbackSelector) {
|
|
107
|
+
return `${theme.fg("warning", "fallback →")} ${formatResolvedModelBadge(fallbackSelector, true)}`;
|
|
108
|
+
}
|
|
86
109
|
const model = ref.session?.model;
|
|
87
110
|
if (model) {
|
|
88
111
|
const level = model.thinking ? ref.session?.thinkingLevel : undefined;
|
|
89
112
|
return formatModelBadge(model.id, level);
|
|
90
113
|
}
|
|
91
|
-
const resolved =
|
|
92
|
-
|
|
93
|
-
// Model ids may themselves contain colons (`qwen3:14b`), so only treat the
|
|
94
|
-
// suffix as a thinking level when it parses as one.
|
|
95
|
-
const colon = resolved.lastIndexOf(":");
|
|
96
|
-
const level = colon >= 0 ? parseThinkingLevel(resolved.slice(colon + 1)) : undefined;
|
|
97
|
-
const selector = level !== undefined ? resolved.slice(0, colon) : resolved;
|
|
98
|
-
return formatModelBadge(selector.slice(selector.indexOf("/") + 1), level);
|
|
114
|
+
const resolved = progress?.resolvedModel;
|
|
115
|
+
return resolved ? formatResolvedModelBadge(resolved) : undefined;
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
/** Result of one host-backed transcript read for the Agent Hub viewer. */
|
|
@@ -619,7 +636,7 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
619
636
|
if (ref.status === "running" && ref.session) {
|
|
620
637
|
await ref.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
621
638
|
}
|
|
622
|
-
await this.#lifecycle().release(ref.id);
|
|
639
|
+
await this.#lifecycle().release(ref.id, ref);
|
|
623
640
|
} catch (error) {
|
|
624
641
|
logger.warn("Agent hub: kill failed", { id: ref.id, error: String(error) });
|
|
625
642
|
this.#notice = error instanceof Error ? error.message : String(error);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
2
|
-
import { Container, getKeybindings, Input, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { Container, getKeybindings, Input, Spacer, Text, type TUI, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
|
+
import { urlHyperlinkAlways, WidthAwareText } from "../../tui";
|
|
4
5
|
import { openPath } from "../../utils/open";
|
|
5
6
|
import { DynamicBorder } from "./dynamic-border";
|
|
6
7
|
|
|
@@ -75,13 +76,22 @@ export class LoginDialogComponent extends Container {
|
|
|
75
76
|
* is offered as an additional local shortcut so narrow local terminals still
|
|
76
77
|
* have a truncation-safe copy target (viewport clipping on a long authorize
|
|
77
78
|
* URL silently drops trailing OAuth query parameters — e.g.
|
|
78
|
-
* `code_challenge_method=S256`).
|
|
79
|
-
*
|
|
79
|
+
* `code_challenge_method=S256`). Every physical URL row carries its own OSC 8
|
|
80
|
+
* link to the full URL, so clicking any wrapped fragment opens the same target.
|
|
80
81
|
*/
|
|
81
82
|
showAuth(url: string, instructions?: string, launchUrl?: string): void {
|
|
82
83
|
this.#contentContainer.clear();
|
|
83
84
|
this.#contentContainer.addChild(new Spacer(1));
|
|
84
|
-
this.#contentContainer.addChild(
|
|
85
|
+
this.#contentContainer.addChild(
|
|
86
|
+
new WidthAwareText(
|
|
87
|
+
contentWidth =>
|
|
88
|
+
wrapTextWithAnsi(url, contentWidth)
|
|
89
|
+
.map(row => theme.fg("accent", urlHyperlinkAlways(url, row)))
|
|
90
|
+
.join("\n"),
|
|
91
|
+
1,
|
|
92
|
+
0,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
85
95
|
|
|
86
96
|
const clickHint = process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
|
|
87
97
|
const hyperlink = `\x1b]8;;${url}\x07${clickHint}\x1b]8;;\x07`;
|