@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
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
exactUnlink,
|
|
7
|
+
linkNoReplacePath,
|
|
8
|
+
type NativeExactFileIdentity,
|
|
9
|
+
type NativeNoReplaceResult,
|
|
10
|
+
renameNoReplacePath,
|
|
11
|
+
} from "@gajae-code/natives";
|
|
4
12
|
import { compileGjcPluginBundle } from "./compiler";
|
|
5
13
|
import { migrateGjcPluginEntries } from "./migration";
|
|
6
14
|
import { gjcPluginProjectRoot, gjcPluginUserRoot } from "./paths";
|
|
@@ -11,6 +19,11 @@ const LOCK_FILENAME = "registry.lock";
|
|
|
11
19
|
const LOCK_TIMEOUT_MS = 5000;
|
|
12
20
|
const LOCK_RETRY_MS = 50;
|
|
13
21
|
|
|
22
|
+
export const RegistryLockTestHooks: {
|
|
23
|
+
beforeEviction?: (lockPath: string, raw: string) => void | Promise<void>;
|
|
24
|
+
beforePublish?: (stagingPath: string, lockPath: string) => void | Promise<void>;
|
|
25
|
+
} = {};
|
|
26
|
+
|
|
14
27
|
export function registryRootForScope(scope: GjcPluginScope, cwd: string): string {
|
|
15
28
|
return scope === "user" ? gjcPluginUserRoot() : gjcPluginProjectRoot(cwd);
|
|
16
29
|
}
|
|
@@ -323,57 +336,203 @@ export async function readRegistry(
|
|
|
323
336
|
if (!migrated.changed && discovered.length === 0) return registry;
|
|
324
337
|
// Re-check under the lock before persisting. Migration and legacy-root
|
|
325
338
|
// discovery are one transaction, never a normal runtime loader path.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
await
|
|
333
|
-
|
|
339
|
+
// Contention (a concurrent install elsewhere) must not fail startup reads:
|
|
340
|
+
// the effective registry is already computed, so degrade to returning it
|
|
341
|
+
// unpersisted and let a later uncontended session write the migrated
|
|
342
|
+
// state. Mutating install paths keep their fail-loud install_conflict.
|
|
343
|
+
try {
|
|
344
|
+
return await withRegistryLock(scope, cwd, async () => {
|
|
345
|
+
const latest = await readRegistryRaw(scope, cwd);
|
|
346
|
+
const latestDiscovered = await discoverLegacyEntries(scope, cwd, latest.plugins);
|
|
347
|
+
const latestMigrated = await migrateGjcPluginEntries([...latest.plugins, ...latestDiscovered]);
|
|
348
|
+
if (latestMigrated.changed || latestDiscovered.length > 0) {
|
|
349
|
+
const next: GjcPluginRegistry = { ...latest, plugins: sortRegistryEntries(latestMigrated.entries) };
|
|
350
|
+
await writeRegistryUnlocked(next, cwd, scope);
|
|
351
|
+
return next;
|
|
352
|
+
}
|
|
353
|
+
return latest;
|
|
354
|
+
});
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (error instanceof GjcPluginLoadError && error.code === "install_conflict") {
|
|
357
|
+
return { ...registry, plugins: sortRegistryEntries(migrated.entries) };
|
|
334
358
|
}
|
|
335
|
-
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const LOCK_NONCE_PATTERN = /^[0-9a-f]{16}$/;
|
|
364
|
+
|
|
365
|
+
interface RegistryLockToken {
|
|
366
|
+
pid: number;
|
|
367
|
+
/** Host segment of a host-tagged token; null for legacy `pid-nonce` tokens. */
|
|
368
|
+
host: string | null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function parseRegistryLockToken(raw: string): RegistryLockToken | null {
|
|
372
|
+
const parts = raw.trim().split("-");
|
|
373
|
+
if (parts.length < 2) return null;
|
|
374
|
+
const pid = Number(parts[0]);
|
|
375
|
+
const nonce = parts[parts.length - 1];
|
|
376
|
+
if (!Number.isInteger(pid) || pid <= 0 || !LOCK_NONCE_PATTERN.test(nonce)) return null;
|
|
377
|
+
// Hostnames may contain '-', so everything between the first and last
|
|
378
|
+
// segment is the host; a single middle segment that is empty (or absent)
|
|
379
|
+
// means a legacy token.
|
|
380
|
+
const host = parts.slice(1, -1).join("-");
|
|
381
|
+
return { pid, host: host.length > 0 ? host : null };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* A holder is assumed alive unless provably dead: ourselves, a live PID
|
|
386
|
+
* (EPERM counts — the process exists under another user), or a lock written
|
|
387
|
+
* on another host (a project registry can live on shared storage, where a
|
|
388
|
+
* local PID probe would be meaningless) all stay fail-closed.
|
|
389
|
+
*/
|
|
390
|
+
function registryLockHolderAlive(holder: RegistryLockToken): boolean {
|
|
391
|
+
if (holder.pid === process.pid) return true;
|
|
392
|
+
if (holder.host !== null && holder.host !== os.hostname()) return true;
|
|
393
|
+
try {
|
|
394
|
+
process.kill(holder.pid, 0);
|
|
395
|
+
return true;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return (error as NodeJS.ErrnoException)?.code === "EPERM";
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function captureRegistryLockIdentity(lockPath: string): Promise<NativeExactFileIdentity | null> {
|
|
402
|
+
try {
|
|
403
|
+
const [bytes, stat, parent] = await Promise.all([
|
|
404
|
+
fs.readFile(lockPath),
|
|
405
|
+
fs.lstat(lockPath, { bigint: true }),
|
|
406
|
+
fs.stat(path.dirname(lockPath), { bigint: true }),
|
|
407
|
+
]);
|
|
408
|
+
if (!stat.isFile()) return null;
|
|
409
|
+
return {
|
|
410
|
+
dev: stat.dev,
|
|
411
|
+
ino: stat.ino,
|
|
412
|
+
nlink: stat.nlink,
|
|
413
|
+
parentDev: parent.dev,
|
|
414
|
+
parentIno: parent.ino,
|
|
415
|
+
size: stat.size,
|
|
416
|
+
mtimeNs: stat.mtimeNs,
|
|
417
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
418
|
+
};
|
|
419
|
+
} catch (error) {
|
|
420
|
+
if (isEnoent(error)) return null;
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function evictRegistryLock(lockPath: string, raw: string): Promise<boolean> {
|
|
426
|
+
const identity = await captureRegistryLockIdentity(lockPath);
|
|
427
|
+
if (!identity) return false;
|
|
428
|
+
const expectedHash = createHash("sha256").update(raw).digest("hex");
|
|
429
|
+
if (identity.sha256 !== expectedHash) return false;
|
|
430
|
+
await RegistryLockTestHooks.beforeEviction?.(lockPath, raw);
|
|
431
|
+
const result = exactUnlink(lockPath, {
|
|
432
|
+
...identity,
|
|
433
|
+
quarantineName: `${LOCK_FILENAME}.evict-${process.pid}-${randomBytes(4).toString("hex")}`,
|
|
336
434
|
});
|
|
435
|
+
return result.ok || result.code === "cleanup_pending";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function publishRegistryLock(stagingPath: string, lockPath: string): NativeNoReplaceResult {
|
|
439
|
+
let result = renameNoReplacePath(stagingPath, lockPath);
|
|
440
|
+
if (
|
|
441
|
+
!result.ok &&
|
|
442
|
+
result.mutationState === "not_committed" &&
|
|
443
|
+
(result.reason === "atomic_unavailable" || result.reason === "invalid_request")
|
|
444
|
+
) {
|
|
445
|
+
// linkat is the no-overwrite fallback on filesystems without a
|
|
446
|
+
// no-replace rename. Both paths expose the complete token only after the
|
|
447
|
+
// atomic claim, so a crash cannot publish an empty or partial lock.
|
|
448
|
+
result = linkNoReplacePath(stagingPath, lockPath);
|
|
449
|
+
}
|
|
450
|
+
return result;
|
|
337
451
|
}
|
|
338
452
|
|
|
339
453
|
async function acquireLock(lockPath: string): Promise<() => Promise<void>> {
|
|
340
454
|
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
341
|
-
const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
455
|
+
const token = `${process.pid}-${os.hostname()}-${randomBytes(8).toString("hex")}`;
|
|
342
456
|
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
343
457
|
for (;;) {
|
|
458
|
+
const stagingPath = `${lockPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
459
|
+
let publication: NativeNoReplaceResult | undefined;
|
|
344
460
|
try {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
461
|
+
await fs.writeFile(stagingPath, token, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
462
|
+
await RegistryLockTestHooks.beforePublish?.(stagingPath, lockPath);
|
|
463
|
+
publication = publishRegistryLock(stagingPath, lockPath);
|
|
464
|
+
if (publication.ok) {
|
|
465
|
+
let released = false;
|
|
466
|
+
return async () => {
|
|
467
|
+
if (released) return;
|
|
468
|
+
released = true;
|
|
469
|
+
// Owner-safe release: only remove the lock if it is still ours.
|
|
470
|
+
try {
|
|
471
|
+
const current = await fs.readFile(lockPath, "utf8");
|
|
472
|
+
if (current === token) await fs.rm(lockPath, { force: true });
|
|
473
|
+
} catch {
|
|
474
|
+
// Lock already gone; nothing to release.
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (publication.reason !== "destination_exists") {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Failed to publish GJC plugin registry lock at ${lockPath}: ${publication.code ?? publication.reason}`,
|
|
481
|
+
);
|
|
350
482
|
}
|
|
351
|
-
let released = false;
|
|
352
|
-
return async () => {
|
|
353
|
-
if (released) return;
|
|
354
|
-
released = true;
|
|
355
|
-
// Owner-safe release: only remove the lock if it is still ours.
|
|
356
|
-
try {
|
|
357
|
-
const current = await fs.readFile(lockPath, "utf8");
|
|
358
|
-
if (current === token) await fs.rm(lockPath, { force: true });
|
|
359
|
-
} catch {
|
|
360
|
-
// Lock already gone; nothing to release.
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
483
|
} catch (error) {
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
484
|
+
if (publication?.reason === "destination_exists") {
|
|
485
|
+
// Continue into stale inspection below.
|
|
486
|
+
} else if (
|
|
487
|
+
publication === undefined &&
|
|
488
|
+
["EACCES", "EPERM"].includes((error as NodeJS.ErrnoException)?.code ?? "")
|
|
489
|
+
) {
|
|
490
|
+
// A read-only registry directory can still contain a lock left by a
|
|
491
|
+
// dead writer. Inspect that lock and return the normal bounded
|
|
492
|
+
// install_conflict rather than surfacing the staging-file permission
|
|
493
|
+
// error immediately.
|
|
494
|
+
const lockExists = await fs.stat(lockPath).then(
|
|
495
|
+
() => true,
|
|
496
|
+
(error: unknown) => !isEnoent(error),
|
|
373
497
|
);
|
|
498
|
+
if (!lockExists) throw error;
|
|
499
|
+
} else throw error;
|
|
500
|
+
} finally {
|
|
501
|
+
if (publication?.mutationState !== "unknown") await fs.rm(stagingPath, { force: true }).catch(() => {});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Stale recovery: a writer that died mid-transaction never releases
|
|
505
|
+
// its lock, and without recovery that lock poisons every later
|
|
506
|
+
// session until manual cleanup. Evict only a provably dead holder:
|
|
507
|
+
// a host-tagged token on this host. Legacy `pid-nonce` tokens have no
|
|
508
|
+
// trustworthy host identity, so they always fail closed: a local PID
|
|
509
|
+
// probe cannot prove that a remote holder on shared storage is dead.
|
|
510
|
+
// Live holders still fail closed below — they may legitimately outlive
|
|
511
|
+
// our timeout.
|
|
512
|
+
const raw = await fs.readFile(lockPath, "utf8").catch(() => null);
|
|
513
|
+
const holder = raw === null ? null : parseRegistryLockToken(raw);
|
|
514
|
+
if (raw !== null && holder !== null && holder.host !== null && !registryLockHolderAlive(holder)) {
|
|
515
|
+
try {
|
|
516
|
+
// exactUnlink verifies the observed inode, parent, size, mtime,
|
|
517
|
+
// and bytes inside the removal primitive. A replacement published
|
|
518
|
+
// after this read is therefore never consumed by stale recovery.
|
|
519
|
+
await evictRegistryLock(lockPath, raw);
|
|
520
|
+
} catch {
|
|
521
|
+
// Raced with another evictor or a fresh holder, or the removal
|
|
522
|
+
// itself failed (lockfile owned by another user, read-only mount,
|
|
523
|
+
// EBUSY/EPERM on Windows). Fall through to the shared deadline
|
|
524
|
+
// and backoff below either way: a removal that keeps failing must
|
|
525
|
+
// end in the bounded install_conflict timeout, never an unbounded
|
|
526
|
+
// spin.
|
|
374
527
|
}
|
|
375
|
-
await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_MS));
|
|
376
528
|
}
|
|
529
|
+
if (Date.now() > deadline) {
|
|
530
|
+
throw new GjcPluginLoadError(
|
|
531
|
+
"install_conflict",
|
|
532
|
+
`Timed out acquiring GJC plugin registry lock at ${lockPath}; remove it manually if no install is running`,
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_MS));
|
|
377
536
|
}
|
|
378
537
|
}
|
|
379
538
|
|
|
@@ -54,6 +54,16 @@ export interface RuntimeSkillDiscoveryResult {
|
|
|
54
54
|
candidates: RuntimeSkillDiscoveryCandidate[];
|
|
55
55
|
/** Deduped skill metadata the query filter ran against, including bundled entries; candidates.length <= scanned. */
|
|
56
56
|
scanned: number;
|
|
57
|
+
/**
|
|
58
|
+
* Skills left after the query filter, before the page slice: the denominator
|
|
59
|
+
* a page is a page *of*. `scanned` counts everything the filter ran against,
|
|
60
|
+
* so it cannot serve this role.
|
|
61
|
+
*/
|
|
62
|
+
matching: number;
|
|
63
|
+
/** Normalized zero-based offset the returned page starts at. */
|
|
64
|
+
offset: number;
|
|
65
|
+
/** Offset of the next page, present only while one exists; omitted on the final page. */
|
|
66
|
+
nextOffset?: number;
|
|
57
67
|
diagnostics: RuntimeSkillDiscoveryDiagnostics;
|
|
58
68
|
}
|
|
59
69
|
|
|
@@ -65,6 +75,8 @@ export interface DiscoverRuntimeSkillsOptions {
|
|
|
65
75
|
profileAuthority?: "default" | "custom";
|
|
66
76
|
query?: string;
|
|
67
77
|
limit?: number;
|
|
78
|
+
/** Zero-based index into the matching set the returned page starts at; defaults to 0. */
|
|
79
|
+
offset?: number;
|
|
68
80
|
source?: RuntimeSkillDiscoveryScope | "all";
|
|
69
81
|
policy?: SkillsSettings;
|
|
70
82
|
}
|
|
@@ -74,11 +86,26 @@ function getRuntimeHome(): string {
|
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
const DEFAULT_LIMIT = 20;
|
|
77
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Upper bound every caller's `limit` clamps to. Exported so `gjc skills discover`
|
|
91
|
+
* can default a human to the widest page the library will serve instead of the
|
|
92
|
+
* model-context-sized `DEFAULT_LIMIT`.
|
|
93
|
+
*/
|
|
94
|
+
export const SKILL_DISCOVERY_MAX_LIMIT = 50;
|
|
78
95
|
const MAX_DIAGNOSTICS = 10;
|
|
79
96
|
function normalizeLimit(limit: number | undefined): number {
|
|
80
97
|
if (limit === undefined || !Number.isFinite(limit)) return DEFAULT_LIMIT;
|
|
81
|
-
return Math.max(1, Math.min(
|
|
98
|
+
return Math.max(1, Math.min(SKILL_DISCOVERY_MAX_LIMIT, Math.trunc(limit)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Offsets are clamped from below only. An offset past the end is a legitimate
|
|
103
|
+
* empty final page (the catalog shrank between pages), not an error, so it is
|
|
104
|
+
* reported rather than silently snapped back onto the last populated page.
|
|
105
|
+
*/
|
|
106
|
+
function normalizeOffset(offset: number | undefined): number {
|
|
107
|
+
if (offset === undefined || !Number.isFinite(offset) || offset < 0) return 0;
|
|
108
|
+
return Math.trunc(offset);
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
interface ProjectScanDir {
|
|
@@ -558,9 +585,40 @@ export async function discoverRuntimeSkills(
|
|
|
558
585
|
seenNames,
|
|
559
586
|
diagnostics,
|
|
560
587
|
);
|
|
588
|
+
const limit = normalizeLimit(options.limit);
|
|
589
|
+
const offset = normalizeOffset(options.offset);
|
|
590
|
+
const matching = candidates.length;
|
|
591
|
+
const page = candidates.slice(offset, offset + limit);
|
|
592
|
+
// Continuation is omitted on the final page, so "is there more" is the
|
|
593
|
+
// presence of the field rather than a value the caller has to compare.
|
|
594
|
+
const nextOffset = offset + page.length < matching ? offset + page.length : undefined;
|
|
595
|
+
// Appended directly instead of through pushDiagnostic: these are the only
|
|
596
|
+
// messages that explain why a skill the caller can see on disk is absent
|
|
597
|
+
// from the result, and the reported case (several stale
|
|
598
|
+
// skills.customDirectories entries, one "does not exist" message each)
|
|
599
|
+
// fills the MAX_DIAGNOSTICS budget before the slice is ever reached. A
|
|
600
|
+
// dropped paging notice makes the truncation invisible again.
|
|
601
|
+
if (page.length === 0) {
|
|
602
|
+
if (offset > 0 && matching > 0) {
|
|
603
|
+
diagnostics.push(
|
|
604
|
+
`no skills at offset ${offset}; ${matching} matching skill${matching === 1 ? "" : "s"} total, pass --offset 0 to start over`,
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
} else if (nextOffset !== undefined) {
|
|
608
|
+
diagnostics.push(
|
|
609
|
+
`showing ${offset + 1}-${offset + page.length} of ${matching} matching skills; pass --offset ${nextOffset} for the next page, or narrow the query`,
|
|
610
|
+
);
|
|
611
|
+
} else if (offset > 0) {
|
|
612
|
+
// Final page of a multi-page walk: no continuation to offer, but the
|
|
613
|
+
// caller still needs to know this page is not the whole matching set.
|
|
614
|
+
diagnostics.push(`showing ${offset + 1}-${offset + page.length} of ${matching} matching skills`);
|
|
615
|
+
}
|
|
561
616
|
return {
|
|
562
|
-
candidates:
|
|
617
|
+
candidates: page,
|
|
563
618
|
scanned,
|
|
619
|
+
matching,
|
|
620
|
+
offset,
|
|
621
|
+
...(nextOffset === undefined ? {} : { nextOffset }),
|
|
564
622
|
diagnostics: { messages: diagnostics },
|
|
565
623
|
};
|
|
566
624
|
}
|
|
@@ -93,9 +93,11 @@ export interface SessionCompactEvent {
|
|
|
93
93
|
fromExtension: boolean;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** Fired
|
|
96
|
+
/** Fired during session teardown, including process exit and SDK host self-reap. */
|
|
97
97
|
export interface SessionShutdownEvent {
|
|
98
98
|
type: "session_shutdown";
|
|
99
|
+
/** Set only when the SDK host reaped itself after detached-idle grace. */
|
|
100
|
+
reason?: "detached_idle";
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
/** Preparation data for tree navigation (used by session_before_tree event) */
|
|
@@ -128,8 +128,15 @@ function safeChildToken(value: string): boolean {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
/**
|
|
132
|
+
* The exact reader needs the Linux-only recovery-fs authority, so an unavailable
|
|
133
|
+
* read has two very different causes. A blocked operator cannot act on either
|
|
134
|
+
* unless the outcome says which one it was, so the unavailable arm names it.
|
|
135
|
+
*/
|
|
136
|
+
type ExactJsonRead = { values: unknown[] } | { reader: "unsupported_platform" | "read_failed" };
|
|
137
|
+
|
|
138
|
+
async function readExactJsons(root: string, files: readonly string[]): Promise<ExactJsonRead> {
|
|
139
|
+
if (process.platform !== "linux") return { reader: "unsupported_platform" };
|
|
133
140
|
try {
|
|
134
141
|
const { openRecoveryFsRoot } = require("@gajae-code/natives") as Pick<
|
|
135
142
|
typeof import("@gajae-code/natives"),
|
|
@@ -140,17 +147,37 @@ async function readExactJsons(root: string, files: readonly string[]): Promise<u
|
|
|
140
147
|
const values: unknown[] = [];
|
|
141
148
|
for (const file of files) {
|
|
142
149
|
const result = authority.read(file, 64 * 1024);
|
|
143
|
-
if (!result.ok || !result.data) return
|
|
150
|
+
if (!result.ok || !result.data) return { reader: "read_failed" };
|
|
144
151
|
const content = Buffer.from(result.data).toString("utf8");
|
|
145
|
-
if (!content.endsWith("\n") || content.indexOf("\n") !== content.length - 1)
|
|
152
|
+
if (!content.endsWith("\n") || content.indexOf("\n") !== content.length - 1)
|
|
153
|
+
return { reader: "read_failed" };
|
|
146
154
|
values.push(JSON.parse(content));
|
|
147
155
|
}
|
|
148
|
-
return values;
|
|
156
|
+
return { values };
|
|
149
157
|
} finally {
|
|
150
158
|
authority.close();
|
|
151
159
|
}
|
|
152
160
|
} catch {
|
|
153
|
-
return
|
|
161
|
+
return { reader: "read_failed" };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function exactJsonValues(read: ExactJsonRead): unknown[] {
|
|
166
|
+
return "values" in read ? read.values : [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function exactReaderDetails(read: ExactJsonRead): Record<string, unknown> {
|
|
170
|
+
return "values" in read ? {} : { platform: process.platform, evidence_reader: read.reader };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readerExplanation(details: Record<string, unknown>): string {
|
|
174
|
+
switch (details.evidence_reader) {
|
|
175
|
+
case "unsupported_platform":
|
|
176
|
+
return ` (platform ${process.platform}: exact binding reader unsupported)`;
|
|
177
|
+
case "read_failed":
|
|
178
|
+
return ` (platform ${process.platform}: exact binding evidence unreadable)`;
|
|
179
|
+
default:
|
|
180
|
+
return "";
|
|
154
181
|
}
|
|
155
182
|
}
|
|
156
183
|
|
|
@@ -187,6 +214,22 @@ async function durableHandoff(
|
|
|
187
214
|
}
|
|
188
215
|
}
|
|
189
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Fails closed and says so. The durable handoff is the record of authority, but
|
|
219
|
+
* nothing reads it during an incident, so the same reason also goes to stderr:
|
|
220
|
+
* otherwise a blocked child exits 75 having printed nothing at all.
|
|
221
|
+
*/
|
|
222
|
+
async function blockAdmission(
|
|
223
|
+
owner: { root: string; generation: string; sessionId: string },
|
|
224
|
+
reason: string,
|
|
225
|
+
details: Record<string, unknown> = {},
|
|
226
|
+
): Promise<{ kind: "blocked" }> {
|
|
227
|
+
await durableHandoff(owner.root, owner.generation, owner.sessionId, reason, details);
|
|
228
|
+
process.stderr.write(`child admission blocked: ${reason}${readerExplanation(details)}\n`);
|
|
229
|
+
process.exitCode = 75;
|
|
230
|
+
return { kind: "blocked" };
|
|
231
|
+
}
|
|
232
|
+
|
|
190
233
|
/**
|
|
191
234
|
* This is the pre-CLI barrier. A replacement is identified only by the exact
|
|
192
235
|
* predecessor token supplied by its launch binding; directory enumeration is
|
|
@@ -198,12 +241,9 @@ export async function admitManagedOwnerBeforeCli(): Promise<ManagedOwnerAdmissio
|
|
|
198
241
|
const childToken = process.env[MANAGED_OWNER_CHILD_TOKEN_ENV]?.trim();
|
|
199
242
|
const predecessorToken = process.env[MANAGED_OWNER_PREDECESSOR_TOKEN_ENV]?.trim();
|
|
200
243
|
if (childToken && !predecessorToken) {
|
|
201
|
-
if (!safeChildToken(childToken))
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return { kind: "blocked" };
|
|
205
|
-
}
|
|
206
|
-
const [binding] = (await readExactJsons(owner.root, [`child-${childToken}.binding.json`])) ?? [];
|
|
244
|
+
if (!safeChildToken(childToken)) return blockAdmission(owner, "exact_child_binding_unavailable");
|
|
245
|
+
const read = await readExactJsons(owner.root, [`child-${childToken}.binding.json`]);
|
|
246
|
+
const [binding] = exactJsonValues(read);
|
|
207
247
|
if (
|
|
208
248
|
isBinding(binding, {
|
|
209
249
|
generation: owner.generation,
|
|
@@ -214,33 +254,20 @@ export async function admitManagedOwnerBeforeCli(): Promise<ManagedOwnerAdmissio
|
|
|
214
254
|
})
|
|
215
255
|
)
|
|
216
256
|
return { kind: "supervised" };
|
|
217
|
-
|
|
218
|
-
process.exitCode = 75;
|
|
219
|
-
return { kind: "blocked" };
|
|
257
|
+
return blockAdmission(owner, "exact_child_binding_unavailable", exactReaderDetails(read));
|
|
220
258
|
}
|
|
221
259
|
const predecessorGeneration = process.env[MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]?.trim();
|
|
222
260
|
const predecessorRunId = process.env[MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]?.trim();
|
|
223
261
|
const predecessorIncarnation = process.env[MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]?.trim();
|
|
224
|
-
if (!predecessorGeneration || !predecessorRunId || !predecessorIncarnation)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
if (!safeChildToken(predecessorToken)) {
|
|
235
|
-
await durableHandoff(owner.root, owner.generation, owner.sessionId, "replacement_predecessor_binding_untrusted");
|
|
236
|
-
process.exitCode = 75;
|
|
237
|
-
return { kind: "blocked" };
|
|
238
|
-
}
|
|
239
|
-
const [binding, receipt] =
|
|
240
|
-
(await readExactJsons(owner.root, [
|
|
241
|
-
`child-${predecessorToken}.binding.json`,
|
|
242
|
-
`sigabrt-${predecessorToken}.receipt.json`,
|
|
243
|
-
])) ?? [];
|
|
262
|
+
if (!predecessorGeneration || !predecessorRunId || !predecessorIncarnation)
|
|
263
|
+
return blockAdmission(owner, "replacement_predecessor_identity_missing");
|
|
264
|
+
if (!predecessorToken) return blockAdmission(owner, "replacement_predecessor_binding_missing");
|
|
265
|
+
if (!safeChildToken(predecessorToken)) return blockAdmission(owner, "replacement_predecessor_binding_untrusted");
|
|
266
|
+
const read = await readExactJsons(owner.root, [
|
|
267
|
+
`child-${predecessorToken}.binding.json`,
|
|
268
|
+
`sigabrt-${predecessorToken}.receipt.json`,
|
|
269
|
+
]);
|
|
270
|
+
const [binding, receipt] = exactJsonValues(read);
|
|
244
271
|
if (
|
|
245
272
|
!isBinding(binding, {
|
|
246
273
|
generation: predecessorGeneration,
|
|
@@ -249,16 +276,10 @@ export async function admitManagedOwnerBeforeCli(): Promise<ManagedOwnerAdmissio
|
|
|
249
276
|
incarnation: predecessorIncarnation,
|
|
250
277
|
token: predecessorToken,
|
|
251
278
|
})
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return
|
|
256
|
-
}
|
|
257
|
-
if (!isReceipt(receipt, binding)) {
|
|
258
|
-
await durableHandoff(owner.root, owner.generation, owner.sessionId, "exact_sigabrt_receipt_untrusted");
|
|
259
|
-
process.exitCode = 75;
|
|
260
|
-
return { kind: "blocked" };
|
|
261
|
-
}
|
|
279
|
+
)
|
|
280
|
+
return blockAdmission(owner, "replacement_predecessor_binding_untrusted", exactReaderDetails(read));
|
|
281
|
+
if (!isReceipt(receipt, binding))
|
|
282
|
+
return blockAdmission(owner, "exact_sigabrt_receipt_untrusted", exactReaderDetails(read));
|
|
262
283
|
const admission = {
|
|
263
284
|
session_id: owner.sessionId,
|
|
264
285
|
endpoint_incarnation: predecessorIncarnation,
|
|
@@ -287,11 +308,7 @@ export async function admitManagedOwnerBeforeCli(): Promise<ManagedOwnerAdmissio
|
|
|
287
308
|
},
|
|
288
309
|
decision,
|
|
289
310
|
});
|
|
290
|
-
if (decision.disposition !== "resume")
|
|
291
|
-
await durableHandoff(owner.root, owner.generation, owner.sessionId, decision.reason);
|
|
292
|
-
process.exitCode = 75;
|
|
293
|
-
return { kind: "blocked" };
|
|
294
|
-
}
|
|
311
|
+
if (decision.disposition !== "resume") return blockAdmission(owner, decision.reason);
|
|
295
312
|
return { kind: "recovery", context: { root: owner.root, binding, receipt, admission, decision } };
|
|
296
313
|
}
|
|
297
314
|
|