@gajae-code/coding-agent 0.17.1 → 0.17.4
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 +266 -0
- package/dist/types/async/job-manager.d.ts +7 -6
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/file-lock.d.ts +10 -1
- package/dist/types/config/model-profile-activation.d.ts +63 -4
- package/dist/types/config/model-registry.d.ts +20 -2
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/coordinator-mcp/server.d.ts +3 -0
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +22 -0
- package/dist/types/modes/acp/acp-agent.d.ts +94 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +56 -0
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/acp/adapter.d.ts +27 -0
- package/dist/types/sdk/acp/final-text.d.ts +8 -0
- package/dist/types/sdk/broker/broker.d.ts +13 -2
- package/dist/types/sdk/broker/ensure.d.ts +46 -1
- package/dist/types/sdk/broker/identity.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +38 -1
- package/dist/types/sdk/prompt-failure.d.ts +30 -1
- package/dist/types/sdk/prompt-status.d.ts +8 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +73 -2
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +168 -21
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/thinking.d.ts +3 -1
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +60 -9
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +535 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +9 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +232 -100
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +16 -0
- package/src/config/file-lock.ts +199 -68
- package/src/config/model-profile-activation.ts +406 -53
- package/src/config/model-registry.ts +204 -25
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +175 -8
- package/src/coordinator-mcp/server.ts +528 -87
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/debug/report-bundle.ts +114 -24
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/managed-owner-admission.ts +67 -50
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/internal-urls/docs-index.generated.ts +21 -19
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +123 -55
- package/src/modes/acp/acp-agent.ts +593 -47
- package/src/modes/acp/acp-event-mapper.ts +9 -0
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/model-selector.ts +31 -15
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/components/thinking-selector.ts +7 -3
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +177 -18
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/manager.ts +689 -55
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +90 -9
- package/src/sdk/acp/final-text.ts +19 -2
- package/src/sdk/broker/broker.ts +378 -52
- package/src/sdk/broker/ensure.ts +97 -19
- package/src/sdk/broker/identity.ts +9 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +35 -7
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +721 -84
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +687 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +636 -9
- package/src/sdk/host/session-runtime.ts +499 -118
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/prompt-deadline-flush.ts +334 -0
- package/src/sdk/prompt-deadline-manager.ts +136 -4
- package/src/sdk/prompt-failure.ts +43 -1
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
- package/src/sdk/router/session-router.ts +31 -4
- package/src/sdk/session.ts +194 -16
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +494 -49
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2437 -400
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +58 -18
- package/src/session/internal/managed-session-storage.ts +304 -11
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/session-import/codex.ts +12 -3
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/slash-commands/builtin-registry.ts +258 -14
- package/src/task/index.ts +14 -2
- package/src/thinking.ts +21 -2
- package/src/tools/bash.ts +98 -78
- package/src/tools/fetch.ts +119 -20
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf-wasm-embedded.ts +3 -0
- package/src/utils/mupdf-wasm.ts +24 -2
- package/src/utils/mupdf.ts +129 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
package/src/sdk/broker/broker.ts
CHANGED
|
@@ -37,7 +37,12 @@ import {
|
|
|
37
37
|
redactBrokerDiscovery,
|
|
38
38
|
} from "./discovery";
|
|
39
39
|
import { endpointIncarnation, matchesIndexedEndpointFile, readEndpointFile } from "./endpoint-authority";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
deriveIdempotencyIdentity,
|
|
42
|
+
deriveLegacyIdentity,
|
|
43
|
+
deriveLegacyTargetIdentity,
|
|
44
|
+
getBrokerIdentityKey,
|
|
45
|
+
} from "./identity";
|
|
41
46
|
import {
|
|
42
47
|
canonicalDeleteLocatorPath,
|
|
43
48
|
executeLifecycle,
|
|
@@ -52,6 +57,7 @@ import {
|
|
|
52
57
|
LifecycleLedger,
|
|
53
58
|
type LifecycleStartupFailureReceipt,
|
|
54
59
|
type LifecycleState,
|
|
60
|
+
type TerminalReadBack,
|
|
55
61
|
} from "./lifecycle-ledger";
|
|
56
62
|
import { createMasterCapabilityVerifier, readEndpoint } from "./master-capability";
|
|
57
63
|
import { sdkInternalRuntimeImage } from "./runtime";
|
|
@@ -334,7 +340,7 @@ function lifecycleResponseState(response: BrokerResponse): LifecycleState {
|
|
|
334
340
|
: "terminal_error";
|
|
335
341
|
}
|
|
336
342
|
|
|
337
|
-
type InputNormalization = { input: Record<string, unknown> } | BrokerResponse;
|
|
343
|
+
export type InputNormalization = { input: Record<string, unknown> } | BrokerResponse;
|
|
338
344
|
|
|
339
345
|
type SessionListCursor = {
|
|
340
346
|
sessions: IndexedSession[];
|
|
@@ -563,6 +569,26 @@ function matchesSpawnPin(candidate: IndexedSession, pinned: SpawnHostRegistratio
|
|
|
563
569
|
);
|
|
564
570
|
}
|
|
565
571
|
|
|
572
|
+
/** Exact admission predicate for the child host row published after launch. */
|
|
573
|
+
export function spawnRegistrationMatches(
|
|
574
|
+
candidate: IndexedSession,
|
|
575
|
+
expected: { childId: string; cwd: string; stateRoot: string },
|
|
576
|
+
): boolean {
|
|
577
|
+
return (
|
|
578
|
+
candidate.sessionId === expected.childId &&
|
|
579
|
+
candidate.endpointGeneration > 0 &&
|
|
580
|
+
candidate.live &&
|
|
581
|
+
!candidate.terminal &&
|
|
582
|
+
!candidate.terminalUncertain &&
|
|
583
|
+
// The child publishes a realpath-canonicalized locator while the launch
|
|
584
|
+
// spec carries the caller's lexical spelling. Compare path identity so a
|
|
585
|
+
// symlinked workspace (macOS /var vs /private/var) cannot strand a valid
|
|
586
|
+
// registration behind different spellings.
|
|
587
|
+
resolveEquivalentPath(candidate.locator.cwd) === resolveEquivalentPath(expected.cwd) &&
|
|
588
|
+
resolveEquivalentPath(candidate.locator.stateRoot) === resolveEquivalentPath(expected.stateRoot)
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
566
592
|
/** Rebuilds the provider proof from durable authority facts only. */
|
|
567
593
|
function spawnProofFromAuthority(authority: SpawnAuthorityV1): SpawnSubstrateProof {
|
|
568
594
|
return {
|
|
@@ -635,7 +661,7 @@ function normalizeAliasedString(
|
|
|
635
661
|
return { value: values[0] };
|
|
636
662
|
}
|
|
637
663
|
|
|
638
|
-
function normalizeBrokerInput(operation: string, input: Record<string, unknown>): InputNormalization {
|
|
664
|
+
export function normalizeBrokerInput(operation: string, input: Record<string, unknown>): InputNormalization {
|
|
639
665
|
const normalized: Record<string, unknown> = { ...input };
|
|
640
666
|
const session = normalizeAliasedString(input, "sessionId", ["id"]);
|
|
641
667
|
if (session.error) return error("invalid_input", session.error);
|
|
@@ -725,11 +751,47 @@ function canonicalJson(value: unknown): string {
|
|
|
725
751
|
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
726
752
|
const record = value as Record<string, unknown>;
|
|
727
753
|
return `{${Object.keys(record)
|
|
754
|
+
.filter(key => record[key] !== undefined)
|
|
728
755
|
.sort()
|
|
729
756
|
.map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
730
757
|
.join(",")}}`;
|
|
731
758
|
}
|
|
732
759
|
|
|
760
|
+
type TerminalPersistenceVerification =
|
|
761
|
+
| { kind: "unverified" }
|
|
762
|
+
| { kind: "verified" }
|
|
763
|
+
| { kind: "uncertain"; mismatches: readonly string[] };
|
|
764
|
+
|
|
765
|
+
function verifyTerminalPersistence(
|
|
766
|
+
readBack: TerminalReadBack,
|
|
767
|
+
storedResponse: BrokerResponse,
|
|
768
|
+
durableEffects: LifecycleDurableEffectsReceipt | undefined,
|
|
769
|
+
startupFailure: LifecycleStartupFailureReceipt | undefined,
|
|
770
|
+
): TerminalPersistenceVerification {
|
|
771
|
+
// An absent read-back is a verification failure, not evidence that the
|
|
772
|
+
// operation itself is ambiguous. The terminal transition was already synced
|
|
773
|
+
// before this read, so preserve its settled outcome and let a later reader
|
|
774
|
+
// reconcile the durable row.
|
|
775
|
+
if (readBack.kind === "absent") return { kind: "unverified" };
|
|
776
|
+
// A *rejected* row is the opposite: the ledger holds something for this request
|
|
777
|
+
// and refused it. That is damaged durable state, which previously fenced the
|
|
778
|
+
// session and must keep fencing it — treating it as "not yet written" would let
|
|
779
|
+
// a corrupt row clear the fence it exists to raise.
|
|
780
|
+
if (readBack.kind === "rejected") return { kind: "uncertain", mismatches: [`readBack:${readBack.reason}`] };
|
|
781
|
+
const persisted = readBack.entry;
|
|
782
|
+
const optionalReceiptJson = (value: unknown): string | undefined =>
|
|
783
|
+
value === undefined || value === null ? undefined : canonicalJson(value);
|
|
784
|
+
// The field name is always present; only the two compared receipt renderings are
|
|
785
|
+
// optional. Naming that shape keeps the mismatch list a plain string list.
|
|
786
|
+
const comparisons: readonly (readonly [string, string | undefined, string | undefined])[] = [
|
|
787
|
+
["response", canonicalJson(persisted.response), canonicalJson(storedResponse)],
|
|
788
|
+
["durableEffects", optionalReceiptJson(persisted.durableEffects), optionalReceiptJson(durableEffects)],
|
|
789
|
+
["startupFailure", optionalReceiptJson(persisted.startupFailure), optionalReceiptJson(startupFailure)],
|
|
790
|
+
];
|
|
791
|
+
const mismatches = comparisons.filter(([, actual, expected]) => actual !== expected).map(([field]) => field);
|
|
792
|
+
return mismatches.length === 0 ? { kind: "verified" } : { kind: "uncertain", mismatches };
|
|
793
|
+
}
|
|
794
|
+
|
|
733
795
|
function credentialFreeLifecycleResponse(value: unknown): unknown {
|
|
734
796
|
if (Array.isArray(value)) return value.map(credentialFreeLifecycleResponse);
|
|
735
797
|
if (value === null || typeof value !== "object") return value;
|
|
@@ -903,15 +965,31 @@ function lifecycleTarget(operation: string, input: Record<string, unknown>): unk
|
|
|
903
965
|
sourceSessionPath: string(input.sourceSessionPath, input.sourcePath, input.sessionPath),
|
|
904
966
|
};
|
|
905
967
|
case "session.resume":
|
|
906
|
-
case "session.close":
|
|
907
968
|
case "session.delete":
|
|
908
969
|
case "session.reconcile_uncertain":
|
|
909
970
|
return { sessionId: id };
|
|
971
|
+
case "session.close":
|
|
972
|
+
return { sessionId: id, ...closeTargetAuthority(input) };
|
|
910
973
|
default:
|
|
911
974
|
return { operation, root, sessionId: id };
|
|
912
975
|
}
|
|
913
976
|
}
|
|
914
977
|
|
|
978
|
+
function closeTargetAuthority(input: Record<string, unknown>): {
|
|
979
|
+
endpointGeneration?: number;
|
|
980
|
+
endpointIncarnation?: string;
|
|
981
|
+
} {
|
|
982
|
+
if (
|
|
983
|
+
typeof input.endpointGeneration !== "number" ||
|
|
984
|
+
!Number.isSafeInteger(input.endpointGeneration) ||
|
|
985
|
+
input.endpointGeneration <= 0 ||
|
|
986
|
+
typeof input.endpointIncarnation !== "string" ||
|
|
987
|
+
!/^[a-f0-9]{64}$/u.test(input.endpointIncarnation)
|
|
988
|
+
)
|
|
989
|
+
return {};
|
|
990
|
+
return { endpointGeneration: input.endpointGeneration, endpointIncarnation: input.endpointIncarnation };
|
|
991
|
+
}
|
|
992
|
+
|
|
915
993
|
/**
|
|
916
994
|
* A lifecycle request without a worktree remains serialized by its state root.
|
|
917
995
|
* Worktree launches instead serialize on the source repository plus the
|
|
@@ -990,10 +1068,11 @@ function isBrokerLockArtifactName(name: string): boolean {
|
|
|
990
1068
|
* Decide whether one candidate directory is provably abandoned.
|
|
991
1069
|
*
|
|
992
1070
|
* Fail-closed by construction: every branch that cannot prove abandonment
|
|
993
|
-
* returns a retention reason. A tombstone
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1071
|
+
* returns a retention reason. A tombstone with an owner record is only
|
|
1072
|
+
* abandoned when that record names a dead PID. A missing record is abandoned
|
|
1073
|
+
* only when the aged tombstone is empty; non-empty tombstones remain retained
|
|
1074
|
+
* as ambiguous evidence. Backup directories carry no owner contract, so an
|
|
1075
|
+
* absent record there is not ambiguity and age alone governs.
|
|
997
1076
|
*/
|
|
998
1077
|
async function classifyBrokerLockArtifact(
|
|
999
1078
|
directory: string,
|
|
@@ -1014,7 +1093,14 @@ async function classifyBrokerLockArtifact(
|
|
|
1014
1093
|
} catch (error) {
|
|
1015
1094
|
const code = (error as NodeJS.ErrnoException).code;
|
|
1016
1095
|
if (code !== "ENOENT" && code !== "ENOTDIR") return "owner-record-unreadable";
|
|
1017
|
-
|
|
1096
|
+
if (!name.startsWith(BROKER_LOCK_TOMBSTONE_PREFIX)) return "abandoned";
|
|
1097
|
+
try {
|
|
1098
|
+
const entries = await fs.readdir(target);
|
|
1099
|
+
return entries.length === 0 ? "abandoned" : "owner-record-missing";
|
|
1100
|
+
} catch (readError) {
|
|
1101
|
+
if ((readError as NodeJS.ErrnoException).code === "ENOENT") throw readError;
|
|
1102
|
+
return "owner-record-unreadable";
|
|
1103
|
+
}
|
|
1018
1104
|
}
|
|
1019
1105
|
let pid: unknown;
|
|
1020
1106
|
try {
|
|
@@ -1034,7 +1120,8 @@ async function classifyBrokerLockArtifact(
|
|
|
1034
1120
|
* of the lock's dev+ino, so a machine accrues one directory per dead owner and
|
|
1035
1121
|
* nothing ever removed them (54 on the install in #3963). Reaping is
|
|
1036
1122
|
* best-effort and fail-closed: anything live, unreadable, permission-denied, or
|
|
1037
|
-
* otherwise ambiguous is kept
|
|
1123
|
+
* otherwise ambiguous is kept. Retained paths are logged at debug level and
|
|
1124
|
+
* summarized in one warning per reap pass.
|
|
1038
1125
|
*/
|
|
1039
1126
|
export async function reapStaleBrokerLockArtifacts(input: {
|
|
1040
1127
|
agentDir: string;
|
|
@@ -1048,6 +1135,7 @@ export async function reapStaleBrokerLockArtifacts(input: {
|
|
|
1048
1135
|
const pidAlive = input.pidAlive ?? isPidAlive;
|
|
1049
1136
|
const removed: string[] = [];
|
|
1050
1137
|
const retained: BrokerLockArtifactRetention[] = [];
|
|
1138
|
+
const retainedByReason = new Map<BrokerLockArtifactRetentionReason, number>();
|
|
1051
1139
|
let names: string[];
|
|
1052
1140
|
try {
|
|
1053
1141
|
names = await fs.readdir(directory);
|
|
@@ -1069,7 +1157,10 @@ export async function reapStaleBrokerLockArtifacts(input: {
|
|
|
1069
1157
|
}
|
|
1070
1158
|
if (verdict !== "abandoned") {
|
|
1071
1159
|
retained.push({ path: target, reason: verdict });
|
|
1072
|
-
if (verdict !== "within-grace")
|
|
1160
|
+
if (verdict !== "within-grace") {
|
|
1161
|
+
logger.debug(`sdk broker: retained stale lock artifact ${name} (${verdict})`);
|
|
1162
|
+
retainedByReason.set(verdict, (retainedByReason.get(verdict) ?? 0) + 1);
|
|
1163
|
+
}
|
|
1073
1164
|
continue;
|
|
1074
1165
|
}
|
|
1075
1166
|
try {
|
|
@@ -1078,9 +1169,18 @@ export async function reapStaleBrokerLockArtifacts(input: {
|
|
|
1078
1169
|
} catch (error) {
|
|
1079
1170
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
1080
1171
|
retained.push({ path: target, reason: "removal-failed" });
|
|
1081
|
-
logger.
|
|
1172
|
+
logger.debug(`sdk broker: retained stale lock artifact ${name} (removal-failed)`);
|
|
1173
|
+
retainedByReason.set("removal-failed", (retainedByReason.get("removal-failed") ?? 0) + 1);
|
|
1082
1174
|
}
|
|
1083
1175
|
}
|
|
1176
|
+
if (retainedByReason.size > 0) {
|
|
1177
|
+
const details = [...retainedByReason.entries()]
|
|
1178
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1179
|
+
.map(([reason, count]) => `${reason}: ${count}`)
|
|
1180
|
+
.join(", ");
|
|
1181
|
+
const retainedCount = [...retainedByReason.values()].reduce((total, count) => total + count, 0);
|
|
1182
|
+
logger.warn(`sdk broker: retained ${retainedCount} stale lock artifact(s) (${details})`);
|
|
1183
|
+
}
|
|
1084
1184
|
if (removed.length > 0) logger.info(`sdk broker: reaped ${removed.length} stale lock artifact(s)`);
|
|
1085
1185
|
return { removed, retained };
|
|
1086
1186
|
}
|
|
@@ -1369,11 +1469,17 @@ export class Broker {
|
|
|
1369
1469
|
async #handleSpawn(input: Record<string, unknown>, idempotencyKey: string | undefined): Promise<BrokerResponse> {
|
|
1370
1470
|
const admission = parseSpawnInput(input, idempotencyKey);
|
|
1371
1471
|
if (isBrokerResponse(admission)) return admission;
|
|
1372
|
-
|
|
1472
|
+
// session.spawn predates target-bound lifecycle identities and its durable
|
|
1473
|
+
// authority store already binds the raw request. Keep the v3 identity for
|
|
1474
|
+
// new claims, while recognizing the v4 identity written by the short-lived
|
|
1475
|
+
// target-bound implementation so an upgrade cannot admit a second child.
|
|
1476
|
+
const targetBoundIdentity = await deriveIdempotencyIdentity(
|
|
1373
1477
|
this.settings.agentDir,
|
|
1374
1478
|
"session.spawn",
|
|
1375
1479
|
idempotencyKey!,
|
|
1376
1480
|
);
|
|
1481
|
+
const legacyIdentity = await deriveLegacyIdentity(this.settings.agentDir, "session.spawn", idempotencyKey!);
|
|
1482
|
+
const lifecycleIdentity = this.#spawnAuthority?.claim(targetBoundIdentity) ? targetBoundIdentity : legacyIdentity;
|
|
1377
1483
|
const active = this.#spawnInFlight.get(lifecycleIdentity);
|
|
1378
1484
|
if (active) {
|
|
1379
1485
|
if (this.#spawnTasks.get(active) !== admission.task)
|
|
@@ -1504,8 +1610,11 @@ export class Broker {
|
|
|
1504
1610
|
);
|
|
1505
1611
|
if (decision.kind === "in_progress")
|
|
1506
1612
|
return finish(error("spawn_in_progress", `session.spawn is ${decision.claim.state}`));
|
|
1507
|
-
if (decision.kind === "terminal_uncertain")
|
|
1613
|
+
if (decision.kind === "terminal_uncertain") {
|
|
1614
|
+
if (decision.claim.substrateProof !== undefined && decision.claim.authorityRef === undefined)
|
|
1615
|
+
return finish(await this.#reconcileUncertainRegistration(lifecycleIdentity, decision.claim));
|
|
1508
1616
|
return finish(error("terminal_uncertain", "session.spawn outcome is uncertain"));
|
|
1617
|
+
}
|
|
1509
1618
|
if (decision.kind === "terminal") return finish(this.#spawnTerminalResponse(decision.claim));
|
|
1510
1619
|
if (decision.kind === "replay")
|
|
1511
1620
|
return finish(await this.#reconcileSpawnReplay(lifecycleIdentity, decision.claim));
|
|
@@ -1560,6 +1669,53 @@ export class Broker {
|
|
|
1560
1669
|
return error("resource_gone", "session.spawn claim is closed");
|
|
1561
1670
|
}
|
|
1562
1671
|
|
|
1672
|
+
/** Re-prove an unbound launch on an idempotent retry instead of fencing it forever. */
|
|
1673
|
+
async #reconcileUncertainRegistration(lifecycleIdentity: string, claim: SpawnClaimV2): Promise<BrokerResponse> {
|
|
1674
|
+
const proof = claim.substrateProof;
|
|
1675
|
+
if (claim.state !== "uncertain" || claim.authorityRef !== undefined || !claim.childId || proof === undefined)
|
|
1676
|
+
return error("terminal_uncertain", "session.spawn registration authority is unavailable for reconciliation");
|
|
1677
|
+
const provider = this.#spawnSubstrateProvider();
|
|
1678
|
+
let verdict: "verified" | "mismatch" | "gone";
|
|
1679
|
+
try {
|
|
1680
|
+
verdict = await provider.verify(proof);
|
|
1681
|
+
} catch {
|
|
1682
|
+
return error("terminal_uncertain", "session.spawn registration proof could not be re-verified");
|
|
1683
|
+
}
|
|
1684
|
+
if (verdict === "mismatch")
|
|
1685
|
+
return error("terminal_uncertain", "session.spawn registration proof no longer matches the launch");
|
|
1686
|
+
if (verdict === "verified") {
|
|
1687
|
+
try {
|
|
1688
|
+
const closed = await provider.close(proof);
|
|
1689
|
+
if (!closed.ok)
|
|
1690
|
+
return error(
|
|
1691
|
+
"terminal_uncertain",
|
|
1692
|
+
"session.spawn child registration remains unresolved after the bounded close attempt",
|
|
1693
|
+
);
|
|
1694
|
+
} catch {
|
|
1695
|
+
return error("terminal_uncertain", "session.spawn child registration close could not be verified");
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
const failure: SpawnSubstrateFailure = {
|
|
1699
|
+
substrateKind: proof.substrateKind,
|
|
1700
|
+
code: "child_registration_reconciled_failed",
|
|
1701
|
+
message:
|
|
1702
|
+
verdict === "gone"
|
|
1703
|
+
? "session.spawn child host exited before registration"
|
|
1704
|
+
: "session.spawn child host was closed before registration",
|
|
1705
|
+
};
|
|
1706
|
+
try {
|
|
1707
|
+
await this.#spawnAuthority?.persistTransition(lifecycleIdentity, {
|
|
1708
|
+
claimId: claim.claimId,
|
|
1709
|
+
from: "uncertain",
|
|
1710
|
+
to: "pre_send_rejected",
|
|
1711
|
+
failure,
|
|
1712
|
+
});
|
|
1713
|
+
} catch {
|
|
1714
|
+
return error("terminal_uncertain", "session.spawn registration reconciliation could not be persisted");
|
|
1715
|
+
}
|
|
1716
|
+
return spawnFailureError(failure);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1563
1719
|
#spawnSubstrateProvider(): SpawnSubstrateProvider {
|
|
1564
1720
|
if (this.settings.spawnSubstrateProvider) return this.settings.spawnSubstrateProvider;
|
|
1565
1721
|
this.settings.spawnSubstrateProvider = createSpawnSubstrateProvider();
|
|
@@ -1635,8 +1791,26 @@ export class Broker {
|
|
|
1635
1791
|
code: "substrate_proof_failed",
|
|
1636
1792
|
message: "session.spawn substrate lacks lifecycle process authority",
|
|
1637
1793
|
};
|
|
1638
|
-
await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
1639
|
-
|
|
1794
|
+
const release = await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
1795
|
+
if (release === "unresolved") {
|
|
1796
|
+
current = (
|
|
1797
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
1798
|
+
claimId: current.claimId,
|
|
1799
|
+
from: "substrate_starting",
|
|
1800
|
+
to: "uncertain",
|
|
1801
|
+
failure: {
|
|
1802
|
+
substrateKind: launched.proof.substrateKind,
|
|
1803
|
+
code: "child_registration_release_unproven",
|
|
1804
|
+
message: "session.spawn child registration could not prove substrate release",
|
|
1805
|
+
},
|
|
1806
|
+
})
|
|
1807
|
+
).claim;
|
|
1808
|
+
launchedProof = undefined;
|
|
1809
|
+
return error(
|
|
1810
|
+
"terminal_uncertain",
|
|
1811
|
+
"session.spawn substrate release could not be proven after the launch proof failed",
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1640
1814
|
current = (
|
|
1641
1815
|
await store.persistTransition(lifecycleIdentity, {
|
|
1642
1816
|
claimId: current.claimId,
|
|
@@ -1645,8 +1819,18 @@ export class Broker {
|
|
|
1645
1819
|
failure,
|
|
1646
1820
|
})
|
|
1647
1821
|
).claim;
|
|
1822
|
+
launchedProof = undefined;
|
|
1648
1823
|
return spawnFailureError(failure);
|
|
1649
1824
|
}
|
|
1825
|
+
current = (
|
|
1826
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
1827
|
+
claimId: current.claimId,
|
|
1828
|
+
from: "substrate_starting",
|
|
1829
|
+
to: "substrate_starting",
|
|
1830
|
+
childId: prep.childId,
|
|
1831
|
+
substrateProof: launched.proof,
|
|
1832
|
+
})
|
|
1833
|
+
).claim;
|
|
1650
1834
|
const marker = { pid, incarnation, effectMarker: prep.effectMarker };
|
|
1651
1835
|
await writeEffectMarker(prep.stateRoot, prep.childId, marker);
|
|
1652
1836
|
const registration = await this.#spawnPromptLayer.awaitRegistration({
|
|
@@ -1656,18 +1840,44 @@ export class Broker {
|
|
|
1656
1840
|
});
|
|
1657
1841
|
if (!registration.ok) {
|
|
1658
1842
|
const startupFailure = await readSessionLifecycleFailure(prep.stateRoot, prep.childId, marker);
|
|
1659
|
-
await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
1660
|
-
|
|
1843
|
+
const release = await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
1844
|
+
if (release === "closed" || release === "gone") {
|
|
1845
|
+
const failure: SpawnSubstrateFailure = {
|
|
1846
|
+
substrateKind: launched.proof.substrateKind,
|
|
1847
|
+
code: startupFailure === undefined ? "child_registration_timeout" : "child_registration_failed",
|
|
1848
|
+
message:
|
|
1849
|
+
startupFailure === undefined
|
|
1850
|
+
? "session.spawn child host did not register before the bounded admission deadline"
|
|
1851
|
+
: `session.spawn child host failed during ${startupFailure.phase}/${startupFailure.reason}`,
|
|
1852
|
+
};
|
|
1853
|
+
current = (
|
|
1854
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
1855
|
+
claimId: current.claimId,
|
|
1856
|
+
from: "substrate_starting",
|
|
1857
|
+
to: "pre_send_rejected",
|
|
1858
|
+
failure,
|
|
1859
|
+
})
|
|
1860
|
+
).claim;
|
|
1861
|
+
launchedProof = undefined;
|
|
1862
|
+
return spawnFailureError(failure);
|
|
1863
|
+
}
|
|
1864
|
+
const unresolvedRelease: SpawnSubstrateFailure = {
|
|
1865
|
+
substrateKind: launched.proof.substrateKind,
|
|
1866
|
+
code: "child_registration_release_unproven",
|
|
1867
|
+
message: "session.spawn child registration could not prove substrate release",
|
|
1868
|
+
};
|
|
1661
1869
|
current = (
|
|
1662
1870
|
await store.persistTransition(lifecycleIdentity, {
|
|
1663
1871
|
claimId: current.claimId,
|
|
1664
1872
|
from: "substrate_starting",
|
|
1665
1873
|
to: "uncertain",
|
|
1874
|
+
failure: unresolvedRelease,
|
|
1666
1875
|
})
|
|
1667
1876
|
).claim;
|
|
1877
|
+
launchedProof = undefined;
|
|
1668
1878
|
return error(
|
|
1669
1879
|
"terminal_uncertain",
|
|
1670
|
-
"session.spawn child registration is uncertain" +
|
|
1880
|
+
"session.spawn child registration is uncertain (substrate release could not be proven)" +
|
|
1671
1881
|
(startupFailure ? ` (${startupFailure.phase}/${startupFailure.reason})` : ""),
|
|
1672
1882
|
);
|
|
1673
1883
|
}
|
|
@@ -1833,15 +2043,72 @@ export class Broker {
|
|
|
1833
2043
|
} catch {
|
|
1834
2044
|
// Once a substrate exists the outcome is ambiguous even before handoff:
|
|
1835
2045
|
// reporting an ordinary failure would downgrade retained uncertainty.
|
|
1836
|
-
|
|
2046
|
+
let ambiguous = handedOff || launchedProof !== undefined;
|
|
1837
2047
|
// A launched substrate with no persisted authority is invisible to the
|
|
1838
2048
|
// reaper, so in-process cleanup is its only chance. This must run on
|
|
1839
2049
|
// EVERY post-launch failure exit, not just a returned registration
|
|
1840
2050
|
// failure: a throw from awaitRegistration, verify, close, or a durable
|
|
1841
2051
|
// transition all land here.
|
|
1842
|
-
if (!handedOff)
|
|
2052
|
+
if (!handedOff) {
|
|
2053
|
+
const release = await this.#releaseUnownedSubstrate(provider, launchedProof);
|
|
2054
|
+
if ((release === "closed" || release === "gone") && current.state === "substrate_starting") {
|
|
2055
|
+
const failure: SpawnSubstrateFailure = {
|
|
2056
|
+
substrateKind: launchedProof?.substrateKind ?? "headless",
|
|
2057
|
+
code: "child_registration_failed",
|
|
2058
|
+
message: "session.spawn child host failed before registration",
|
|
2059
|
+
};
|
|
2060
|
+
try {
|
|
2061
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
2062
|
+
claimId: current.claimId,
|
|
2063
|
+
from: "substrate_starting",
|
|
2064
|
+
to: "pre_send_rejected",
|
|
2065
|
+
failure,
|
|
2066
|
+
});
|
|
2067
|
+
launchedProof = undefined;
|
|
2068
|
+
return spawnFailureError(failure);
|
|
2069
|
+
} catch {
|
|
2070
|
+
// The substrate is closed, but a failed terminal write still leaves
|
|
2071
|
+
// the durable claim unresolved for the next broker recovery pass.
|
|
2072
|
+
}
|
|
2073
|
+
} else if (release === "unresolved" && current.state === "substrate_starting") {
|
|
2074
|
+
try {
|
|
2075
|
+
current = (
|
|
2076
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
2077
|
+
claimId: current.claimId,
|
|
2078
|
+
from: "substrate_starting",
|
|
2079
|
+
to: "uncertain",
|
|
2080
|
+
failure: {
|
|
2081
|
+
substrateKind: launchedProof?.substrateKind ?? "headless",
|
|
2082
|
+
code: "child_registration_release_unproven",
|
|
2083
|
+
message: "session.spawn child registration could not prove substrate release",
|
|
2084
|
+
},
|
|
2085
|
+
})
|
|
2086
|
+
).claim;
|
|
2087
|
+
launchedProof = undefined;
|
|
2088
|
+
} catch {
|
|
2089
|
+
// Preserve the generic durable-state uncertainty if the reason itself
|
|
2090
|
+
// cannot be appended.
|
|
2091
|
+
}
|
|
2092
|
+
} else if (release === "absent" && current.state === "substrate_starting") {
|
|
2093
|
+
try {
|
|
2094
|
+
current = (
|
|
2095
|
+
await store.persistTransition(lifecycleIdentity, {
|
|
2096
|
+
claimId: current.claimId,
|
|
2097
|
+
from: "substrate_starting",
|
|
2098
|
+
to: "uncertain",
|
|
2099
|
+
})
|
|
2100
|
+
).claim;
|
|
2101
|
+
ambiguous = true;
|
|
2102
|
+
} catch {
|
|
2103
|
+
// Keep the generic failure when even the uncertainty transition cannot be written.
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
1843
2107
|
return ambiguous
|
|
1844
|
-
? error(
|
|
2108
|
+
? error(
|
|
2109
|
+
"terminal_uncertain",
|
|
2110
|
+
current.failure?.message ?? "session.spawn state could not be advanced durably",
|
|
2111
|
+
)
|
|
1845
2112
|
: error("spawn_failed", "session.spawn could not be advanced durably");
|
|
1846
2113
|
}
|
|
1847
2114
|
}
|
|
@@ -1855,22 +2122,26 @@ export class Broker {
|
|
|
1855
2122
|
async #releaseUnownedSubstrate(
|
|
1856
2123
|
provider: SpawnSubstrateProvider,
|
|
1857
2124
|
proof: SpawnSubstrateProof | undefined,
|
|
1858
|
-
): Promise<
|
|
1859
|
-
|
|
2125
|
+
): Promise<"closed" | "gone" | "absent" | "unresolved"> {
|
|
2126
|
+
// A launch that throws before returning proof has no substrate identity to
|
|
2127
|
+
// classify as gone; keep the claim uncertain instead of fabricating a kind.
|
|
2128
|
+
if (!proof) return "absent";
|
|
1860
2129
|
let verdict: "verified" | "mismatch" | "gone";
|
|
1861
2130
|
try {
|
|
1862
2131
|
verdict = await provider.verify(proof);
|
|
1863
2132
|
} catch {
|
|
1864
2133
|
// An unprovable substrate is never mutated; uncertainty is retained by
|
|
1865
2134
|
// the caller's durable transition instead.
|
|
1866
|
-
return;
|
|
2135
|
+
return "unresolved";
|
|
1867
2136
|
}
|
|
1868
|
-
if (verdict
|
|
2137
|
+
if (verdict === "gone") return "gone";
|
|
2138
|
+
if (verdict !== "verified") return "unresolved";
|
|
1869
2139
|
try {
|
|
1870
|
-
await provider.close(proof);
|
|
2140
|
+
return (await provider.close(proof)).ok ? "closed" : "unresolved";
|
|
1871
2141
|
} catch {
|
|
1872
2142
|
// The substrate may survive. The caller still records uncertainty, which
|
|
1873
2143
|
// is the honest durable outcome for an unclosed unowned substrate.
|
|
2144
|
+
return "unresolved";
|
|
1874
2145
|
}
|
|
1875
2146
|
}
|
|
1876
2147
|
|
|
@@ -2043,21 +2314,9 @@ export class Broker {
|
|
|
2043
2314
|
await this.index.refresh();
|
|
2044
2315
|
// The launch locator is authority: a same-id row registered by an
|
|
2045
2316
|
// unrelated workspace must never be adopted as this spawn's child.
|
|
2046
|
-
const row = this.index
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
candidate.endpointGeneration > 0 &&
|
|
2050
|
-
candidate.live &&
|
|
2051
|
-
!candidate.terminal &&
|
|
2052
|
-
!candidate.terminalUncertain &&
|
|
2053
|
-
// Path IDENTITY, not spelling. The child publishes a
|
|
2054
|
-
// realpath-canonicalized locator while the launch spec carries the
|
|
2055
|
-
// caller's lexical path, so `path.resolve` never reconciles a
|
|
2056
|
-
// symlinked workspace (macOS /var vs /private/var) and a
|
|
2057
|
-
// legitimate child would never match.
|
|
2058
|
-
resolveEquivalentPath(candidate.locator.cwd) === resolveEquivalentPath(input.cwd) &&
|
|
2059
|
-
resolveEquivalentPath(candidate.locator.stateRoot) === resolveEquivalentPath(input.stateRoot),
|
|
2060
|
-
);
|
|
2317
|
+
const row = this.index
|
|
2318
|
+
.listSessionIdentities()
|
|
2319
|
+
.find(candidate => spawnRegistrationMatches(candidate, input));
|
|
2061
2320
|
if (row) {
|
|
2062
2321
|
const incarnation = row.hostIncarnation ?? row.processIncarnation;
|
|
2063
2322
|
// An incarnation-less row is incomplete endpoint evidence; a partial pin
|
|
@@ -3390,7 +3649,9 @@ export class Broker {
|
|
|
3390
3649
|
return error("invalid_input", "session.spawn does not support lifecycle lookup");
|
|
3391
3650
|
if (!LIFECYCLE_OPERATIONS.has(requestedOperation)) return error("not_found", "lifecycle operation was not found");
|
|
3392
3651
|
const identity = await deriveIdempotencyIdentity(this.settings.agentDir, requestedOperation, idempotencyKey);
|
|
3393
|
-
const entry =
|
|
3652
|
+
const entry =
|
|
3653
|
+
this.ledger.get(identity) ??
|
|
3654
|
+
this.ledger.findByOperationKey(`${requestedOperation}\0${idempotencyKey}`, requestedFingerprint);
|
|
3394
3655
|
if (!entry) return error("not_found", "lifecycle operation was not found");
|
|
3395
3656
|
if (entry.fingerprint !== requestedFingerprint)
|
|
3396
3657
|
return error("idempotency_conflict", "lifecycle request fingerprint differs");
|
|
@@ -3450,10 +3711,10 @@ export class Broker {
|
|
|
3450
3711
|
lifecycleFingerprint(lookup.operation, lookup.target),
|
|
3451
3712
|
);
|
|
3452
3713
|
}
|
|
3453
|
-
const fingerprint = lifecycleFingerprint(operation, input);
|
|
3454
3714
|
const normalization = normalizeBrokerInput(operation, input);
|
|
3455
3715
|
if (isBrokerResponse(normalization)) return normalization;
|
|
3456
3716
|
input = normalization.input;
|
|
3717
|
+
const fingerprint = lifecycleFingerprint(operation, input);
|
|
3457
3718
|
if (operation === "session.list") {
|
|
3458
3719
|
if (input.cursor === undefined) await this.index.refresh();
|
|
3459
3720
|
const page = await this.#sessionListPage(input, this.index.listSessions());
|
|
@@ -3523,8 +3784,64 @@ export class Broker {
|
|
|
3523
3784
|
const target = createHash("sha256")
|
|
3524
3785
|
.update(canonicalJson(lifecycleTarget(operation, input)))
|
|
3525
3786
|
.digest("hex");
|
|
3526
|
-
const identity = await deriveIdempotencyIdentity(
|
|
3787
|
+
const identity = await deriveIdempotencyIdentity(
|
|
3788
|
+
this.settings.agentDir,
|
|
3789
|
+
operation,
|
|
3790
|
+
idempotencyKey,
|
|
3791
|
+
operation === "session.create" ? undefined : target,
|
|
3792
|
+
);
|
|
3527
3793
|
const operationKey = `${operation}\0${idempotencyKey}`;
|
|
3794
|
+
const requestedRequestHash = createHash("sha256")
|
|
3795
|
+
.update(canonicalJson({ operation, input: lifecycleRequestIdentity(input) }))
|
|
3796
|
+
.digest("hex");
|
|
3797
|
+
const legacyIdentity = await deriveLegacyIdentity(this.settings.agentDir, operation, idempotencyKey);
|
|
3798
|
+
const legacyTargetIdentity = await deriveLegacyTargetIdentity(
|
|
3799
|
+
this.settings.agentDir,
|
|
3800
|
+
operation,
|
|
3801
|
+
idempotencyKey,
|
|
3802
|
+
target,
|
|
3803
|
+
);
|
|
3804
|
+
const metadata = {
|
|
3805
|
+
operationKey,
|
|
3806
|
+
fingerprint,
|
|
3807
|
+
...(operation === "session.close" && typeof input.sessionId === "string"
|
|
3808
|
+
? { intendedSessionId: input.sessionId }
|
|
3809
|
+
: {}),
|
|
3810
|
+
};
|
|
3811
|
+
if (!this.ledger.get(identity)) {
|
|
3812
|
+
const matchingOperation = this.ledger.findAnyByOperationKey(operationKey);
|
|
3813
|
+
const sameCloseSession =
|
|
3814
|
+
operation === "session.close" &&
|
|
3815
|
+
(matchingOperation?.intendedSessionId === input.sessionId ||
|
|
3816
|
+
matchingOperation?.resultSessionId === input.sessionId);
|
|
3817
|
+
if (
|
|
3818
|
+
matchingOperation &&
|
|
3819
|
+
!sameCloseSession &&
|
|
3820
|
+
(matchingOperation.fingerprint !== fingerprint || matchingOperation.requestHash !== requestedRequestHash)
|
|
3821
|
+
)
|
|
3822
|
+
return error("idempotency_conflict", "idempotency key was used with a different request");
|
|
3823
|
+
const matchingLegacy = matchingOperation;
|
|
3824
|
+
if (matchingLegacy) {
|
|
3825
|
+
if (matchingLegacy.identity === legacyIdentity || matchingLegacy.identity === legacyTargetIdentity)
|
|
3826
|
+
await this.ledger.migrateIdentity(matchingLegacy.identity, identity, metadata);
|
|
3827
|
+
} else if (this.ledger.get(legacyIdentity)) {
|
|
3828
|
+
return error("idempotency_conflict", "idempotency key was used with a different request");
|
|
3829
|
+
} else {
|
|
3830
|
+
const legacyTargetEntry = this.ledger.get(legacyTargetIdentity);
|
|
3831
|
+
if (legacyTargetEntry) {
|
|
3832
|
+
if (legacyTargetEntry.requestHash !== requestedRequestHash)
|
|
3833
|
+
return error("idempotency_conflict", "idempotency key was used with a different request");
|
|
3834
|
+
await this.ledger.migrateIdentity(legacyTargetIdentity, identity, metadata);
|
|
3835
|
+
// Exact target-derived legacy identity migrated without granting new authority.
|
|
3836
|
+
} else if (
|
|
3837
|
+
this.ledger.hasLegacyIdentity(
|
|
3838
|
+
new Set(this.#spawnAuthority?.claims().map(claim => claim.lifecycleIdentity) ?? []),
|
|
3839
|
+
)
|
|
3840
|
+
) {
|
|
3841
|
+
return error("idempotency_conflict", "legacy lifecycle request has an ambiguous target");
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3528
3845
|
|
|
3529
3846
|
let reconstructedDeleteCleanup: BrokerCleanupEvidence | undefined;
|
|
3530
3847
|
if (operation === "session.delete" && input.cwd === undefined && input.sessionPath === undefined) {
|
|
@@ -3594,7 +3911,7 @@ export class Broker {
|
|
|
3594
3911
|
await prev;
|
|
3595
3912
|
try {
|
|
3596
3913
|
const beforeBegin = this.ledger.get(identity);
|
|
3597
|
-
const begun = await this.ledger.begin(identity, requestHash,
|
|
3914
|
+
const begun = await this.ledger.begin(identity, requestHash, metadata);
|
|
3598
3915
|
if (begun.kind === "replay") {
|
|
3599
3916
|
const replay = begun.entry.response as BrokerResponse;
|
|
3600
3917
|
const cleanup = cleanupFromResponse(replay) ?? reconstructedDeleteCleanup;
|
|
@@ -3710,12 +4027,21 @@ export class Broker {
|
|
|
3710
4027
|
});
|
|
3711
4028
|
if (isCleanupPending(response)) return response;
|
|
3712
4029
|
const persisted = await this.ledger.readTerminal(identity, requestHash);
|
|
3713
|
-
const
|
|
3714
|
-
persisted
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
4030
|
+
const persistenceVerification = verifyTerminalPersistence(
|
|
4031
|
+
persisted,
|
|
4032
|
+
storedResponse,
|
|
4033
|
+
outcome.durableEffects,
|
|
4034
|
+
outcome.startupFailure,
|
|
4035
|
+
);
|
|
4036
|
+
if (persistenceVerification.kind === "unverified") {
|
|
4037
|
+
logger.warn("sdk broker terminal persistence read-back was unavailable; preserving settled outcome", {
|
|
4038
|
+
identity,
|
|
4039
|
+
});
|
|
4040
|
+
} else if (persistenceVerification.kind === "uncertain") {
|
|
4041
|
+
logger.warn("sdk broker terminal persistence verification found conflicting evidence", {
|
|
4042
|
+
identity,
|
|
4043
|
+
mismatches: persistenceVerification.mismatches,
|
|
4044
|
+
});
|
|
3719
4045
|
const uncertain = error(
|
|
3720
4046
|
"terminal_uncertain",
|
|
3721
4047
|
"Lifecycle terminal evidence could not be verified after persistence; retained artifacts require reconciliation.",
|