@gajae-code/coding-agent 0.17.2 → 0.17.5
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 +267 -0
- 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 +1049 -3
- package/dist/types/config/file-lock.d.ts +13 -3
- package/dist/types/config/model-profile-activation.d.ts +45 -1
- package/dist/types/config/model-registry.d.ts +14 -3
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/exec/bash-executor.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/gjc-plugins/runtime-adapters.d.ts +11 -2
- 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/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
- package/dist/types/harness-control-plane/finalize.d.ts +14 -3
- 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 +23 -1
- package/dist/types/modes/acp/acp-agent.d.ts +55 -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/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/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +69 -6
- 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/broker/broker.d.ts +17 -4
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/identity.d.ts +5 -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/managed-task-dag.d.ts +1335 -0
- package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
- 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 +12 -0
- package/dist/types/sdk/broker/startup-failure.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 +7 -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 +85 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +3 -3
- 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 +48 -4
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +175 -23
- 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/task/types.d.ts +2 -0
- package/dist/types/tools/bash-interactive.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/fetch.d.ts +10 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/tools/subagent.d.ts +8 -1
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-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 +22 -1
- 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 +541 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +10 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli/stats-cli.ts +11 -1
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +175 -117
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +543 -3
- package/src/config/file-lock.ts +206 -65
- package/src/config/model-discovery-manager.ts +4 -4
- package/src/config/model-profile-activation.ts +417 -66
- package/src/config/model-registry.ts +254 -36
- package/src/config/model-resolver.ts +6 -1
- 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 +179 -9
- package/src/coordinator-mcp/server.ts +521 -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/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/exec/bash-executor.ts +3 -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/gjc-plugins/runtime-adapters.ts +46 -8
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/state-writer.ts +317 -6
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/harness-control-plane/control-endpoint.ts +24 -6
- package/src/harness-control-plane/finalize.ts +312 -25
- package/src/harness-control-plane/owner.ts +323 -89
- package/src/internal-urls/docs-index.generated.ts +23 -20
- 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 +192 -74
- package/src/modes/acp/acp-agent.ts +973 -56
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/gjc-bundle-settings.ts +21 -1
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/tool-status-header.ts +72 -6
- 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/runtime-mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +161 -15
- 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/loader.ts +6 -3
- package/src/runtime-mcp/manager.ts +845 -109
- 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 +27 -8
- package/src/sdk/broker/broker.ts +1431 -198
- package/src/sdk/broker/ensure.ts +21 -3
- package/src/sdk/broker/identity.ts +22 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/managed-task-dag.ts +1927 -0
- package/src/sdk/broker/managed-task-verification.ts +642 -0
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +175 -14
- package/src/sdk/broker/startup-failure.ts +80 -1
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/broker/transport.ts +1 -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 +707 -81
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -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 +705 -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 +184 -2
- package/src/sdk/host/session-runtime.ts +322 -101
- 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/lifecycle/service.ts +2 -0
- package/src/sdk/prompt-deadline-flush.ts +356 -0
- package/src/sdk/prompt-deadline-manager.ts +133 -1
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/session-router.ts +9 -1
- package/src/sdk/session.ts +763 -116
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +397 -42
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2472 -407
- 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 +68 -18
- package/src/session/internal/managed-session-storage.ts +334 -12
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +259 -15
- package/src/task/executor.ts +46 -17
- package/src/task/index.ts +14 -2
- package/src/task/types.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -0
- package/src/tools/bash.ts +118 -78
- package/src/tools/fetch.ts +140 -25
- 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/subagent-render.ts +24 -1
- package/src/tools/subagent.ts +27 -1
- 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.ts +136 -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
|
@@ -5,6 +5,7 @@ import { logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
|
5
5
|
import { nativeProcessBindings } from "@gajae-code/utils/native-process";
|
|
6
6
|
import { withFileLock } from "../../config/file-lock";
|
|
7
7
|
import { repo } from "../../utils/git";
|
|
8
|
+
import { endpointIncarnation } from "./endpoint-authority";
|
|
8
9
|
import { processIncarnation } from "./process-incarnation";
|
|
9
10
|
import {
|
|
10
11
|
assertSupportedSessionIndexEventVersion,
|
|
@@ -26,7 +27,7 @@ export type SessionIndexEventType =
|
|
|
26
27
|
| "record_reconciled";
|
|
27
28
|
|
|
28
29
|
export type SessionActivityState = "active" | "idle";
|
|
29
|
-
/**
|
|
30
|
+
/** Host-reported or broker-coalesced heartbeat: state plus the observation time. */
|
|
30
31
|
export interface SessionActivity {
|
|
31
32
|
state: SessionActivityState;
|
|
32
33
|
at: number;
|
|
@@ -34,6 +35,8 @@ export interface SessionActivity {
|
|
|
34
35
|
/** Events persisted without an OS process incarnation (v1/v2 era) are legacy provenance. */
|
|
35
36
|
export type SessionIdentityProvenance = "composite" | "legacy";
|
|
36
37
|
export type SessionTombstoneRule = "retain" | "expire";
|
|
38
|
+
/** Durable provenance for a retired host registration. */
|
|
39
|
+
export type HostUnregisteredReason = "process_exited" | "detached_idle";
|
|
37
40
|
/**
|
|
38
41
|
* Injected retention policy (C3). The broker schedules compaction independently of
|
|
39
42
|
* rotation; settings apply at the next scheduled compaction. `clock` drives both
|
|
@@ -141,6 +144,7 @@ export interface SessionIndexEvent {
|
|
|
141
144
|
/** Immutable endpoint file identity captured by the broker at registration. */
|
|
142
145
|
endpointFileId?: string;
|
|
143
146
|
lifecycleRequestId?: string;
|
|
147
|
+
hostUnregisteredReason?: HostUnregisteredReason;
|
|
144
148
|
terminalUncertain?: boolean;
|
|
145
149
|
/**
|
|
146
150
|
* Distinguishes the terminal-uncertain claim written by a forced stop of a
|
|
@@ -170,6 +174,7 @@ export interface IndexedSession {
|
|
|
170
174
|
live: boolean;
|
|
171
175
|
indexSeq: number;
|
|
172
176
|
lifecycleRequestId?: string;
|
|
177
|
+
hostUnregisteredReason?: HostUnregisteredReason;
|
|
173
178
|
terminalUncertain?: boolean;
|
|
174
179
|
/** True only for a terminal-uncertain claim written by a forced stale-worktree release. */
|
|
175
180
|
forcedStaleRelease?: boolean;
|
|
@@ -345,6 +350,8 @@ function sameIndexChangeStamp(a: SessionIndexChangeStamp, b: SessionIndexChangeS
|
|
|
345
350
|
const ROTATE_BYTES = 4 * 1024 * 1024;
|
|
346
351
|
/** Coalesced heartbeat checkpoint rate cap (C2): at most one per session per minute. */
|
|
347
352
|
export const SESSION_HEARTBEAT_INTERVAL_MS = 60_000;
|
|
353
|
+
/** A stale-host retirement requires silence well beyond the broker freshness window. */
|
|
354
|
+
export const DEAD_HOST_HEARTBEAT_SILENCE_MS = 10 * SESSION_HEARTBEAT_INTERVAL_MS;
|
|
348
355
|
export const DEFAULT_SESSION_RETENTION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
349
356
|
export const DEFAULT_SESSION_RETENTION_MAX_ROWS = 25_000;
|
|
350
357
|
|
|
@@ -571,6 +578,10 @@ function projectIdentity(
|
|
|
571
578
|
const { latest, heartbeat } = state;
|
|
572
579
|
const terminal = isTerminalEvent(latest);
|
|
573
580
|
const terminalUncertain = latest.type === "lifecycle_terminal" || latest.terminalUncertain === true;
|
|
581
|
+
const hostUnregisteredReason =
|
|
582
|
+
latest.hostUnregisteredReason === "process_exited" || latest.hostUnregisteredReason === "detached_idle"
|
|
583
|
+
? latest.hostUnregisteredReason
|
|
584
|
+
: undefined;
|
|
574
585
|
const pidAlive = alive(latest.pid);
|
|
575
586
|
// Liveness evidence is host-written: a checkpointed heartbeat, or the
|
|
576
587
|
// `host_registered` event the host appended itself. Counting registration
|
|
@@ -606,6 +617,7 @@ function projectIdentity(
|
|
|
606
617
|
endpointMtimeMs: latest.endpointMtimeMs,
|
|
607
618
|
endpointFileId: latest.endpointFileId,
|
|
608
619
|
lifecycleRequestId: latest.lifecycleRequestId,
|
|
620
|
+
...(hostUnregisteredReason === undefined ? {} : { hostUnregisteredReason }),
|
|
609
621
|
terminalUncertain,
|
|
610
622
|
...(latest.forcedStaleRelease === true ? { forcedStaleRelease: true } : {}),
|
|
611
623
|
indexSeq: latest.indexSeq,
|
|
@@ -1687,6 +1699,82 @@ export class SessionIndex {
|
|
|
1687
1699
|
});
|
|
1688
1700
|
});
|
|
1689
1701
|
}
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Atomically fence an exact, long-silent dead-host authority before a caller
|
|
1705
|
+
* removes state outside the broker index. The comparison and terminal event
|
|
1706
|
+
* append share the machine-global index lock, so a heartbeat, successor
|
|
1707
|
+
* registration, or unresolved-authority update that wins the lock prevents
|
|
1708
|
+
* destructive coordinator cleanup.
|
|
1709
|
+
*/
|
|
1710
|
+
async retireStaleIfCurrent(expected: {
|
|
1711
|
+
sessionId: string;
|
|
1712
|
+
workspace: string;
|
|
1713
|
+
endpointGeneration: number;
|
|
1714
|
+
endpointIncarnation: string;
|
|
1715
|
+
}): Promise<"retired" | "already_retired" | "not_found" | "stale"> {
|
|
1716
|
+
const indexPath = path.resolve(logFor(this.#agentDir));
|
|
1717
|
+
// Process-incarnation observations are gathered before the lock. The locked
|
|
1718
|
+
// projection then uses this fixed batch, avoiding an unbounded OS probe while
|
|
1719
|
+
// holding the machine-global index lock.
|
|
1720
|
+
const probed = new Map<string, string | undefined>();
|
|
1721
|
+
for (const row of reduceEvents(this.#events, this.#policy.clock(), this.#agentDir).identities) {
|
|
1722
|
+
const recordedIncarnation = row.hostIncarnation ?? row.processIncarnation;
|
|
1723
|
+
if (recordedIncarnation === undefined || !alive(row.pid)) continue;
|
|
1724
|
+
probed.set(`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`, processIncarnation(row.pid));
|
|
1725
|
+
}
|
|
1726
|
+
return await SessionIndex.#enqueue(indexPath, async () => {
|
|
1727
|
+
try {
|
|
1728
|
+
await fs.stat(dirFor(this.#agentDir));
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return "not_found";
|
|
1731
|
+
throw error;
|
|
1732
|
+
}
|
|
1733
|
+
return await withSessionIndexLock("stale session retirement", this.#agentDir, async () => {
|
|
1734
|
+
await this.#replayUnderLock();
|
|
1735
|
+
if (this.#corruptSuffix) throw new Error("Cannot retire a session from a corrupt session index");
|
|
1736
|
+
const current = this.listSessions(probed).sessions.find(
|
|
1737
|
+
session => session.sessionId === expected.sessionId,
|
|
1738
|
+
);
|
|
1739
|
+
if (!current) return "not_found";
|
|
1740
|
+
if (
|
|
1741
|
+
resolveEquivalentPath(current.locator.cwd) !== resolveEquivalentPath(expected.workspace) ||
|
|
1742
|
+
current.endpointGeneration !== expected.endpointGeneration ||
|
|
1743
|
+
endpointIncarnation(current, expected.sessionId) !== expected.endpointIncarnation
|
|
1744
|
+
)
|
|
1745
|
+
return "stale";
|
|
1746
|
+
if (current.ambiguous || current.terminalUncertain) return "stale";
|
|
1747
|
+
if (current.terminal) return "already_retired";
|
|
1748
|
+
if (
|
|
1749
|
+
current.live !== false ||
|
|
1750
|
+
current.lastHeartbeatAt === undefined ||
|
|
1751
|
+
this.#policy.clock() - current.lastHeartbeatAt < DEAD_HOST_HEARTBEAT_SILENCE_MS
|
|
1752
|
+
)
|
|
1753
|
+
return "stale";
|
|
1754
|
+
const unsigned: Omit<SessionIndexEvent, "checksum"> = {
|
|
1755
|
+
version: SESSION_INDEX_EVENT_VERSION,
|
|
1756
|
+
indexSeq: this.indexSeq + 1,
|
|
1757
|
+
ts: Date.now(),
|
|
1758
|
+
type: "session_closed",
|
|
1759
|
+
sessionId: current.sessionId,
|
|
1760
|
+
locator: current.locator,
|
|
1761
|
+
endpointGeneration: current.endpointGeneration,
|
|
1762
|
+
pid: current.pid,
|
|
1763
|
+
...(current.processIncarnation === undefined ? {} : { processIncarnation: current.processIncarnation }),
|
|
1764
|
+
...(current.hostIncarnation === undefined ? {} : { hostIncarnation: current.hostIncarnation }),
|
|
1765
|
+
...(current.endpointMtimeMs === undefined ? {} : { endpointMtimeMs: current.endpointMtimeMs }),
|
|
1766
|
+
...(current.endpointFileId === undefined ? {} : { endpointFileId: current.endpointFileId }),
|
|
1767
|
+
...(current.lifecycleRequestId === undefined ? {} : { lifecycleRequestId: current.lifecycleRequestId }),
|
|
1768
|
+
};
|
|
1769
|
+
const event: SessionIndexEvent = { ...unsigned, checksum: sessionIndexChecksum(unsigned) };
|
|
1770
|
+
await appendSync(logFor(this.#agentDir), JSON.stringify(event));
|
|
1771
|
+
await this.#refreshUnderLock();
|
|
1772
|
+
if ((await fs.stat(logFor(this.#agentDir))).size >= ROTATE_BYTES) await this.#rotate();
|
|
1773
|
+
return "retired";
|
|
1774
|
+
});
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1690
1778
|
async unregisterIfCurrent(
|
|
1691
1779
|
expected: Pick<
|
|
1692
1780
|
SessionIndexEvent,
|
|
@@ -1701,6 +1789,7 @@ export class SessionIndex {
|
|
|
1701
1789
|
| "processIncarnation"
|
|
1702
1790
|
| "hostIncarnation"
|
|
1703
1791
|
>,
|
|
1792
|
+
reason?: HostUnregisteredReason,
|
|
1704
1793
|
): Promise<boolean> {
|
|
1705
1794
|
const indexPath = path.resolve(logFor(this.#agentDir));
|
|
1706
1795
|
return await SessionIndex.#enqueue(indexPath, async () => {
|
|
@@ -1781,6 +1870,7 @@ export class SessionIndex {
|
|
|
1781
1870
|
...(expected.lifecycleRequestId === undefined
|
|
1782
1871
|
? {}
|
|
1783
1872
|
: { lifecycleRequestId: expected.lifecycleRequestId }),
|
|
1873
|
+
...(reason === undefined ? {} : { hostUnregisteredReason: reason }),
|
|
1784
1874
|
};
|
|
1785
1875
|
const event: SessionIndexEvent = { ...unsigned, checksum: sessionIndexChecksum(unsigned) };
|
|
1786
1876
|
await appendSync(logFor(this.#agentDir), JSON.stringify(event));
|
|
@@ -1866,10 +1956,10 @@ export class SessionIndex {
|
|
|
1866
1956
|
this.#changeStamp = await readIndexChangeStamp(this.#agentDir);
|
|
1867
1957
|
}
|
|
1868
1958
|
|
|
1869
|
-
listSessions(): SessionList {
|
|
1959
|
+
listSessions(probedIncarnations?: ReadonlyMap<string, string | undefined>): SessionList {
|
|
1870
1960
|
return {
|
|
1871
1961
|
indexSeq: this.indexSeq,
|
|
1872
|
-
sessions: reduceEvents(this.#events, this.#policy.clock(), this.#agentDir).sessions,
|
|
1962
|
+
sessions: reduceEvents(this.#events, this.#policy.clock(), this.#agentDir, probedIncarnations).sessions,
|
|
1873
1963
|
warnings: this.#warnings,
|
|
1874
1964
|
};
|
|
1875
1965
|
}
|
|
@@ -2323,7 +2413,10 @@ export class SessionIndex {
|
|
|
2323
2413
|
...(row.processIncarnation === undefined ? {} : { processIncarnation: row.processIncarnation }),
|
|
2324
2414
|
...(row.hostIncarnation === undefined ? {} : { hostIncarnation: row.hostIncarnation }),
|
|
2325
2415
|
...(row.masterRole === undefined ? {} : { masterRole: row.masterRole }),
|
|
2326
|
-
|
|
2416
|
+
// Preserve the host's last observed activity state while this
|
|
2417
|
+
// broker-owned heartbeat only renews liveness. A live idle host
|
|
2418
|
+
// must not be rewritten as active merely because its pid is alive.
|
|
2419
|
+
activity: row.activity ?? { state: "active", at: now },
|
|
2327
2420
|
ts: now,
|
|
2328
2421
|
};
|
|
2329
2422
|
events.push({ ...unsigned, checksum: sessionIndexChecksum(unsigned) });
|
|
@@ -23,6 +23,8 @@ export type SpawnClaimV2 = {
|
|
|
23
23
|
failure?: SpawnSubstrateFailure;
|
|
24
24
|
preSendLease?: { epoch: string; status: "owned" | "consumed" };
|
|
25
25
|
childId?: string;
|
|
26
|
+
/** Launch proof retained while registration is still unbound so a later retry can re-prove or close it. */
|
|
27
|
+
substrateProof?: SpawnSubstrateProof;
|
|
26
28
|
seed?: SeedDeliveryV2;
|
|
27
29
|
authorityRef?: string;
|
|
28
30
|
createdAt: number;
|
|
@@ -139,6 +141,8 @@ export type SpawnClaimTransition = {
|
|
|
139
141
|
leaseEpoch?: string;
|
|
140
142
|
/** The fresh child ID is committed before substrate launch. */
|
|
141
143
|
childId?: string;
|
|
144
|
+
/** Launch proof may be appended to the pre-registration state for later reconciliation. */
|
|
145
|
+
substrateProof?: SpawnSubstrateProof;
|
|
142
146
|
/** Required for seed and terminal state transitions that carry Q26 facts. */
|
|
143
147
|
seed?: SeedDeliveryV2;
|
|
144
148
|
failure?: SpawnSubstrateFailure;
|
|
@@ -166,7 +170,8 @@ export type SpawnClaimDecision =
|
|
|
166
170
|
| { kind: "replay"; claim: SpawnClaimV2 }
|
|
167
171
|
| { kind: "terminal"; claim: SpawnClaimV2 }
|
|
168
172
|
| { kind: "terminal_uncertain"; claim: SpawnClaimV2 }
|
|
169
|
-
| { kind: "idempotency_conflict"; claim: SpawnClaimV2 }
|
|
173
|
+
| { kind: "idempotency_conflict"; claim: SpawnClaimV2 }
|
|
174
|
+
| { kind: "managed_key_conflict" };
|
|
170
175
|
|
|
171
176
|
type StoredClaim = {
|
|
172
177
|
version: 1;
|
|
@@ -175,6 +180,15 @@ type StoredClaim = {
|
|
|
175
180
|
integrity: string;
|
|
176
181
|
};
|
|
177
182
|
|
|
183
|
+
type ManagedKeyReservation = {
|
|
184
|
+
version: 1;
|
|
185
|
+
kind: "managed_key_reservation";
|
|
186
|
+
aliasIdentities: string[];
|
|
187
|
+
managedIdentity: string;
|
|
188
|
+
controlRoot: string;
|
|
189
|
+
integrity: string;
|
|
190
|
+
};
|
|
191
|
+
|
|
178
192
|
type SpawnAuthorityDurableWrite = {
|
|
179
193
|
claim: SpawnClaimV2;
|
|
180
194
|
authority?: SpawnAuthorityV1;
|
|
@@ -225,6 +239,7 @@ const CLAIM_KEYS = new Set([
|
|
|
225
239
|
"failure",
|
|
226
240
|
"preSendLease",
|
|
227
241
|
"childId",
|
|
242
|
+
"substrateProof",
|
|
228
243
|
"seed",
|
|
229
244
|
"authorityRef",
|
|
230
245
|
"createdAt",
|
|
@@ -272,13 +287,13 @@ const FORBIDDEN_FIELD =
|
|
|
272
287
|
/(?:task|prompt|capability|idempotency|fingerprint|requesthash|digest|credential|token|secret|password|stderr)/i;
|
|
273
288
|
const TRANSITION_EDGES: Readonly<Record<SpawnClaimV2["state"], readonly SpawnClaimV2["state"][]>> = {
|
|
274
289
|
prepared: ["substrate_starting", "pre_send_rejected", "uncertain", "closed"],
|
|
275
|
-
substrate_starting: ["authority_active", "pre_send_rejected", "uncertain", "closed"],
|
|
290
|
+
substrate_starting: ["substrate_starting", "authority_active", "pre_send_rejected", "uncertain", "closed"],
|
|
276
291
|
authority_active: ["seed_prepared", "pre_send_rejected", "uncertain", "closed"],
|
|
277
292
|
seed_prepared: ["dispatching", "pre_send_rejected", "uncertain", "closed"],
|
|
278
293
|
dispatching: ["accepted", "pre_send_rejected", "uncertain", "closed"],
|
|
279
294
|
accepted: ["uncertain", "closed"],
|
|
280
295
|
pre_send_rejected: ["uncertain", "closed"],
|
|
281
|
-
uncertain: ["closed"],
|
|
296
|
+
uncertain: ["pre_send_rejected", "closed"],
|
|
282
297
|
closed: [],
|
|
283
298
|
};
|
|
284
299
|
|
|
@@ -476,6 +491,7 @@ export function isSpawnClaimV2(value: unknown): value is SpawnClaimV2 {
|
|
|
476
491
|
((lease as { status?: unknown }).status === "owned" ||
|
|
477
492
|
(lease as { status?: unknown }).status === "consumed"))) &&
|
|
478
493
|
(claim.childId === undefined || isOpaque(claim.childId)) &&
|
|
494
|
+
(claim.substrateProof === undefined || isSpawnSubstrateProof(claim.substrateProof)) &&
|
|
479
495
|
(claim.seed === undefined || isSeed(claim.seed)) &&
|
|
480
496
|
(claim.authorityRef === undefined || isOpaque(claim.authorityRef)) &&
|
|
481
497
|
(claim.failure === undefined || isSpawnSubstrateFailure(claim.failure)) &&
|
|
@@ -554,6 +570,8 @@ export class SpawnAuthorityStore {
|
|
|
554
570
|
readonly #options: SpawnAuthorityStoreOptions;
|
|
555
571
|
readonly #latest = new Map<string, SpawnClaimV2>();
|
|
556
572
|
readonly #authorities = new Map<string, SpawnAuthorityV1>();
|
|
573
|
+
readonly #managedKeyAliases = new Map<string, Map<string, string>>();
|
|
574
|
+
readonly #managedIdentityBindings = new Map<string, { aliasIdentities: string[]; controlRoot: string }>();
|
|
557
575
|
readonly #active = new Set<string>();
|
|
558
576
|
#tail: Promise<void> = Promise.resolve();
|
|
559
577
|
|
|
@@ -586,6 +604,8 @@ export class SpawnAuthorityStore {
|
|
|
586
604
|
await fs.mkdir(path.dirname(this.#file), { recursive: true, mode: 0o700 });
|
|
587
605
|
this.#latest.clear();
|
|
588
606
|
this.#authorities.clear();
|
|
607
|
+
this.#managedKeyAliases.clear();
|
|
608
|
+
this.#managedIdentityBindings.clear();
|
|
589
609
|
let source = "";
|
|
590
610
|
try {
|
|
591
611
|
source = await Bun.file(this.#file).text();
|
|
@@ -600,6 +620,12 @@ export class SpawnAuthorityStore {
|
|
|
600
620
|
} catch {
|
|
601
621
|
throw new Error("Spawn authority journal contains malformed durable evidence.");
|
|
602
622
|
}
|
|
623
|
+
if (this.#isManagedKeyReservation(row)) {
|
|
624
|
+
const reservation = row as ManagedKeyReservation;
|
|
625
|
+
if (!this.#rememberManagedKeyReservation(reservation))
|
|
626
|
+
throw new Error("Spawn authority journal managed key history is invalid.");
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
603
629
|
if (!this.#isStoredClaim(row))
|
|
604
630
|
throw new Error("Spawn authority journal contains invalid durable evidence.");
|
|
605
631
|
const stored = row as StoredClaim;
|
|
@@ -610,6 +636,52 @@ export class SpawnAuthorityStore {
|
|
|
610
636
|
if (stored.authority) this.#authorities.set(stored.claim.lifecycleIdentity, stored.authority);
|
|
611
637
|
else this.#authorities.delete(stored.claim.lifecycleIdentity);
|
|
612
638
|
}
|
|
639
|
+
for (const alias of this.#managedKeyAliases.keys())
|
|
640
|
+
if (this.#latest.has(alias)) throw new Error("Spawn authority journal aliases an ordinary claim.");
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Durably reserves an ordinary session.spawn idempotency identity for one
|
|
646
|
+
* managed native identity. Multiple managed roots may share the alias, but an
|
|
647
|
+
* ordinary claim and a managed reservation can never both win.
|
|
648
|
+
*/
|
|
649
|
+
async reserveManagedKeyAliases(
|
|
650
|
+
aliasIdentities: readonly string[],
|
|
651
|
+
managedIdentity: string,
|
|
652
|
+
controlRoot: string,
|
|
653
|
+
): Promise<boolean> {
|
|
654
|
+
const aliases = [...new Set(aliasIdentities)].sort();
|
|
655
|
+
if (
|
|
656
|
+
aliases.length === 0 ||
|
|
657
|
+
aliases.some(identity => !/^[0-9a-f]{64}$/i.test(identity)) ||
|
|
658
|
+
!/^[0-9a-f]{64}$/i.test(managedIdentity) ||
|
|
659
|
+
!path.isAbsolute(controlRoot) ||
|
|
660
|
+
path.normalize(controlRoot) !== controlRoot
|
|
661
|
+
)
|
|
662
|
+
throw new Error("Invalid managed spawn key reservation.");
|
|
663
|
+
return await this.#serial(async () => {
|
|
664
|
+
if (aliases.some(alias => this.#latest.has(alias))) return false;
|
|
665
|
+
const prior = this.#managedIdentityBindings.get(managedIdentity);
|
|
666
|
+
if (prior)
|
|
667
|
+
return prior.controlRoot === controlRoot && canonicalJson(prior.aliasIdentities) === canonicalJson(aliases);
|
|
668
|
+
for (const alias of aliases) {
|
|
669
|
+
const roots = this.#managedKeyAliases.get(alias);
|
|
670
|
+
const existingRoot = roots?.get(managedIdentity);
|
|
671
|
+
if (existingRoot !== undefined && existingRoot !== controlRoot) return false;
|
|
672
|
+
}
|
|
673
|
+
const reservation: ManagedKeyReservation = {
|
|
674
|
+
version: 1,
|
|
675
|
+
kind: "managed_key_reservation",
|
|
676
|
+
aliasIdentities: aliases,
|
|
677
|
+
managedIdentity,
|
|
678
|
+
controlRoot,
|
|
679
|
+
integrity: "",
|
|
680
|
+
};
|
|
681
|
+
reservation.integrity = this.#managedKeyReservationIntegrity(reservation);
|
|
682
|
+
await this.#appendRow(reservation);
|
|
683
|
+
this.#rememberManagedKeyReservation(reservation);
|
|
684
|
+
return true;
|
|
613
685
|
});
|
|
614
686
|
}
|
|
615
687
|
|
|
@@ -626,6 +698,7 @@ export class SpawnAuthorityStore {
|
|
|
626
698
|
)
|
|
627
699
|
throw new Error("Invalid opaque spawn claim key.");
|
|
628
700
|
return await this.#serial(async () => {
|
|
701
|
+
if (this.#managedKeyAliases.has(lifecycleIdentity)) return { kind: "managed_key_conflict" };
|
|
629
702
|
const current = this.#latest.get(lifecycleIdentity);
|
|
630
703
|
if (!current) {
|
|
631
704
|
if (bindingMac === undefined) throw new Error("A canonical spawn binding is required for a new claim.");
|
|
@@ -698,10 +771,19 @@ export class SpawnAuthorityStore {
|
|
|
698
771
|
!isOpaque(transition.childId) ||
|
|
699
772
|
transition.seed !== undefined ||
|
|
700
773
|
transition.authority !== undefined ||
|
|
701
|
-
transition.failure !== undefined
|
|
774
|
+
transition.failure !== undefined ||
|
|
775
|
+
(transition.substrateProof !== undefined && !isSpawnSubstrateProof(transition.substrateProof)) ||
|
|
776
|
+
(current.state === "prepared" && transition.substrateProof !== undefined) ||
|
|
777
|
+
(current.state === "substrate_starting" && transition.substrateProof === undefined) ||
|
|
778
|
+
(current.state === "substrate_starting" && transition.childId !== current.childId)
|
|
702
779
|
)
|
|
703
780
|
throw new SpawnAuthorityTransitionError("invalid_transition");
|
|
704
|
-
next = {
|
|
781
|
+
next = {
|
|
782
|
+
...current,
|
|
783
|
+
state: "substrate_starting",
|
|
784
|
+
childId: transition.childId,
|
|
785
|
+
...(transition.substrateProof === undefined ? {} : { substrateProof: transition.substrateProof }),
|
|
786
|
+
};
|
|
705
787
|
break;
|
|
706
788
|
}
|
|
707
789
|
case "authority_active": {
|
|
@@ -804,7 +886,11 @@ export class SpawnAuthorityStore {
|
|
|
804
886
|
break;
|
|
805
887
|
}
|
|
806
888
|
case "uncertain": {
|
|
807
|
-
if (
|
|
889
|
+
if (
|
|
890
|
+
transition.childId !== undefined ||
|
|
891
|
+
transition.authority !== undefined ||
|
|
892
|
+
(transition.failure !== undefined && !isSpawnSubstrateFailure(transition.failure))
|
|
893
|
+
)
|
|
808
894
|
throw new SpawnAuthorityTransitionError("invalid_transition");
|
|
809
895
|
if (current.seed) {
|
|
810
896
|
if (
|
|
@@ -813,10 +899,19 @@ export class SpawnAuthorityStore {
|
|
|
813
899
|
!sameSeedIdentity(current.seed, transition.seed)
|
|
814
900
|
)
|
|
815
901
|
throw new SpawnAuthorityTransitionError("invalid_transition");
|
|
816
|
-
next = {
|
|
902
|
+
next = {
|
|
903
|
+
...current,
|
|
904
|
+
state: "uncertain",
|
|
905
|
+
seed: transition.seed,
|
|
906
|
+
...(transition.failure === undefined ? {} : { failure: transition.failure }),
|
|
907
|
+
};
|
|
817
908
|
} else {
|
|
818
909
|
if (transition.seed !== undefined) throw new SpawnAuthorityTransitionError("invalid_transition");
|
|
819
|
-
next = {
|
|
910
|
+
next = {
|
|
911
|
+
...current,
|
|
912
|
+
state: "uncertain",
|
|
913
|
+
...(transition.failure === undefined ? {} : { failure: transition.failure }),
|
|
914
|
+
};
|
|
820
915
|
}
|
|
821
916
|
break;
|
|
822
917
|
}
|
|
@@ -936,6 +1031,68 @@ export class SpawnAuthorityStore {
|
|
|
936
1031
|
.digest("hex");
|
|
937
1032
|
}
|
|
938
1033
|
|
|
1034
|
+
#managedKeyReservationIntegrity(reservation: ManagedKeyReservation): string {
|
|
1035
|
+
const { integrity: _integrity, ...facts } = reservation;
|
|
1036
|
+
return createHmac("sha256", this.#identityKey).update(canonicalJson(facts)).digest("hex");
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
#isManagedKeyReservation(value: unknown): value is ManagedKeyReservation {
|
|
1040
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1041
|
+
const row = value as Partial<ManagedKeyReservation>;
|
|
1042
|
+
return (
|
|
1043
|
+
Object.keys(row).every(
|
|
1044
|
+
key =>
|
|
1045
|
+
key === "version" ||
|
|
1046
|
+
key === "kind" ||
|
|
1047
|
+
key === "aliasIdentities" ||
|
|
1048
|
+
key === "managedIdentity" ||
|
|
1049
|
+
key === "controlRoot" ||
|
|
1050
|
+
key === "integrity",
|
|
1051
|
+
) &&
|
|
1052
|
+
row.version === 1 &&
|
|
1053
|
+
row.kind === "managed_key_reservation" &&
|
|
1054
|
+
Array.isArray(row.aliasIdentities) &&
|
|
1055
|
+
row.aliasIdentities.length > 0 &&
|
|
1056
|
+
row.aliasIdentities.every(identity => typeof identity === "string" && /^[0-9a-f]{64}$/i.test(identity)) &&
|
|
1057
|
+
new Set(row.aliasIdentities).size === row.aliasIdentities.length &&
|
|
1058
|
+
canonicalJson(row.aliasIdentities) === canonicalJson([...row.aliasIdentities].sort()) &&
|
|
1059
|
+
typeof row.managedIdentity === "string" &&
|
|
1060
|
+
/^[0-9a-f]{64}$/i.test(row.managedIdentity) &&
|
|
1061
|
+
typeof row.controlRoot === "string" &&
|
|
1062
|
+
path.isAbsolute(row.controlRoot) &&
|
|
1063
|
+
path.normalize(row.controlRoot) === row.controlRoot &&
|
|
1064
|
+
typeof row.integrity === "string" &&
|
|
1065
|
+
/^[0-9a-f]{64}$/i.test(row.integrity) &&
|
|
1066
|
+
timingSafeEqual(
|
|
1067
|
+
Buffer.from(row.integrity, "hex"),
|
|
1068
|
+
Buffer.from(this.#managedKeyReservationIntegrity(row as ManagedKeyReservation), "hex"),
|
|
1069
|
+
)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
#rememberManagedKeyReservation(reservation: ManagedKeyReservation): boolean {
|
|
1074
|
+
const prior = this.#managedIdentityBindings.get(reservation.managedIdentity);
|
|
1075
|
+
if (
|
|
1076
|
+
prior &&
|
|
1077
|
+
(prior.controlRoot !== reservation.controlRoot ||
|
|
1078
|
+
canonicalJson(prior.aliasIdentities) !== canonicalJson(reservation.aliasIdentities))
|
|
1079
|
+
)
|
|
1080
|
+
return false;
|
|
1081
|
+
if (prior) return false;
|
|
1082
|
+
this.#managedIdentityBindings.set(reservation.managedIdentity, {
|
|
1083
|
+
aliasIdentities: [...reservation.aliasIdentities],
|
|
1084
|
+
controlRoot: reservation.controlRoot,
|
|
1085
|
+
});
|
|
1086
|
+
for (const alias of reservation.aliasIdentities) {
|
|
1087
|
+
const roots = this.#managedKeyAliases.get(alias) ?? new Map<string, string>();
|
|
1088
|
+
const existingRoot = roots.get(reservation.managedIdentity);
|
|
1089
|
+
if (existingRoot !== undefined && existingRoot !== reservation.controlRoot) return false;
|
|
1090
|
+
roots.set(reservation.managedIdentity, reservation.controlRoot);
|
|
1091
|
+
this.#managedKeyAliases.set(alias, roots);
|
|
1092
|
+
}
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
939
1096
|
#isStoredClaim(value: unknown): value is StoredClaim {
|
|
940
1097
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
941
1098
|
const row = value as Partial<StoredClaim>;
|
|
@@ -961,6 +1118,13 @@ export class SpawnAuthorityStore {
|
|
|
961
1118
|
throw new Error("Refusing to persist invalid spawn authority evidence.");
|
|
962
1119
|
const write: SpawnAuthorityDurableWrite = { claim, ...(authority === undefined ? {} : { authority }) };
|
|
963
1120
|
const row: StoredClaim = { version: 1, ...write, integrity: this.#integrity(claim, authority) };
|
|
1121
|
+
await this.#appendRow(row, write);
|
|
1122
|
+
this.#latest.set(claim.lifecycleIdentity, claim);
|
|
1123
|
+
if (authority) this.#authorities.set(claim.lifecycleIdentity, authority);
|
|
1124
|
+
else this.#authorities.delete(claim.lifecycleIdentity);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
async #appendRow(row: unknown, write?: SpawnAuthorityDurableWrite): Promise<void> {
|
|
964
1128
|
// The row is appended before the durability barrier, so a barrier failure
|
|
965
1129
|
// can leave a physically written line that in-memory state never accepted.
|
|
966
1130
|
// A retry would then append a second row for the same claim and reopen
|
|
@@ -981,13 +1145,13 @@ export class SpawnAuthorityStore {
|
|
|
981
1145
|
try {
|
|
982
1146
|
try {
|
|
983
1147
|
await handle.writeFile(`${canonicalJson(row)}\n`);
|
|
984
|
-
await this.#options.beforeSyncForTest?.(write);
|
|
1148
|
+
if (write) await this.#options.beforeSyncForTest?.(write);
|
|
985
1149
|
await handle.sync();
|
|
986
1150
|
// The parent-directory barrier belongs INSIDE the rolled-back region:
|
|
987
1151
|
// a throw here would otherwise leave a durable row that in-memory
|
|
988
1152
|
// state never accepted, and the retry would append a duplicate that
|
|
989
1153
|
// makes the journal unreopenable.
|
|
990
|
-
await this.#options.beforeDirectorySyncForTest?.(write);
|
|
1154
|
+
if (write) await this.#options.beforeDirectorySyncForTest?.(write);
|
|
991
1155
|
const directory = await fs.open(path.dirname(this.#file), fsSync.constants.O_RDONLY);
|
|
992
1156
|
try {
|
|
993
1157
|
await directory.sync();
|
|
@@ -1011,10 +1175,7 @@ export class SpawnAuthorityStore {
|
|
|
1011
1175
|
} finally {
|
|
1012
1176
|
await handle.close();
|
|
1013
1177
|
}
|
|
1014
|
-
this.#
|
|
1015
|
-
if (authority) this.#authorities.set(claim.lifecycleIdentity, authority);
|
|
1016
|
-
else this.#authorities.delete(claim.lifecycleIdentity);
|
|
1017
|
-
await this.#options.afterSyncForTest?.(write);
|
|
1178
|
+
if (write) await this.#options.afterSyncForTest?.(write);
|
|
1018
1179
|
}
|
|
1019
1180
|
|
|
1020
1181
|
async #serial<T>(operation: () => Promise<T>): Promise<T> {
|
|
@@ -5,6 +5,8 @@ import { processIncarnation } from "./process-incarnation";
|
|
|
5
5
|
export interface BrokerStartupFailureMarker {
|
|
6
6
|
version: 2;
|
|
7
7
|
reason: string;
|
|
8
|
+
/** Validated manual lock cleanup command, stored separately when it cannot fit in `reason`. */
|
|
9
|
+
cleanupCommand?: string;
|
|
8
10
|
exitCode: number | null;
|
|
9
11
|
signal: string | null;
|
|
10
12
|
writtenAt: number;
|
|
@@ -23,11 +25,82 @@ export interface BrokerStartupFailureMarker {
|
|
|
23
25
|
|
|
24
26
|
const BROKER_STARTUP_FAILURE_FILE = "broker.startup-failure.json";
|
|
25
27
|
const MAX_BROKER_STARTUP_FAILURE_REASON = 512;
|
|
28
|
+
// Windows extended paths can be 32,767 UTF-16 code units and PowerShell quoting
|
|
29
|
+
// can double every apostrophe; this also comfortably covers POSIX PATH_MAX paths.
|
|
30
|
+
const MAX_BROKER_STARTUP_FAILURE_CLEANUP_COMMAND = 66_000;
|
|
31
|
+
const FILE_LOCK_CLEANUP_GUIDANCE =
|
|
32
|
+
"; manual cleanup is appropriate only after independently verifying this exact directory still belongs to the dead owner and no successor has taken it: ";
|
|
33
|
+
const FILE_LOCK_OWNER_GUIDANCE =
|
|
34
|
+
"; a live owner is never displaced — if this is an SDK broker (gjc sdk session list), it must finish or be stopped before retrying";
|
|
35
|
+
const COMPACT_FILE_LOCK_CLEANUP_GUIDANCE =
|
|
36
|
+
"Manual cleanup is appropriate only after independently verifying this exact directory still belongs to the dead owner and no successor has taken it: ";
|
|
37
|
+
|
|
38
|
+
function decodeFileLockCleanupCommand(command: string): string | undefined {
|
|
39
|
+
if (command.length === 0 || command.length > MAX_BROKER_STARTUP_FAILURE_CLEANUP_COMMAND) return undefined;
|
|
40
|
+
const prefix = process.platform === "win32" ? "Remove-Item -LiteralPath '" : "rm -rf -- '";
|
|
41
|
+
const suffix = process.platform === "win32" ? "' -Recurse -Force" : "'";
|
|
42
|
+
if (!command.startsWith(prefix) || !command.endsWith(suffix)) return undefined;
|
|
43
|
+
|
|
44
|
+
const encodedPath = command.slice(prefix.length, -suffix.length);
|
|
45
|
+
const quoteEscape = process.platform === "win32" ? "''" : "'\\''";
|
|
46
|
+
const decodedPath = encodedPath.replaceAll(quoteEscape, "'");
|
|
47
|
+
if (!decodedPath || decodedPath.replaceAll("'", quoteEscape) !== encodedPath) return undefined;
|
|
48
|
+
return decodedPath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validatedFileLockCleanupCommand(reason: string): string | undefined {
|
|
52
|
+
let guidanceIndex = reason.indexOf(FILE_LOCK_CLEANUP_GUIDANCE);
|
|
53
|
+
while (guidanceIndex >= 0) {
|
|
54
|
+
const command = reason.slice(guidanceIndex + FILE_LOCK_CLEANUP_GUIDANCE.length);
|
|
55
|
+
const lockPath = decodeFileLockCleanupCommand(command);
|
|
56
|
+
if (lockPath) {
|
|
57
|
+
const beforeCleanup = reason.slice(0, guidanceIndex);
|
|
58
|
+
const lockAndOwnerGuidance = ` (${lockPath})${FILE_LOCK_OWNER_GUIDANCE}`;
|
|
59
|
+
const acquisition = beforeCleanup.endsWith(lockAndOwnerGuidance)
|
|
60
|
+
? beforeCleanup.slice(0, -lockAndOwnerGuidance.length)
|
|
61
|
+
: "";
|
|
62
|
+
if (/^Failed to acquire lock for [\s\S]+ after \d+ attempts: [\s\S]+$/.test(acquisition)) {
|
|
63
|
+
return command;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
guidanceIndex = reason.indexOf(FILE_LOCK_CLEANUP_GUIDANCE, guidanceIndex + 1);
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function boundedFailureDetails(reason: string): { reason: string; cleanupCommand?: string } {
|
|
72
|
+
if (reason.length <= MAX_BROKER_STARTUP_FAILURE_REASON) return { reason };
|
|
73
|
+
|
|
74
|
+
const cleanupCommand = validatedFileLockCleanupCommand(reason);
|
|
75
|
+
if (cleanupCommand) {
|
|
76
|
+
const compactReason = `${COMPACT_FILE_LOCK_CLEANUP_GUIDANCE}${cleanupCommand}`;
|
|
77
|
+
if (compactReason.length <= MAX_BROKER_STARTUP_FAILURE_REASON) return { reason: compactReason };
|
|
78
|
+
return { reason: COMPACT_FILE_LOCK_CLEANUP_GUIDANCE, cleanupCommand };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { reason: reason.slice(0, MAX_BROKER_STARTUP_FAILURE_REASON) };
|
|
82
|
+
}
|
|
26
83
|
|
|
27
84
|
export function brokerStartupFailurePath(agentDir: string): string {
|
|
28
85
|
return path.join(agentDir, "sdk", BROKER_STARTUP_FAILURE_FILE);
|
|
29
86
|
}
|
|
30
87
|
|
|
88
|
+
/** Whether validated compact cleanup guidance targets the exact lock path. */
|
|
89
|
+
export function brokerStartupFailureCleanupTargetsLock(
|
|
90
|
+
marker: BrokerStartupFailureMarker | undefined,
|
|
91
|
+
lockPath: string,
|
|
92
|
+
): boolean {
|
|
93
|
+
if (!marker) return false;
|
|
94
|
+
if (marker.cleanupCommand !== undefined) {
|
|
95
|
+
return (
|
|
96
|
+
marker.reason === COMPACT_FILE_LOCK_CLEANUP_GUIDANCE &&
|
|
97
|
+
decodeFileLockCleanupCommand(marker.cleanupCommand) === lockPath
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (!marker.reason.startsWith(COMPACT_FILE_LOCK_CLEANUP_GUIDANCE)) return false;
|
|
101
|
+
return decodeFileLockCleanupCommand(marker.reason.slice(COMPACT_FILE_LOCK_CLEANUP_GUIDANCE.length)) === lockPath;
|
|
102
|
+
}
|
|
103
|
+
|
|
31
104
|
function boundedMarker(
|
|
32
105
|
reason: string,
|
|
33
106
|
exitCode: number | null,
|
|
@@ -35,9 +108,10 @@ function boundedMarker(
|
|
|
35
108
|
pid: number,
|
|
36
109
|
incarnation: string,
|
|
37
110
|
): BrokerStartupFailureMarker {
|
|
111
|
+
const boundedFailure = boundedFailureDetails(reason);
|
|
38
112
|
return {
|
|
39
113
|
version: 2,
|
|
40
|
-
|
|
114
|
+
...boundedFailure,
|
|
41
115
|
exitCode,
|
|
42
116
|
signal,
|
|
43
117
|
writtenAt: Date.now(),
|
|
@@ -84,6 +158,11 @@ export async function readBrokerStartupFailureMarker(
|
|
|
84
158
|
marker.version !== 2 ||
|
|
85
159
|
typeof marker.reason !== "string" ||
|
|
86
160
|
marker.reason.length === 0 ||
|
|
161
|
+
marker.reason.length > MAX_BROKER_STARTUP_FAILURE_REASON ||
|
|
162
|
+
(marker.cleanupCommand !== undefined &&
|
|
163
|
+
(typeof marker.cleanupCommand !== "string" ||
|
|
164
|
+
marker.reason !== COMPACT_FILE_LOCK_CLEANUP_GUIDANCE ||
|
|
165
|
+
decodeFileLockCleanupCommand(marker.cleanupCommand) === undefined)) ||
|
|
87
166
|
(marker.exitCode !== null && typeof marker.exitCode !== "number") ||
|
|
88
167
|
(marker.signal !== null && typeof marker.signal !== "string") ||
|
|
89
168
|
typeof marker.writtenAt !== "number" ||
|