@gajae-code/coding-agent 0.18.6 → 0.18.8
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 +318 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +8 -3
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +3 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +18 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +71 -0
- package/dist/types/session/session-storage.d.ts +62 -48
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/job-manager.ts +228 -122
- package/src/capability/index.ts +18 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/cli-main.ts +7 -8
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/sdk.ts +140 -29
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/settings.ts +44 -4
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +3 -0
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +168 -23
- package/src/discovery/mcp-json.ts +6 -0
- package/src/discovery/ssh.ts +9 -4
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +6 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -8
- package/src/gjc-runtime/launch-worktree.ts +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +83 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +19 -18
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/modes/acp/acp-agent.ts +163 -7
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +83 -7
- package/src/sdk/broker/ensure.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +820 -73
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +241 -46
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/bus/telegram-daemon.ts +4 -2
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2805 -411
- package/src/sdk/host/session-runtime.ts +428 -111
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +94 -14
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +81 -38
- package/src/session/agent-session.ts +674 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +482 -61
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +245 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
- package/src/session/session-manager.ts +939 -133
- package/src/session/session-retirement.ts +714 -0
- package/src/session/session-storage.ts +418 -174
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +641 -0
- package/src/session/task-artifact-owner-retirement.ts +477 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -5,6 +5,7 @@ import * as fs from "node:fs/promises";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { isContinuingMidRunMaintenanceOutcome, isNonDispatchedToolEvent, ThinkingLevel } from "@gajae-code/agent-core";
|
|
8
|
+
import type { AttemptScope } from "@gajae-code/agent-core/attempt-scope";
|
|
8
9
|
import type { Api, ImageContent, Model, ProviderDiagnostic } from "@gajae-code/ai/core";
|
|
9
10
|
|
|
10
11
|
import { logger } from "@gajae-code/utils";
|
|
@@ -229,6 +230,8 @@ class SdkOnlyIdempotencyConflictError extends Error {
|
|
|
229
230
|
* the outcome synchronously on its capture slot, while this runtime observes
|
|
230
231
|
* the publication from the separate `agent_end` handler. */
|
|
231
232
|
const SDK_ONLY_TERMINAL_PUBLICATION_WAIT_MS = 1_000;
|
|
233
|
+
/** Queue-owner cancellation is synchronous in AgentSession; this bound fails closed if its disposition is lost. */
|
|
234
|
+
const SDK_ONLY_QUEUE_CANCELLATION_WAIT_MS = 1_000;
|
|
232
235
|
/** Bounded wait for in-flight workflow gate resolutions to settle during SDK
|
|
233
236
|
* runtime shutdown before proceeding with cleanup. Unresolved resolutions
|
|
234
237
|
* after this bound are abandoned — their durable broker state is the recovery
|
|
@@ -746,6 +749,49 @@ export interface InvocationCorrelation {
|
|
|
746
749
|
turnId: string;
|
|
747
750
|
}
|
|
748
751
|
|
|
752
|
+
type AcceptedQueueDisposition = "preflight" | "queued" | "consumed" | "promoted" | "removed" | "teardown";
|
|
753
|
+
|
|
754
|
+
interface AcceptedQueueCancellationResult {
|
|
755
|
+
removed: boolean;
|
|
756
|
+
consumed: boolean;
|
|
757
|
+
unconfirmed: boolean;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
interface AcceptedQueueCancellation {
|
|
761
|
+
correlation: InvocationCorrelation;
|
|
762
|
+
connectionId: string | undefined;
|
|
763
|
+
controller: AbortController;
|
|
764
|
+
accepted: boolean;
|
|
765
|
+
queueCandidate: boolean;
|
|
766
|
+
disposition: AcceptedQueueDisposition;
|
|
767
|
+
dispositionPromise: Promise<AcceptedQueueDisposition>;
|
|
768
|
+
resolveDisposition: (disposition: AcceptedQueueDisposition) => void;
|
|
769
|
+
removalTerminalization?: Promise<boolean>;
|
|
770
|
+
removalFailure?: unknown;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function retireAcceptedQueueCancellation(
|
|
774
|
+
cancellations: Map<string, AcceptedQueueCancellation>,
|
|
775
|
+
correlation: InvocationCorrelation,
|
|
776
|
+
durableTerminalConfirmed = false,
|
|
777
|
+
): void {
|
|
778
|
+
const key = `${correlation.commandId}:${correlation.turnId}`;
|
|
779
|
+
const cancellation = cancellations.get(key);
|
|
780
|
+
if (!cancellation) return;
|
|
781
|
+
if (!cancellation.controller.signal.aborted) cancellation.controller.abort();
|
|
782
|
+
if (!durableTerminalConfirmed && cancellation.disposition === "removed" && cancellation.removalTerminalization) {
|
|
783
|
+
void cancellation.removalTerminalization.then(published => {
|
|
784
|
+
if (published && cancellations.get(key) === cancellation) cancellations.delete(key);
|
|
785
|
+
});
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (cancellations.get(key) === cancellation) cancellations.delete(key);
|
|
789
|
+
if (cancellation.disposition === "queued") {
|
|
790
|
+
cancellation.disposition = "teardown";
|
|
791
|
+
cancellation.resolveDisposition("teardown");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
749
795
|
export type InvocationKind = "prompt" | "skill" | "steer";
|
|
750
796
|
type InvocationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed" | "uncertain";
|
|
751
797
|
type InvocationOutcome = SdkPromptTerminalOutcome;
|
|
@@ -2063,12 +2109,7 @@ function createQuerySurface(
|
|
|
2063
2109
|
};
|
|
2064
2110
|
const getProfileCredentialSessionId = () => ctx.credentialSessionId ?? id;
|
|
2065
2111
|
const profileSettings = (options.settings ?? ctx.settings) as Pick<Settings, "get"> | undefined;
|
|
2066
|
-
const getProfileAvailableModels = (): Model<Api>[] =>
|
|
2067
|
-
const getAvailableForProfileActivation = ctx.modelRegistry.getAvailableForProfileActivation;
|
|
2068
|
-
return typeof getAvailableForProfileActivation === "function"
|
|
2069
|
-
? getAvailableForProfileActivation.call(ctx.modelRegistry)
|
|
2070
|
-
: ctx.modelRegistry.getAvailable();
|
|
2071
|
-
};
|
|
2112
|
+
const getProfileAvailableModels = (): Model<Api>[] => ctx.modelRegistry.getAvailable();
|
|
2072
2113
|
const resolveProfileAvailability = async (
|
|
2073
2114
|
profile: ModelProfileDefinition,
|
|
2074
2115
|
authenticatedProviders: ReadonlySet<string>,
|
|
@@ -2422,6 +2463,9 @@ function createQuerySurface(
|
|
|
2422
2463
|
getExtensions: () => ctx.getExtensions(),
|
|
2423
2464
|
getArtifactRange: (artifactId, offset, length) => ctx.getArtifactRange?.(artifactId, offset, length),
|
|
2424
2465
|
getJobs: () => ctx.getJobs(),
|
|
2466
|
+
...(typeof (ctx as Partial<ExtensionContext>).getProjectProgress === "function"
|
|
2467
|
+
? { getProjectProgress: () => ctx.getProjectProgress!() }
|
|
2468
|
+
: {}),
|
|
2425
2469
|
getPromptStatus: (selector: { commandId?: string; turnId?: string; clientRef?: string }) =>
|
|
2426
2470
|
reconciliation.lookup("prompt", selector),
|
|
2427
2471
|
getSkillInvokeStatus: (selector: { commandId?: string; turnId?: string; clientRef?: string }) =>
|
|
@@ -2694,6 +2738,11 @@ export function providerFailureFromAgentEnd(
|
|
|
2694
2738
|
return undefined;
|
|
2695
2739
|
}
|
|
2696
2740
|
if (errorKind === "local_snapshot_failure" || errorKind === "local_buffer_overflow") return undefined;
|
|
2741
|
+
if (errorKind === "local_empty_response")
|
|
2742
|
+
return {
|
|
2743
|
+
code: "empty_response",
|
|
2744
|
+
message: PROMPT_FAILURE_MESSAGE_SUBMISSION,
|
|
2745
|
+
};
|
|
2697
2746
|
let errorMessage: unknown;
|
|
2698
2747
|
try {
|
|
2699
2748
|
errorMessage = assistant.errorMessage;
|
|
@@ -2874,7 +2923,7 @@ function createControlSurface(
|
|
|
2874
2923
|
connectionId: string | undefined,
|
|
2875
2924
|
sdkRunToken: string,
|
|
2876
2925
|
promotion?: { startsOwnRun?: boolean; removed?: boolean },
|
|
2877
|
-
) =>
|
|
2926
|
+
) => undefined | Promise<boolean>,
|
|
2878
2927
|
policy?: SdkSurfacePolicy,
|
|
2879
2928
|
settings?: Settings,
|
|
2880
2929
|
configOverrides?: Map<string, unknown>,
|
|
@@ -2895,6 +2944,7 @@ function createControlSurface(
|
|
|
2895
2944
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
|
|
2896
2945
|
onInvocationCompletionReconciledForTests?: (kind: InvocationKind, correlation: InvocationCorrelation) => void,
|
|
2897
2946
|
publishLifecycleFrame?: (frame: SdkFrame) => void,
|
|
2947
|
+
acceptedQueueCancellations: Map<string, AcceptedQueueCancellation> = new Map(),
|
|
2898
2948
|
): ControlSurface {
|
|
2899
2949
|
const normalizePromptImages = (value: unknown): ImageContent[] => {
|
|
2900
2950
|
if (!Array.isArray(value)) return [];
|
|
@@ -2998,6 +3048,49 @@ function createControlSurface(
|
|
|
2998
3048
|
}
|
|
2999
3049
|
return pending;
|
|
3000
3050
|
};
|
|
3051
|
+
const cancelAcceptedQueueSubmissions = async (
|
|
3052
|
+
connectionId: string | undefined,
|
|
3053
|
+
admittedRequests: readonly AcceptedQueueCancellation[] = [...acceptedQueueCancellations.values()],
|
|
3054
|
+
): Promise<AcceptedQueueCancellationResult> => {
|
|
3055
|
+
const result: AcceptedQueueCancellationResult = { removed: false, consumed: false, unconfirmed: false };
|
|
3056
|
+
if (connectionId === undefined) return result;
|
|
3057
|
+
const owned = admittedRequests.filter(
|
|
3058
|
+
request => request.connectionId === connectionId && request.accepted && request.queueCandidate,
|
|
3059
|
+
);
|
|
3060
|
+
for (const request of owned) {
|
|
3061
|
+
let disposition = request.disposition;
|
|
3062
|
+
if (disposition === "queued") {
|
|
3063
|
+
request.controller.abort();
|
|
3064
|
+
disposition = request.disposition;
|
|
3065
|
+
if (disposition === "queued") {
|
|
3066
|
+
const timedOut = Promise.withResolvers<AcceptedQueueDisposition>();
|
|
3067
|
+
const timer = setTimeout(() => timedOut.resolve("teardown"), SDK_ONLY_QUEUE_CANCELLATION_WAIT_MS);
|
|
3068
|
+
timer.unref();
|
|
3069
|
+
try {
|
|
3070
|
+
disposition = await Promise.race([request.dispositionPromise, timedOut.promise]);
|
|
3071
|
+
} finally {
|
|
3072
|
+
clearTimeout(timer);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
if (disposition === "removed") {
|
|
3077
|
+
if (await request.removalTerminalization) result.removed = true;
|
|
3078
|
+
else result.unconfirmed = true;
|
|
3079
|
+
} else if (disposition === "consumed" || disposition === "promoted") {
|
|
3080
|
+
result.consumed = true;
|
|
3081
|
+
} else if (disposition === "teardown") {
|
|
3082
|
+
result.unconfirmed = true;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
return result;
|
|
3086
|
+
};
|
|
3087
|
+
const requesterOwnsActiveRun = (connectionId: string | undefined): boolean => {
|
|
3088
|
+
if (connectionId === undefined) return false;
|
|
3089
|
+
const seamOwner = terminalAbortSeams?.getActivePromptOwnerConnectionId?.();
|
|
3090
|
+
return seamOwner === undefined
|
|
3091
|
+
? (activePromptOwner.connectionIds?.has(connectionId) ?? false)
|
|
3092
|
+
: seamOwner === connectionId;
|
|
3093
|
+
};
|
|
3001
3094
|
const normalizeClientRef = (clientRef: string | undefined): string | undefined => {
|
|
3002
3095
|
if (clientRef === undefined) return undefined;
|
|
3003
3096
|
const trimmed = clientRef.trim();
|
|
@@ -3012,6 +3105,7 @@ function createControlSurface(
|
|
|
3012
3105
|
clientRef: string | undefined,
|
|
3013
3106
|
run: (options: {
|
|
3014
3107
|
sdkRunCapability: SdkRunCapability;
|
|
3108
|
+
preflightSignal: AbortSignal;
|
|
3015
3109
|
onPreflightAccepted: () => void;
|
|
3016
3110
|
onPreflightAcceptCommit: () => Promise<void>;
|
|
3017
3111
|
/** Internal disposition before a queued submission is actually consumed. */
|
|
@@ -3034,6 +3128,7 @@ function createControlSurface(
|
|
|
3034
3128
|
const sdkRunToken = `${correlation.commandId}:${correlation.turnId}`;
|
|
3035
3129
|
const sdkRunCapability = createSdkRunCapability(sdkRunToken);
|
|
3036
3130
|
const publishTerminal = (outcome: InvocationOutcome): void => {
|
|
3131
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
3037
3132
|
publishLifecycleFrame?.({
|
|
3038
3133
|
type: "agent_end",
|
|
3039
3134
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
@@ -3052,9 +3147,22 @@ function createControlSurface(
|
|
|
3052
3147
|
publishTerminal(canonicalFailedOutcome(failure));
|
|
3053
3148
|
};
|
|
3054
3149
|
const preflight = Promise.withResolvers<void>();
|
|
3150
|
+
const preflightController = new AbortController();
|
|
3151
|
+
const queueDisposition = Promise.withResolvers<AcceptedQueueDisposition>();
|
|
3152
|
+
const queueCancellation: AcceptedQueueCancellation = {
|
|
3153
|
+
correlation,
|
|
3154
|
+
connectionId: requesterConnectionId,
|
|
3155
|
+
controller: preflightController,
|
|
3156
|
+
accepted: false,
|
|
3157
|
+
queueCandidate: false,
|
|
3158
|
+
disposition: "preflight",
|
|
3159
|
+
dispositionPromise: queueDisposition.promise,
|
|
3160
|
+
resolveDisposition: queueDisposition.resolve,
|
|
3161
|
+
};
|
|
3055
3162
|
let accepted = false;
|
|
3056
3163
|
let settled = false;
|
|
3057
3164
|
const cancelPreflight = () => {
|
|
3165
|
+
preflightController.abort();
|
|
3058
3166
|
if (settled) return;
|
|
3059
3167
|
settled = true;
|
|
3060
3168
|
preflight.reject(
|
|
@@ -3073,6 +3181,8 @@ function createControlSurface(
|
|
|
3073
3181
|
try {
|
|
3074
3182
|
await reconciliation.noteAccepted(kind, correlation, retainedClientRef);
|
|
3075
3183
|
accepted = true;
|
|
3184
|
+
queueCancellation.accepted = true;
|
|
3185
|
+
if (queueCancellation.queueCandidate) queueCancellation.disposition = "queued";
|
|
3076
3186
|
settled = true;
|
|
3077
3187
|
if (kind === "prompt" && startsOwnTurn) armPromptDeadline(correlation);
|
|
3078
3188
|
// The accepted submission does NOT own the active turn until its run
|
|
@@ -3105,16 +3215,23 @@ function createControlSurface(
|
|
|
3105
3215
|
// Skills always start their own invocation; a plain prompt starts one
|
|
3106
3216
|
// only when idle at dispatch time.
|
|
3107
3217
|
const startsOwnTurn = kind === "skill" || (kind === "prompt" && !alwaysQueued && !queuedAtDispatch);
|
|
3218
|
+
queueCancellation.queueCandidate = queuedAtDispatch;
|
|
3219
|
+
if (queuedAtDispatch && queueCancellation.accepted) queueCancellation.disposition = "queued";
|
|
3220
|
+
if (kind === "prompt")
|
|
3221
|
+
acceptedQueueCancellations.set(`${correlation.commandId}:${correlation.turnId}`, queueCancellation);
|
|
3108
3222
|
let promotionStartsOwnRun: boolean | undefined;
|
|
3109
3223
|
try {
|
|
3110
3224
|
const submission = Promise.resolve(
|
|
3111
3225
|
run({
|
|
3112
3226
|
onPreflightAccepted: () => void accept().catch(() => undefined),
|
|
3227
|
+
preflightSignal: preflightController.signal,
|
|
3113
3228
|
sdkRunCapability,
|
|
3114
3229
|
onPreflightAcceptCommit: accept,
|
|
3115
3230
|
onDispatchDisposition: promotion => {
|
|
3116
3231
|
promotionStartsOwnRun = promotion.startsOwnRun;
|
|
3117
3232
|
if (promotion.startsOwnRun === false) {
|
|
3233
|
+
queueCancellation.queueCandidate = true;
|
|
3234
|
+
if (queueCancellation.accepted) queueCancellation.disposition = "queued";
|
|
3118
3235
|
// Dispatch-race diversion (#4668 review P1): the idle snapshot leased
|
|
3119
3236
|
// this prompt at acceptance, but it was actually diverted into the
|
|
3120
3237
|
// in-flight run's steering queue. While it sits queued — legitimately
|
|
@@ -3131,7 +3248,37 @@ function createControlSurface(
|
|
|
3131
3248
|
// terminal-abort that turn (review threads P1/P2).
|
|
3132
3249
|
onQueuedPromoted: (promotion?: { startsOwnRun?: boolean; removed?: boolean }) => {
|
|
3133
3250
|
promotionStartsOwnRun = promotion?.startsOwnRun;
|
|
3134
|
-
|
|
3251
|
+
if (promotion?.removed) {
|
|
3252
|
+
queueCancellation.disposition = "removed";
|
|
3253
|
+
queueCancellation.removalTerminalization = (async () => {
|
|
3254
|
+
try {
|
|
3255
|
+
return (
|
|
3256
|
+
(await onPromotedTurn?.(
|
|
3257
|
+
kind,
|
|
3258
|
+
correlation,
|
|
3259
|
+
requesterConnectionId,
|
|
3260
|
+
sdkRunToken,
|
|
3261
|
+
promotion,
|
|
3262
|
+
)) === true
|
|
3263
|
+
);
|
|
3264
|
+
} catch (error) {
|
|
3265
|
+
queueCancellation.removalFailure = error;
|
|
3266
|
+
logger.error("SDK queued prompt terminal publication failed", {
|
|
3267
|
+
commandId: correlation.commandId,
|
|
3268
|
+
turnId: correlation.turnId,
|
|
3269
|
+
error: String(error),
|
|
3270
|
+
});
|
|
3271
|
+
return false;
|
|
3272
|
+
}
|
|
3273
|
+
})();
|
|
3274
|
+
queueCancellation.resolveDisposition("removed");
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
if (queueCancellation.queueCandidate) {
|
|
3278
|
+
queueCancellation.disposition = promotion?.startsOwnRun === false ? "consumed" : "promoted";
|
|
3279
|
+
queueCancellation.resolveDisposition(queueCancellation.disposition);
|
|
3280
|
+
}
|
|
3281
|
+
void onPromotedTurn?.(kind, correlation, requesterConnectionId, sdkRunToken, promotion);
|
|
3135
3282
|
},
|
|
3136
3283
|
queuedAtDispatch,
|
|
3137
3284
|
}),
|
|
@@ -3218,6 +3365,7 @@ function createControlSurface(
|
|
|
3218
3365
|
},
|
|
3219
3366
|
error => {
|
|
3220
3367
|
if (settled) {
|
|
3368
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
3221
3369
|
// The submission promise rejects after preflight acceptance only when the
|
|
3222
3370
|
// work itself is over (provider stream interrupt, abort, queue failure).
|
|
3223
3371
|
// The accepted run never started (agent_start never fired), so its pending
|
|
@@ -3338,6 +3486,7 @@ function createControlSurface(
|
|
|
3338
3486
|
...(acceptedFields?.() ?? {}),
|
|
3339
3487
|
};
|
|
3340
3488
|
} catch (error) {
|
|
3489
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
3341
3490
|
if (!accepted) reconciliation.release(kind, retainedClientRef);
|
|
3342
3491
|
throw error;
|
|
3343
3492
|
} finally {
|
|
@@ -3705,6 +3854,36 @@ function createControlSurface(
|
|
|
3705
3854
|
return boundTerminalRetentionState(state.keys, mutate(state.scopes), terminalReservationLimit);
|
|
3706
3855
|
});
|
|
3707
3856
|
};
|
|
3857
|
+
const unconfirmedQueueTerminal = async (): Promise<unknown> => {
|
|
3858
|
+
const result = {
|
|
3859
|
+
ok: true,
|
|
3860
|
+
selection: scope,
|
|
3861
|
+
turn: "uncertain",
|
|
3862
|
+
ownedWork: scope === "turn" ? "left_running" : "uncertain",
|
|
3863
|
+
automaticDelivery: scope === "turn" ? "enabled" : "none",
|
|
3864
|
+
resumeOnOwnedCompletion: scope === "turn",
|
|
3865
|
+
reason: "queue_terminal_unconfirmed",
|
|
3866
|
+
};
|
|
3867
|
+
const payloadHash = hashResult(result);
|
|
3868
|
+
await transactBoundedTerminalScopes(scopes =>
|
|
3869
|
+
scopes.map(record =>
|
|
3870
|
+
(keyHash
|
|
3871
|
+
? record.idempotencyKeyHash === keyHash
|
|
3872
|
+
: record.turnContinuationFence.abortedAttemptEpoch === epoch) &&
|
|
3873
|
+
record.turnDisposition === "no_effect_reserved"
|
|
3874
|
+
? {
|
|
3875
|
+
...record,
|
|
3876
|
+
turnDisposition: "uncertain",
|
|
3877
|
+
responsePayloadHash: payloadHash,
|
|
3878
|
+
replayPayloadHash: replayShapedHash(record, result, payloadHash),
|
|
3879
|
+
terminalAt: Date.now(),
|
|
3880
|
+
}
|
|
3881
|
+
: record,
|
|
3882
|
+
),
|
|
3883
|
+
);
|
|
3884
|
+
return result;
|
|
3885
|
+
};
|
|
3886
|
+
const abortingConnectionId = sdkControlRequesterContext.getStore();
|
|
3708
3887
|
let handle = terminalAbortSeams.getActivePromptHandle();
|
|
3709
3888
|
let epoch = terminalAbortSeams.getTerminalTurnEpoch();
|
|
3710
3889
|
// Set when the no-effect reservation found an existing SAME-input row or
|
|
@@ -3796,9 +3975,12 @@ function createControlSurface(
|
|
|
3796
3975
|
const recheckedHandle = terminalAbortSeams.getActivePromptHandle();
|
|
3797
3976
|
const recheckedEpoch = terminalAbortSeams.getTerminalTurnEpoch();
|
|
3798
3977
|
if (!recheckedHandle || recheckedEpoch === undefined) {
|
|
3799
|
-
//
|
|
3800
|
-
//
|
|
3801
|
-
//
|
|
3978
|
+
// An accepted SDK queue owner is independent of the root prompt. Remove
|
|
3979
|
+
// only this requester's exact queued submissions, and do not claim the
|
|
3980
|
+
// root stopped; terminal success waits for each correlated removal
|
|
3981
|
+
// failure+terminal transaction to become durable.
|
|
3982
|
+
const queueCancellation = await cancelAcceptedQueueSubmissions(abortingConnectionId);
|
|
3983
|
+
if (queueCancellation.unconfirmed) return await unconfirmedQueueTerminal();
|
|
3802
3984
|
return await returnNoActiveTurn();
|
|
3803
3985
|
}
|
|
3804
3986
|
handle = recheckedHandle;
|
|
@@ -3816,7 +3998,6 @@ function createControlSurface(
|
|
|
3816
3998
|
// per-connection selection of the full bus path. The owner is re-read
|
|
3817
3999
|
// through the seam when provided (deterministic tests) and otherwise
|
|
3818
4000
|
// from the runtime-tracked accepting connection.
|
|
3819
|
-
const abortingConnectionId = sdkControlRequesterContext.getStore();
|
|
3820
4001
|
const currentOwnerConnectionIds = (): ReadonlySet<string> => {
|
|
3821
4002
|
const seam = terminalAbortSeams.getActivePromptOwnerConnectionId?.();
|
|
3822
4003
|
// The seam reports a single deterministic owner (test harnesses); the
|
|
@@ -3864,9 +4045,10 @@ function createControlSurface(
|
|
|
3864
4045
|
abortingConnectionId === undefined ||
|
|
3865
4046
|
!recheckedOwners.has(abortingConnectionId)
|
|
3866
4047
|
) {
|
|
3867
|
-
//
|
|
3868
|
-
//
|
|
3869
|
-
|
|
4048
|
+
// Request-owned steering cannot borrow the unrelated root's abort
|
|
4049
|
+
// authority. It may still be removed by its exact queue capability.
|
|
4050
|
+
const queueCancellation = await cancelAcceptedQueueSubmissions(abortingConnectionId);
|
|
4051
|
+
if (queueCancellation.unconfirmed) return await unconfirmedQueueTerminal();
|
|
3870
4052
|
const noActiveTurnResult = {
|
|
3871
4053
|
ok: true,
|
|
3872
4054
|
selection: scope,
|
|
@@ -4289,6 +4471,26 @@ function createControlSurface(
|
|
|
4289
4471
|
);
|
|
4290
4472
|
},
|
|
4291
4473
|
abort: async () => {
|
|
4474
|
+
const connectionId = sdkControlRequesterContext.getStore();
|
|
4475
|
+
const ownedAdmissions = [...acceptedQueueCancellations.values()].filter(
|
|
4476
|
+
request => connectionId !== undefined && request.connectionId === connectionId,
|
|
4477
|
+
);
|
|
4478
|
+
const ownedPreflights = connectionId === undefined ? [] : [...(pendingPreflights.get(connectionId) ?? [])];
|
|
4479
|
+
// Capture both phases before cancellation or any awaited terminal work:
|
|
4480
|
+
// acceptance can settle its callback before agent_start owns the run.
|
|
4481
|
+
for (const cancel of ownedPreflights) cancel();
|
|
4482
|
+
let cancelledAdmission = ownedPreflights.length > 0;
|
|
4483
|
+
for (const request of ownedAdmissions) {
|
|
4484
|
+
if (request.disposition !== "preflight" || request.controller.signal.aborted) continue;
|
|
4485
|
+
request.controller.abort();
|
|
4486
|
+
cancelledAdmission = true;
|
|
4487
|
+
}
|
|
4488
|
+
if (connectionId !== undefined && !requesterOwnsActiveRun(connectionId)) {
|
|
4489
|
+
const queueCancellation = await cancelAcceptedQueueSubmissions(connectionId, ownedAdmissions);
|
|
4490
|
+
if (queueCancellation.unconfirmed) return { aborted: false, reason: "queue_terminal_unconfirmed" };
|
|
4491
|
+
if (queueCancellation.removed || cancelledAdmission) return { aborted: true };
|
|
4492
|
+
return { aborted: false, turn: "no_active_turn" };
|
|
4493
|
+
}
|
|
4292
4494
|
await Promise.resolve(ctx.abort()).catch(() => undefined);
|
|
4293
4495
|
return { aborted: true };
|
|
4294
4496
|
},
|
|
@@ -4372,8 +4574,8 @@ function createControlSurface(
|
|
|
4372
4574
|
},
|
|
4373
4575
|
setModelProfile: id => (ctx.setModelProfile ? ctx.setModelProfile(id) : unavailable("model.profile.set")()),
|
|
4374
4576
|
cycleModel: () => (ctx.cycleModel ? ctx.cycleModel() : unavailable("model.cycle")()),
|
|
4375
|
-
setThinking: level => {
|
|
4376
|
-
api.
|
|
4577
|
+
setThinking: async level => {
|
|
4578
|
+
await api.setThinkingLevelForControl(level as ThinkingLevel, false);
|
|
4377
4579
|
return { changed: true };
|
|
4378
4580
|
},
|
|
4379
4581
|
cycleThinking: () =>
|
|
@@ -4628,6 +4830,61 @@ function quiescingFrame(frame: Record<string, unknown>): Record<string, unknown>
|
|
|
4628
4830
|
|
|
4629
4831
|
/** Install a complete SDK host for a session when notifications are inactive. */
|
|
4630
4832
|
export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: CreateSdkSessionRuntimeOptions): void {
|
|
4833
|
+
const acceptedQueueCancellations = new Map<string, AcceptedQueueCancellation>();
|
|
4834
|
+
const disposeAcceptedQueueCancellations = async (): Promise<void> => {
|
|
4835
|
+
const cancellations = [...acceptedQueueCancellations.values()];
|
|
4836
|
+
const queuedRemovals = cancellations.filter(
|
|
4837
|
+
cancellation =>
|
|
4838
|
+
cancellation.accepted &&
|
|
4839
|
+
cancellation.queueCandidate &&
|
|
4840
|
+
(cancellation.disposition === "queued" || cancellation.disposition === "removed"),
|
|
4841
|
+
);
|
|
4842
|
+
for (const cancellation of cancellations)
|
|
4843
|
+
if (!cancellation.controller.signal.aborted) cancellation.controller.abort();
|
|
4844
|
+
|
|
4845
|
+
const failures: unknown[] = [];
|
|
4846
|
+
const timedOut = Promise.withResolvers<void>();
|
|
4847
|
+
const timer = setTimeout(timedOut.resolve, SDK_ONLY_QUEUE_CANCELLATION_WAIT_MS);
|
|
4848
|
+
timer.unref();
|
|
4849
|
+
try {
|
|
4850
|
+
await Promise.all(
|
|
4851
|
+
queuedRemovals.map(async cancellation => {
|
|
4852
|
+
const publication = (async (): Promise<boolean> => {
|
|
4853
|
+
const disposition =
|
|
4854
|
+
cancellation.disposition === "queued"
|
|
4855
|
+
? await cancellation.dispositionPromise
|
|
4856
|
+
: cancellation.disposition;
|
|
4857
|
+
if (disposition === "promoted" || disposition === "consumed") return true;
|
|
4858
|
+
return disposition === "removed" && (await cancellation.removalTerminalization) === true;
|
|
4859
|
+
})();
|
|
4860
|
+
const published = await Promise.race([publication, timedOut.promise.then(() => false)]);
|
|
4861
|
+
const key = `${cancellation.correlation.commandId}:${cancellation.correlation.turnId}`;
|
|
4862
|
+
if (published || acceptedQueueCancellations.get(key) !== cancellation) {
|
|
4863
|
+
if (acceptedQueueCancellations.get(key) === cancellation) acceptedQueueCancellations.delete(key);
|
|
4864
|
+
} else {
|
|
4865
|
+
failures.push(
|
|
4866
|
+
cancellation.removalFailure ??
|
|
4867
|
+
new Error(
|
|
4868
|
+
`Queued prompt ${cancellation.correlation.commandId}:${cancellation.correlation.turnId} terminal publication was not confirmed during shutdown.`,
|
|
4869
|
+
),
|
|
4870
|
+
);
|
|
4871
|
+
}
|
|
4872
|
+
}),
|
|
4873
|
+
);
|
|
4874
|
+
} finally {
|
|
4875
|
+
clearTimeout(timer);
|
|
4876
|
+
}
|
|
4877
|
+
for (const cancellation of cancellations) {
|
|
4878
|
+
if (queuedRemovals.includes(cancellation)) continue;
|
|
4879
|
+
const key = `${cancellation.correlation.commandId}:${cancellation.correlation.turnId}`;
|
|
4880
|
+
if (acceptedQueueCancellations.get(key) === cancellation) acceptedQueueCancellations.delete(key);
|
|
4881
|
+
}
|
|
4882
|
+
if (failures.length > 0)
|
|
4883
|
+
throw Object.assign(
|
|
4884
|
+
new AggregateError(failures, "SDK runtime could not durably publish queued prompt cancellations."),
|
|
4885
|
+
{ code: "sdk_reconciliation_teardown_failed" },
|
|
4886
|
+
);
|
|
4887
|
+
};
|
|
4631
4888
|
let active:
|
|
4632
4889
|
| {
|
|
4633
4890
|
sessionId: string;
|
|
@@ -4658,6 +4915,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4658
4915
|
}>;
|
|
4659
4916
|
openLifecycleBatches: Array<{
|
|
4660
4917
|
epoch: number;
|
|
4918
|
+
/** Session-initiated starts still reserve their own unmatched end. */
|
|
4919
|
+
unownedStart: boolean;
|
|
4661
4920
|
invocations: Array<{
|
|
4662
4921
|
kind: InvocationKind;
|
|
4663
4922
|
correlation: InvocationCorrelation;
|
|
@@ -4713,6 +4972,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4713
4972
|
string,
|
|
4714
4973
|
{ state: RuntimeState; batch?: LifecycleBatch; correlationKey?: string }
|
|
4715
4974
|
>();
|
|
4975
|
+
const lifecycleScopeOwners = new WeakMap<AttemptScope, { state: RuntimeState; batch: LifecycleBatch }>();
|
|
4716
4976
|
const lifecycleCorrelationKey = (correlation: InvocationCorrelation): string =>
|
|
4717
4977
|
`${correlation.commandId}:${correlation.turnId}`;
|
|
4718
4978
|
const sessionIdentityForContext = (ctx: ExtensionContext): string | undefined => {
|
|
@@ -4782,13 +5042,18 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4782
5042
|
owner.unrecordedFailureReasons?.clear();
|
|
4783
5043
|
for (const [token, binding] of lifecycleRunOwners) if (binding.state === owner) lifecycleRunOwners.delete(token);
|
|
4784
5044
|
};
|
|
5045
|
+
const hasQueuedTerminalRecovery = (owner: RuntimeState): boolean =>
|
|
5046
|
+
[...acceptedQueueCancellations.values()].some(
|
|
5047
|
+
cancellation => cancellation.disposition === "removed" && owner.deadlineManager.has(cancellation.correlation),
|
|
5048
|
+
);
|
|
4785
5049
|
const maybeRetireLifecycleOwner = (owner: RuntimeState): void => {
|
|
4786
5050
|
if (
|
|
4787
5051
|
owner.pending.length > 0 ||
|
|
4788
5052
|
owner.openLifecycleBatches.length > 0 ||
|
|
4789
5053
|
(owner.attachedInvocations?.length ?? 0) > 0 ||
|
|
4790
5054
|
(owner.drainedInvocations?.length ?? 0) > 0 ||
|
|
4791
|
-
owner.lifecycleTasks.size > 0
|
|
5055
|
+
owner.lifecycleTasks.size > 0 ||
|
|
5056
|
+
hasQueuedTerminalRecovery(owner)
|
|
4792
5057
|
)
|
|
4793
5058
|
return;
|
|
4794
5059
|
removeRetiredLifecycleOwner(owner);
|
|
@@ -4809,8 +5074,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4809
5074
|
entry => lifecycleCorrelationKey(entry.correlation) !== target,
|
|
4810
5075
|
);
|
|
4811
5076
|
}
|
|
5077
|
+
// Retiring an SDK correlation must not discard an unrelated session-
|
|
5078
|
+
// initiated start. Its delayed end still belongs to that empty batch.
|
|
4812
5079
|
owner.openLifecycleBatches = owner.openLifecycleBatches.filter(
|
|
4813
|
-
batch => batch.invocations.length > 0 || batch.attachedInvocations.length > 0,
|
|
5080
|
+
batch => batch.unownedStart || batch.invocations.length > 0 || batch.attachedInvocations.length > 0,
|
|
4814
5081
|
);
|
|
4815
5082
|
owner.drainedInvocations = owner.drainedInvocations?.filter(
|
|
4816
5083
|
entry => lifecycleCorrelationKey(entry.correlation) !== target,
|
|
@@ -5089,32 +5356,24 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5089
5356
|
stopReason?: AgentEndEvent["stopReason"],
|
|
5090
5357
|
startToken?: string,
|
|
5091
5358
|
startTokens?: string[],
|
|
5359
|
+
startLifecycleScope?: AttemptScope,
|
|
5092
5360
|
): Promise<void> => {
|
|
5093
5361
|
const current = lifecycleOwner?.state ?? active;
|
|
5094
5362
|
if (!current) return;
|
|
5095
5363
|
const failureBatch = lifecycleOwner?.batch;
|
|
5096
|
-
const adoptLifecycleBatch = (
|
|
5097
|
-
batch
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
if (!batch || batch.length === 0) {
|
|
5106
|
-
current.activeInvocation = undefined;
|
|
5107
|
-
current.drainedInvocations = undefined;
|
|
5108
|
-
current.attachedInvocations = undefined;
|
|
5109
|
-
if (current === active) activePromptOwnerHolder.connectionIds = undefined;
|
|
5110
|
-
return;
|
|
5111
|
-
}
|
|
5112
|
-
current.activeInvocation = batch[0];
|
|
5113
|
-
current.drainedInvocations = batch.map(({ kind, correlation }) => ({ kind, correlation }));
|
|
5114
|
-
current.attachedInvocations = undefined;
|
|
5364
|
+
const adoptLifecycleBatch = (batch: LifecycleBatch | undefined): void => {
|
|
5365
|
+
current.activeInvocation = batch?.invocations[0];
|
|
5366
|
+
current.drainedInvocations = batch
|
|
5367
|
+
? [...batch.invocations, ...batch.attachedInvocations].map(({ kind, correlation }) => ({
|
|
5368
|
+
kind,
|
|
5369
|
+
correlation,
|
|
5370
|
+
}))
|
|
5371
|
+
: undefined;
|
|
5372
|
+
current.attachedInvocations = batch?.attachedInvocations.slice();
|
|
5115
5373
|
const owners = new Set<string>();
|
|
5116
|
-
for (const entry of batch
|
|
5117
|
-
|
|
5374
|
+
for (const entry of batch?.invocations ?? [])
|
|
5375
|
+
if (entry.connectionId !== undefined) owners.add(entry.connectionId);
|
|
5376
|
+
if (current === active) activePromptOwnerHolder.connectionIds = owners.size > 0 ? owners : undefined;
|
|
5118
5377
|
};
|
|
5119
5378
|
let transitions: Array<{ kind: InvocationKind; correlation: InvocationCorrelation }> = [];
|
|
5120
5379
|
if (type === "agent_failed") {
|
|
@@ -5148,13 +5407,18 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5148
5407
|
);
|
|
5149
5408
|
transitions = [...fallback, ...attached].map(({ kind, correlation }) => ({ kind, correlation }));
|
|
5150
5409
|
} else if (type === "agent_start") {
|
|
5151
|
-
|
|
5410
|
+
const scopeOwner = startLifecycleScope ? lifecycleScopeOwners.get(startLifecycleScope) : undefined;
|
|
5411
|
+
const scopeBatch =
|
|
5412
|
+
scopeOwner?.state === current && current.openLifecycleBatches.includes(scopeOwner.batch)
|
|
5413
|
+
? scopeOwner.batch
|
|
5414
|
+
: undefined;
|
|
5415
|
+
current.lifecycleEpoch = scopeBatch?.epoch ?? ++nextLifecycleEpoch;
|
|
5152
5416
|
if (current === active) activePromptOwnerHolder.lifecycleEpoch = current.lifecycleEpoch;
|
|
5153
5417
|
// Mark lifecycle active even when the drain is empty: a monitor/cron
|
|
5154
5418
|
// run started by the session has no SDK pending entry but is still a
|
|
5155
5419
|
// real active run that later in-run promotions must attach to instead
|
|
5156
|
-
// of falling back to pending (review P1).
|
|
5157
|
-
// previous
|
|
5420
|
+
// of falling back to pending (review P1). Only an explicitly bound
|
|
5421
|
+
// continuation retains a previous public lifecycle boundary.
|
|
5158
5422
|
current.lifecycleActive = true;
|
|
5159
5423
|
// Drain EVERY entry admitted for this run: a continuation may promote
|
|
5160
5424
|
// several follow-ups (each with its own requester correlation) into one
|
|
@@ -5175,40 +5439,47 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5175
5439
|
typeof startToken === "string" && startToken.length > 0
|
|
5176
5440
|
? lifecycleRunOwners.get(startToken)?.batch
|
|
5177
5441
|
: undefined;
|
|
5178
|
-
|
|
5179
|
-
|
|
5442
|
+
const continuationBatch =
|
|
5443
|
+
scopeBatch ??
|
|
5444
|
+
(drained.length === 0 && existingTokenBatch && current.openLifecycleBatches.includes(existingTokenBatch)
|
|
5445
|
+
? existingTokenBatch
|
|
5446
|
+
: undefined);
|
|
5447
|
+
let batch: LifecycleBatch;
|
|
5448
|
+
if (!continuationBatch) {
|
|
5449
|
+
// Every new run owns an end, including interactive/monitor runs
|
|
5450
|
+
// without SDK invocations. Their delayed ends must never consume
|
|
5451
|
+
// the next SDK run's terminal receipt.
|
|
5452
|
+
batch = {
|
|
5180
5453
|
epoch: current.lifecycleEpoch,
|
|
5454
|
+
unownedStart: drained.length === 0,
|
|
5181
5455
|
invocations: drained,
|
|
5182
5456
|
attachedInvocations: [],
|
|
5183
|
-
startPublished:
|
|
5457
|
+
startPublished: drained.length === 0,
|
|
5184
5458
|
heldContentDropped: false,
|
|
5185
5459
|
heldContent: [],
|
|
5186
5460
|
};
|
|
5187
5461
|
current.openLifecycleBatches.push(batch);
|
|
5188
|
-
for (const entry of drained) {
|
|
5189
|
-
lifecycleRunOwners.set(entry.sdkRunToken, {
|
|
5190
|
-
state: current,
|
|
5191
|
-
batch,
|
|
5192
|
-
correlationKey: lifecycleCorrelationKey(entry.correlation),
|
|
5193
|
-
});
|
|
5194
|
-
}
|
|
5195
|
-
adoptLifecycleBatch(drained);
|
|
5196
|
-
for (const entry of drained) {
|
|
5197
|
-
if (entry.kind !== "prompt") continue;
|
|
5198
|
-
if (current.deadlineManager.isExpiring(entry.correlation))
|
|
5199
|
-
current.deadlineManager.captureExpiringRun(entry.correlation);
|
|
5200
|
-
else current.deadlineManager.onRunStarted(entry.correlation);
|
|
5201
|
-
}
|
|
5202
|
-
} else if (existingTokenBatch && current.openLifecycleBatches.includes(existingTokenBatch)) {
|
|
5203
|
-
// A continuation within the same SDK-owned run carries the same token
|
|
5204
|
-
// but has no newly pending invocation. Preserve the established owner so
|
|
5205
|
-
// later chunks and the final terminal remain directed to its submitters.
|
|
5206
|
-
adoptLifecycleBatch(existingTokenBatch.invocations);
|
|
5207
5462
|
} else {
|
|
5208
|
-
//
|
|
5209
|
-
//
|
|
5210
|
-
|
|
5211
|
-
|
|
5463
|
+
// The producer consumed the predecessor end, or this is the same
|
|
5464
|
+
// token-owned run. Preserve its attached owners and single final end.
|
|
5465
|
+
batch = continuationBatch;
|
|
5466
|
+
batch.invocations.push(...drained);
|
|
5467
|
+
if (drained.length > 0) batch.startPublished = false;
|
|
5468
|
+
}
|
|
5469
|
+
if (startLifecycleScope) lifecycleScopeOwners.set(startLifecycleScope, { state: current, batch });
|
|
5470
|
+
for (const entry of drained) {
|
|
5471
|
+
lifecycleRunOwners.set(entry.sdkRunToken, {
|
|
5472
|
+
state: current,
|
|
5473
|
+
batch,
|
|
5474
|
+
correlationKey: lifecycleCorrelationKey(entry.correlation),
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5477
|
+
adoptLifecycleBatch(batch);
|
|
5478
|
+
for (const entry of drained) {
|
|
5479
|
+
if (entry.kind !== "prompt") continue;
|
|
5480
|
+
if (current.deadlineManager.isExpiring(entry.correlation))
|
|
5481
|
+
current.deadlineManager.captureExpiringRun(entry.correlation);
|
|
5482
|
+
else current.deadlineManager.onRunStarted(entry.correlation);
|
|
5212
5483
|
}
|
|
5213
5484
|
transitions = drained.map(({ kind, correlation }) => ({ kind, correlation }));
|
|
5214
5485
|
} else {
|
|
@@ -5284,7 +5555,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5284
5555
|
transitions.map(({ correlation }) => `${correlation.commandId}:${correlation.turnId}`),
|
|
5285
5556
|
);
|
|
5286
5557
|
if (
|
|
5287
|
-
|
|
5558
|
+
(ended.invocations.length > 0 || ended.attachedInvocations.length > 0) &&
|
|
5559
|
+
![...ended.invocations, ...ended.attachedInvocations].some(({ correlation }) =>
|
|
5288
5560
|
transitionKeys.has(`${correlation.commandId}:${correlation.turnId}`),
|
|
5289
5561
|
)
|
|
5290
5562
|
)
|
|
@@ -5331,7 +5603,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5331
5603
|
);
|
|
5332
5604
|
}
|
|
5333
5605
|
current.openLifecycleBatches = current.openLifecycleBatches.filter(
|
|
5334
|
-
batch => batch.invocations.length > 0,
|
|
5606
|
+
batch => batch.unownedStart || batch.invocations.length > 0 || batch.attachedInvocations.length > 0,
|
|
5335
5607
|
);
|
|
5336
5608
|
current.drainedInvocations = current.drainedInvocations?.filter(
|
|
5337
5609
|
entry =>
|
|
@@ -5342,7 +5614,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5342
5614
|
current.activeInvocation?.correlation.commandId === invocation.correlation.commandId &&
|
|
5343
5615
|
current.activeInvocation.correlation.turnId === invocation.correlation.turnId
|
|
5344
5616
|
)
|
|
5345
|
-
adoptLifecycleBatch(current.openLifecycleBatches[0]
|
|
5617
|
+
adoptLifecycleBatch(current.openLifecycleBatches[0]);
|
|
5346
5618
|
if (current.openLifecycleBatches.length === 0) current.lifecycleActive = false;
|
|
5347
5619
|
} catch (error) {
|
|
5348
5620
|
if (controller.signal.aborted) {
|
|
@@ -5714,6 +5986,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5714
5986
|
}
|
|
5715
5987
|
if (type === "agent_end" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) return;
|
|
5716
5988
|
if (type === "agent_end") {
|
|
5989
|
+
for (const invocation of transitions)
|
|
5990
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, invocation.correlation);
|
|
5717
5991
|
if (current.lifecycleEpoch !== eventLifecycleEpoch) {
|
|
5718
5992
|
// A successor agent_start won the lifecycle race while this event's
|
|
5719
5993
|
// durable transitions were awaiting persistence. Retire the ended
|
|
@@ -5763,7 +6037,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5763
6037
|
!current.deadlineManager.shouldDeferTerminalTransition(invocation.correlation)
|
|
5764
6038
|
)
|
|
5765
6039
|
current.deadlineManager.clear(invocation.correlation);
|
|
5766
|
-
adoptLifecycleBatch(current.openLifecycleBatches[0]
|
|
6040
|
+
adoptLifecycleBatch(current.openLifecycleBatches[0]);
|
|
5767
6041
|
if (current.openLifecycleBatches.length === 0) current.lifecycleActive = false;
|
|
5768
6042
|
// Resolve EVERY concurrent waiter for the aborted turn: the turn emits
|
|
5769
6043
|
// exactly one agent_end, and each admitted abort of it must observe the
|
|
@@ -5797,6 +6071,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5797
6071
|
undefined,
|
|
5798
6072
|
typeof event.sdkRunToken === "string" ? event.sdkRunToken : undefined,
|
|
5799
6073
|
event.sdkRunTokens,
|
|
6074
|
+
event.lifecycleScope,
|
|
5800
6075
|
),
|
|
5801
6076
|
owner,
|
|
5802
6077
|
).catch(error => {
|
|
@@ -5965,7 +6240,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5965
6240
|
api.on("turn_start", async (_event, ctx) => {
|
|
5966
6241
|
const current = lifecycleStateForContext(ctx, "agent_start");
|
|
5967
6242
|
if (!current) return;
|
|
5968
|
-
|
|
6243
|
+
// Optional broker discovery must not hold an interactive extension event open.
|
|
6244
|
+
// registerBroker owns single-flight recovery and optional-failure diagnostics.
|
|
6245
|
+
const registration = current.registerBroker();
|
|
6246
|
+
if (options.brokerRegistrationRequired) await registration;
|
|
5969
6247
|
current.runtime.emitEvent({ type: "turn_start", sessionId: ctx.sessionManager.getSessionId() });
|
|
5970
6248
|
});
|
|
5971
6249
|
const consumedMasterNonces = new Map<string, number>();
|
|
@@ -6124,6 +6402,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6124
6402
|
return code !== undefined && /^[A-Za-z0-9_.-]{1,64}$/u.test(code) ? code : "unavailable";
|
|
6125
6403
|
};
|
|
6126
6404
|
const startRuntime = async (ctx: ExtensionContext): Promise<void> => {
|
|
6405
|
+
if (active) return;
|
|
6406
|
+
if (acceptedQueueCancellations.size > 0) await disposeAcceptedQueueCancellations();
|
|
6127
6407
|
if (active) return;
|
|
6128
6408
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
6129
6409
|
const stateRoot = path.join(ctx.cwd, ".gjc", "state");
|
|
@@ -6364,6 +6644,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6364
6644
|
});
|
|
6365
6645
|
},
|
|
6366
6646
|
onExpired: correlation => {
|
|
6647
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation, true);
|
|
6367
6648
|
const owner = lifecycleOwnerHolder.state;
|
|
6368
6649
|
if (!owner) return;
|
|
6369
6650
|
removeLifecycleReferences(owner, correlation);
|
|
@@ -6400,26 +6681,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6400
6681
|
true,
|
|
6401
6682
|
);
|
|
6402
6683
|
}
|
|
6403
|
-
const openLifecycleBatches:
|
|
6404
|
-
epoch: number;
|
|
6405
|
-
invocations: Array<{
|
|
6406
|
-
kind: InvocationKind;
|
|
6407
|
-
correlation: InvocationCorrelation;
|
|
6408
|
-
connectionId: string | undefined;
|
|
6409
|
-
sdkRunToken: string;
|
|
6410
|
-
}>;
|
|
6411
|
-
attachedInvocations: Array<{
|
|
6412
|
-
kind: InvocationKind;
|
|
6413
|
-
correlation: InvocationCorrelation;
|
|
6414
|
-
connectionId: string | undefined;
|
|
6415
|
-
}>;
|
|
6416
|
-
startPublished: boolean;
|
|
6417
|
-
heldContentDropped: boolean;
|
|
6418
|
-
heldContent: Array<{
|
|
6419
|
-
event: AgentSessionEvent;
|
|
6420
|
-
recipients: Array<{ correlation: InvocationCorrelation; connectionId: string | undefined }>;
|
|
6421
|
-
}>;
|
|
6422
|
-
}> = [];
|
|
6684
|
+
const openLifecycleBatches: LifecycleBatch[] = [];
|
|
6423
6685
|
const configRevision = { current: 0 };
|
|
6424
6686
|
let acceptingGateResolutions = true;
|
|
6425
6687
|
const inFlightGateResolutions = new Set<Promise<unknown>>();
|
|
@@ -6521,9 +6783,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6521
6783
|
kind: InvocationKind,
|
|
6522
6784
|
correlation: InvocationCorrelation,
|
|
6523
6785
|
error: { code: string; message: string },
|
|
6524
|
-
):
|
|
6786
|
+
): Promise<boolean> => {
|
|
6525
6787
|
if (lifecycleOwnerHolder.state) removeLifecycleTokenAliases(lifecycleOwnerHolder.state, correlation);
|
|
6526
|
-
const attempt = async (remaining: number): Promise<
|
|
6788
|
+
const attempt = async (remaining: number): Promise<boolean> => {
|
|
6527
6789
|
try {
|
|
6528
6790
|
// 1. Record the failure reason durably FIRST. If this write fails,
|
|
6529
6791
|
// never fall through to agent_end: the record still has no error,
|
|
@@ -6546,7 +6808,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6546
6808
|
turnId: correlation.turnId,
|
|
6547
6809
|
error: sanitizePromptFailure(reasonError),
|
|
6548
6810
|
});
|
|
6549
|
-
return;
|
|
6811
|
+
return false;
|
|
6550
6812
|
}
|
|
6551
6813
|
await Bun.sleep(1_000);
|
|
6552
6814
|
return attempt(remaining - 1);
|
|
@@ -6570,6 +6832,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6570
6832
|
});
|
|
6571
6833
|
// 3. Release recovery ownership ONLY after durable terminalization.
|
|
6572
6834
|
deadlineManager.clear(correlation);
|
|
6835
|
+
if (kind === "prompt") retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
6836
|
+
return true;
|
|
6573
6837
|
} catch (transitionError) {
|
|
6574
6838
|
// Keep prompt recovery leased; skill recovery has no prompt lease.
|
|
6575
6839
|
if (kind === "skill") scheduleSkillRecovery(correlation);
|
|
@@ -6580,9 +6844,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6580
6844
|
turnId: correlation.turnId,
|
|
6581
6845
|
error: sanitizePromptFailure(transitionError),
|
|
6582
6846
|
});
|
|
6847
|
+
return false;
|
|
6583
6848
|
}
|
|
6584
6849
|
};
|
|
6585
|
-
|
|
6850
|
+
return attempt(3);
|
|
6586
6851
|
};
|
|
6587
6852
|
|
|
6588
6853
|
const controlSurface = createControlSurface(
|
|
@@ -6632,11 +6897,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6632
6897
|
});
|
|
6633
6898
|
};
|
|
6634
6899
|
if (lifecycleOwnerHolder.quiescing) {
|
|
6635
|
-
terminalizeAbandonedSubmission(kind, correlation, {
|
|
6900
|
+
return terminalizeAbandonedSubmission(kind, correlation, {
|
|
6636
6901
|
code: "session_quiescing",
|
|
6637
6902
|
message: "Session endpoint was replaced before the invocation started.",
|
|
6638
6903
|
});
|
|
6639
|
-
return;
|
|
6640
6904
|
}
|
|
6641
6905
|
// Lease at the ACTUAL promotion boundary (#4668 review): a promoted
|
|
6642
6906
|
// submission that wedges before its run's agent_start must still
|
|
@@ -6656,11 +6920,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6656
6920
|
entry.correlation.turnId === correlation.turnId,
|
|
6657
6921
|
);
|
|
6658
6922
|
if (pendingIdx >= 0) pending.splice(pendingIdx, 1);
|
|
6659
|
-
terminalizeAbandonedSubmission(kind, correlation, {
|
|
6923
|
+
return terminalizeAbandonedSubmission(kind, correlation, {
|
|
6660
6924
|
code: "cancelled",
|
|
6661
6925
|
message: "Queued prompt was removed before consumption.",
|
|
6662
6926
|
});
|
|
6663
|
-
return;
|
|
6664
6927
|
}
|
|
6665
6928
|
if (promotion?.startsOwnRun !== false) {
|
|
6666
6929
|
// A submission PROMOTED to its own run (finished prompt unwinding)
|
|
@@ -6706,7 +6969,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6706
6969
|
entry.correlation.turnId === current.activeInvocation?.correlation.turnId,
|
|
6707
6970
|
),
|
|
6708
6971
|
)
|
|
6709
|
-
:
|
|
6972
|
+
: current.openLifecycleBatches.at(-1);
|
|
6710
6973
|
const alreadyAttached = current.drainedInvocations.some(
|
|
6711
6974
|
entry =>
|
|
6712
6975
|
entry.correlation.commandId === correlation.commandId &&
|
|
@@ -6813,6 +7076,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6813
7076
|
trackGateResolution,
|
|
6814
7077
|
options.onInvocationCompletionReconciledForTests,
|
|
6815
7078
|
frame => runtime.emitEvent(frame),
|
|
7079
|
+
acceptedQueueCancellations,
|
|
6816
7080
|
);
|
|
6817
7081
|
const installProviderDefinitions = (capability: string, definitions: unknown): void => {
|
|
6818
7082
|
if (capability === "permission") {
|
|
@@ -7074,7 +7338,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7074
7338
|
};
|
|
7075
7339
|
const startupImageCapture = captureStartupRuntimeImage();
|
|
7076
7340
|
const registerBroker = async (): Promise<void> => {
|
|
7077
|
-
if (brokerRegistered) return;
|
|
7341
|
+
if (brokerRecoveryStopped || brokerRegistered) return;
|
|
7078
7342
|
if (brokerRegistrationInFlight !== undefined) {
|
|
7079
7343
|
await brokerRegistrationInFlight;
|
|
7080
7344
|
return;
|
|
@@ -7107,6 +7371,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7107
7371
|
throw new Error("SDK transport endpoint was not published before broker registration.");
|
|
7108
7372
|
const endpointPath = path.join(input.stateRoot, "sdk", `${input.sessionId}.json`);
|
|
7109
7373
|
const file = await readEndpointFile(endpointPath);
|
|
7374
|
+
if (brokerRecoveryStopped) return;
|
|
7110
7375
|
if (!file) throw new Error("SDK endpoint could not be read as a stable regular file.");
|
|
7111
7376
|
const endpoint = JSON.parse(file.source) as Record<string, unknown>;
|
|
7112
7377
|
if (
|
|
@@ -7310,13 +7575,32 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7310
7575
|
active = runtimeOwner;
|
|
7311
7576
|
try {
|
|
7312
7577
|
publishedEndpointUrl = (await runtime.start()).url;
|
|
7313
|
-
|
|
7578
|
+
// Required lifecycle hosts remain fail-closed; optional hosts publish locally
|
|
7579
|
+
// and recover broker availability outside the extension watchdog.
|
|
7580
|
+
const registration = registerBroker();
|
|
7581
|
+
if (options.brokerRegistrationRequired) await registration;
|
|
7314
7582
|
// Await the startup runtime image capture before arming recovery.
|
|
7315
7583
|
// This ensures replacement detection works correctly on the first recovery check.
|
|
7316
7584
|
await startupImageCapture;
|
|
7317
|
-
|
|
7585
|
+
// Recovery arms only after the startup attempt settles, so no tick overlaps it, and
|
|
7586
|
+
// a failed startup attempt holds the backoff like a failed recovery registration.
|
|
7587
|
+
const armBrokerRecovery = (): void => {
|
|
7588
|
+
if (brokerRecoveryStopped) return;
|
|
7589
|
+
if (!brokerRegistered) brokerRecoveryBackoff.recordFailure(options.agentDir);
|
|
7590
|
+
startBrokerRecovery();
|
|
7591
|
+
};
|
|
7592
|
+
if (options.brokerRegistrationRequired) armBrokerRecovery();
|
|
7593
|
+
else void registration.then(armBrokerRecovery);
|
|
7318
7594
|
} catch (error) {
|
|
7595
|
+
runtimeOwner.quiesceInput();
|
|
7596
|
+
runtimeOwner.fenceGateResolutions();
|
|
7319
7597
|
active = undefined;
|
|
7598
|
+
let queueCancellationFailure: unknown;
|
|
7599
|
+
try {
|
|
7600
|
+
await disposeAcceptedQueueCancellations();
|
|
7601
|
+
} catch (cleanupError) {
|
|
7602
|
+
queueCancellationFailure = cleanupError;
|
|
7603
|
+
}
|
|
7320
7604
|
stopBrokerRecovery();
|
|
7321
7605
|
disposeGate?.();
|
|
7322
7606
|
try {
|
|
@@ -7356,16 +7640,40 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7356
7640
|
};
|
|
7357
7641
|
lifecycleOwnerHolder.state = failedRuntimeOwner;
|
|
7358
7642
|
active = failedRuntimeOwner;
|
|
7359
|
-
|
|
7643
|
+
const failures = [
|
|
7644
|
+
error,
|
|
7645
|
+
...(queueCancellationFailure === undefined ? [] : [queueCancellationFailure]),
|
|
7646
|
+
cleanupError,
|
|
7647
|
+
];
|
|
7648
|
+
const startupFailure = new AggregateError(failures, "SDK runtime startup failed and cleanup failed.");
|
|
7649
|
+
if (queueCancellationFailure !== undefined)
|
|
7650
|
+
Object.assign(startupFailure, { code: "sdk_reconciliation_teardown_failed" });
|
|
7651
|
+
throw startupFailure;
|
|
7360
7652
|
}
|
|
7361
7653
|
cursors.close();
|
|
7362
7654
|
await revisions.close().catch(() => undefined);
|
|
7655
|
+
if (queueCancellationFailure !== undefined)
|
|
7656
|
+
throw Object.assign(
|
|
7657
|
+
new AggregateError(
|
|
7658
|
+
[error, queueCancellationFailure],
|
|
7659
|
+
"SDK runtime startup failed with queued cancellation publication errors.",
|
|
7660
|
+
),
|
|
7661
|
+
{ code: "sdk_reconciliation_teardown_failed" },
|
|
7662
|
+
);
|
|
7363
7663
|
throw error;
|
|
7364
7664
|
}
|
|
7365
7665
|
};
|
|
7366
7666
|
const stopActive = async (cancelSkillRecovery = false, unregisterReason?: "detached_idle"): Promise<void> => {
|
|
7367
7667
|
const current = active;
|
|
7368
7668
|
if (!current) return;
|
|
7669
|
+
current.quiesceInput();
|
|
7670
|
+
current.fenceGateResolutions();
|
|
7671
|
+
let queueCancellationFailure: unknown;
|
|
7672
|
+
try {
|
|
7673
|
+
await disposeAcceptedQueueCancellations();
|
|
7674
|
+
} catch (cleanupError) {
|
|
7675
|
+
queueCancellationFailure = cleanupError;
|
|
7676
|
+
}
|
|
7369
7677
|
if (cancelSkillRecovery) {
|
|
7370
7678
|
for (const controller of skillRecoveryControllers.values()) controller.abort();
|
|
7371
7679
|
for (const controller of skillTerminalRecoveryControllers.values()) controller.abort();
|
|
@@ -7373,8 +7681,6 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7373
7681
|
activePromptOwnerHolder.connectionIds = undefined;
|
|
7374
7682
|
activePromptOwnerHolder.lifecycleEpoch = undefined;
|
|
7375
7683
|
current.stopBrokerRecovery();
|
|
7376
|
-
current.quiesceInput();
|
|
7377
|
-
current.fenceGateResolutions();
|
|
7378
7684
|
try {
|
|
7379
7685
|
await current.waitForGateResolutionQuiescence();
|
|
7380
7686
|
if (current.lifecycleTasks.size > 0) {
|
|
@@ -7395,7 +7701,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7395
7701
|
current.openLifecycleBatches.length > 0 ||
|
|
7396
7702
|
(current.attachedInvocations?.length ?? 0) > 0 ||
|
|
7397
7703
|
(current.drainedInvocations?.length ?? 0) > 0 ||
|
|
7398
|
-
current.lifecycleTasks.size > 0
|
|
7704
|
+
current.lifecycleTasks.size > 0 ||
|
|
7705
|
+
hasQueuedTerminalRecovery(current);
|
|
7399
7706
|
if (retainsLifecycleWork) {
|
|
7400
7707
|
const owners = retiredLifecycleOwners.get(current.sessionId) ?? [];
|
|
7401
7708
|
if (!owners.includes(current)) owners.push(current);
|
|
@@ -7407,7 +7714,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7407
7714
|
current.openLifecycleBatches.length > 0 ||
|
|
7408
7715
|
(current.attachedInvocations?.length ?? 0) > 0 ||
|
|
7409
7716
|
(current.drainedInvocations?.length ?? 0) > 0 ||
|
|
7410
|
-
current.lifecycleTasks.size > 0
|
|
7717
|
+
current.lifecycleTasks.size > 0 ||
|
|
7718
|
+
hasQueuedTerminalRecovery(current)
|
|
7411
7719
|
) {
|
|
7412
7720
|
const retry = setTimeout(retryCleanup, LIFECYCLE_QUIESCENCE_MS);
|
|
7413
7721
|
retry.unref();
|
|
@@ -7431,10 +7739,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7431
7739
|
// shutdown from retrying the failed endpoint removal.
|
|
7432
7740
|
if (active === undefined) active = current;
|
|
7433
7741
|
logger.error("sdk runtime stop failed", { code: errorCode(error), error: String(error) });
|
|
7742
|
+
if (queueCancellationFailure !== undefined)
|
|
7743
|
+
throw Object.assign(
|
|
7744
|
+
new AggregateError(
|
|
7745
|
+
[queueCancellationFailure, error],
|
|
7746
|
+
"SDK runtime stop and queued cancellation publication both failed.",
|
|
7747
|
+
),
|
|
7748
|
+
{ code: "sdk_reconciliation_teardown_failed" },
|
|
7749
|
+
);
|
|
7434
7750
|
throw error;
|
|
7435
7751
|
}
|
|
7436
7752
|
current.cursors.close();
|
|
7437
7753
|
await current.revisions.close();
|
|
7754
|
+
if (queueCancellationFailure !== undefined) throw queueCancellationFailure;
|
|
7438
7755
|
};
|
|
7439
7756
|
api.on("session_start", async (_event, ctx) => {
|
|
7440
7757
|
await startRuntime(ctx);
|