@gajae-code/coding-agent 0.16.3 → 0.16.6
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 +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +26 -1
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +12 -12
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +11 -8
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
package/src/modes/types.ts
CHANGED
|
@@ -74,6 +74,20 @@ export type ActivityIndicatorStopOptions = Readonly<{
|
|
|
74
74
|
foregroundSettled?: boolean;
|
|
75
75
|
}>;
|
|
76
76
|
|
|
77
|
+
export function clearInteractiveActivityIndicator(ctx: {
|
|
78
|
+
loadingAnimation?: Loader;
|
|
79
|
+
statusContainer?: PartialActivityStatusContainer;
|
|
80
|
+
clearLoadingAnimation?: () => void;
|
|
81
|
+
}): void {
|
|
82
|
+
if (ctx.clearLoadingAnimation) {
|
|
83
|
+
ctx.clearLoadingAnimation();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
ctx.loadingAnimation?.stop();
|
|
87
|
+
ctx.loadingAnimation = undefined;
|
|
88
|
+
ctx.statusContainer?.clear?.();
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
export function stopInteractiveActivityIndicator(
|
|
78
92
|
ctx: {
|
|
79
93
|
loadingAnimation?: Loader;
|
|
@@ -86,9 +100,7 @@ export function stopInteractiveActivityIndicator(
|
|
|
86
100
|
ctx.stopLoadingAnimation(options);
|
|
87
101
|
return;
|
|
88
102
|
}
|
|
89
|
-
ctx
|
|
90
|
-
ctx.loadingAnimation = undefined;
|
|
91
|
-
ctx.statusContainer?.clear?.();
|
|
103
|
+
clearInteractiveActivityIndicator(ctx);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
export function clearInteractiveActivityLoaders(
|
|
@@ -285,6 +297,7 @@ export interface InteractiveModeContext {
|
|
|
285
297
|
setWorkingMessage(message?: string): void;
|
|
286
298
|
applyPendingWorkingMessage(): void;
|
|
287
299
|
ensureLoadingAnimation(): void;
|
|
300
|
+
clearLoadingAnimation(): void;
|
|
288
301
|
syncActivityIndicator(): void;
|
|
289
302
|
suspendActivityIndicator(): () => void;
|
|
290
303
|
stopLoadingAnimation(options?: ActivityIndicatorStopOptions): void;
|
|
@@ -355,6 +368,7 @@ export interface InteractiveModeContext {
|
|
|
355
368
|
extractAssistantText(message: AssistantMessage): string;
|
|
356
369
|
/** Records one semantic visible-transcript mutation for the sticky viewport. */
|
|
357
370
|
recordVisibleTranscriptMutation?(): void;
|
|
371
|
+
resetAssistantTextPresentation(): void;
|
|
358
372
|
updateEditorTopBorder(): void;
|
|
359
373
|
updateEditorBorderColor(): void;
|
|
360
374
|
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void;
|
|
@@ -530,6 +530,7 @@ export class UiHelpers {
|
|
|
530
530
|
component.appendOutput(message.output);
|
|
531
531
|
}
|
|
532
532
|
component.setComplete(message.exitCode, message.cancelled, {
|
|
533
|
+
signal: message.signal,
|
|
533
534
|
truncation: message.meta?.truncation,
|
|
534
535
|
});
|
|
535
536
|
addChatChild(this.ctx, component);
|
|
@@ -944,6 +945,7 @@ export class UiHelpers {
|
|
|
944
945
|
this.ctx.pendingPythonComponents = this.ctx.pendingPythonComponents.filter(component =>
|
|
945
946
|
runningExecutionComponents.includes(component),
|
|
946
947
|
);
|
|
948
|
+
this.ctx.resetAssistantTextPresentation();
|
|
947
949
|
this.ctx.chatContainer.clear();
|
|
948
950
|
|
|
949
951
|
// Reuse a pre-built context when available (e.g. from navigateTree) to avoid a second O(N) walk.
|
|
@@ -47,6 +47,14 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
|
|
|
47
47
|
- Do not defer actionable work. Underpromise and overdeliver: report only what is done or in progress, never announce remaining work instead of doing it.
|
|
48
48
|
</communication>
|
|
49
49
|
|
|
50
|
+
{{#if reasoningLanguageEnglish}}
|
|
51
|
+
<reasoning-language>
|
|
52
|
+
- Reason through development and technical problem-solving in English.
|
|
53
|
+
- Keep user-facing answers in the language the user requested or used.
|
|
54
|
+
- This changes reasoning language only; it does not relax correctness, safety, or communication requirements.
|
|
55
|
+
</reasoning-language>
|
|
56
|
+
{{/if}}
|
|
57
|
+
|
|
50
58
|
<completion-contract>
|
|
51
59
|
- Never present partial work as complete.
|
|
52
60
|
- Never suppress tests or warnings to make code pass.
|
|
@@ -5,6 +5,7 @@ Searches files using powerful regex matching.
|
|
|
5
5
|
- `paths` accepts an array of files, directories, globs, or internal URLs; when omitted, the whole working directory is searched
|
|
6
6
|
- `paths` is an array; do not embed commas or spaces inside a single entry. Pass `["src", "tests"]` not `["src,tests"]`.
|
|
7
7
|
- Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
|
|
8
|
+
- `timeout` is in seconds (default 5, range 0.5–60). Increase it only for intentionally broad searches; narrow `paths` first
|
|
8
9
|
</instruction>
|
|
9
10
|
|
|
10
11
|
<output>
|
|
@@ -20,4 +21,5 @@ Searches files using powerful regex matching.
|
|
|
20
21
|
<critical>
|
|
21
22
|
- Search paths are an array; pass separate entries rather than comma-joined paths.
|
|
22
23
|
- Use a cross-line pattern only when the match actually spans lines.
|
|
24
|
+
- Do not retry a timed-out search unchanged; narrow `paths`/`pattern` or explicitly increase `timeout`.
|
|
23
25
|
</critical>
|
|
@@ -15,6 +15,7 @@ Inspect selected subagents by `ids`; omit `ids` to inspect current running subag
|
|
|
15
15
|
## `action: "await"`
|
|
16
16
|
Wait for selected subagents by `ids`; omit `ids` to wait for current running subagents.
|
|
17
17
|
- Always set `timeout_ms` when the result is not immediately required forever.
|
|
18
|
+
- `heartbeat_ms` accepts integer intervals from 100 to 60,000 milliseconds (for example, 10,000 for ten seconds); `0` disables heartbeat updates and omission retains the 500 ms default. Terminal completion is event-driven, not delayed until the next heartbeat.
|
|
18
19
|
- Await timeout only bounds this tool call's wait; it does not stop the subagent and is not a failure reason.
|
|
19
20
|
- On timeout, inspect progress and keep doing independent work. Never cancel just because an await timed out; cancel only if the subagent has actually failed, gone off-track, or become unrecoverably wrong.
|
|
20
21
|
- Completed results are receipt-first by default: bounded preview plus `agent://<id>` output ref when available, not full retained output.
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
normalizeMcpCacheHints,
|
|
25
25
|
resolveMCPProtocolPreference,
|
|
26
26
|
} from "./protocol";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
27
|
+
import { HttpTransport } from "./transports/http";
|
|
28
|
+
import { StdioTransport } from "./transports/stdio";
|
|
29
29
|
import type {
|
|
30
30
|
MCPDiscoverResultLite,
|
|
31
31
|
MCPGetPromptParams,
|
|
@@ -188,18 +188,18 @@ async function defaultRequestHandler(method: string, _params: unknown): Promise<
|
|
|
188
188
|
/**
|
|
189
189
|
* Create a transport for the given server config.
|
|
190
190
|
*/
|
|
191
|
-
|
|
191
|
+
function createTransport(config: MCPServerConfig): StdioTransport | HttpTransport {
|
|
192
192
|
const serverType = config.type ?? "stdio";
|
|
193
193
|
|
|
194
194
|
switch (serverType) {
|
|
195
195
|
case "stdio":
|
|
196
|
-
return
|
|
196
|
+
return new StdioTransport(config as MCPStdioServerConfig);
|
|
197
197
|
case "http":
|
|
198
|
-
return
|
|
198
|
+
return new HttpTransport(config as MCPHttpServerConfig);
|
|
199
199
|
case "sse":
|
|
200
200
|
// Compatibility: `sse` configs use Streamable HTTP, not the legacy MCP SSE
|
|
201
201
|
// protocol. The configured URL receives JSON-RPC POST requests directly.
|
|
202
|
-
return
|
|
202
|
+
return new HttpTransport(config as MCPSseServerConfig);
|
|
203
203
|
default:
|
|
204
204
|
throw new Error(`Unknown server type: ${serverType}`);
|
|
205
205
|
}
|
|
@@ -295,6 +295,42 @@ function unwrapTransportFailure(error: unknown): unknown {
|
|
|
295
295
|
return error instanceof MCPExpectedFailure && error.cause !== undefined ? error.cause : error;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
function transportFailureCauses(error: unknown): unknown[] {
|
|
299
|
+
const failure = unwrapTransportFailure(error);
|
|
300
|
+
return failure instanceof AggregateError ? failure.errors : [failure];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function connectionAndCleanupCause(connectionError: unknown, cleanupError: unknown): AggregateError {
|
|
304
|
+
return new AggregateError(
|
|
305
|
+
[...transportFailureCauses(connectionError), ...transportFailureCauses(cleanupError)],
|
|
306
|
+
"MCP connection failed and transport cleanup also failed",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function combinedConnectionAndCleanupFailure(connectionError: unknown, cleanupError: unknown): MCPExpectedFailure {
|
|
311
|
+
return new MCPExpectedFailure(connectionAndCleanupCause(connectionError, cleanupError));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Failed connection whose still-owned transport cleanup can be retried explicitly. */
|
|
315
|
+
export class MCPConnectionCleanupFailure extends MCPExpectedFailure {
|
|
316
|
+
#transport: MCPTransport;
|
|
317
|
+
|
|
318
|
+
constructor(cause: unknown, transport: MCPTransport) {
|
|
319
|
+
super(cause);
|
|
320
|
+
this.name = "MCPConnectionCleanupFailure";
|
|
321
|
+
this.#transport = transport;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Retry the same retained transport cleanup; failures preserve all prior causes. */
|
|
325
|
+
async close(): Promise<void> {
|
|
326
|
+
try {
|
|
327
|
+
await this.#transport.close();
|
|
328
|
+
} catch (cleanupError) {
|
|
329
|
+
throw new MCPConnectionCleanupFailure(connectionAndCleanupCause(this, cleanupError), this.#transport);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
298
334
|
type ModernNegotiation =
|
|
299
335
|
| { outcome: "modern"; discovery: MCPDiscoverResultLite | null; discoverState: "yes" | "no"; versionRetry: boolean }
|
|
300
336
|
| { outcome: "legacy-fallback"; reason: MCPDowngradeReason };
|
|
@@ -505,14 +541,14 @@ export async function connectToServer(
|
|
|
505
541
|
},
|
|
506
542
|
): Promise<MCPServerConnection> {
|
|
507
543
|
const timeoutMs = config.timeout ?? CONNECTION_TIMEOUT_MS;
|
|
508
|
-
let transport:
|
|
544
|
+
let transport: StdioTransport | HttpTransport | undefined;
|
|
509
545
|
const connectAbort = new AbortController();
|
|
510
546
|
const connectSignal = options?.signal ? AbortSignal.any([options.signal, connectAbort.signal]) : connectAbort.signal;
|
|
511
547
|
|
|
512
548
|
const preference = resolveMCPProtocolPreference(config.protocol);
|
|
513
549
|
|
|
514
550
|
const connect = async (): Promise<MCPServerConnection> => {
|
|
515
|
-
transport =
|
|
551
|
+
transport = createTransport(config);
|
|
516
552
|
if (options?.onNotification) {
|
|
517
553
|
transport.onNotification = options.onNotification;
|
|
518
554
|
}
|
|
@@ -561,6 +597,7 @@ export async function connectToServer(
|
|
|
561
597
|
};
|
|
562
598
|
|
|
563
599
|
try {
|
|
600
|
+
await transport.connect();
|
|
564
601
|
// stdio has no per-request HTTP era signals; it stays on the legacy handshake.
|
|
565
602
|
if ((config.type ?? "stdio") === "stdio") {
|
|
566
603
|
return await connectLegacy("legacy-forced", "stdio-transport");
|
|
@@ -609,8 +646,8 @@ export async function connectToServer(
|
|
|
609
646
|
} catch (error) {
|
|
610
647
|
try {
|
|
611
648
|
await transport.close();
|
|
612
|
-
} catch {
|
|
613
|
-
|
|
649
|
+
} catch (cleanupError) {
|
|
650
|
+
throw combinedConnectionAndCleanupFailure(error, cleanupError);
|
|
614
651
|
}
|
|
615
652
|
throw error;
|
|
616
653
|
}
|
|
@@ -625,20 +662,26 @@ export async function connectToServer(
|
|
|
625
662
|
// abort initialization and wait for transport cleanup before returning.
|
|
626
663
|
const aborted = options?.signal?.aborted === true;
|
|
627
664
|
connectAbort.abort(error);
|
|
665
|
+
let failure = error;
|
|
666
|
+
let cleanupIncomplete = false;
|
|
628
667
|
if (transport) {
|
|
629
668
|
try {
|
|
630
669
|
await transport.close();
|
|
631
|
-
} catch {
|
|
632
|
-
|
|
670
|
+
} catch (cleanupError) {
|
|
671
|
+
failure = combinedConnectionAndCleanupFailure(failure, cleanupError);
|
|
672
|
+
cleanupIncomplete = true;
|
|
633
673
|
}
|
|
634
674
|
}
|
|
635
|
-
if (
|
|
636
|
-
throw
|
|
675
|
+
if (cleanupIncomplete && transport) {
|
|
676
|
+
throw new MCPConnectionCleanupFailure(unwrapTransportFailure(failure), transport);
|
|
637
677
|
}
|
|
638
|
-
if (
|
|
639
|
-
throw
|
|
678
|
+
if (failure instanceof MCPExpectedFailure) {
|
|
679
|
+
throw failure;
|
|
640
680
|
}
|
|
641
|
-
|
|
681
|
+
if (aborted || (failure instanceof Error && failure.message === connectionTimeoutMessage)) {
|
|
682
|
+
throw new MCPExpectedFailure(failure);
|
|
683
|
+
}
|
|
684
|
+
throw failure;
|
|
642
685
|
}
|
|
643
686
|
}
|
|
644
687
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
listResources,
|
|
21
21
|
listResourceTemplates,
|
|
22
22
|
listTools,
|
|
23
|
+
MCPConnectionCleanupFailure,
|
|
23
24
|
readResource,
|
|
24
25
|
serverSupportsPrompts,
|
|
25
26
|
serverSupportsResources,
|
|
@@ -344,6 +345,8 @@ export class MCPManager {
|
|
|
344
345
|
#pendingConnections = new Map<string, Promise<MCPServerConnection>>();
|
|
345
346
|
#pendingConnectionControllers = new Map<string, AbortController>();
|
|
346
347
|
#pendingToolLoads = new Map<string, Promise<ToolLoadResult>>();
|
|
348
|
+
#pendingConnectionCleanups = new Map<string, MCPConnectionCleanupFailure>();
|
|
349
|
+
#pendingAcquireCleanups = new Map<string, Set<MCPPoolAcquireAbortError>>();
|
|
347
350
|
#sources = new Map<string, SourceMeta>();
|
|
348
351
|
#authStorage: AuthStorage | null = null;
|
|
349
352
|
#inputRequestHandler: MCPInputRequestHandler | null = null;
|
|
@@ -373,6 +376,88 @@ export class MCPManager {
|
|
|
373
376
|
#serverError(message: string): string {
|
|
374
377
|
return this.#toolsOnly ? "MCP server unavailable" : message;
|
|
375
378
|
}
|
|
379
|
+
|
|
380
|
+
#removePendingAcquireCleanup(name: string, error: MCPPoolAcquireAbortError): void {
|
|
381
|
+
const pending = this.#pendingAcquireCleanups.get(name);
|
|
382
|
+
pending?.delete(error);
|
|
383
|
+
if (pending?.size === 0) this.#pendingAcquireCleanups.delete(name);
|
|
384
|
+
}
|
|
385
|
+
#pruneSettledPendingAcquireCleanups(name: string): void {
|
|
386
|
+
for (const error of this.#pendingAcquireCleanups.get(name) ?? []) {
|
|
387
|
+
if (!this.#pool.hasPendingAcquireCleanup(error)) this.#removePendingAcquireCleanup(name, error);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
#retainPendingAcquireCleanup(name: string, error: MCPPoolAcquireAbortError): void {
|
|
392
|
+
if (error.cleanupGeneration === undefined || !error.cleanup || !this.#pool.hasPendingAcquireCleanup(error))
|
|
393
|
+
return;
|
|
394
|
+
let pending = this.#pendingAcquireCleanups.get(name);
|
|
395
|
+
if (!pending) {
|
|
396
|
+
pending = new Set();
|
|
397
|
+
this.#pendingAcquireCleanups.set(name, pending);
|
|
398
|
+
}
|
|
399
|
+
if (pending.has(error)) return;
|
|
400
|
+
pending.add(error);
|
|
401
|
+
void error.cleanup
|
|
402
|
+
.finally(() => {
|
|
403
|
+
if (!this.#pool.hasPendingAcquireCleanup(error)) this.#removePendingAcquireCleanup(name, error);
|
|
404
|
+
})
|
|
405
|
+
.catch(() => {});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
#retainConnectionCleanupFailure(name: string, error: unknown): MCPConnectionCleanupFailure | undefined {
|
|
409
|
+
if (error instanceof MCPPoolAcquireAbortError) {
|
|
410
|
+
this.#retainPendingAcquireCleanup(name, error);
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
if (!(error instanceof MCPConnectionCleanupFailure)) return undefined;
|
|
414
|
+
if (!this.#pool.ownsPendingAcquireCleanupFailure(error)) this.#pendingConnectionCleanups.set(name, error);
|
|
415
|
+
return error;
|
|
416
|
+
}
|
|
417
|
+
async #closePendingConnectionCleanup(name: string, waitForAcquireFlight = true): Promise<void> {
|
|
418
|
+
const failures: unknown[] = [];
|
|
419
|
+
const pendingAcquires = [...(this.#pendingAcquireCleanups.get(name) ?? [])];
|
|
420
|
+
const acquireResults = await Promise.allSettled(
|
|
421
|
+
pendingAcquires.map(error => {
|
|
422
|
+
if (!error.cleanup) return Promise.resolve();
|
|
423
|
+
if (!waitForAcquireFlight && !this.#pool.getPendingAcquireCleanupFailure(error)) return Promise.resolve();
|
|
424
|
+
return error.cleanup.then(
|
|
425
|
+
() => undefined,
|
|
426
|
+
() =>
|
|
427
|
+
this.#pool.closePendingAcquireCleanup(error.poolKey, error.cleanupGeneration!).then(() => undefined),
|
|
428
|
+
);
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
for (const [index, result] of acquireResults.entries()) {
|
|
432
|
+
const pendingAcquire = pendingAcquires[index];
|
|
433
|
+
if (!pendingAcquire) continue;
|
|
434
|
+
if (result.status === "rejected") failures.push(result.reason);
|
|
435
|
+
if (!this.#pool.hasPendingAcquireCleanup(pendingAcquire)) {
|
|
436
|
+
this.#removePendingAcquireCleanup(name, pendingAcquire);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const cleanup = this.#pendingConnectionCleanups.get(name);
|
|
441
|
+
if (cleanup) {
|
|
442
|
+
let cleanupFailed = false;
|
|
443
|
+
try {
|
|
444
|
+
await cleanup.close();
|
|
445
|
+
} catch (error) {
|
|
446
|
+
cleanupFailed = true;
|
|
447
|
+
const retained = error instanceof MCPConnectionCleanupFailure ? error : cleanup;
|
|
448
|
+
if (this.#pendingConnectionCleanups.get(name) === cleanup) {
|
|
449
|
+
this.#pendingConnectionCleanups.set(name, retained);
|
|
450
|
+
}
|
|
451
|
+
failures.push(error);
|
|
452
|
+
}
|
|
453
|
+
if (this.#pendingConnectionCleanups.get(name) === cleanup && !cleanupFailed) {
|
|
454
|
+
this.#pendingConnectionCleanups.delete(name);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (failures.length === 1) throw failures[0];
|
|
458
|
+
if (failures.length > 1) throw new AggregateError(failures, `MCP server cleanup failed: ${name}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
376
461
|
#assertRawMCPAccessAllowed(): void {
|
|
377
462
|
if (this.#toolsOnly) throw new Error("Tools-only MCP manager does not allow raw MCP access");
|
|
378
463
|
}
|
|
@@ -413,6 +498,11 @@ export class MCPManager {
|
|
|
413
498
|
connectionAbort: AbortController,
|
|
414
499
|
trackManagerLease = true,
|
|
415
500
|
): Promise<MCPPoolLease> {
|
|
501
|
+
const pendingCleanup = this.#pendingConnectionCleanups.get(name);
|
|
502
|
+
if (pendingCleanup) throw pendingCleanup;
|
|
503
|
+
if (connectionAbort.signal.aborted) {
|
|
504
|
+
throw connectionAbort.signal.reason ?? new Error(`MCP connection acquisition aborted: ${name}`);
|
|
505
|
+
}
|
|
416
506
|
const sharedConfig = originalConfig.sharing === "shared";
|
|
417
507
|
const sharedEligible =
|
|
418
508
|
sharedConfig &&
|
|
@@ -435,7 +525,9 @@ export class MCPManager {
|
|
|
435
525
|
effectiveHeaders:
|
|
436
526
|
resolvedConfig.type === "http" || resolvedConfig.type === "sse" ? resolvedConfig.headers : undefined,
|
|
437
527
|
onRequest: (method, params) => this.#handleServerRequest(method, params),
|
|
528
|
+
onCleanupPending: error => this.#retainPendingAcquireCleanup(name, error),
|
|
438
529
|
});
|
|
530
|
+
this.#pruneSettledPendingAcquireCleanups(name);
|
|
439
531
|
if (trackManagerLease) this.#leaseByConnection.set(lease.connection, lease);
|
|
440
532
|
if (!this.#toolsOnly) lease.updateRoots(this.#getRoots().roots);
|
|
441
533
|
return lease;
|
|
@@ -958,6 +1050,25 @@ export class MCPManager {
|
|
|
958
1050
|
const connectionTasks: ConnectionTask[] = [];
|
|
959
1051
|
|
|
960
1052
|
for (const [name, config] of Object.entries(configs)) {
|
|
1053
|
+
// Another manager sharing the pool may have retried and completed an
|
|
1054
|
+
// abandoned-acquire cleanup. Retire this manager's observation before
|
|
1055
|
+
// applying its reconnect fence; pool ownership remains authoritative.
|
|
1056
|
+
this.#pruneSettledPendingAcquireCleanups(name);
|
|
1057
|
+
const pendingAcquire = this.#pendingAcquireCleanups.get(name);
|
|
1058
|
+
const pendingCleanup = this.#pendingConnectionCleanups.get(name);
|
|
1059
|
+
if (pendingCleanup || pendingAcquire) {
|
|
1060
|
+
const message =
|
|
1061
|
+
pendingCleanup?.message ??
|
|
1062
|
+
(pendingAcquire
|
|
1063
|
+
? [...pendingAcquire]
|
|
1064
|
+
.map(error => this.#pool.getPendingAcquireCleanupFailure(error)?.message ?? error.message)
|
|
1065
|
+
.at(0)
|
|
1066
|
+
: undefined) ??
|
|
1067
|
+
"MCP connection cleanup pending";
|
|
1068
|
+
errors.set(name, this.#serverError(message));
|
|
1069
|
+
reportedErrors.add(name);
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
961
1072
|
if (sources[name]) {
|
|
962
1073
|
this.#sources.set(name, sources[name]);
|
|
963
1074
|
const existing = this.#connections.get(name);
|
|
@@ -1136,6 +1247,7 @@ export class MCPManager {
|
|
|
1136
1247
|
if (!this.#toolsOnly) await this.#loadServerResourcesAndPrompts(name, connection);
|
|
1137
1248
|
})
|
|
1138
1249
|
.catch(error => {
|
|
1250
|
+
this.#retainConnectionCleanupFailure(name, error);
|
|
1139
1251
|
if (this.#pendingToolLoads.get(name) !== toolsPromise) return;
|
|
1140
1252
|
this.#pendingToolLoads.delete(name);
|
|
1141
1253
|
if (!allowBackgroundLogging || reportedErrors.has(name) || this.#toolsOnly) return;
|
|
@@ -1270,6 +1382,7 @@ export class MCPManager {
|
|
|
1270
1382
|
}
|
|
1271
1383
|
} else if (task.tracked.status === "rejected") {
|
|
1272
1384
|
const reason = task.tracked.reason;
|
|
1385
|
+
this.#retainConnectionCleanupFailure(name, reason);
|
|
1273
1386
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
1274
1387
|
errors.set(name, this.#serverError(message));
|
|
1275
1388
|
reportedErrors.add(name);
|
|
@@ -1582,6 +1695,7 @@ export class MCPManager {
|
|
|
1582
1695
|
});
|
|
1583
1696
|
result = await Promise.race([callbackPromise, abortPromise]);
|
|
1584
1697
|
} catch (error) {
|
|
1698
|
+
this.#retainConnectionCleanupFailure(operation.name, error);
|
|
1585
1699
|
failed = true;
|
|
1586
1700
|
primaryError = error;
|
|
1587
1701
|
} finally {
|
|
@@ -1609,6 +1723,28 @@ export class MCPManager {
|
|
|
1609
1723
|
get retiredLeaseReleaseCountForTests(): number {
|
|
1610
1724
|
return this.#retiredLeaseReleases.size;
|
|
1611
1725
|
}
|
|
1726
|
+
|
|
1727
|
+
/** Read-only test seam for retained failed-connection cleanup owners. */
|
|
1728
|
+
get pendingConnectionCleanupCountForTests(): number {
|
|
1729
|
+
return (
|
|
1730
|
+
this.#pendingConnectionCleanups.size +
|
|
1731
|
+
[...this.#pendingAcquireCleanups.values()].reduce((count, pending) => count + pending.size, 0)
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/** Read-only test seam for retained failed-connection cleanup causes. */
|
|
1736
|
+
getPendingConnectionCleanupFailureForTests(name: string): MCPConnectionCleanupFailure | undefined {
|
|
1737
|
+
const pendingAcquire = this.#pendingAcquireCleanups.get(name);
|
|
1738
|
+
return (
|
|
1739
|
+
this.#pendingConnectionCleanups.get(name) ??
|
|
1740
|
+
(pendingAcquire
|
|
1741
|
+
? [...pendingAcquire]
|
|
1742
|
+
.map(error => this.#pool.getPendingAcquireCleanupFailure(error))
|
|
1743
|
+
.find((error): error is MCPConnectionCleanupFailure => error !== undefined)
|
|
1744
|
+
: undefined)
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1612
1748
|
/**
|
|
1613
1749
|
* Get all connected server names.
|
|
1614
1750
|
*/
|
|
@@ -1621,7 +1757,13 @@ export class MCPManager {
|
|
|
1621
1757
|
*/
|
|
1622
1758
|
getAllServerNames(): string[] {
|
|
1623
1759
|
return Array.from(
|
|
1624
|
-
new Set([
|
|
1760
|
+
new Set([
|
|
1761
|
+
...this.#sources.keys(),
|
|
1762
|
+
...this.#connections.keys(),
|
|
1763
|
+
...this.#pendingConnections.keys(),
|
|
1764
|
+
...this.#pendingConnectionCleanups.keys(),
|
|
1765
|
+
...this.#pendingAcquireCleanups.keys(),
|
|
1766
|
+
]),
|
|
1625
1767
|
);
|
|
1626
1768
|
}
|
|
1627
1769
|
|
|
@@ -1652,11 +1794,24 @@ export class MCPManager {
|
|
|
1652
1794
|
this.#subscribedResources.delete(name);
|
|
1653
1795
|
|
|
1654
1796
|
let closeError: unknown;
|
|
1797
|
+
try {
|
|
1798
|
+
// A transport may ignore its abort signal while the initial connection
|
|
1799
|
+
// is still opening. The pool owns that abandoned flight and closes any
|
|
1800
|
+
// late connection, so disconnect must not wait for the uncooperative
|
|
1801
|
+
// opener. Already-materialized cleanup failures are still retried here.
|
|
1802
|
+
await this.#closePendingConnectionCleanup(name, false);
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
closeError = error;
|
|
1805
|
+
}
|
|
1655
1806
|
if (connection) {
|
|
1656
1807
|
try {
|
|
1657
1808
|
await this.#releaseLease(name);
|
|
1658
1809
|
} catch (error) {
|
|
1659
|
-
|
|
1810
|
+
const leaseError = this.#logLeaseReleaseFailure(name, connection, error);
|
|
1811
|
+
closeError =
|
|
1812
|
+
closeError !== undefined
|
|
1813
|
+
? new AggregateError([closeError, leaseError], `MCP server cleanup failed: ${name}`)
|
|
1814
|
+
: leaseError;
|
|
1660
1815
|
}
|
|
1661
1816
|
if (this.#connections.get(name) === connection) this.#connections.delete(name);
|
|
1662
1817
|
}
|
|
@@ -1672,7 +1827,7 @@ export class MCPManager {
|
|
|
1672
1827
|
|
|
1673
1828
|
// Notify prompt consumers so stale commands are cleared
|
|
1674
1829
|
if (connection?.prompts?.length) this.#onPromptsChanged?.(name);
|
|
1675
|
-
if (closeError) throw closeError;
|
|
1830
|
+
if (closeError !== undefined) throw closeError;
|
|
1676
1831
|
}
|
|
1677
1832
|
|
|
1678
1833
|
#abortConnectionTask(task: ConnectionTask): void {
|
|
@@ -1692,12 +1847,8 @@ export class MCPManager {
|
|
|
1692
1847
|
}
|
|
1693
1848
|
|
|
1694
1849
|
async #terminateConnectionTask(task: ConnectionTask): Promise<void> {
|
|
1695
|
-
const connection = await task.connectionPromise.catch(
|
|
1696
|
-
|
|
1697
|
-
await error.cleanup.catch(cleanupError => {
|
|
1698
|
-
logger.error("MCP aborted acquire cleanup failed", { path: `mcp:${task.name}`, error: cleanupError });
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1850
|
+
const connection = await task.connectionPromise.catch(error => {
|
|
1851
|
+
this.#retainConnectionCleanupFailure(task.name, error);
|
|
1701
1852
|
logger.debug("MCP connection task did not publish before cleanup", { path: `mcp:${task.name}`, error });
|
|
1702
1853
|
return undefined;
|
|
1703
1854
|
});
|
|
@@ -1731,6 +1882,14 @@ export class MCPManager {
|
|
|
1731
1882
|
const lifecycleEpoch = this.#beginScopedLifecycle("disconnecting");
|
|
1732
1883
|
this.#deferredSharedRebinds.clear();
|
|
1733
1884
|
try {
|
|
1885
|
+
const cleanupNames = new Set([
|
|
1886
|
+
...this.#pendingConnectionCleanups.keys(),
|
|
1887
|
+
...this.#pendingAcquireCleanups.keys(),
|
|
1888
|
+
...this.#pendingConnectionControllers.keys(),
|
|
1889
|
+
...this.#pendingConnections.keys(),
|
|
1890
|
+
...this.#serverConfigs.keys(),
|
|
1891
|
+
...[...this.#scopedOperations.values()].map(operation => operation.name),
|
|
1892
|
+
]);
|
|
1734
1893
|
// Invalidate any in-flight reconnection attempts that outlive this call.
|
|
1735
1894
|
// They captured the old epoch; after increment they'll detect staleness.
|
|
1736
1895
|
this.#epoch++;
|
|
@@ -1757,6 +1916,11 @@ export class MCPManager {
|
|
|
1757
1916
|
}
|
|
1758
1917
|
this.#reconnectBackoffs.clear();
|
|
1759
1918
|
this.#pendingReconnections.clear();
|
|
1919
|
+
for (const name of this.#pendingConnectionCleanups.keys()) cleanupNames.add(name);
|
|
1920
|
+
for (const name of this.#pendingAcquireCleanups.keys()) cleanupNames.add(name);
|
|
1921
|
+
const pendingCleanupResults = await Promise.allSettled(
|
|
1922
|
+
[...cleanupNames].map(name => this.#closePendingConnectionCleanup(name, false)),
|
|
1923
|
+
);
|
|
1760
1924
|
const retiredReleaseFailures = await this.#drainRetiredLeaseReleases();
|
|
1761
1925
|
this.#deferredSharedRebinds.clear();
|
|
1762
1926
|
this.#pendingResourceRefresh.clear();
|
|
@@ -1770,6 +1934,9 @@ export class MCPManager {
|
|
|
1770
1934
|
...releaseResults
|
|
1771
1935
|
.filter((result): result is PromiseRejectedResult => result.status === "rejected")
|
|
1772
1936
|
.map(result => result.reason),
|
|
1937
|
+
...pendingCleanupResults
|
|
1938
|
+
.filter((result): result is PromiseRejectedResult => result.status === "rejected")
|
|
1939
|
+
.map(result => result.reason),
|
|
1773
1940
|
...retiredReleaseFailures,
|
|
1774
1941
|
];
|
|
1775
1942
|
if (releaseFailures.length > 0) {
|
|
@@ -1793,7 +1960,8 @@ export class MCPManager {
|
|
|
1793
1960
|
* Tears down the stale connection, re-resolves auth, establishes a new
|
|
1794
1961
|
* connection, reloads tools, and notifies consumers.
|
|
1795
1962
|
* Concurrent calls for the same server share one reconnection attempt.
|
|
1796
|
-
* Returns the new connection, or null if reconnection
|
|
1963
|
+
* Returns the new connection, or null if reconnection fails after cleanup.
|
|
1964
|
+
* Throws a retryable cleanup failure while the previous transport owner remains incomplete.
|
|
1797
1965
|
*/
|
|
1798
1966
|
async reconnectServer(name: string): Promise<MCPServerConnection | null> {
|
|
1799
1967
|
if (this.#connectionSetSealed) return null;
|
|
@@ -1801,6 +1969,14 @@ export class MCPManager {
|
|
|
1801
1969
|
const pending = this.#pendingReconnections.get(name);
|
|
1802
1970
|
if (pending) return pending;
|
|
1803
1971
|
|
|
1972
|
+
const attempt = this.#closePendingConnectionCleanup(name).then(() => this.#startReconnect(name));
|
|
1973
|
+
this.#pendingReconnections.set(name, attempt);
|
|
1974
|
+
return attempt.finally(() => {
|
|
1975
|
+
if (this.#pendingReconnections.get(name) === attempt) this.#pendingReconnections.delete(name);
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
#startReconnect(name: string): Promise<MCPServerConnection | null> {
|
|
1804
1980
|
const lease = this.#leases.get(name);
|
|
1805
1981
|
const sharedKey = lease?.sharingMode === "shared" ? lease.key : undefined;
|
|
1806
1982
|
let attempt: Promise<MCPServerConnection | null>;
|
|
@@ -1831,10 +2007,7 @@ export class MCPManager {
|
|
|
1831
2007
|
.finally(() => this.#pool.releaseRestart(sharedKey));
|
|
1832
2008
|
}
|
|
1833
2009
|
}
|
|
1834
|
-
|
|
1835
|
-
return attempt.finally(() => {
|
|
1836
|
-
if (this.#pendingReconnections.get(name) === attempt) this.#pendingReconnections.delete(name);
|
|
1837
|
-
});
|
|
2010
|
+
return attempt;
|
|
1838
2011
|
}
|
|
1839
2012
|
|
|
1840
2013
|
async #doReconnect(name: string): Promise<MCPServerConnection | null> {
|
|
@@ -1905,6 +2078,7 @@ export class MCPManager {
|
|
|
1905
2078
|
logger.debug("MCP reconnected", { path: `mcp:${name}`, tools: connection.tools?.length ?? 0 });
|
|
1906
2079
|
return connection;
|
|
1907
2080
|
} catch (error) {
|
|
2081
|
+
const cleanupFailure = this.#retainConnectionCleanupFailure(name, error);
|
|
1908
2082
|
if ((this.#disconnectEpochs.get(name) ?? 0) !== reconnectEpoch || backoffAbort.signal.aborted) {
|
|
1909
2083
|
logger.debug("MCP reconnect aborted after server disconnected", {
|
|
1910
2084
|
path: `mcp:${name}`,
|
|
@@ -1913,6 +2087,7 @@ export class MCPManager {
|
|
|
1913
2087
|
});
|
|
1914
2088
|
return null;
|
|
1915
2089
|
}
|
|
2090
|
+
if (cleanupFailure) throw cleanupFailure;
|
|
1916
2091
|
|
|
1917
2092
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1918
2093
|
if (attempt < delays.length) {
|