@gajae-code/coding-agent 0.18.7 → 0.18.9
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 +377 -1
- package/dist/types/async/endpoint-id.d.ts +16 -0
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/async/support.d.ts +0 -14
- 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 +2 -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/commands/mcp-serve.d.ts +4 -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/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 +6 -1
- package/dist/types/discovery/mcp-json.d.ts +1 -0
- package/dist/types/eval/py/kernel.d.ts +18 -0
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/html-export-destination.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
- 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 +4 -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/psmux-detect.d.ts +3 -3
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
- 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 +14 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
- 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/config.d.ts +8 -5
- 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/manager.d.ts +2 -1
- package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +27 -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 +24 -2
- package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -1
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- 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/client/client.d.ts +2 -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 +2 -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 +26 -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 +2 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +31 -7
- package/dist/types/session/session-storage.d.ts +69 -2
- package/dist/types/session/task-artifact-owner-codec.d.ts +2 -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/skill-state/workflow-mutation-guard.d.ts +3 -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/task/isolation-dir.d.ts +26 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/browser/cdp-url.d.ts +2 -0
- package/dist/types/tools/github-side-effect.d.ts +22 -0
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/monitor-mutation.d.ts +9 -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/verify-managed-empty-stop.ts +28 -0
- package/src/async/endpoint-id.ts +26 -0
- package/src/async/job-manager.ts +247 -132
- package/src/async/support.ts +0 -25
- package/src/capability/index.ts +15 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +2 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/doctor/repairs.ts +4 -1
- package/src/cli/doctor-supervisor.ts +3 -0
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/public-command-registry.ts +28 -6
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/mcp-serve.ts +7 -0
- package/src/commands/sdk.ts +455 -397
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +316 -79
- 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/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/coordinator-mcp/server.ts +2 -0
- 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 +4 -13
- package/src/dap/config.ts +2 -2
- 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/register.ts +10 -6
- 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 +13 -1
- package/src/discovery/helpers.ts +171 -29
- package/src/discovery/mcp-json.ts +26 -12
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +796 -154
- package/src/eval/py/kernel.ts +79 -13
- package/src/eval/py/tool-bridge.ts +4 -0
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/html-export-destination.ts +16 -0
- package/src/export/html/index.ts +7 -10
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
- package/src/extensibility/extensions/runner.ts +15 -1
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -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 +9 -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 -9
- package/src/gjc-runtime/launch-worktree.ts +70 -9
- package/src/gjc-runtime/psmux-detect.ts +28 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/session-state-sidecar.ts +3 -2
- package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
- 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 +105 -16
- package/src/gjc-runtime/tmux-common.ts +8 -5
- 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/internal-urls/docs-index.generated.ts +20 -19
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/config.ts +12 -63
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/memories/index.ts +88 -7
- package/src/modes/acp/acp-agent.ts +88 -4
- 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/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +1 -1
- 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 +76 -31
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
- package/src/modes/controllers/selector-controller.ts +24 -15
- package/src/modes/controllers/todo-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -1
- 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 +4 -2
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/json-rpc.ts +2 -1
- package/src/runtime-mcp/manager.ts +29 -11
- package/src/runtime-mcp/mcp-redirect.ts +73 -0
- package/src/runtime-mcp/oauth-flow.ts +14 -2
- package/src/runtime-mcp/oauth-public-url.ts +14 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/pool.ts +6 -0
- package/src/runtime-mcp/project-host-values.ts +46 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +4 -3
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +95 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +81 -6
- package/src/sdk/broker/doctor-restart.ts +4 -0
- package/src/sdk/broker/ensure.ts +31 -2
- package/src/sdk/broker/lifecycle.ts +711 -66
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +238 -43
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2705 -303
- package/src/sdk/host/session-runtime.ts +490 -143
- 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/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 +82 -18
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +69 -45
- package/src/session/agent-session.ts +679 -238
- package/src/session/artifacts.ts +176 -71
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3779 -462
- package/src/session/internal/managed-session-storage.ts +501 -78
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +19 -3
- package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
- package/src/session/session-manager.ts +951 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +447 -15
- package/src/session/task-artifact-owner-codec.ts +71 -8
- package/src/session/task-artifact-owner-retirement.ts +21 -19
- 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 +145 -2
- package/src/skill-state/workflow-mutation-guard.ts +102 -10
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/mcp.ts +8 -4
- 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/task/isolation-dir.ts +143 -0
- package/src/task/worktree.ts +16 -7
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit-write-path.ts +68 -0
- package/src/tools/ast-edit.ts +10 -1
- 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/browser/cdp-url.ts +18 -0
- package/src/tools/browser.ts +8 -1
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/gh.ts +10 -0
- package/src/tools/github-side-effect.ts +112 -0
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/monitor-mutation.ts +27 -0
- package/src/tools/monitor.ts +7 -0
- package/src/tools/python.ts +188 -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/external-editor.ts +3 -2
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/insane/url-guard.ts +2 -1
- 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";
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
validateSettingPatch,
|
|
35
36
|
} from "../../config/settings";
|
|
36
37
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
|
|
38
|
+
import { tagSdkLifecycleObserver } from "../../extensibility/extensions/function-hooks-internal";
|
|
37
39
|
import type { AgentEndEvent } from "../../extensibility/shared-events";
|
|
38
40
|
import { normalizeGoal } from "../../goals/state";
|
|
39
41
|
import { toAgentWireEventPayload } from "../../modes/shared/agent-wire/event-envelope";
|
|
@@ -229,6 +231,8 @@ class SdkOnlyIdempotencyConflictError extends Error {
|
|
|
229
231
|
* the outcome synchronously on its capture slot, while this runtime observes
|
|
230
232
|
* the publication from the separate `agent_end` handler. */
|
|
231
233
|
const SDK_ONLY_TERMINAL_PUBLICATION_WAIT_MS = 1_000;
|
|
234
|
+
/** Queue-owner cancellation is synchronous in AgentSession; this bound fails closed if its disposition is lost. */
|
|
235
|
+
const SDK_ONLY_QUEUE_CANCELLATION_WAIT_MS = 1_000;
|
|
232
236
|
/** Bounded wait for in-flight workflow gate resolutions to settle during SDK
|
|
233
237
|
* runtime shutdown before proceeding with cleanup. Unresolved resolutions
|
|
234
238
|
* after this bound are abandoned — their durable broker state is the recovery
|
|
@@ -746,6 +750,49 @@ export interface InvocationCorrelation {
|
|
|
746
750
|
turnId: string;
|
|
747
751
|
}
|
|
748
752
|
|
|
753
|
+
type AcceptedQueueDisposition = "preflight" | "queued" | "consumed" | "promoted" | "removed" | "teardown";
|
|
754
|
+
|
|
755
|
+
interface AcceptedQueueCancellationResult {
|
|
756
|
+
removed: boolean;
|
|
757
|
+
consumed: boolean;
|
|
758
|
+
unconfirmed: boolean;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
interface AcceptedQueueCancellation {
|
|
762
|
+
correlation: InvocationCorrelation;
|
|
763
|
+
connectionId: string | undefined;
|
|
764
|
+
controller: AbortController;
|
|
765
|
+
accepted: boolean;
|
|
766
|
+
queueCandidate: boolean;
|
|
767
|
+
disposition: AcceptedQueueDisposition;
|
|
768
|
+
dispositionPromise: Promise<AcceptedQueueDisposition>;
|
|
769
|
+
resolveDisposition: (disposition: AcceptedQueueDisposition) => void;
|
|
770
|
+
removalTerminalization?: Promise<boolean>;
|
|
771
|
+
removalFailure?: unknown;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function retireAcceptedQueueCancellation(
|
|
775
|
+
cancellations: Map<string, AcceptedQueueCancellation>,
|
|
776
|
+
correlation: InvocationCorrelation,
|
|
777
|
+
durableTerminalConfirmed = false,
|
|
778
|
+
): void {
|
|
779
|
+
const key = `${correlation.commandId}:${correlation.turnId}`;
|
|
780
|
+
const cancellation = cancellations.get(key);
|
|
781
|
+
if (!cancellation) return;
|
|
782
|
+
if (!cancellation.controller.signal.aborted) cancellation.controller.abort();
|
|
783
|
+
if (!durableTerminalConfirmed && cancellation.disposition === "removed" && cancellation.removalTerminalization) {
|
|
784
|
+
void cancellation.removalTerminalization.then(published => {
|
|
785
|
+
if (published && cancellations.get(key) === cancellation) cancellations.delete(key);
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (cancellations.get(key) === cancellation) cancellations.delete(key);
|
|
790
|
+
if (cancellation.disposition === "queued") {
|
|
791
|
+
cancellation.disposition = "teardown";
|
|
792
|
+
cancellation.resolveDisposition("teardown");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
749
796
|
export type InvocationKind = "prompt" | "skill" | "steer";
|
|
750
797
|
type InvocationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed" | "uncertain";
|
|
751
798
|
type InvocationOutcome = SdkPromptTerminalOutcome;
|
|
@@ -2063,12 +2110,7 @@ function createQuerySurface(
|
|
|
2063
2110
|
};
|
|
2064
2111
|
const getProfileCredentialSessionId = () => ctx.credentialSessionId ?? id;
|
|
2065
2112
|
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
|
-
};
|
|
2113
|
+
const getProfileAvailableModels = (): Model<Api>[] => ctx.modelRegistry.getAvailable();
|
|
2072
2114
|
const resolveProfileAvailability = async (
|
|
2073
2115
|
profile: ModelProfileDefinition,
|
|
2074
2116
|
authenticatedProviders: ReadonlySet<string>,
|
|
@@ -2422,6 +2464,9 @@ function createQuerySurface(
|
|
|
2422
2464
|
getExtensions: () => ctx.getExtensions(),
|
|
2423
2465
|
getArtifactRange: (artifactId, offset, length) => ctx.getArtifactRange?.(artifactId, offset, length),
|
|
2424
2466
|
getJobs: () => ctx.getJobs(),
|
|
2467
|
+
...(typeof (ctx as Partial<ExtensionContext>).getProjectProgress === "function"
|
|
2468
|
+
? { getProjectProgress: () => ctx.getProjectProgress!() }
|
|
2469
|
+
: {}),
|
|
2425
2470
|
getPromptStatus: (selector: { commandId?: string; turnId?: string; clientRef?: string }) =>
|
|
2426
2471
|
reconciliation.lookup("prompt", selector),
|
|
2427
2472
|
getSkillInvokeStatus: (selector: { commandId?: string; turnId?: string; clientRef?: string }) =>
|
|
@@ -2694,6 +2739,11 @@ export function providerFailureFromAgentEnd(
|
|
|
2694
2739
|
return undefined;
|
|
2695
2740
|
}
|
|
2696
2741
|
if (errorKind === "local_snapshot_failure" || errorKind === "local_buffer_overflow") return undefined;
|
|
2742
|
+
if (errorKind === "local_empty_response")
|
|
2743
|
+
return {
|
|
2744
|
+
code: "empty_response",
|
|
2745
|
+
message: PROMPT_FAILURE_MESSAGE_SUBMISSION,
|
|
2746
|
+
};
|
|
2697
2747
|
let errorMessage: unknown;
|
|
2698
2748
|
try {
|
|
2699
2749
|
errorMessage = assistant.errorMessage;
|
|
@@ -2874,7 +2924,7 @@ function createControlSurface(
|
|
|
2874
2924
|
connectionId: string | undefined,
|
|
2875
2925
|
sdkRunToken: string,
|
|
2876
2926
|
promotion?: { startsOwnRun?: boolean; removed?: boolean },
|
|
2877
|
-
) =>
|
|
2927
|
+
) => undefined | Promise<boolean>,
|
|
2878
2928
|
policy?: SdkSurfacePolicy,
|
|
2879
2929
|
settings?: Settings,
|
|
2880
2930
|
configOverrides?: Map<string, unknown>,
|
|
@@ -2895,6 +2945,7 @@ function createControlSurface(
|
|
|
2895
2945
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
|
|
2896
2946
|
onInvocationCompletionReconciledForTests?: (kind: InvocationKind, correlation: InvocationCorrelation) => void,
|
|
2897
2947
|
publishLifecycleFrame?: (frame: SdkFrame) => void,
|
|
2948
|
+
acceptedQueueCancellations: Map<string, AcceptedQueueCancellation> = new Map(),
|
|
2898
2949
|
): ControlSurface {
|
|
2899
2950
|
const normalizePromptImages = (value: unknown): ImageContent[] => {
|
|
2900
2951
|
if (!Array.isArray(value)) return [];
|
|
@@ -2998,6 +3049,49 @@ function createControlSurface(
|
|
|
2998
3049
|
}
|
|
2999
3050
|
return pending;
|
|
3000
3051
|
};
|
|
3052
|
+
const cancelAcceptedQueueSubmissions = async (
|
|
3053
|
+
connectionId: string | undefined,
|
|
3054
|
+
admittedRequests: readonly AcceptedQueueCancellation[] = [...acceptedQueueCancellations.values()],
|
|
3055
|
+
): Promise<AcceptedQueueCancellationResult> => {
|
|
3056
|
+
const result: AcceptedQueueCancellationResult = { removed: false, consumed: false, unconfirmed: false };
|
|
3057
|
+
if (connectionId === undefined) return result;
|
|
3058
|
+
const owned = admittedRequests.filter(
|
|
3059
|
+
request => request.connectionId === connectionId && request.accepted && request.queueCandidate,
|
|
3060
|
+
);
|
|
3061
|
+
for (const request of owned) {
|
|
3062
|
+
let disposition = request.disposition;
|
|
3063
|
+
if (disposition === "queued") {
|
|
3064
|
+
request.controller.abort();
|
|
3065
|
+
disposition = request.disposition;
|
|
3066
|
+
if (disposition === "queued") {
|
|
3067
|
+
const timedOut = Promise.withResolvers<AcceptedQueueDisposition>();
|
|
3068
|
+
const timer = setTimeout(() => timedOut.resolve("teardown"), SDK_ONLY_QUEUE_CANCELLATION_WAIT_MS);
|
|
3069
|
+
timer.unref();
|
|
3070
|
+
try {
|
|
3071
|
+
disposition = await Promise.race([request.dispositionPromise, timedOut.promise]);
|
|
3072
|
+
} finally {
|
|
3073
|
+
clearTimeout(timer);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (disposition === "removed") {
|
|
3078
|
+
if (await request.removalTerminalization) result.removed = true;
|
|
3079
|
+
else result.unconfirmed = true;
|
|
3080
|
+
} else if (disposition === "consumed" || disposition === "promoted") {
|
|
3081
|
+
result.consumed = true;
|
|
3082
|
+
} else if (disposition === "teardown") {
|
|
3083
|
+
result.unconfirmed = true;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
return result;
|
|
3087
|
+
};
|
|
3088
|
+
const requesterOwnsActiveRun = (connectionId: string | undefined): boolean => {
|
|
3089
|
+
if (connectionId === undefined) return false;
|
|
3090
|
+
const seamOwner = terminalAbortSeams?.getActivePromptOwnerConnectionId?.();
|
|
3091
|
+
return seamOwner === undefined
|
|
3092
|
+
? (activePromptOwner.connectionIds?.has(connectionId) ?? false)
|
|
3093
|
+
: seamOwner === connectionId;
|
|
3094
|
+
};
|
|
3001
3095
|
const normalizeClientRef = (clientRef: string | undefined): string | undefined => {
|
|
3002
3096
|
if (clientRef === undefined) return undefined;
|
|
3003
3097
|
const trimmed = clientRef.trim();
|
|
@@ -3012,6 +3106,7 @@ function createControlSurface(
|
|
|
3012
3106
|
clientRef: string | undefined,
|
|
3013
3107
|
run: (options: {
|
|
3014
3108
|
sdkRunCapability: SdkRunCapability;
|
|
3109
|
+
preflightSignal: AbortSignal;
|
|
3015
3110
|
onPreflightAccepted: () => void;
|
|
3016
3111
|
onPreflightAcceptCommit: () => Promise<void>;
|
|
3017
3112
|
/** Internal disposition before a queued submission is actually consumed. */
|
|
@@ -3034,6 +3129,7 @@ function createControlSurface(
|
|
|
3034
3129
|
const sdkRunToken = `${correlation.commandId}:${correlation.turnId}`;
|
|
3035
3130
|
const sdkRunCapability = createSdkRunCapability(sdkRunToken);
|
|
3036
3131
|
const publishTerminal = (outcome: InvocationOutcome): void => {
|
|
3132
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
3037
3133
|
publishLifecycleFrame?.({
|
|
3038
3134
|
type: "agent_end",
|
|
3039
3135
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
@@ -3052,9 +3148,22 @@ function createControlSurface(
|
|
|
3052
3148
|
publishTerminal(canonicalFailedOutcome(failure));
|
|
3053
3149
|
};
|
|
3054
3150
|
const preflight = Promise.withResolvers<void>();
|
|
3151
|
+
const preflightController = new AbortController();
|
|
3152
|
+
const queueDisposition = Promise.withResolvers<AcceptedQueueDisposition>();
|
|
3153
|
+
const queueCancellation: AcceptedQueueCancellation = {
|
|
3154
|
+
correlation,
|
|
3155
|
+
connectionId: requesterConnectionId,
|
|
3156
|
+
controller: preflightController,
|
|
3157
|
+
accepted: false,
|
|
3158
|
+
queueCandidate: false,
|
|
3159
|
+
disposition: "preflight",
|
|
3160
|
+
dispositionPromise: queueDisposition.promise,
|
|
3161
|
+
resolveDisposition: queueDisposition.resolve,
|
|
3162
|
+
};
|
|
3055
3163
|
let accepted = false;
|
|
3056
3164
|
let settled = false;
|
|
3057
3165
|
const cancelPreflight = () => {
|
|
3166
|
+
preflightController.abort();
|
|
3058
3167
|
if (settled) return;
|
|
3059
3168
|
settled = true;
|
|
3060
3169
|
preflight.reject(
|
|
@@ -3073,6 +3182,8 @@ function createControlSurface(
|
|
|
3073
3182
|
try {
|
|
3074
3183
|
await reconciliation.noteAccepted(kind, correlation, retainedClientRef);
|
|
3075
3184
|
accepted = true;
|
|
3185
|
+
queueCancellation.accepted = true;
|
|
3186
|
+
if (queueCancellation.queueCandidate) queueCancellation.disposition = "queued";
|
|
3076
3187
|
settled = true;
|
|
3077
3188
|
if (kind === "prompt" && startsOwnTurn) armPromptDeadline(correlation);
|
|
3078
3189
|
// The accepted submission does NOT own the active turn until its run
|
|
@@ -3105,16 +3216,23 @@ function createControlSurface(
|
|
|
3105
3216
|
// Skills always start their own invocation; a plain prompt starts one
|
|
3106
3217
|
// only when idle at dispatch time.
|
|
3107
3218
|
const startsOwnTurn = kind === "skill" || (kind === "prompt" && !alwaysQueued && !queuedAtDispatch);
|
|
3219
|
+
queueCancellation.queueCandidate = queuedAtDispatch;
|
|
3220
|
+
if (queuedAtDispatch && queueCancellation.accepted) queueCancellation.disposition = "queued";
|
|
3221
|
+
if (kind === "prompt")
|
|
3222
|
+
acceptedQueueCancellations.set(`${correlation.commandId}:${correlation.turnId}`, queueCancellation);
|
|
3108
3223
|
let promotionStartsOwnRun: boolean | undefined;
|
|
3109
3224
|
try {
|
|
3110
3225
|
const submission = Promise.resolve(
|
|
3111
3226
|
run({
|
|
3112
3227
|
onPreflightAccepted: () => void accept().catch(() => undefined),
|
|
3228
|
+
preflightSignal: preflightController.signal,
|
|
3113
3229
|
sdkRunCapability,
|
|
3114
3230
|
onPreflightAcceptCommit: accept,
|
|
3115
3231
|
onDispatchDisposition: promotion => {
|
|
3116
3232
|
promotionStartsOwnRun = promotion.startsOwnRun;
|
|
3117
3233
|
if (promotion.startsOwnRun === false) {
|
|
3234
|
+
queueCancellation.queueCandidate = true;
|
|
3235
|
+
if (queueCancellation.accepted) queueCancellation.disposition = "queued";
|
|
3118
3236
|
// Dispatch-race diversion (#4668 review P1): the idle snapshot leased
|
|
3119
3237
|
// this prompt at acceptance, but it was actually diverted into the
|
|
3120
3238
|
// in-flight run's steering queue. While it sits queued — legitimately
|
|
@@ -3131,7 +3249,37 @@ function createControlSurface(
|
|
|
3131
3249
|
// terminal-abort that turn (review threads P1/P2).
|
|
3132
3250
|
onQueuedPromoted: (promotion?: { startsOwnRun?: boolean; removed?: boolean }) => {
|
|
3133
3251
|
promotionStartsOwnRun = promotion?.startsOwnRun;
|
|
3134
|
-
|
|
3252
|
+
if (promotion?.removed) {
|
|
3253
|
+
queueCancellation.disposition = "removed";
|
|
3254
|
+
queueCancellation.removalTerminalization = (async () => {
|
|
3255
|
+
try {
|
|
3256
|
+
return (
|
|
3257
|
+
(await onPromotedTurn?.(
|
|
3258
|
+
kind,
|
|
3259
|
+
correlation,
|
|
3260
|
+
requesterConnectionId,
|
|
3261
|
+
sdkRunToken,
|
|
3262
|
+
promotion,
|
|
3263
|
+
)) === true
|
|
3264
|
+
);
|
|
3265
|
+
} catch (error) {
|
|
3266
|
+
queueCancellation.removalFailure = error;
|
|
3267
|
+
logger.error("SDK queued prompt terminal publication failed", {
|
|
3268
|
+
commandId: correlation.commandId,
|
|
3269
|
+
turnId: correlation.turnId,
|
|
3270
|
+
error: String(error),
|
|
3271
|
+
});
|
|
3272
|
+
return false;
|
|
3273
|
+
}
|
|
3274
|
+
})();
|
|
3275
|
+
queueCancellation.resolveDisposition("removed");
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
if (queueCancellation.queueCandidate) {
|
|
3279
|
+
queueCancellation.disposition = promotion?.startsOwnRun === false ? "consumed" : "promoted";
|
|
3280
|
+
queueCancellation.resolveDisposition(queueCancellation.disposition);
|
|
3281
|
+
}
|
|
3282
|
+
void onPromotedTurn?.(kind, correlation, requesterConnectionId, sdkRunToken, promotion);
|
|
3135
3283
|
},
|
|
3136
3284
|
queuedAtDispatch,
|
|
3137
3285
|
}),
|
|
@@ -3218,6 +3366,7 @@ function createControlSurface(
|
|
|
3218
3366
|
},
|
|
3219
3367
|
error => {
|
|
3220
3368
|
if (settled) {
|
|
3369
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
3221
3370
|
// The submission promise rejects after preflight acceptance only when the
|
|
3222
3371
|
// work itself is over (provider stream interrupt, abort, queue failure).
|
|
3223
3372
|
// The accepted run never started (agent_start never fired), so its pending
|
|
@@ -3338,6 +3487,7 @@ function createControlSurface(
|
|
|
3338
3487
|
...(acceptedFields?.() ?? {}),
|
|
3339
3488
|
};
|
|
3340
3489
|
} catch (error) {
|
|
3490
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
3341
3491
|
if (!accepted) reconciliation.release(kind, retainedClientRef);
|
|
3342
3492
|
throw error;
|
|
3343
3493
|
} finally {
|
|
@@ -3705,6 +3855,36 @@ function createControlSurface(
|
|
|
3705
3855
|
return boundTerminalRetentionState(state.keys, mutate(state.scopes), terminalReservationLimit);
|
|
3706
3856
|
});
|
|
3707
3857
|
};
|
|
3858
|
+
const unconfirmedQueueTerminal = async (): Promise<unknown> => {
|
|
3859
|
+
const result = {
|
|
3860
|
+
ok: true,
|
|
3861
|
+
selection: scope,
|
|
3862
|
+
turn: "uncertain",
|
|
3863
|
+
ownedWork: scope === "turn" ? "left_running" : "uncertain",
|
|
3864
|
+
automaticDelivery: scope === "turn" ? "enabled" : "none",
|
|
3865
|
+
resumeOnOwnedCompletion: scope === "turn",
|
|
3866
|
+
reason: "queue_terminal_unconfirmed",
|
|
3867
|
+
};
|
|
3868
|
+
const payloadHash = hashResult(result);
|
|
3869
|
+
await transactBoundedTerminalScopes(scopes =>
|
|
3870
|
+
scopes.map(record =>
|
|
3871
|
+
(keyHash
|
|
3872
|
+
? record.idempotencyKeyHash === keyHash
|
|
3873
|
+
: record.turnContinuationFence.abortedAttemptEpoch === epoch) &&
|
|
3874
|
+
record.turnDisposition === "no_effect_reserved"
|
|
3875
|
+
? {
|
|
3876
|
+
...record,
|
|
3877
|
+
turnDisposition: "uncertain",
|
|
3878
|
+
responsePayloadHash: payloadHash,
|
|
3879
|
+
replayPayloadHash: replayShapedHash(record, result, payloadHash),
|
|
3880
|
+
terminalAt: Date.now(),
|
|
3881
|
+
}
|
|
3882
|
+
: record,
|
|
3883
|
+
),
|
|
3884
|
+
);
|
|
3885
|
+
return result;
|
|
3886
|
+
};
|
|
3887
|
+
const abortingConnectionId = sdkControlRequesterContext.getStore();
|
|
3708
3888
|
let handle = terminalAbortSeams.getActivePromptHandle();
|
|
3709
3889
|
let epoch = terminalAbortSeams.getTerminalTurnEpoch();
|
|
3710
3890
|
// Set when the no-effect reservation found an existing SAME-input row or
|
|
@@ -3796,9 +3976,12 @@ function createControlSurface(
|
|
|
3796
3976
|
const recheckedHandle = terminalAbortSeams.getActivePromptHandle();
|
|
3797
3977
|
const recheckedEpoch = terminalAbortSeams.getTerminalTurnEpoch();
|
|
3798
3978
|
if (!recheckedHandle || recheckedEpoch === undefined) {
|
|
3799
|
-
//
|
|
3800
|
-
//
|
|
3801
|
-
//
|
|
3979
|
+
// An accepted SDK queue owner is independent of the root prompt. Remove
|
|
3980
|
+
// only this requester's exact queued submissions, and do not claim the
|
|
3981
|
+
// root stopped; terminal success waits for each correlated removal
|
|
3982
|
+
// failure+terminal transaction to become durable.
|
|
3983
|
+
const queueCancellation = await cancelAcceptedQueueSubmissions(abortingConnectionId);
|
|
3984
|
+
if (queueCancellation.unconfirmed) return await unconfirmedQueueTerminal();
|
|
3802
3985
|
return await returnNoActiveTurn();
|
|
3803
3986
|
}
|
|
3804
3987
|
handle = recheckedHandle;
|
|
@@ -3816,7 +3999,6 @@ function createControlSurface(
|
|
|
3816
3999
|
// per-connection selection of the full bus path. The owner is re-read
|
|
3817
4000
|
// through the seam when provided (deterministic tests) and otherwise
|
|
3818
4001
|
// from the runtime-tracked accepting connection.
|
|
3819
|
-
const abortingConnectionId = sdkControlRequesterContext.getStore();
|
|
3820
4002
|
const currentOwnerConnectionIds = (): ReadonlySet<string> => {
|
|
3821
4003
|
const seam = terminalAbortSeams.getActivePromptOwnerConnectionId?.();
|
|
3822
4004
|
// The seam reports a single deterministic owner (test harnesses); the
|
|
@@ -3864,9 +4046,10 @@ function createControlSurface(
|
|
|
3864
4046
|
abortingConnectionId === undefined ||
|
|
3865
4047
|
!recheckedOwners.has(abortingConnectionId)
|
|
3866
4048
|
) {
|
|
3867
|
-
//
|
|
3868
|
-
//
|
|
3869
|
-
|
|
4049
|
+
// Request-owned steering cannot borrow the unrelated root's abort
|
|
4050
|
+
// authority. It may still be removed by its exact queue capability.
|
|
4051
|
+
const queueCancellation = await cancelAcceptedQueueSubmissions(abortingConnectionId);
|
|
4052
|
+
if (queueCancellation.unconfirmed) return await unconfirmedQueueTerminal();
|
|
3870
4053
|
const noActiveTurnResult = {
|
|
3871
4054
|
ok: true,
|
|
3872
4055
|
selection: scope,
|
|
@@ -4289,6 +4472,26 @@ function createControlSurface(
|
|
|
4289
4472
|
);
|
|
4290
4473
|
},
|
|
4291
4474
|
abort: async () => {
|
|
4475
|
+
const connectionId = sdkControlRequesterContext.getStore();
|
|
4476
|
+
const ownedAdmissions = [...acceptedQueueCancellations.values()].filter(
|
|
4477
|
+
request => connectionId !== undefined && request.connectionId === connectionId,
|
|
4478
|
+
);
|
|
4479
|
+
const ownedPreflights = connectionId === undefined ? [] : [...(pendingPreflights.get(connectionId) ?? [])];
|
|
4480
|
+
// Capture both phases before cancellation or any awaited terminal work:
|
|
4481
|
+
// acceptance can settle its callback before agent_start owns the run.
|
|
4482
|
+
for (const cancel of ownedPreflights) cancel();
|
|
4483
|
+
let cancelledAdmission = ownedPreflights.length > 0;
|
|
4484
|
+
for (const request of ownedAdmissions) {
|
|
4485
|
+
if (request.disposition !== "preflight" || request.controller.signal.aborted) continue;
|
|
4486
|
+
request.controller.abort();
|
|
4487
|
+
cancelledAdmission = true;
|
|
4488
|
+
}
|
|
4489
|
+
if (connectionId !== undefined && !requesterOwnsActiveRun(connectionId)) {
|
|
4490
|
+
const queueCancellation = await cancelAcceptedQueueSubmissions(connectionId, ownedAdmissions);
|
|
4491
|
+
if (queueCancellation.unconfirmed) return { aborted: false, reason: "queue_terminal_unconfirmed" };
|
|
4492
|
+
if (queueCancellation.removed || cancelledAdmission) return { aborted: true };
|
|
4493
|
+
return { aborted: false, turn: "no_active_turn" };
|
|
4494
|
+
}
|
|
4292
4495
|
await Promise.resolve(ctx.abort()).catch(() => undefined);
|
|
4293
4496
|
return { aborted: true };
|
|
4294
4497
|
},
|
|
@@ -4628,6 +4831,61 @@ function quiescingFrame(frame: Record<string, unknown>): Record<string, unknown>
|
|
|
4628
4831
|
|
|
4629
4832
|
/** Install a complete SDK host for a session when notifications are inactive. */
|
|
4630
4833
|
export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: CreateSdkSessionRuntimeOptions): void {
|
|
4834
|
+
const acceptedQueueCancellations = new Map<string, AcceptedQueueCancellation>();
|
|
4835
|
+
const disposeAcceptedQueueCancellations = async (): Promise<void> => {
|
|
4836
|
+
const cancellations = [...acceptedQueueCancellations.values()];
|
|
4837
|
+
const queuedRemovals = cancellations.filter(
|
|
4838
|
+
cancellation =>
|
|
4839
|
+
cancellation.accepted &&
|
|
4840
|
+
cancellation.queueCandidate &&
|
|
4841
|
+
(cancellation.disposition === "queued" || cancellation.disposition === "removed"),
|
|
4842
|
+
);
|
|
4843
|
+
for (const cancellation of cancellations)
|
|
4844
|
+
if (!cancellation.controller.signal.aborted) cancellation.controller.abort();
|
|
4845
|
+
|
|
4846
|
+
const failures: unknown[] = [];
|
|
4847
|
+
const timedOut = Promise.withResolvers<void>();
|
|
4848
|
+
const timer = setTimeout(timedOut.resolve, SDK_ONLY_QUEUE_CANCELLATION_WAIT_MS);
|
|
4849
|
+
timer.unref();
|
|
4850
|
+
try {
|
|
4851
|
+
await Promise.all(
|
|
4852
|
+
queuedRemovals.map(async cancellation => {
|
|
4853
|
+
const publication = (async (): Promise<boolean> => {
|
|
4854
|
+
const disposition =
|
|
4855
|
+
cancellation.disposition === "queued"
|
|
4856
|
+
? await cancellation.dispositionPromise
|
|
4857
|
+
: cancellation.disposition;
|
|
4858
|
+
if (disposition === "promoted" || disposition === "consumed") return true;
|
|
4859
|
+
return disposition === "removed" && (await cancellation.removalTerminalization) === true;
|
|
4860
|
+
})();
|
|
4861
|
+
const published = await Promise.race([publication, timedOut.promise.then(() => false)]);
|
|
4862
|
+
const key = `${cancellation.correlation.commandId}:${cancellation.correlation.turnId}`;
|
|
4863
|
+
if (published || acceptedQueueCancellations.get(key) !== cancellation) {
|
|
4864
|
+
if (acceptedQueueCancellations.get(key) === cancellation) acceptedQueueCancellations.delete(key);
|
|
4865
|
+
} else {
|
|
4866
|
+
failures.push(
|
|
4867
|
+
cancellation.removalFailure ??
|
|
4868
|
+
new Error(
|
|
4869
|
+
`Queued prompt ${cancellation.correlation.commandId}:${cancellation.correlation.turnId} terminal publication was not confirmed during shutdown.`,
|
|
4870
|
+
),
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
}),
|
|
4874
|
+
);
|
|
4875
|
+
} finally {
|
|
4876
|
+
clearTimeout(timer);
|
|
4877
|
+
}
|
|
4878
|
+
for (const cancellation of cancellations) {
|
|
4879
|
+
if (queuedRemovals.includes(cancellation)) continue;
|
|
4880
|
+
const key = `${cancellation.correlation.commandId}:${cancellation.correlation.turnId}`;
|
|
4881
|
+
if (acceptedQueueCancellations.get(key) === cancellation) acceptedQueueCancellations.delete(key);
|
|
4882
|
+
}
|
|
4883
|
+
if (failures.length > 0)
|
|
4884
|
+
throw Object.assign(
|
|
4885
|
+
new AggregateError(failures, "SDK runtime could not durably publish queued prompt cancellations."),
|
|
4886
|
+
{ code: "sdk_reconciliation_teardown_failed" },
|
|
4887
|
+
);
|
|
4888
|
+
};
|
|
4631
4889
|
let active:
|
|
4632
4890
|
| {
|
|
4633
4891
|
sessionId: string;
|
|
@@ -4658,6 +4916,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4658
4916
|
}>;
|
|
4659
4917
|
openLifecycleBatches: Array<{
|
|
4660
4918
|
epoch: number;
|
|
4919
|
+
/** Session-initiated starts still reserve their own unmatched end. */
|
|
4920
|
+
unownedStart: boolean;
|
|
4661
4921
|
invocations: Array<{
|
|
4662
4922
|
kind: InvocationKind;
|
|
4663
4923
|
correlation: InvocationCorrelation;
|
|
@@ -4713,6 +4973,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4713
4973
|
string,
|
|
4714
4974
|
{ state: RuntimeState; batch?: LifecycleBatch; correlationKey?: string }
|
|
4715
4975
|
>();
|
|
4976
|
+
const lifecycleScopeOwners = new WeakMap<AttemptScope, { state: RuntimeState; batch: LifecycleBatch }>();
|
|
4716
4977
|
const lifecycleCorrelationKey = (correlation: InvocationCorrelation): string =>
|
|
4717
4978
|
`${correlation.commandId}:${correlation.turnId}`;
|
|
4718
4979
|
const sessionIdentityForContext = (ctx: ExtensionContext): string | undefined => {
|
|
@@ -4782,13 +5043,18 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4782
5043
|
owner.unrecordedFailureReasons?.clear();
|
|
4783
5044
|
for (const [token, binding] of lifecycleRunOwners) if (binding.state === owner) lifecycleRunOwners.delete(token);
|
|
4784
5045
|
};
|
|
5046
|
+
const hasQueuedTerminalRecovery = (owner: RuntimeState): boolean =>
|
|
5047
|
+
[...acceptedQueueCancellations.values()].some(
|
|
5048
|
+
cancellation => cancellation.disposition === "removed" && owner.deadlineManager.has(cancellation.correlation),
|
|
5049
|
+
);
|
|
4785
5050
|
const maybeRetireLifecycleOwner = (owner: RuntimeState): void => {
|
|
4786
5051
|
if (
|
|
4787
5052
|
owner.pending.length > 0 ||
|
|
4788
5053
|
owner.openLifecycleBatches.length > 0 ||
|
|
4789
5054
|
(owner.attachedInvocations?.length ?? 0) > 0 ||
|
|
4790
5055
|
(owner.drainedInvocations?.length ?? 0) > 0 ||
|
|
4791
|
-
owner.lifecycleTasks.size > 0
|
|
5056
|
+
owner.lifecycleTasks.size > 0 ||
|
|
5057
|
+
hasQueuedTerminalRecovery(owner)
|
|
4792
5058
|
)
|
|
4793
5059
|
return;
|
|
4794
5060
|
removeRetiredLifecycleOwner(owner);
|
|
@@ -4809,8 +5075,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4809
5075
|
entry => lifecycleCorrelationKey(entry.correlation) !== target,
|
|
4810
5076
|
);
|
|
4811
5077
|
}
|
|
5078
|
+
// Retiring an SDK correlation must not discard an unrelated session-
|
|
5079
|
+
// initiated start. Its delayed end still belongs to that empty batch.
|
|
4812
5080
|
owner.openLifecycleBatches = owner.openLifecycleBatches.filter(
|
|
4813
|
-
batch => batch.invocations.length > 0 || batch.attachedInvocations.length > 0,
|
|
5081
|
+
batch => batch.unownedStart || batch.invocations.length > 0 || batch.attachedInvocations.length > 0,
|
|
4814
5082
|
);
|
|
4815
5083
|
owner.drainedInvocations = owner.drainedInvocations?.filter(
|
|
4816
5084
|
entry => lifecycleCorrelationKey(entry.correlation) !== target,
|
|
@@ -5089,32 +5357,24 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5089
5357
|
stopReason?: AgentEndEvent["stopReason"],
|
|
5090
5358
|
startToken?: string,
|
|
5091
5359
|
startTokens?: string[],
|
|
5360
|
+
startLifecycleScope?: AttemptScope,
|
|
5092
5361
|
): Promise<void> => {
|
|
5093
5362
|
const current = lifecycleOwner?.state ?? active;
|
|
5094
5363
|
if (!current) return;
|
|
5095
5364
|
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;
|
|
5365
|
+
const adoptLifecycleBatch = (batch: LifecycleBatch | undefined): void => {
|
|
5366
|
+
current.activeInvocation = batch?.invocations[0];
|
|
5367
|
+
current.drainedInvocations = batch
|
|
5368
|
+
? [...batch.invocations, ...batch.attachedInvocations].map(({ kind, correlation }) => ({
|
|
5369
|
+
kind,
|
|
5370
|
+
correlation,
|
|
5371
|
+
}))
|
|
5372
|
+
: undefined;
|
|
5373
|
+
current.attachedInvocations = batch?.attachedInvocations.slice();
|
|
5115
5374
|
const owners = new Set<string>();
|
|
5116
|
-
for (const entry of batch
|
|
5117
|
-
|
|
5375
|
+
for (const entry of batch?.invocations ?? [])
|
|
5376
|
+
if (entry.connectionId !== undefined) owners.add(entry.connectionId);
|
|
5377
|
+
if (current === active) activePromptOwnerHolder.connectionIds = owners.size > 0 ? owners : undefined;
|
|
5118
5378
|
};
|
|
5119
5379
|
let transitions: Array<{ kind: InvocationKind; correlation: InvocationCorrelation }> = [];
|
|
5120
5380
|
if (type === "agent_failed") {
|
|
@@ -5148,13 +5408,18 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5148
5408
|
);
|
|
5149
5409
|
transitions = [...fallback, ...attached].map(({ kind, correlation }) => ({ kind, correlation }));
|
|
5150
5410
|
} else if (type === "agent_start") {
|
|
5151
|
-
|
|
5411
|
+
const scopeOwner = startLifecycleScope ? lifecycleScopeOwners.get(startLifecycleScope) : undefined;
|
|
5412
|
+
const scopeBatch =
|
|
5413
|
+
scopeOwner?.state === current && current.openLifecycleBatches.includes(scopeOwner.batch)
|
|
5414
|
+
? scopeOwner.batch
|
|
5415
|
+
: undefined;
|
|
5416
|
+
current.lifecycleEpoch = scopeBatch?.epoch ?? ++nextLifecycleEpoch;
|
|
5152
5417
|
if (current === active) activePromptOwnerHolder.lifecycleEpoch = current.lifecycleEpoch;
|
|
5153
5418
|
// Mark lifecycle active even when the drain is empty: a monitor/cron
|
|
5154
5419
|
// run started by the session has no SDK pending entry but is still a
|
|
5155
5420
|
// real active run that later in-run promotions must attach to instead
|
|
5156
|
-
// of falling back to pending (review P1).
|
|
5157
|
-
// previous
|
|
5421
|
+
// of falling back to pending (review P1). Only an explicitly bound
|
|
5422
|
+
// continuation retains a previous public lifecycle boundary.
|
|
5158
5423
|
current.lifecycleActive = true;
|
|
5159
5424
|
// Drain EVERY entry admitted for this run: a continuation may promote
|
|
5160
5425
|
// several follow-ups (each with its own requester correlation) into one
|
|
@@ -5175,40 +5440,47 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5175
5440
|
typeof startToken === "string" && startToken.length > 0
|
|
5176
5441
|
? lifecycleRunOwners.get(startToken)?.batch
|
|
5177
5442
|
: undefined;
|
|
5178
|
-
|
|
5179
|
-
|
|
5443
|
+
const continuationBatch =
|
|
5444
|
+
scopeBatch ??
|
|
5445
|
+
(drained.length === 0 && existingTokenBatch && current.openLifecycleBatches.includes(existingTokenBatch)
|
|
5446
|
+
? existingTokenBatch
|
|
5447
|
+
: undefined);
|
|
5448
|
+
let batch: LifecycleBatch;
|
|
5449
|
+
if (!continuationBatch) {
|
|
5450
|
+
// Every new run owns an end, including interactive/monitor runs
|
|
5451
|
+
// without SDK invocations. Their delayed ends must never consume
|
|
5452
|
+
// the next SDK run's terminal receipt.
|
|
5453
|
+
batch = {
|
|
5180
5454
|
epoch: current.lifecycleEpoch,
|
|
5455
|
+
unownedStart: drained.length === 0,
|
|
5181
5456
|
invocations: drained,
|
|
5182
5457
|
attachedInvocations: [],
|
|
5183
|
-
startPublished:
|
|
5458
|
+
startPublished: drained.length === 0,
|
|
5184
5459
|
heldContentDropped: false,
|
|
5185
5460
|
heldContent: [],
|
|
5186
5461
|
};
|
|
5187
5462
|
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
5463
|
} else {
|
|
5208
|
-
//
|
|
5209
|
-
//
|
|
5210
|
-
|
|
5211
|
-
|
|
5464
|
+
// The producer consumed the predecessor end, or this is the same
|
|
5465
|
+
// token-owned run. Preserve its attached owners and single final end.
|
|
5466
|
+
batch = continuationBatch;
|
|
5467
|
+
batch.invocations.push(...drained);
|
|
5468
|
+
if (drained.length > 0) batch.startPublished = false;
|
|
5469
|
+
}
|
|
5470
|
+
if (startLifecycleScope) lifecycleScopeOwners.set(startLifecycleScope, { state: current, batch });
|
|
5471
|
+
for (const entry of drained) {
|
|
5472
|
+
lifecycleRunOwners.set(entry.sdkRunToken, {
|
|
5473
|
+
state: current,
|
|
5474
|
+
batch,
|
|
5475
|
+
correlationKey: lifecycleCorrelationKey(entry.correlation),
|
|
5476
|
+
});
|
|
5477
|
+
}
|
|
5478
|
+
adoptLifecycleBatch(batch);
|
|
5479
|
+
for (const entry of drained) {
|
|
5480
|
+
if (entry.kind !== "prompt") continue;
|
|
5481
|
+
if (current.deadlineManager.isExpiring(entry.correlation))
|
|
5482
|
+
current.deadlineManager.captureExpiringRun(entry.correlation);
|
|
5483
|
+
else current.deadlineManager.onRunStarted(entry.correlation);
|
|
5212
5484
|
}
|
|
5213
5485
|
transitions = drained.map(({ kind, correlation }) => ({ kind, correlation }));
|
|
5214
5486
|
} else {
|
|
@@ -5284,7 +5556,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5284
5556
|
transitions.map(({ correlation }) => `${correlation.commandId}:${correlation.turnId}`),
|
|
5285
5557
|
);
|
|
5286
5558
|
if (
|
|
5287
|
-
|
|
5559
|
+
(ended.invocations.length > 0 || ended.attachedInvocations.length > 0) &&
|
|
5560
|
+
![...ended.invocations, ...ended.attachedInvocations].some(({ correlation }) =>
|
|
5288
5561
|
transitionKeys.has(`${correlation.commandId}:${correlation.turnId}`),
|
|
5289
5562
|
)
|
|
5290
5563
|
)
|
|
@@ -5331,7 +5604,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5331
5604
|
);
|
|
5332
5605
|
}
|
|
5333
5606
|
current.openLifecycleBatches = current.openLifecycleBatches.filter(
|
|
5334
|
-
batch => batch.invocations.length > 0,
|
|
5607
|
+
batch => batch.unownedStart || batch.invocations.length > 0 || batch.attachedInvocations.length > 0,
|
|
5335
5608
|
);
|
|
5336
5609
|
current.drainedInvocations = current.drainedInvocations?.filter(
|
|
5337
5610
|
entry =>
|
|
@@ -5342,7 +5615,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5342
5615
|
current.activeInvocation?.correlation.commandId === invocation.correlation.commandId &&
|
|
5343
5616
|
current.activeInvocation.correlation.turnId === invocation.correlation.turnId
|
|
5344
5617
|
)
|
|
5345
|
-
adoptLifecycleBatch(current.openLifecycleBatches[0]
|
|
5618
|
+
adoptLifecycleBatch(current.openLifecycleBatches[0]);
|
|
5346
5619
|
if (current.openLifecycleBatches.length === 0) current.lifecycleActive = false;
|
|
5347
5620
|
} catch (error) {
|
|
5348
5621
|
if (controller.signal.aborted) {
|
|
@@ -5714,6 +5987,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5714
5987
|
}
|
|
5715
5988
|
if (type === "agent_end" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) return;
|
|
5716
5989
|
if (type === "agent_end") {
|
|
5990
|
+
for (const invocation of transitions)
|
|
5991
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, invocation.correlation);
|
|
5717
5992
|
if (current.lifecycleEpoch !== eventLifecycleEpoch) {
|
|
5718
5993
|
// A successor agent_start won the lifecycle race while this event's
|
|
5719
5994
|
// durable transitions were awaiting persistence. Retire the ended
|
|
@@ -5763,7 +6038,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5763
6038
|
!current.deadlineManager.shouldDeferTerminalTransition(invocation.correlation)
|
|
5764
6039
|
)
|
|
5765
6040
|
current.deadlineManager.clear(invocation.correlation);
|
|
5766
|
-
adoptLifecycleBatch(current.openLifecycleBatches[0]
|
|
6041
|
+
adoptLifecycleBatch(current.openLifecycleBatches[0]);
|
|
5767
6042
|
if (current.openLifecycleBatches.length === 0) current.lifecycleActive = false;
|
|
5768
6043
|
// Resolve EVERY concurrent waiter for the aborted turn: the turn emits
|
|
5769
6044
|
// exactly one agent_end, and each admitted abort of it must observe the
|
|
@@ -5772,39 +6047,43 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5772
6047
|
resolveTerminalPublicationWaiters(observed, terminalPublicationByCorrelation);
|
|
5773
6048
|
}
|
|
5774
6049
|
};
|
|
5775
|
-
api.on(
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
6050
|
+
api.on(
|
|
6051
|
+
"agent_start",
|
|
6052
|
+
tagSdkLifecycleObserver((event, ctx) => {
|
|
6053
|
+
const owner = lifecycleStateForEvent(ctx, "agent_start", event.sdkRunToken);
|
|
6054
|
+
// The activity checkpoint is already fire-and-forget and does not depend on
|
|
6055
|
+
// the handler awaiting trackLifecycle, so it composes with #5683 unchanged.
|
|
6056
|
+
void (owner?.runtime ?? lifecycleStateForContext(ctx, "agent_start")?.runtime)
|
|
6057
|
+
?.reportActivity("active")
|
|
6058
|
+
.catch(error => logger.warn(`sdk: active activity checkpoint failed: ${String(error)}`));
|
|
6059
|
+
// Interactive/skill turns must not wait on durable start persist. Keep
|
|
6060
|
+
// trackLifecycle so drain, persist-then-publish, content-hold release, and
|
|
6061
|
+
// shutdown still run; do not return that promise to the extension runner
|
|
6062
|
+
// (EXTENSION_HANDLER_TIMEOUT_MS would otherwise stall the prompt).
|
|
6063
|
+
void trackLifecycle(
|
|
6064
|
+
async () =>
|
|
6065
|
+
emitLifecycle(
|
|
6066
|
+
"agent_start",
|
|
6067
|
+
ctx,
|
|
6068
|
+
undefined,
|
|
6069
|
+
undefined,
|
|
6070
|
+
owner ? { state: owner, sessionId: owner.sessionId } : undefined,
|
|
6071
|
+
undefined,
|
|
6072
|
+
false,
|
|
6073
|
+
undefined,
|
|
6074
|
+
undefined,
|
|
6075
|
+
typeof event.sdkRunToken === "string" ? event.sdkRunToken : undefined,
|
|
6076
|
+
event.sdkRunTokens,
|
|
6077
|
+
event.lifecycleScope,
|
|
6078
|
+
),
|
|
6079
|
+
owner,
|
|
6080
|
+
).catch(error => {
|
|
6081
|
+
logger.error("SDK agent_start lifecycle task failed", {
|
|
6082
|
+
error: sanitizePromptFailure(error),
|
|
6083
|
+
});
|
|
5805
6084
|
});
|
|
5806
|
-
})
|
|
5807
|
-
|
|
6085
|
+
}),
|
|
6086
|
+
);
|
|
5808
6087
|
api.on("agent_end", (event, ctx) => {
|
|
5809
6088
|
const tokenBinding =
|
|
5810
6089
|
typeof event.sdkRunToken === "string" ? lifecycleRunOwners.get(event.sdkRunToken) : undefined;
|
|
@@ -5965,7 +6244,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5965
6244
|
api.on("turn_start", async (_event, ctx) => {
|
|
5966
6245
|
const current = lifecycleStateForContext(ctx, "agent_start");
|
|
5967
6246
|
if (!current) return;
|
|
5968
|
-
|
|
6247
|
+
// Optional broker discovery must not hold an interactive extension event open.
|
|
6248
|
+
// registerBroker owns single-flight recovery and optional-failure diagnostics.
|
|
6249
|
+
const registration = current.registerBroker();
|
|
6250
|
+
if (options.brokerRegistrationRequired) await registration;
|
|
5969
6251
|
current.runtime.emitEvent({ type: "turn_start", sessionId: ctx.sessionManager.getSessionId() });
|
|
5970
6252
|
});
|
|
5971
6253
|
const consumedMasterNonces = new Map<string, number>();
|
|
@@ -6124,6 +6406,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6124
6406
|
return code !== undefined && /^[A-Za-z0-9_.-]{1,64}$/u.test(code) ? code : "unavailable";
|
|
6125
6407
|
};
|
|
6126
6408
|
const startRuntime = async (ctx: ExtensionContext): Promise<void> => {
|
|
6409
|
+
if (active) return;
|
|
6410
|
+
if (acceptedQueueCancellations.size > 0) await disposeAcceptedQueueCancellations();
|
|
6127
6411
|
if (active) return;
|
|
6128
6412
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
6129
6413
|
const stateRoot = path.join(ctx.cwd, ".gjc", "state");
|
|
@@ -6164,7 +6448,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6164
6448
|
const seams = options.terminalAbortSeams;
|
|
6165
6449
|
const handle = seams?.getActivePromptHandle();
|
|
6166
6450
|
const epoch = seams?.getTerminalTurnEpoch();
|
|
6167
|
-
if (!seams || !handle || epoch === undefined
|
|
6451
|
+
if (!seams || !handle || epoch === undefined) return;
|
|
6168
6452
|
const existing = deadlineTerminalizationObservations.get(key);
|
|
6169
6453
|
if (existing) return () => cleanupDeadlineTerminalizationObservation(key, existing);
|
|
6170
6454
|
|
|
@@ -6364,6 +6648,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6364
6648
|
});
|
|
6365
6649
|
},
|
|
6366
6650
|
onExpired: correlation => {
|
|
6651
|
+
retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation, true);
|
|
6367
6652
|
const owner = lifecycleOwnerHolder.state;
|
|
6368
6653
|
if (!owner) return;
|
|
6369
6654
|
removeLifecycleReferences(owner, correlation);
|
|
@@ -6400,26 +6685,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6400
6685
|
true,
|
|
6401
6686
|
);
|
|
6402
6687
|
}
|
|
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
|
-
}> = [];
|
|
6688
|
+
const openLifecycleBatches: LifecycleBatch[] = [];
|
|
6423
6689
|
const configRevision = { current: 0 };
|
|
6424
6690
|
let acceptingGateResolutions = true;
|
|
6425
6691
|
const inFlightGateResolutions = new Set<Promise<unknown>>();
|
|
@@ -6521,9 +6787,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6521
6787
|
kind: InvocationKind,
|
|
6522
6788
|
correlation: InvocationCorrelation,
|
|
6523
6789
|
error: { code: string; message: string },
|
|
6524
|
-
):
|
|
6790
|
+
): Promise<boolean> => {
|
|
6525
6791
|
if (lifecycleOwnerHolder.state) removeLifecycleTokenAliases(lifecycleOwnerHolder.state, correlation);
|
|
6526
|
-
const attempt = async (remaining: number): Promise<
|
|
6792
|
+
const attempt = async (remaining: number): Promise<boolean> => {
|
|
6527
6793
|
try {
|
|
6528
6794
|
// 1. Record the failure reason durably FIRST. If this write fails,
|
|
6529
6795
|
// never fall through to agent_end: the record still has no error,
|
|
@@ -6546,7 +6812,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6546
6812
|
turnId: correlation.turnId,
|
|
6547
6813
|
error: sanitizePromptFailure(reasonError),
|
|
6548
6814
|
});
|
|
6549
|
-
return;
|
|
6815
|
+
return false;
|
|
6550
6816
|
}
|
|
6551
6817
|
await Bun.sleep(1_000);
|
|
6552
6818
|
return attempt(remaining - 1);
|
|
@@ -6570,6 +6836,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6570
6836
|
});
|
|
6571
6837
|
// 3. Release recovery ownership ONLY after durable terminalization.
|
|
6572
6838
|
deadlineManager.clear(correlation);
|
|
6839
|
+
if (kind === "prompt") retireAcceptedQueueCancellation(acceptedQueueCancellations, correlation);
|
|
6840
|
+
return true;
|
|
6573
6841
|
} catch (transitionError) {
|
|
6574
6842
|
// Keep prompt recovery leased; skill recovery has no prompt lease.
|
|
6575
6843
|
if (kind === "skill") scheduleSkillRecovery(correlation);
|
|
@@ -6580,9 +6848,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6580
6848
|
turnId: correlation.turnId,
|
|
6581
6849
|
error: sanitizePromptFailure(transitionError),
|
|
6582
6850
|
});
|
|
6851
|
+
return false;
|
|
6583
6852
|
}
|
|
6584
6853
|
};
|
|
6585
|
-
|
|
6854
|
+
return attempt(3);
|
|
6586
6855
|
};
|
|
6587
6856
|
|
|
6588
6857
|
const controlSurface = createControlSurface(
|
|
@@ -6632,11 +6901,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6632
6901
|
});
|
|
6633
6902
|
};
|
|
6634
6903
|
if (lifecycleOwnerHolder.quiescing) {
|
|
6635
|
-
terminalizeAbandonedSubmission(kind, correlation, {
|
|
6904
|
+
return terminalizeAbandonedSubmission(kind, correlation, {
|
|
6636
6905
|
code: "session_quiescing",
|
|
6637
6906
|
message: "Session endpoint was replaced before the invocation started.",
|
|
6638
6907
|
});
|
|
6639
|
-
return;
|
|
6640
6908
|
}
|
|
6641
6909
|
// Lease at the ACTUAL promotion boundary (#4668 review): a promoted
|
|
6642
6910
|
// submission that wedges before its run's agent_start must still
|
|
@@ -6656,11 +6924,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6656
6924
|
entry.correlation.turnId === correlation.turnId,
|
|
6657
6925
|
);
|
|
6658
6926
|
if (pendingIdx >= 0) pending.splice(pendingIdx, 1);
|
|
6659
|
-
terminalizeAbandonedSubmission(kind, correlation, {
|
|
6927
|
+
return terminalizeAbandonedSubmission(kind, correlation, {
|
|
6660
6928
|
code: "cancelled",
|
|
6661
6929
|
message: "Queued prompt was removed before consumption.",
|
|
6662
6930
|
});
|
|
6663
|
-
return;
|
|
6664
6931
|
}
|
|
6665
6932
|
if (promotion?.startsOwnRun !== false) {
|
|
6666
6933
|
// A submission PROMOTED to its own run (finished prompt unwinding)
|
|
@@ -6706,7 +6973,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6706
6973
|
entry.correlation.turnId === current.activeInvocation?.correlation.turnId,
|
|
6707
6974
|
),
|
|
6708
6975
|
)
|
|
6709
|
-
:
|
|
6976
|
+
: current.openLifecycleBatches.at(-1);
|
|
6710
6977
|
const alreadyAttached = current.drainedInvocations.some(
|
|
6711
6978
|
entry =>
|
|
6712
6979
|
entry.correlation.commandId === correlation.commandId &&
|
|
@@ -6812,7 +7079,34 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
6812
7079
|
() => acceptingGateResolutions,
|
|
6813
7080
|
trackGateResolution,
|
|
6814
7081
|
options.onInvocationCompletionReconciledForTests,
|
|
6815
|
-
frame =>
|
|
7082
|
+
frame => {
|
|
7083
|
+
// Completion can win the durable transaction while a lifecycle end is
|
|
7084
|
+
// still waiting on it. Both publishers must share the same claim.
|
|
7085
|
+
if (typeof frame.commandId !== "string" || typeof frame.turnId !== "string") return;
|
|
7086
|
+
const correlation = { commandId: frame.commandId, turnId: frame.turnId };
|
|
7087
|
+
if (frame.type !== "agent_end") {
|
|
7088
|
+
if (!hasClaimedTerminalBoundary(correlation)) runtime.emitEvent(frame);
|
|
7089
|
+
return;
|
|
7090
|
+
}
|
|
7091
|
+
const key = lifecycleCorrelationKey(correlation);
|
|
7092
|
+
if (!claimTerminalBoundary(correlation)) {
|
|
7093
|
+
const previouslyPublished = terminalBoundaryClaims.publicationResult(key) === true;
|
|
7094
|
+
if (previouslyPublished) return;
|
|
7095
|
+
terminalBoundaryClaims.releaseClaim(key);
|
|
7096
|
+
if (!claimTerminalBoundary(correlation)) return;
|
|
7097
|
+
}
|
|
7098
|
+
const sequenceBefore = runtime.host.events.sequence;
|
|
7099
|
+
try {
|
|
7100
|
+
runtime.emitEvent(frame);
|
|
7101
|
+
terminalBoundaryClaims.setPublicationResult(key, true);
|
|
7102
|
+
} catch (error) {
|
|
7103
|
+
if (runtime.host.events.sequence > sequenceBefore)
|
|
7104
|
+
terminalBoundaryClaims.setPublicationResult(key, true);
|
|
7105
|
+
else terminalBoundaryClaims.releaseClaim(key);
|
|
7106
|
+
throw error;
|
|
7107
|
+
}
|
|
7108
|
+
},
|
|
7109
|
+
acceptedQueueCancellations,
|
|
6816
7110
|
);
|
|
6817
7111
|
const installProviderDefinitions = (capability: string, definitions: unknown): void => {
|
|
6818
7112
|
if (capability === "permission") {
|
|
@@ -7074,7 +7368,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7074
7368
|
};
|
|
7075
7369
|
const startupImageCapture = captureStartupRuntimeImage();
|
|
7076
7370
|
const registerBroker = async (): Promise<void> => {
|
|
7077
|
-
if (brokerRegistered) return;
|
|
7371
|
+
if (brokerRecoveryStopped || brokerRegistered) return;
|
|
7078
7372
|
if (brokerRegistrationInFlight !== undefined) {
|
|
7079
7373
|
await brokerRegistrationInFlight;
|
|
7080
7374
|
return;
|
|
@@ -7107,6 +7401,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7107
7401
|
throw new Error("SDK transport endpoint was not published before broker registration.");
|
|
7108
7402
|
const endpointPath = path.join(input.stateRoot, "sdk", `${input.sessionId}.json`);
|
|
7109
7403
|
const file = await readEndpointFile(endpointPath);
|
|
7404
|
+
if (brokerRecoveryStopped) return;
|
|
7110
7405
|
if (!file) throw new Error("SDK endpoint could not be read as a stable regular file.");
|
|
7111
7406
|
const endpoint = JSON.parse(file.source) as Record<string, unknown>;
|
|
7112
7407
|
if (
|
|
@@ -7310,13 +7605,32 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7310
7605
|
active = runtimeOwner;
|
|
7311
7606
|
try {
|
|
7312
7607
|
publishedEndpointUrl = (await runtime.start()).url;
|
|
7313
|
-
|
|
7608
|
+
// Required lifecycle hosts remain fail-closed; optional hosts publish locally
|
|
7609
|
+
// and recover broker availability outside the extension watchdog.
|
|
7610
|
+
const registration = registerBroker();
|
|
7611
|
+
if (options.brokerRegistrationRequired) await registration;
|
|
7314
7612
|
// Await the startup runtime image capture before arming recovery.
|
|
7315
7613
|
// This ensures replacement detection works correctly on the first recovery check.
|
|
7316
7614
|
await startupImageCapture;
|
|
7317
|
-
|
|
7615
|
+
// Recovery arms only after the startup attempt settles, so no tick overlaps it, and
|
|
7616
|
+
// a failed startup attempt holds the backoff like a failed recovery registration.
|
|
7617
|
+
const armBrokerRecovery = (): void => {
|
|
7618
|
+
if (brokerRecoveryStopped) return;
|
|
7619
|
+
if (!brokerRegistered) brokerRecoveryBackoff.recordFailure(options.agentDir);
|
|
7620
|
+
startBrokerRecovery();
|
|
7621
|
+
};
|
|
7622
|
+
if (options.brokerRegistrationRequired) armBrokerRecovery();
|
|
7623
|
+
else void registration.then(armBrokerRecovery);
|
|
7318
7624
|
} catch (error) {
|
|
7625
|
+
runtimeOwner.quiesceInput();
|
|
7626
|
+
runtimeOwner.fenceGateResolutions();
|
|
7319
7627
|
active = undefined;
|
|
7628
|
+
let queueCancellationFailure: unknown;
|
|
7629
|
+
try {
|
|
7630
|
+
await disposeAcceptedQueueCancellations();
|
|
7631
|
+
} catch (cleanupError) {
|
|
7632
|
+
queueCancellationFailure = cleanupError;
|
|
7633
|
+
}
|
|
7320
7634
|
stopBrokerRecovery();
|
|
7321
7635
|
disposeGate?.();
|
|
7322
7636
|
try {
|
|
@@ -7356,16 +7670,40 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7356
7670
|
};
|
|
7357
7671
|
lifecycleOwnerHolder.state = failedRuntimeOwner;
|
|
7358
7672
|
active = failedRuntimeOwner;
|
|
7359
|
-
|
|
7673
|
+
const failures = [
|
|
7674
|
+
error,
|
|
7675
|
+
...(queueCancellationFailure === undefined ? [] : [queueCancellationFailure]),
|
|
7676
|
+
cleanupError,
|
|
7677
|
+
];
|
|
7678
|
+
const startupFailure = new AggregateError(failures, "SDK runtime startup failed and cleanup failed.");
|
|
7679
|
+
if (queueCancellationFailure !== undefined)
|
|
7680
|
+
Object.assign(startupFailure, { code: "sdk_reconciliation_teardown_failed" });
|
|
7681
|
+
throw startupFailure;
|
|
7360
7682
|
}
|
|
7361
7683
|
cursors.close();
|
|
7362
7684
|
await revisions.close().catch(() => undefined);
|
|
7685
|
+
if (queueCancellationFailure !== undefined)
|
|
7686
|
+
throw Object.assign(
|
|
7687
|
+
new AggregateError(
|
|
7688
|
+
[error, queueCancellationFailure],
|
|
7689
|
+
"SDK runtime startup failed with queued cancellation publication errors.",
|
|
7690
|
+
),
|
|
7691
|
+
{ code: "sdk_reconciliation_teardown_failed" },
|
|
7692
|
+
);
|
|
7363
7693
|
throw error;
|
|
7364
7694
|
}
|
|
7365
7695
|
};
|
|
7366
7696
|
const stopActive = async (cancelSkillRecovery = false, unregisterReason?: "detached_idle"): Promise<void> => {
|
|
7367
7697
|
const current = active;
|
|
7368
7698
|
if (!current) return;
|
|
7699
|
+
current.quiesceInput();
|
|
7700
|
+
current.fenceGateResolutions();
|
|
7701
|
+
let queueCancellationFailure: unknown;
|
|
7702
|
+
try {
|
|
7703
|
+
await disposeAcceptedQueueCancellations();
|
|
7704
|
+
} catch (cleanupError) {
|
|
7705
|
+
queueCancellationFailure = cleanupError;
|
|
7706
|
+
}
|
|
7369
7707
|
if (cancelSkillRecovery) {
|
|
7370
7708
|
for (const controller of skillRecoveryControllers.values()) controller.abort();
|
|
7371
7709
|
for (const controller of skillTerminalRecoveryControllers.values()) controller.abort();
|
|
@@ -7373,8 +7711,6 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7373
7711
|
activePromptOwnerHolder.connectionIds = undefined;
|
|
7374
7712
|
activePromptOwnerHolder.lifecycleEpoch = undefined;
|
|
7375
7713
|
current.stopBrokerRecovery();
|
|
7376
|
-
current.quiesceInput();
|
|
7377
|
-
current.fenceGateResolutions();
|
|
7378
7714
|
try {
|
|
7379
7715
|
await current.waitForGateResolutionQuiescence();
|
|
7380
7716
|
if (current.lifecycleTasks.size > 0) {
|
|
@@ -7395,7 +7731,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7395
7731
|
current.openLifecycleBatches.length > 0 ||
|
|
7396
7732
|
(current.attachedInvocations?.length ?? 0) > 0 ||
|
|
7397
7733
|
(current.drainedInvocations?.length ?? 0) > 0 ||
|
|
7398
|
-
current.lifecycleTasks.size > 0
|
|
7734
|
+
current.lifecycleTasks.size > 0 ||
|
|
7735
|
+
hasQueuedTerminalRecovery(current);
|
|
7399
7736
|
if (retainsLifecycleWork) {
|
|
7400
7737
|
const owners = retiredLifecycleOwners.get(current.sessionId) ?? [];
|
|
7401
7738
|
if (!owners.includes(current)) owners.push(current);
|
|
@@ -7407,7 +7744,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7407
7744
|
current.openLifecycleBatches.length > 0 ||
|
|
7408
7745
|
(current.attachedInvocations?.length ?? 0) > 0 ||
|
|
7409
7746
|
(current.drainedInvocations?.length ?? 0) > 0 ||
|
|
7410
|
-
current.lifecycleTasks.size > 0
|
|
7747
|
+
current.lifecycleTasks.size > 0 ||
|
|
7748
|
+
hasQueuedTerminalRecovery(current)
|
|
7411
7749
|
) {
|
|
7412
7750
|
const retry = setTimeout(retryCleanup, LIFECYCLE_QUIESCENCE_MS);
|
|
7413
7751
|
retry.unref();
|
|
@@ -7431,10 +7769,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
7431
7769
|
// shutdown from retrying the failed endpoint removal.
|
|
7432
7770
|
if (active === undefined) active = current;
|
|
7433
7771
|
logger.error("sdk runtime stop failed", { code: errorCode(error), error: String(error) });
|
|
7772
|
+
if (queueCancellationFailure !== undefined)
|
|
7773
|
+
throw Object.assign(
|
|
7774
|
+
new AggregateError(
|
|
7775
|
+
[queueCancellationFailure, error],
|
|
7776
|
+
"SDK runtime stop and queued cancellation publication both failed.",
|
|
7777
|
+
),
|
|
7778
|
+
{ code: "sdk_reconciliation_teardown_failed" },
|
|
7779
|
+
);
|
|
7434
7780
|
throw error;
|
|
7435
7781
|
}
|
|
7436
7782
|
current.cursors.close();
|
|
7437
7783
|
await current.revisions.close();
|
|
7784
|
+
if (queueCancellationFailure !== undefined) throw queueCancellationFailure;
|
|
7438
7785
|
};
|
|
7439
7786
|
api.on("session_start", async (_event, ctx) => {
|
|
7440
7787
|
await startRuntime(ctx);
|