@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
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
JsonRpcRequest,
|
|
14
14
|
JsonRpcResponse,
|
|
15
15
|
MCPRequestOptions,
|
|
16
|
+
MCPStdioPreparedLaunch,
|
|
16
17
|
MCPStdioServerConfig,
|
|
17
18
|
MCPTransport,
|
|
18
19
|
} from "../../runtime-mcp/types";
|
|
@@ -56,6 +57,22 @@ function buildMinimalStdioEnv(explicit?: Record<string, string>): Record<string,
|
|
|
56
57
|
return { ...env, ...explicit };
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
function combinedOperationAndCleanupFailure(operationError: unknown, cleanupError: unknown): MCPExpectedFailure {
|
|
61
|
+
return new MCPExpectedFailure(
|
|
62
|
+
new AggregateError(
|
|
63
|
+
[operationError, cleanupError],
|
|
64
|
+
"MCP stdio operation failed and subprocess cleanup also failed",
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type StdioConnectAttempt = {
|
|
70
|
+
generation: number;
|
|
71
|
+
revoked: boolean;
|
|
72
|
+
settled: boolean;
|
|
73
|
+
completion: Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
|
|
59
76
|
export class StdioTransport implements MCPTransport {
|
|
60
77
|
#process: OwnedProcess | null = null;
|
|
61
78
|
#pendingRequests = new Map<
|
|
@@ -69,6 +86,10 @@ export class StdioTransport implements MCPTransport {
|
|
|
69
86
|
#readLoop: Promise<void> | null = null;
|
|
70
87
|
#stderrLoop: Promise<void> | null = null;
|
|
71
88
|
#closePromise: Promise<void> | null = null;
|
|
89
|
+
#connectGeneration = 0;
|
|
90
|
+
#connectAttempt: StdioConnectAttempt | null = null;
|
|
91
|
+
/** Retained until the per-attempt cleanup resolves successfully. */
|
|
92
|
+
#afterProcessExit: (() => Promise<void>) | null = null;
|
|
72
93
|
|
|
73
94
|
onClose?: () => void;
|
|
74
95
|
onError?: (error: Error) => void;
|
|
@@ -85,33 +106,122 @@ export class StdioTransport implements MCPTransport {
|
|
|
85
106
|
return true;
|
|
86
107
|
}
|
|
87
108
|
|
|
109
|
+
#registerCleanup(attempt: StdioConnectAttempt, cleanup: () => Promise<void>): void {
|
|
110
|
+
if (this.#connectAttempt !== attempt) {
|
|
111
|
+
throw new Error("MCP stdio attempt registered cleanup after its ownership ended");
|
|
112
|
+
}
|
|
113
|
+
if (this.#afterProcessExit && this.#afterProcessExit !== cleanup) {
|
|
114
|
+
throw new Error("MCP stdio attempt registered conflicting cleanup owners");
|
|
115
|
+
}
|
|
116
|
+
this.#afterProcessExit = cleanup;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#assertConnectAttemptActive(attempt: StdioConnectAttempt): void {
|
|
120
|
+
if (attempt.revoked || this.#connectAttempt !== attempt || this.#connectGeneration !== attempt.generation) {
|
|
121
|
+
throw new Error("MCP stdio connection attempt was closed");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#revokeConnectAttempt(): StdioConnectAttempt | null {
|
|
126
|
+
const attempt = this.#connectAttempt;
|
|
127
|
+
if (attempt && !attempt.revoked) {
|
|
128
|
+
attempt.revoked = true;
|
|
129
|
+
this.#connectGeneration++;
|
|
130
|
+
}
|
|
131
|
+
return attempt;
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
/**
|
|
89
135
|
* Start the subprocess and begin reading.
|
|
90
136
|
*/
|
|
91
137
|
async connect(): Promise<void> {
|
|
92
138
|
if (this.#connected) return;
|
|
93
|
-
if (this.#closePromise
|
|
139
|
+
if (this.#closePromise) {
|
|
140
|
+
throw new MCPExpectedFailure(new Error("MCP stdio child teardown is incomplete"));
|
|
141
|
+
}
|
|
142
|
+
if (this.#connectAttempt) {
|
|
143
|
+
if (!this.#connectAttempt.settled) return this.#connectAttempt.completion;
|
|
144
|
+
if (this.#process || this.#afterProcessExit) {
|
|
145
|
+
throw new MCPExpectedFailure(new Error("MCP stdio child teardown is incomplete"));
|
|
146
|
+
}
|
|
147
|
+
this.#connectAttempt = null;
|
|
148
|
+
}
|
|
149
|
+
if (this.#process || this.#afterProcessExit) {
|
|
94
150
|
throw new MCPExpectedFailure(new Error("MCP stdio child teardown is incomplete"));
|
|
95
151
|
}
|
|
96
152
|
|
|
97
|
-
const
|
|
153
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
154
|
+
const attempt: StdioConnectAttempt = {
|
|
155
|
+
generation: ++this.#connectGeneration,
|
|
156
|
+
revoked: false,
|
|
157
|
+
settled: false,
|
|
158
|
+
completion: promise,
|
|
159
|
+
};
|
|
160
|
+
this.#connectAttempt = attempt;
|
|
161
|
+
void this.#finishConnect(attempt).then(
|
|
162
|
+
() => {
|
|
163
|
+
attempt.settled = true;
|
|
164
|
+
resolve();
|
|
165
|
+
},
|
|
166
|
+
error => {
|
|
167
|
+
attempt.settled = true;
|
|
168
|
+
reject(error);
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
void promise.catch(() => {});
|
|
172
|
+
return promise;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async #finishConnect(attempt: StdioConnectAttempt): Promise<void> {
|
|
176
|
+
let launch: MCPStdioPreparedLaunch = {
|
|
177
|
+
command: this.config.command,
|
|
178
|
+
args: Object.freeze([...(this.config.args ?? [])]),
|
|
179
|
+
cwd: this.config.cwd ?? process.cwd(),
|
|
180
|
+
registerCleanup: cleanup => this.#registerCleanup(attempt, cleanup),
|
|
181
|
+
};
|
|
98
182
|
const env = this.config.noInheritEnv
|
|
99
183
|
? buildMinimalStdioEnv(this.config.env)
|
|
100
184
|
: {
|
|
101
185
|
...Bun.env,
|
|
102
186
|
...this.config.env,
|
|
103
187
|
};
|
|
104
|
-
const cwd = this.config.cwd ?? process.cwd();
|
|
105
|
-
|
|
106
188
|
try {
|
|
107
|
-
this.#
|
|
108
|
-
|
|
189
|
+
this.#assertConnectAttemptActive(attempt);
|
|
190
|
+
if (this.config.prepareSpawn) launch = await this.config.prepareSpawn(launch);
|
|
191
|
+
if (launch.afterProcessExit) this.#registerCleanup(attempt, launch.afterProcessExit);
|
|
192
|
+
else if (!this.#afterProcessExit && this.config.afterProcessExit) {
|
|
193
|
+
this.#registerCleanup(attempt, this.config.afterProcessExit);
|
|
194
|
+
}
|
|
195
|
+
this.#assertConnectAttemptActive(attempt);
|
|
196
|
+
await this.config.spawnGuard?.(launch);
|
|
197
|
+
this.#assertConnectAttemptActive(attempt);
|
|
198
|
+
await this.config.afterSpawnGuardForTest?.();
|
|
199
|
+
this.#assertConnectAttemptActive(attempt);
|
|
200
|
+
const process = spawnOwnedProcess([launch.command, ...launch.args], {
|
|
201
|
+
cwd: launch.cwd,
|
|
109
202
|
env,
|
|
110
203
|
stdin: "pipe",
|
|
111
204
|
gracefulMs: CLOSE_WAIT_MS,
|
|
112
|
-
name: `mcp-stdio:${
|
|
205
|
+
name: `mcp-stdio:${launch.command}`,
|
|
113
206
|
});
|
|
207
|
+
if (attempt.revoked) {
|
|
208
|
+
const teardown = await process.dispose();
|
|
209
|
+
await process.awaitExit({ timeoutMs: CLOSE_WAIT_MS }).catch(() => ({ exited: false, code: null }));
|
|
210
|
+
if (teardown.status !== "terminated") {
|
|
211
|
+
this.#process = process;
|
|
212
|
+
throw new MCPExpectedFailure(new Error(`stdio child teardown ${teardown.status}`));
|
|
213
|
+
}
|
|
214
|
+
throw new Error("MCP stdio connection attempt was closed");
|
|
215
|
+
}
|
|
216
|
+
this.#process = process;
|
|
114
217
|
} catch (error) {
|
|
218
|
+
if (!this.#process) {
|
|
219
|
+
try {
|
|
220
|
+
await this.#runAfterProcessExit();
|
|
221
|
+
} catch (cleanupError) {
|
|
222
|
+
throw combinedOperationAndCleanupFailure(error, cleanupError);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
115
225
|
throw new MCPExpectedFailure(error);
|
|
116
226
|
}
|
|
117
227
|
|
|
@@ -367,19 +477,32 @@ export class StdioTransport implements MCPTransport {
|
|
|
367
477
|
}
|
|
368
478
|
}
|
|
369
479
|
|
|
480
|
+
async #runAfterProcessExit(): Promise<void> {
|
|
481
|
+
const cleanup = this.#afterProcessExit;
|
|
482
|
+
if (!cleanup) return;
|
|
483
|
+
await cleanup();
|
|
484
|
+
if (this.#afterProcessExit === cleanup) this.#afterProcessExit = null;
|
|
485
|
+
}
|
|
486
|
+
|
|
370
487
|
async close(): Promise<void> {
|
|
371
488
|
await this.#closeInternal(false);
|
|
372
489
|
}
|
|
373
490
|
|
|
374
491
|
#closeInternal(fromReadLoop: boolean, failure?: MCPExpectedFailure): Promise<void> {
|
|
375
492
|
if (this.#closePromise) return this.#closePromise;
|
|
376
|
-
|
|
493
|
+
const connectAttempt = this.#revokeConnectAttempt();
|
|
494
|
+
this.#closePromise = this.#finishClose(fromReadLoop, failure, connectAttempt).finally(() => {
|
|
495
|
+
if (this.#connectAttempt === connectAttempt) this.#connectAttempt = null;
|
|
377
496
|
this.#closePromise = null;
|
|
378
497
|
});
|
|
379
498
|
return this.#closePromise;
|
|
380
499
|
}
|
|
381
500
|
|
|
382
|
-
async #finishClose(
|
|
501
|
+
async #finishClose(
|
|
502
|
+
fromReadLoop: boolean,
|
|
503
|
+
failure: MCPExpectedFailure | undefined,
|
|
504
|
+
connectAttempt: StdioConnectAttempt | null,
|
|
505
|
+
): Promise<void> {
|
|
383
506
|
const wasConnected = this.#connected;
|
|
384
507
|
this.#connected = false;
|
|
385
508
|
|
|
@@ -388,16 +511,36 @@ export class StdioTransport implements MCPTransport {
|
|
|
388
511
|
}
|
|
389
512
|
this.#pendingRequests.clear();
|
|
390
513
|
|
|
514
|
+
let primaryFailure: unknown = failure;
|
|
515
|
+
if (connectAttempt) {
|
|
516
|
+
try {
|
|
517
|
+
await connectAttempt.completion;
|
|
518
|
+
} catch (error) {
|
|
519
|
+
if (primaryFailure === undefined && this.#process) primaryFailure = error;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
391
522
|
const stdin = this.#getStdin();
|
|
392
523
|
const process = this.#process;
|
|
393
524
|
if (process) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
525
|
+
try {
|
|
526
|
+
stdin?.end();
|
|
527
|
+
const teardown = await process.dispose();
|
|
528
|
+
await process.awaitExit({ timeoutMs: CLOSE_WAIT_MS }).catch(() => ({ exited: false, code: null }));
|
|
529
|
+
if (teardown.status !== "terminated") {
|
|
530
|
+
throw new MCPExpectedFailure(new Error(`stdio child teardown ${teardown.status}`));
|
|
531
|
+
}
|
|
532
|
+
this.#process = null;
|
|
533
|
+
} catch (cleanupError) {
|
|
534
|
+
if (primaryFailure) throw combinedOperationAndCleanupFailure(primaryFailure, cleanupError);
|
|
535
|
+
throw cleanupError;
|
|
399
536
|
}
|
|
400
|
-
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
await this.#runAfterProcessExit();
|
|
541
|
+
} catch (cleanupError) {
|
|
542
|
+
if (primaryFailure) throw combinedOperationAndCleanupFailure(primaryFailure, cleanupError);
|
|
543
|
+
throw cleanupError;
|
|
401
544
|
}
|
|
402
545
|
|
|
403
546
|
if (!fromReadLoop && this.#readLoop) {
|
|
@@ -413,12 +556,3 @@ export class StdioTransport implements MCPTransport {
|
|
|
413
556
|
if (wasConnected) this.onClose?.();
|
|
414
557
|
}
|
|
415
558
|
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Create and connect a stdio transport.
|
|
419
|
-
*/
|
|
420
|
-
export async function createStdioTransport(config: MCPStdioServerConfig): Promise<StdioTransport> {
|
|
421
|
-
const transport = new StdioTransport(config);
|
|
422
|
-
await transport.connect();
|
|
423
|
-
return transport;
|
|
424
|
-
}
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -95,6 +95,22 @@ export interface MCPServerConfigBase {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/** Stdio server configuration */
|
|
98
|
+
export interface MCPStdioSpawnLaunch {
|
|
99
|
+
command: string;
|
|
100
|
+
args: readonly string[];
|
|
101
|
+
cwd: string;
|
|
102
|
+
/** Transport-supplied registrar for retryable cleanup before preparation allocates resources. */
|
|
103
|
+
registerCleanup?: (cleanup: () => Promise<void>) => void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface MCPStdioPreparedLaunch extends MCPStdioSpawnLaunch {
|
|
107
|
+
/**
|
|
108
|
+
* Per-attempt cleanup run after confirmed child exit or failed spawn. The same
|
|
109
|
+
* callback may already have been registered during preparation.
|
|
110
|
+
*/
|
|
111
|
+
afterProcessExit?: () => Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
export interface MCPStdioServerConfig extends MCPServerConfigBase {
|
|
99
115
|
type?: "stdio"; // Default if not specified
|
|
100
116
|
command: string;
|
|
@@ -108,6 +124,14 @@ export interface MCPStdioServerConfig extends MCPServerConfigBase {
|
|
|
108
124
|
*/
|
|
109
125
|
noInheritEnv?: boolean;
|
|
110
126
|
cwd?: string;
|
|
127
|
+
/** Internal trusted-boundary preparation of immutable per-spawn authority. */
|
|
128
|
+
prepareSpawn?: (launch: MCPStdioSpawnLaunch) => Promise<MCPStdioPreparedLaunch>;
|
|
129
|
+
/** Internal trusted-boundary check run immediately before process creation. */
|
|
130
|
+
spawnGuard?: (launch: MCPStdioSpawnLaunch) => Promise<void>;
|
|
131
|
+
/** Internal cleanup run only after the owned stdio child is confirmed exited. */
|
|
132
|
+
afterProcessExit?: () => Promise<void>;
|
|
133
|
+
/** Test seam for deterministic mutation after the guard and before process creation. */
|
|
134
|
+
afterSpawnGuardForTest?: () => Promise<void>;
|
|
111
135
|
}
|
|
112
136
|
|
|
113
137
|
/** HTTP server configuration (Streamable HTTP transport) */
|
package/src/sdk/broker/broker.ts
CHANGED
|
@@ -41,7 +41,9 @@ import {
|
|
|
41
41
|
executeLifecycle,
|
|
42
42
|
isCanonicalSessionId,
|
|
43
43
|
prepareSpawnChildHostLaunch,
|
|
44
|
+
readSessionLifecycleFailure,
|
|
44
45
|
validateBrokerModelPresetSync,
|
|
46
|
+
writeEffectMarker,
|
|
45
47
|
} from "./lifecycle";
|
|
46
48
|
import {
|
|
47
49
|
type LifecycleDurableEffectsReceipt,
|
|
@@ -724,6 +726,7 @@ function credentialFreeLifecycleResponse(value: unknown): unknown {
|
|
|
724
726
|
type LifecycleReplayEndpoint = {
|
|
725
727
|
endpoint: Record<string, unknown>;
|
|
726
728
|
endpointGeneration: number;
|
|
729
|
+
endpointIncarnation: string;
|
|
727
730
|
pid: number;
|
|
728
731
|
endpointMtimeMs: number;
|
|
729
732
|
};
|
|
@@ -1588,12 +1591,34 @@ export class Broker {
|
|
|
1588
1591
|
).claim;
|
|
1589
1592
|
return spawnFailureError(failure);
|
|
1590
1593
|
}
|
|
1594
|
+
const { pid, processIncarnation: incarnation } = launched.proof;
|
|
1595
|
+
if (pid === undefined || incarnation === undefined) {
|
|
1596
|
+
const failure: SpawnSubstrateFailure = {
|
|
1597
|
+
substrateKind: launched.proof.substrateKind,
|
|
1598
|
+
code: "substrate_proof_failed",
|
|
1599
|
+
message: "session.spawn substrate lacks lifecycle process authority",
|
|
1600
|
+
};
|
|
1601
|
+
await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
1602
|
+
launchedProof = undefined;
|
|
1603
|
+
current = (
|
|
1604
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
1605
|
+
claimId: current.claimId,
|
|
1606
|
+
from: "substrate_starting",
|
|
1607
|
+
to: "pre_send_rejected",
|
|
1608
|
+
failure,
|
|
1609
|
+
})
|
|
1610
|
+
).claim;
|
|
1611
|
+
return spawnFailureError(failure);
|
|
1612
|
+
}
|
|
1613
|
+
const marker = { pid, incarnation, effectMarker: prep.effectMarker };
|
|
1614
|
+
await writeEffectMarker(prep.stateRoot, prep.childId, marker);
|
|
1591
1615
|
const registration = await this.#spawnPromptLayer.awaitRegistration({
|
|
1592
1616
|
childId: prep.childId,
|
|
1593
1617
|
cwd: prep.cwd,
|
|
1594
1618
|
stateRoot: prep.stateRoot,
|
|
1595
1619
|
});
|
|
1596
1620
|
if (!registration.ok) {
|
|
1621
|
+
const startupFailure = await readSessionLifecycleFailure(prep.stateRoot, prep.childId, marker);
|
|
1597
1622
|
await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
1598
1623
|
launchedProof = undefined;
|
|
1599
1624
|
current = (
|
|
@@ -1603,7 +1628,11 @@ export class Broker {
|
|
|
1603
1628
|
to: "uncertain",
|
|
1604
1629
|
})
|
|
1605
1630
|
).claim;
|
|
1606
|
-
return error(
|
|
1631
|
+
return error(
|
|
1632
|
+
"terminal_uncertain",
|
|
1633
|
+
"session.spawn child registration is uncertain" +
|
|
1634
|
+
(startupFailure ? ` (${startupFailure.phase}/${startupFailure.reason})` : ""),
|
|
1635
|
+
);
|
|
1607
1636
|
}
|
|
1608
1637
|
pinnedRegistration = registration.registration;
|
|
1609
1638
|
const now = Date.now();
|
|
@@ -2404,26 +2433,35 @@ export class Broker {
|
|
|
2404
2433
|
)
|
|
2405
2434
|
return;
|
|
2406
2435
|
|
|
2407
|
-
//
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2436
|
+
// Give every reclaimed generation a fresh quarantine name. Older brokers
|
|
2437
|
+
// used only the lock inode hash; if that tombstone survived a crash, the
|
|
2438
|
+
// deterministic name collided forever and left the dead canonical lock in
|
|
2439
|
+
// place. The random suffix preserves no-replace rename semantics without
|
|
2440
|
+
// letting retained cleanup debris block takeover.
|
|
2441
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
2442
|
+
const tombstone = path.join(
|
|
2443
|
+
path.dirname(this.#lock),
|
|
2444
|
+
`.broker.lock.stale-${createHash("sha256").update(snapshot.lockIdentity).digest("hex")}-${randomBytes(8).toString("hex")}`,
|
|
2445
|
+
);
|
|
2446
|
+
try {
|
|
2447
|
+
await fs.rename(this.#lock, tombstone);
|
|
2448
|
+
return;
|
|
2449
|
+
} catch (e) {
|
|
2450
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
2451
|
+
if (code === "EEXIST" || code === "ENOTEMPTY") continue;
|
|
2452
|
+
if (["ENOENT", "EISDIR", "ENOTDIR"].includes(code ?? "")) return;
|
|
2453
|
+
if (code === "EPERM") {
|
|
2454
|
+
try {
|
|
2455
|
+
await fs.lstat(tombstone);
|
|
2456
|
+
continue;
|
|
2457
|
+
} catch (statError) {
|
|
2458
|
+
if ((statError as NodeJS.ErrnoException).code !== "ENOENT") throw statError;
|
|
2459
|
+
}
|
|
2423
2460
|
}
|
|
2461
|
+
throw e;
|
|
2424
2462
|
}
|
|
2425
|
-
throw e;
|
|
2426
2463
|
}
|
|
2464
|
+
throw new Error(`Broker lock quarantine namespace is saturated for ${this.#lock}`);
|
|
2427
2465
|
}
|
|
2428
2466
|
async #releaseOwnedLock(): Promise<void> {
|
|
2429
2467
|
try {
|
|
@@ -2549,6 +2587,11 @@ export class Broker {
|
|
|
2549
2587
|
startedAt: now,
|
|
2550
2588
|
heartbeatAt: now,
|
|
2551
2589
|
};
|
|
2590
|
+
// Readiness must not be externally visible until the initial session
|
|
2591
|
+
// checkpoint settles. The bootstrap watchdog owns this pre-publication
|
|
2592
|
+
// interval; publishing first allowed it to kill an endpoint already handed
|
|
2593
|
+
// to callers when a legitimate index-lock wait outlived the fence.
|
|
2594
|
+
await this.#checkpointSessionHeartbeats();
|
|
2552
2595
|
this.#publication = await publishBrokerDiscovery(this.settings.agentDir, this.discovery);
|
|
2553
2596
|
this.#publicationState = "healthy-owned";
|
|
2554
2597
|
this.#publishedAt = process.hrtime.bigint();
|
|
@@ -2560,7 +2603,6 @@ export class Broker {
|
|
|
2560
2603
|
void this.#watchPublication();
|
|
2561
2604
|
void this.#reapSpawnOrphans();
|
|
2562
2605
|
}, cadenceMs);
|
|
2563
|
-
await this.#checkpointSessionHeartbeats();
|
|
2564
2606
|
return this.discovery;
|
|
2565
2607
|
} catch (error) {
|
|
2566
2608
|
await this.#transport?.stop();
|
|
@@ -2862,6 +2904,8 @@ export class Broker {
|
|
|
2862
2904
|
endpointMtimeMs <= 0
|
|
2863
2905
|
)
|
|
2864
2906
|
return error("endpoint_stale", "session endpoint authority is incomplete");
|
|
2907
|
+
const currentIncarnation = endpointIncarnation(record, sessionId);
|
|
2908
|
+
if (!currentIncarnation) return error("endpoint_stale", "session endpoint incarnation is unavailable");
|
|
2865
2909
|
const endpoint = await this.#readEndpoint(record, {});
|
|
2866
2910
|
if (!endpoint.ok) return endpoint;
|
|
2867
2911
|
if (endpoint.result === null || typeof endpoint.result !== "object" || Array.isArray(endpoint.result))
|
|
@@ -2869,6 +2913,7 @@ export class Broker {
|
|
|
2869
2913
|
return {
|
|
2870
2914
|
endpoint: endpoint.result as Record<string, unknown>,
|
|
2871
2915
|
endpointGeneration: record.endpointGeneration,
|
|
2916
|
+
endpointIncarnation: currentIncarnation,
|
|
2872
2917
|
pid: record.pid,
|
|
2873
2918
|
endpointMtimeMs,
|
|
2874
2919
|
};
|
|
@@ -3041,16 +3086,21 @@ export class Broker {
|
|
|
3041
3086
|
if (this.#chains.get(target) === chain) this.#chains.delete(target);
|
|
3042
3087
|
}
|
|
3043
3088
|
}
|
|
3044
|
-
#storeSessionListCursor(cursor: SessionListCursor, replacingToken?: string): string
|
|
3089
|
+
#storeSessionListCursor(cursor: SessionListCursor, replacingToken?: string): string {
|
|
3045
3090
|
const now = Date.now();
|
|
3046
3091
|
for (const [token, stored] of this.#sessionListCursors) {
|
|
3047
3092
|
if (stored.expiresAt <= now) this.#sessionListCursors.delete(token);
|
|
3048
3093
|
}
|
|
3049
|
-
const replacing = replacingToken !== undefined && this.#sessionListCursors.has(replacingToken);
|
|
3050
|
-
if (!replacing && this.#sessionListCursors.size >= SESSION_LIST_MAX_CURSORS)
|
|
3051
|
-
return error("invalid_input", "session.list cursor capacity is exhausted");
|
|
3052
|
-
const token = randomBytes(24).toString("base64url");
|
|
3053
3094
|
if (replacingToken !== undefined) this.#sessionListCursors.delete(replacingToken);
|
|
3095
|
+
// Pagination cursors are a paging convenience, not durable state (#5370).
|
|
3096
|
+
// Evict the oldest cursor when the budget is full so abandoned or partial
|
|
3097
|
+
// paginations degrade gracefully instead of failing unrelated session ops.
|
|
3098
|
+
while (this.#sessionListCursors.size >= SESSION_LIST_MAX_CURSORS) {
|
|
3099
|
+
const oldest = this.#sessionListCursors.keys().next();
|
|
3100
|
+
if (oldest.done) break;
|
|
3101
|
+
this.#sessionListCursors.delete(oldest.value);
|
|
3102
|
+
}
|
|
3103
|
+
const token = randomBytes(24).toString("base64url");
|
|
3054
3104
|
this.#sessionListCursors.set(token, cursor);
|
|
3055
3105
|
return token;
|
|
3056
3106
|
}
|
|
@@ -3095,11 +3145,12 @@ export class Broker {
|
|
|
3095
3145
|
}
|
|
3096
3146
|
const limit = stored?.limit ?? requestedLimit ?? SESSION_LIST_DEFAULT_LIMIT;
|
|
3097
3147
|
const observedAt = stored?.observedAt ?? (scope === undefined ? undefined : new Date().toISOString());
|
|
3148
|
+
const resolveSessionId = typeof input.resolveSessionId === "string" ? input.resolveSessionId : undefined;
|
|
3098
3149
|
const snapshot = stored ?? {
|
|
3099
|
-
sessions:
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3150
|
+
sessions: (scope === undefined
|
|
3151
|
+
? [...result.sessions]
|
|
3152
|
+
: result.sessions.filter(session => scopeMatchesLocator(scope, session.locator))
|
|
3153
|
+
).filter(session => resolveSessionId === undefined || session.sessionId === resolveSessionId),
|
|
3103
3154
|
indexSeq: result.indexSeq,
|
|
3104
3155
|
warnings: [...result.warnings],
|
|
3105
3156
|
limit,
|
|
@@ -3120,7 +3171,6 @@ export class Broker {
|
|
|
3120
3171
|
{ ...snapshot, offset, expiresAt: Date.now() + SESSION_LIST_CURSOR_TTL_MS },
|
|
3121
3172
|
typeof cursor === "string" ? cursor : undefined,
|
|
3122
3173
|
);
|
|
3123
|
-
if (isBrokerResponse(continuationCursor)) return continuationCursor;
|
|
3124
3174
|
return {
|
|
3125
3175
|
ok: true,
|
|
3126
3176
|
result: {
|
|
@@ -3325,15 +3375,32 @@ export class Broker {
|
|
|
3325
3375
|
(operation === "session.create" || operation === "session.fork" || operation === "session.resume") &&
|
|
3326
3376
|
typeof (replay.result as { sessionId?: unknown } | undefined)?.sessionId === "string"
|
|
3327
3377
|
) {
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
|
|
3378
|
+
const replayResult = objectRecord(replay.result);
|
|
3379
|
+
const replaySessionId = (replay.result as { sessionId: string }).sessionId;
|
|
3380
|
+
const replayIncarnation =
|
|
3381
|
+
typeof replayResult?.endpointIncarnation === "string" &&
|
|
3382
|
+
/^[a-f0-9]{64}$/.test(replayResult.endpointIncarnation)
|
|
3383
|
+
? replayResult.endpointIncarnation
|
|
3384
|
+
: endpointIncarnation(
|
|
3385
|
+
{
|
|
3386
|
+
endpointGeneration: replayResult?.endpointGeneration as number,
|
|
3387
|
+
endpointMtimeMs: replayResult?.endpointMtimeMs as number,
|
|
3388
|
+
pid: replayResult?.pid as number,
|
|
3389
|
+
},
|
|
3390
|
+
replaySessionId,
|
|
3391
|
+
);
|
|
3392
|
+
if (!replayIncarnation)
|
|
3393
|
+
return error("endpoint_stale", "lifecycle replay lacks original endpoint authority");
|
|
3394
|
+
const refreshed = await this.#readLifecycleReplayEndpoint(replaySessionId);
|
|
3331
3395
|
if (isBrokerResponse(refreshed)) return refreshed;
|
|
3396
|
+
if (refreshed.endpointIncarnation !== replayIncarnation)
|
|
3397
|
+
return error("endpoint_stale", "lifecycle replay target was replaced");
|
|
3332
3398
|
return {
|
|
3333
3399
|
ok: true,
|
|
3334
3400
|
result: {
|
|
3335
3401
|
...(replay.result as Record<string, unknown>),
|
|
3336
3402
|
endpointGeneration: refreshed.endpointGeneration,
|
|
3403
|
+
endpointIncarnation: refreshed.endpointIncarnation,
|
|
3337
3404
|
pid: refreshed.pid,
|
|
3338
3405
|
endpointMtimeMs: refreshed.endpointMtimeMs,
|
|
3339
3406
|
endpoint: refreshed.endpoint,
|
|
@@ -3346,6 +3413,12 @@ export class Broker {
|
|
|
3346
3413
|
const response = outcome.response;
|
|
3347
3414
|
const storedResponse = credentialFreeLifecycleResponse(response) as BrokerResponse;
|
|
3348
3415
|
await this.ledger.transition(identity, lifecycleResponseState(response), {
|
|
3416
|
+
...(operation === "session.delete" &&
|
|
3417
|
+
typeof input.sessionId === "string" &&
|
|
3418
|
+
lifecycleResponseState(response) === "terminal_uncertain" &&
|
|
3419
|
+
!pendingCleanupSessionId(response)
|
|
3420
|
+
? { intendedSessionId: input.sessionId }
|
|
3421
|
+
: {}),
|
|
3349
3422
|
response: storedResponse,
|
|
3350
3423
|
responseDigest: createHash("sha256").update(canonicalJson(storedResponse)).digest("hex"),
|
|
3351
3424
|
...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
|
|
@@ -3365,6 +3438,12 @@ export class Broker {
|
|
|
3365
3438
|
const storedResponse = credentialFreeLifecycleResponse(response) as BrokerResponse;
|
|
3366
3439
|
await this.ledger.transition(identity, lifecycleResponseState(response), {
|
|
3367
3440
|
...(pendingCleanupSessionId(response) ? { intendedSessionId: pendingCleanupSessionId(response) } : {}),
|
|
3441
|
+
...(operation === "session.delete" &&
|
|
3442
|
+
typeof input.sessionId === "string" &&
|
|
3443
|
+
lifecycleResponseState(response) === "terminal_uncertain" &&
|
|
3444
|
+
!pendingCleanupSessionId(response)
|
|
3445
|
+
? { intendedSessionId: input.sessionId }
|
|
3446
|
+
: {}),
|
|
3368
3447
|
response: storedResponse,
|
|
3369
3448
|
responseDigest: createHash("sha256").update(canonicalJson(storedResponse)).digest("hex"),
|
|
3370
3449
|
...(outcome.durableEffects ? { durableEffects: outcome.durableEffects } : {}),
|
|
@@ -3376,8 +3455,21 @@ export class Broker {
|
|
|
3376
3455
|
const outcome = await executeLifecycle(this, operation, input, identity);
|
|
3377
3456
|
const response = outcome.response;
|
|
3378
3457
|
const storedResponse = credentialFreeLifecycleResponse(response) as BrokerResponse;
|
|
3458
|
+
// Record the refusal's own target session so the fence it may leave
|
|
3459
|
+
// is scoped to that session. Without this, a session.delete refusal
|
|
3460
|
+
// for X persists as an unbound terminal_uncertain row and fences
|
|
3461
|
+
// every later delete for unrelated sessions (#5364). A refusal that
|
|
3462
|
+
// truly cannot name its target fences nothing (see
|
|
3463
|
+
// hasUncertainCleanupForSession).
|
|
3464
|
+
const refusalSessionId =
|
|
3465
|
+
operation === "session.delete" && typeof input.sessionId === "string" ? input.sessionId : undefined;
|
|
3379
3466
|
await this.ledger.transition(identity, lifecycleResponseState(response), {
|
|
3380
3467
|
...(pendingCleanupSessionId(response) ? { intendedSessionId: pendingCleanupSessionId(response) } : {}),
|
|
3468
|
+
...(refusalSessionId !== undefined &&
|
|
3469
|
+
lifecycleResponseState(response) === "terminal_uncertain" &&
|
|
3470
|
+
!pendingCleanupSessionId(response)
|
|
3471
|
+
? { intendedSessionId: refusalSessionId }
|
|
3472
|
+
: {}),
|
|
3381
3473
|
resultSessionId:
|
|
3382
3474
|
response.ok && typeof (response.result as { sessionId?: unknown } | undefined)?.sessionId === "string"
|
|
3383
3475
|
? (response.result as { sessionId: string }).sessionId
|