@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
|
@@ -3,6 +3,7 @@ import type { Dirent } from "node:fs";
|
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { redactCrashSecrets, safeErrorDescription } from "@gajae-code/utils";
|
|
6
7
|
import { bindPluginMcpToPublicNetwork } from "../../runtime-mcp/plugin-network-boundary";
|
|
7
8
|
import type { MCPStdioPreparedLaunch, MCPStdioSpawnLaunch } from "../../runtime-mcp/types";
|
|
8
9
|
import { loadCustomTools } from "../custom-tools/loader";
|
|
@@ -20,7 +21,13 @@ import { isV2Tool } from "./migration";
|
|
|
20
21
|
import { gjcPluginInstallRoot, resolveWithinRoot } from "./paths";
|
|
21
22
|
import { loadEffectiveGjcPluginRegistry, registryPathForScope, registryRootForScope } from "./registry";
|
|
22
23
|
import { type SessionQuarantine, type SessionValidationResult, validateSessionBundles } from "./session-validation";
|
|
23
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
GjcBundleIdentity,
|
|
26
|
+
GjcPluginRegistryEntry,
|
|
27
|
+
GjcPluginScope,
|
|
28
|
+
JsonSchema202012,
|
|
29
|
+
NormalizedToolSurfaceV2,
|
|
30
|
+
} from "./types";
|
|
24
31
|
|
|
25
32
|
export interface AlwaysOnPluginTools {
|
|
26
33
|
tools: CustomTool[];
|
|
@@ -32,6 +39,23 @@ export interface GjcPluginToolDeclaration extends NormalizedToolSurfaceV2 {
|
|
|
32
39
|
scope: GjcPluginScope;
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
export interface GjcPluginMcpServerProvenance {
|
|
43
|
+
identity: GjcBundleIdentity;
|
|
44
|
+
surfaceId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Plugin MCP manifests reject per-server timeout controls; generated transports
|
|
48
|
+
// share one bounded window so startup evidence reaches a terminal result.
|
|
49
|
+
const PLUGIN_MCP_STARTUP_TIMEOUT_MS = 5_000;
|
|
50
|
+
const MAX_PLUGIN_MCP_DIAGNOSTIC_LENGTH = 1_024;
|
|
51
|
+
|
|
52
|
+
/** Bound and scrub plugin-controlled MCP causes before they enter logs or runtime findings. */
|
|
53
|
+
export function safePluginMcpDiagnostic(value: unknown): string {
|
|
54
|
+
const message = safeErrorDescription(value).replace(/[\u0000-\u001f\u007f-\u009f]/gu, "");
|
|
55
|
+
const normalized = message.replace(/\b(Bearer|Basic|Token)(?=[A-Za-z0-9._~+/=-]{8,})/giu, "$1 ");
|
|
56
|
+
return redactCrashSecrets(normalized).slice(0, MAX_PLUGIN_MCP_DIAGNOSTIC_LENGTH);
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
function isWithin(root: string, target: string): boolean {
|
|
36
60
|
const rel = path.relative(root, target);
|
|
37
61
|
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
@@ -557,7 +581,9 @@ async function prepareVerifiedStdioLaunch(input: {
|
|
|
557
581
|
throw new Error("Authenticated plugin MCP stdio launch capsules are available only on Linux");
|
|
558
582
|
}
|
|
559
583
|
if (input.launcher === "bun") {
|
|
560
|
-
throw new Error(
|
|
584
|
+
throw new Error(
|
|
585
|
+
"Authenticated plugin MCP Bun launch capsules are unavailable: Bun's clearable runtime plugin hooks cannot enforce the authenticated module-loading boundary required to restrict plugin imports to verified capsule bytes",
|
|
586
|
+
);
|
|
561
587
|
}
|
|
562
588
|
const registerCleanup = input.registerCleanup;
|
|
563
589
|
if (!registerCleanup) {
|
|
@@ -1045,18 +1071,22 @@ export async function renderSkillAdvertisement(input: {
|
|
|
1045
1071
|
* Convert active plugin-bundle MCP surfaces into runtime MCPServerConfig entries,
|
|
1046
1072
|
* applying install + runtime MCP policy (URL scheme/private-range deny, DNS
|
|
1047
1073
|
* re-resolution for http/sse, stdio root-confinement) before connection. Servers
|
|
1048
|
-
* failing policy are quarantined and excluded.
|
|
1074
|
+
* failing policy are quarantined and excluded. The provenance map contains
|
|
1075
|
+
* only servers present in `configs`, so connection errors can be attributed to
|
|
1076
|
+
* the authenticated bundle and stable surface that supplied each server.
|
|
1049
1077
|
*/
|
|
1050
1078
|
export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
|
|
1051
1079
|
configs: Record<string, any>;
|
|
1052
1080
|
quarantine: SessionQuarantine[];
|
|
1081
|
+
serverProvenance: ReadonlyMap<string, GjcPluginMcpServerProvenance>;
|
|
1053
1082
|
}> {
|
|
1054
1083
|
const { effective, active, quarantine } = await loadValidatedPluginRegistry(input.cwd, true);
|
|
1055
|
-
if (effective.length === 0) return { configs: {}, quarantine: [] };
|
|
1084
|
+
if (effective.length === 0) return { configs: {}, quarantine: [], serverProvenance: new Map() };
|
|
1056
1085
|
|
|
1057
1086
|
// A manifest-controlled MCP name such as "constructor" or "toString" must
|
|
1058
1087
|
// remain an ordinary own key rather than interacting with Object.prototype.
|
|
1059
1088
|
const configs: Record<string, any> = Object.create(null) as Record<string, any>;
|
|
1089
|
+
const serverProvenance = new Map<string, GjcPluginMcpServerProvenance>();
|
|
1060
1090
|
for (const entry of active) {
|
|
1061
1091
|
const disabled = new Set(entry.disabledSurfaceIds);
|
|
1062
1092
|
let compiledMcps: Map<string, (typeof entry.surfaces.mcps)[number]> | undefined;
|
|
@@ -1119,7 +1149,7 @@ export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
|
|
|
1119
1149
|
command,
|
|
1120
1150
|
args: initialArgs,
|
|
1121
1151
|
cwd: invocation.cwd,
|
|
1122
|
-
timeout:
|
|
1152
|
+
timeout: PLUGIN_MCP_STARTUP_TIMEOUT_MS,
|
|
1123
1153
|
// Third-party plugin MCP processes must not inherit host secrets;
|
|
1124
1154
|
// only a minimal OS allowlist (PATH/HOME/temp/locale) is provided.
|
|
1125
1155
|
noInheritEnv: true,
|
|
@@ -1207,18 +1237,26 @@ export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
|
|
|
1207
1237
|
// resolution path expands ${env:...}/shell templates, which would let
|
|
1208
1238
|
// a third-party bundle exfiltrate host secrets. Plugin-bundle MCP
|
|
1209
1239
|
// servers connect without bundle-declared headers.
|
|
1210
|
-
configs[m.name] = bindPluginMcpToPublicNetwork({
|
|
1240
|
+
configs[m.name] = bindPluginMcpToPublicNetwork({
|
|
1241
|
+
type: cfg.transport,
|
|
1242
|
+
url: url.toString(),
|
|
1243
|
+
timeout: PLUGIN_MCP_STARTUP_TIMEOUT_MS,
|
|
1244
|
+
});
|
|
1211
1245
|
}
|
|
1246
|
+
serverProvenance.set(m.name, {
|
|
1247
|
+
identity: bundleIdentity(entry.scope, entry.name),
|
|
1248
|
+
surfaceId: `mcp:${m.name}`,
|
|
1249
|
+
});
|
|
1212
1250
|
} catch (error) {
|
|
1213
1251
|
quarantine.push({
|
|
1214
1252
|
identity: bundleIdentity(entry.scope, entry.name),
|
|
1215
1253
|
plugin: entry.name,
|
|
1216
1254
|
surfaceId: m.extensionId,
|
|
1217
1255
|
code: "security_policy",
|
|
1218
|
-
message:
|
|
1256
|
+
message: safePluginMcpDiagnostic(error),
|
|
1219
1257
|
});
|
|
1220
1258
|
}
|
|
1221
1259
|
}
|
|
1222
1260
|
}
|
|
1223
|
-
return { configs, quarantine };
|
|
1261
|
+
return { configs, quarantine, serverProvenance };
|
|
1224
1262
|
}
|
|
@@ -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) */
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
readFileLockObservationForGc,
|
|
15
15
|
} from "../config/file-lock";
|
|
16
16
|
import { loadInstallationHostId, loadLegacyInstallationHostId } from "../config/machine-identity";
|
|
17
|
-
import { readLinuxProcStartTimeSync } from "./linux-proc";
|
|
17
|
+
import { type LinuxProcPidProbeResult, probeLinuxProcPidSync, readLinuxProcStartTimeSync } from "./linux-proc";
|
|
18
18
|
|
|
19
19
|
/** SHA-256 of the empty payload; the constant identity of every verified-empty receipt. */
|
|
20
20
|
const EMPTY_FILE_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
@@ -122,6 +122,10 @@ function lockRetryExhausted(budget: LockRetryBudget): boolean {
|
|
|
122
122
|
return lockRetryRemainingMs(budget) <= 0;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function lockRetryTotalWaitExhausted(budget: LockRetryBudget): boolean {
|
|
126
|
+
return Math.max(0, performance.now() - budget.firstAttemptAt) >= LOCK_ACQUIRE_MAX_WAIT_MS;
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
async function waitForLockRetry(budget: LockRetryBudget): Promise<boolean> {
|
|
126
130
|
budget.attempts++;
|
|
127
131
|
const remainingMs = lockRetryRemainingMs(budget);
|
|
@@ -501,6 +505,13 @@ export const SessionStateLockTestHooks: {
|
|
|
501
505
|
* configuration.
|
|
502
506
|
*/
|
|
503
507
|
probeProcessSignal?: (pid: number) => void;
|
|
508
|
+
/**
|
|
509
|
+
* @internal How an owner pid's `/proc` entry is read. Whether a killed pid is still an
|
|
510
|
+
* unreaped zombie depends on when its parent happens to reap it, so it cannot be
|
|
511
|
+
* arranged deterministically from a test. Production code never sets it, and it is
|
|
512
|
+
* never serialized or exposed as runtime configuration.
|
|
513
|
+
*/
|
|
514
|
+
probeLinuxProcPid?: (pid: number) => LinuxProcPidProbeResult;
|
|
504
515
|
/**
|
|
505
516
|
* @internal Runs inside a NEW owner record's write, after the exclusive create has
|
|
506
517
|
* taken the pathname and before the record's bytes land. Throwing from it fails that
|
|
@@ -560,6 +571,33 @@ export type SessionStateLockUnavailableReason =
|
|
|
560
571
|
| "transition_claim_timeout"
|
|
561
572
|
| "unsafe_lock_path_type";
|
|
562
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Why a stale-transition reclaim refused, as a bounded classifier.
|
|
576
|
+
*
|
|
577
|
+
* `transition_claim_timeout` collapsed several structurally different refusals into
|
|
578
|
+
* one message, so an operator could not tell a foreign-host tombstone from a
|
|
579
|
+
* live owner from a directory an external process was stamping — the gap #5606
|
|
580
|
+
* asked for. Every member is a fixed enum: never a path, token, host id, pid or
|
|
581
|
+
* raw errno, because this rides on an error message users paste into reports.
|
|
582
|
+
*/
|
|
583
|
+
export type TransitionReclaimRefusal =
|
|
584
|
+
/** The claim vanished, or its owner sidecar was absent/unparseable/invalid. */
|
|
585
|
+
| "claim_or_owner_unreadable"
|
|
586
|
+
/** A released tombstone carrying no `owner_host_id` at all. */
|
|
587
|
+
| "released_owner_unprovenanced"
|
|
588
|
+
/** A released tombstone belonging to a different installation. */
|
|
589
|
+
| "released_owner_foreign_host"
|
|
590
|
+
/** A released tombstone still inside `RELEASED_TRANSITION_GRACE_MS`. */
|
|
591
|
+
| "released_owner_within_grace"
|
|
592
|
+
/** The owner is alive, or its liveness could not be disproven. */
|
|
593
|
+
| "owner_live_or_unverifiable"
|
|
594
|
+
/** The claim changed between inspection and capture — an external writer. */
|
|
595
|
+
| "claim_generation_changed"
|
|
596
|
+
/** The claim was not empty when captured, so it is not ours to remove. */
|
|
597
|
+
| "claim_not_empty"
|
|
598
|
+
/** The claim's canonical path or directory snapshot could not be taken. */
|
|
599
|
+
| "claim_capture_failed";
|
|
600
|
+
|
|
563
601
|
interface SessionStateLockUnavailableDetails {
|
|
564
602
|
lockPath: string;
|
|
565
603
|
reason: SessionStateLockUnavailableReason;
|
|
@@ -750,6 +788,28 @@ function lockUnavailable(
|
|
|
750
788
|
});
|
|
751
789
|
}
|
|
752
790
|
|
|
791
|
+
/**
|
|
792
|
+
* `transition_claim_timeout` with the reclaim refusal that kept repeating.
|
|
793
|
+
*
|
|
794
|
+
* The bare classifier could not distinguish several structurally different
|
|
795
|
+
* refusals, so an operator had no way to tell which action would help (#5606).
|
|
796
|
+
* The suffix is a fixed enum member and nothing else — this string reaches user
|
|
797
|
+
* reports, so it must never carry a path, token, host id, pid or errno.
|
|
798
|
+
*/
|
|
799
|
+
function transitionClaimTimeout(
|
|
800
|
+
transitionDir: string,
|
|
801
|
+
budget: LockRetryBudget,
|
|
802
|
+
cause: unknown,
|
|
803
|
+
refusal: TransitionReclaimRefusal | undefined,
|
|
804
|
+
): SessionStateLockUnavailableError {
|
|
805
|
+
const error = lockUnavailable(transitionDir, "transition_claim_timeout", budget, cause);
|
|
806
|
+
if (refusal === undefined) return error;
|
|
807
|
+
return Object.assign(error, {
|
|
808
|
+
reclaimRefusal: refusal,
|
|
809
|
+
message: `${error.message.replace(/\.$/, "")} (reclaim refused: ${refusal}).`,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
753
813
|
function lockDiagnostic(
|
|
754
814
|
error: unknown,
|
|
755
815
|
lockPath: string,
|
|
@@ -941,7 +1001,17 @@ function probeOwnerProcess(pid: number): OwnerProcessLiveness {
|
|
|
941
1001
|
try {
|
|
942
1002
|
const probe = SessionStateLockTestHooks.probeProcessSignal;
|
|
943
1003
|
if (probe) probe(pid);
|
|
944
|
-
else
|
|
1004
|
+
else {
|
|
1005
|
+
// Linux keeps a killed child as a zombie until its parent reaps it. `kill(pid, 0)`
|
|
1006
|
+
// still succeeds for that zombie even though it can no longer hold the lock, so
|
|
1007
|
+
// inspect `/proc` first and treat the terminal `Z` state as proof of death.
|
|
1008
|
+
if (process.platform === "linux") {
|
|
1009
|
+
const proc = (SessionStateLockTestHooks.probeLinuxProcPid ?? probeLinuxProcPidSync)(pid);
|
|
1010
|
+
if (proc.kind === "absent") return "dead";
|
|
1011
|
+
if (proc.kind === "live" && proc.state === "Z") return "dead";
|
|
1012
|
+
}
|
|
1013
|
+
process.kill(pid, 0);
|
|
1014
|
+
}
|
|
945
1015
|
return "alive";
|
|
946
1016
|
} catch (error) {
|
|
947
1017
|
const code = (error as NodeJS.ErrnoException).code;
|
|
@@ -1846,12 +1916,27 @@ async function releaseTransitionClaim(
|
|
|
1846
1916
|
}
|
|
1847
1917
|
}
|
|
1848
1918
|
|
|
1849
|
-
|
|
1919
|
+
/**
|
|
1920
|
+
* Outcome of one stale-transition reclaim attempt. `reclaimed` drives the
|
|
1921
|
+
* caller's retry accounting exactly as the previous boolean did; `refusal`
|
|
1922
|
+
* exists only so the eventual `transition_claim_timeout` can name which
|
|
1923
|
+
* precondition said no (#5606).
|
|
1924
|
+
*/
|
|
1925
|
+
interface TransitionReclaimOutcome {
|
|
1926
|
+
reclaimed: boolean;
|
|
1927
|
+
reclaimReason?: "released_handoff" | "dead_owner";
|
|
1928
|
+
refusal?: TransitionReclaimRefusal;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
async function reclaimStaleTransitionClaim(
|
|
1932
|
+
transitionDir: string,
|
|
1933
|
+
quarantineName: string,
|
|
1934
|
+
): Promise<TransitionReclaimOutcome> {
|
|
1850
1935
|
let stat: fsSync.BigIntStats;
|
|
1851
1936
|
try {
|
|
1852
1937
|
stat = await fs.lstat(transitionDir, { bigint: true });
|
|
1853
1938
|
} catch {
|
|
1854
|
-
return false;
|
|
1939
|
+
return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
|
|
1855
1940
|
}
|
|
1856
1941
|
// Regular-file claims belong to the superseded protocol. They retain the old
|
|
1857
1942
|
// exact-identity stale path; released PID-1 tombstones deliberately require
|
|
@@ -1865,41 +1950,46 @@ async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName
|
|
|
1865
1950
|
},
|
|
1866
1951
|
quarantineName,
|
|
1867
1952
|
);
|
|
1868
|
-
return
|
|
1953
|
+
return {
|
|
1954
|
+
reclaimed: reclaimed === "owner_removed",
|
|
1955
|
+
...(reclaimed === "owner_removed" ? { reclaimReason: "dead_owner" as const } : {}),
|
|
1956
|
+
};
|
|
1869
1957
|
}
|
|
1870
1958
|
if (!stat.isDirectory()) throw new SessionStateLockUnavailableError();
|
|
1871
1959
|
const ownerSnapshot = await captureRegularLockOwner(`${transitionDir}.owner`);
|
|
1872
|
-
if (!ownerSnapshot) return false;
|
|
1960
|
+
if (!ownerSnapshot) return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
|
|
1873
1961
|
let owner: unknown;
|
|
1874
1962
|
try {
|
|
1875
1963
|
owner = JSON.parse(ownerSnapshot.bytes);
|
|
1876
1964
|
} catch {
|
|
1877
|
-
return false;
|
|
1965
|
+
return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
|
|
1878
1966
|
}
|
|
1879
|
-
if (!validLockOwner(owner)) return false;
|
|
1967
|
+
if (!validLockOwner(owner)) return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
|
|
1880
1968
|
await SessionStateLockTestHooks.afterTransitionStaleInspection?.(transitionDir);
|
|
1881
1969
|
if (owner.released === true) {
|
|
1882
1970
|
// A released record is safe only when it is qualified by this installation.
|
|
1883
1971
|
// Never let a shared-volume tombstone, an absent identity, or a legacy foreign
|
|
1884
1972
|
// identity authorize removal of a claim this process cannot own.
|
|
1885
|
-
if (owner.owner_host_id === undefined) return false;
|
|
1973
|
+
if (owner.owner_host_id === undefined) return { reclaimed: false, refusal: "released_owner_unprovenanced" };
|
|
1886
1974
|
const currentHost = await currentOwnerHostId();
|
|
1887
1975
|
const legacyHost = await currentLegacyOwnerHostId();
|
|
1888
|
-
if (owner.owner_host_id !== currentHost && owner.owner_host_id !== legacyHost)
|
|
1889
|
-
|
|
1976
|
+
if (owner.owner_host_id !== currentHost && owner.owner_host_id !== legacyHost)
|
|
1977
|
+
return { reclaimed: false, refusal: "released_owner_foreign_host" };
|
|
1978
|
+
if (Date.now() - Number(ownerSnapshot.mtimeNs / 1_000_000n) < RELEASED_TRANSITION_GRACE_MS)
|
|
1979
|
+
return { reclaimed: false, refusal: "released_owner_within_grace" };
|
|
1890
1980
|
} else if (await lockOwnerIsAlive(owner)) {
|
|
1891
1981
|
// Only a host-qualified owner whose pid is PROVEN dead (ESRCH, or a live pid
|
|
1892
1982
|
// with a provably different incarnation) authorizes reclaim. The generation
|
|
1893
1983
|
// + exact-tree checks below then bind the removal to the very directory
|
|
1894
1984
|
// inspected here, on every platform; a claim stranded by a force-quit
|
|
1895
1985
|
// (`postmortem.quit`) would otherwise wall the session directory forever.
|
|
1896
|
-
return false;
|
|
1986
|
+
return { reclaimed: false, refusal: "owner_live_or_unverifiable" };
|
|
1897
1987
|
}
|
|
1898
1988
|
const generation = transitionGenerationFromStat(stat);
|
|
1899
1989
|
const nativePath = await canonicalOwnedTransitionPath(transitionDir).catch(() => null);
|
|
1900
|
-
if (!nativePath) return false;
|
|
1990
|
+
if (!nativePath) return { reclaimed: false, refusal: "claim_capture_failed" };
|
|
1901
1991
|
const captured = nativeSessionStateLock().snapshotDirectoryTree(nativePath);
|
|
1902
|
-
if (!captured.ok || !captured.snapshot) return false;
|
|
1992
|
+
if (!captured.ok || !captured.snapshot) return { reclaimed: false, refusal: "claim_capture_failed" };
|
|
1903
1993
|
const root = captured.snapshot.entries.find(entry => entry.relativePath === "");
|
|
1904
1994
|
if (
|
|
1905
1995
|
root?.kind !== "directory" ||
|
|
@@ -1909,12 +1999,15 @@ async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName
|
|
|
1909
1999
|
root.mtimeNs !== String(generation.mtimeNs) ||
|
|
1910
2000
|
root.ctimeNs !== String(generation.ctimeNs)
|
|
1911
2001
|
)
|
|
1912
|
-
return false;
|
|
1913
|
-
if (captured.snapshot.entries.length !== 1) return false;
|
|
2002
|
+
return { reclaimed: false, refusal: "claim_generation_changed" };
|
|
2003
|
+
if (captured.snapshot.entries.length !== 1) return { reclaimed: false, refusal: "claim_not_empty" };
|
|
1914
2004
|
const removed = nativeSessionStateLock().exactRemoveDirectoryTree(nativePath, captured.snapshot);
|
|
1915
2005
|
if (removed.ok || removed.code === "not_found") {
|
|
1916
2006
|
exactUnlinkOwnerRecord(`${transitionDir}.owner`, ownerSnapshot, quarantineName);
|
|
1917
|
-
return
|
|
2007
|
+
return {
|
|
2008
|
+
reclaimed: removed.ok,
|
|
2009
|
+
...(removed.ok ? { reclaimReason: owner.released === true ? "released_handoff" : "dead_owner" } : {}),
|
|
2010
|
+
};
|
|
1918
2011
|
}
|
|
1919
2012
|
if (
|
|
1920
2013
|
removed.code === "cleanup_pending" &&
|
|
@@ -1931,7 +2024,7 @@ async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName
|
|
|
1931
2024
|
// successor populated the detached path.
|
|
1932
2025
|
await removeTransitionDir(removed.detachedPath);
|
|
1933
2026
|
exactUnlinkOwnerRecord(`${transitionDir}.owner`, ownerSnapshot, quarantineName);
|
|
1934
|
-
return true;
|
|
2027
|
+
return { reclaimed: true, reclaimReason: owner.released === true ? "released_handoff" : "dead_owner" };
|
|
1935
2028
|
}
|
|
1936
2029
|
throw new SessionStateLockUnavailableError(
|
|
1937
2030
|
new Error(`Stale transition claim could not be reclaimed (${removed.code ?? "unknown"}).`),
|
|
@@ -2310,6 +2403,7 @@ async function runLockPathTransition<T>(
|
|
|
2310
2403
|
throw new SessionStateLockUnavailableError(new Error("Safe transition ownership is unsupported."));
|
|
2311
2404
|
const transitionDir = `${lockFile}${LOCK_TRANSITION_RESOURCE_SUFFIX}`;
|
|
2312
2405
|
const ownerFile = `${transitionDir}.owner`;
|
|
2406
|
+
let lastReclaimRefusal: TransitionReclaimRefusal | undefined;
|
|
2313
2407
|
|
|
2314
2408
|
for (;;) {
|
|
2315
2409
|
if (ownerAccessStrategy() === "unsupported" && fsSync.existsSync(transitionDir))
|
|
@@ -2332,18 +2426,37 @@ async function runLockPathTransition<T>(
|
|
|
2332
2426
|
// Claims are held only for pathname bookkeeping, so a changed claim identity is
|
|
2333
2427
|
// proof that contenders are cycling through rather than that one is wedged.
|
|
2334
2428
|
const observedClaim = await observedLockHolderIdentity(transitionDir);
|
|
2335
|
-
const
|
|
2336
|
-
creditLockHandoffProgress(budget, observedClaim, reclaimed);
|
|
2429
|
+
const outcome = await reclaimStaleTransitionClaim(transitionDir, quarantineName);
|
|
2430
|
+
creditLockHandoffProgress(budget, observedClaim, outcome.reclaimed);
|
|
2431
|
+
// Remember the LAST refusal: on a wedged claim every retry refuses for the
|
|
2432
|
+
// same reason, and that reason is the actionable one. Carrying it onto the
|
|
2433
|
+
// timeout is the whole point — `transition_claim_timeout` alone cannot tell
|
|
2434
|
+
// a foreign-host tombstone from a live owner from an external writer
|
|
2435
|
+
// restamping the claim directory (#5606).
|
|
2436
|
+
if (outcome.refusal !== undefined) lastReclaimRefusal = outcome.refusal;
|
|
2337
2437
|
// One safe reclaim may make progress, but a try-acquire never follows a
|
|
2338
2438
|
// succession of contenders. A later maintenance pass can take the freed claim.
|
|
2339
2439
|
if (budget.nonBlocking) throw SESSION_STATE_LOCK_BUSY;
|
|
2340
|
-
if (reclaimed) {
|
|
2341
|
-
if (lockRetryExhausted(budget))
|
|
2342
|
-
|
|
2440
|
+
if (outcome.reclaimed) {
|
|
2441
|
+
if (lockRetryExhausted(budget)) {
|
|
2442
|
+
// Only a released tombstone is the previous holder's explicit handoff.
|
|
2443
|
+
// Ordinary dead-owner reclaims remain bounded by the original deadline,
|
|
2444
|
+
// even though both reclaim paths unlink their owner sidecar.
|
|
2445
|
+
const ownerStillPresent = await fs.lstat(`${transitionDir}.owner`).then(
|
|
2446
|
+
() => true,
|
|
2447
|
+
error => (error as NodeJS.ErrnoException).code !== "ENOENT",
|
|
2448
|
+
);
|
|
2449
|
+
if (
|
|
2450
|
+
outcome.reclaimReason !== "released_handoff" ||
|
|
2451
|
+
ownerStillPresent ||
|
|
2452
|
+
lockRetryTotalWaitExhausted(budget)
|
|
2453
|
+
)
|
|
2454
|
+
throw transitionClaimTimeout(transitionDir, budget, error, lastReclaimRefusal);
|
|
2455
|
+
}
|
|
2343
2456
|
continue;
|
|
2344
2457
|
}
|
|
2345
2458
|
if (!(await waitForLockRetry(budget)))
|
|
2346
|
-
throw
|
|
2459
|
+
throw transitionClaimTimeout(transitionDir, budget, error, lastReclaimRefusal);
|
|
2347
2460
|
continue;
|
|
2348
2461
|
}
|
|
2349
2462
|
const pendingSetup: PendingTransitionRelease = {
|
|
@@ -2753,6 +2866,34 @@ async function observedLockHolderIdentity(lockPath: string): Promise<string | un
|
|
|
2753
2866
|
return `d:${stat.dev}:${stat.ino}:${stat.ctimeNs}`;
|
|
2754
2867
|
}
|
|
2755
2868
|
|
|
2869
|
+
/**
|
|
2870
|
+
* Classify one stale-transition reclaim attempt.
|
|
2871
|
+
*
|
|
2872
|
+
* @internal exported as the seam the refusal classifier is tested through. Driving
|
|
2873
|
+
* a real `transition_claim_timeout` would mean waiting out `LOCK_ACQUIRE_MAX_WAIT_MS`
|
|
2874
|
+
* (60s) per case, and `tryWithSessionStateFileLock` throws BUSY before the timeout
|
|
2875
|
+
* is ever built, so the per-branch reasons have no other reachable surface (#5606).
|
|
2876
|
+
*/
|
|
2877
|
+
export async function classifyTransitionReclaimForTests(
|
|
2878
|
+
transitionDir: string,
|
|
2879
|
+
quarantineName: string,
|
|
2880
|
+
): Promise<TransitionReclaimOutcome> {
|
|
2881
|
+
return reclaimStaleTransitionClaim(transitionDir, quarantineName);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* Build the timeout error exactly as the transition loop does.
|
|
2886
|
+
*
|
|
2887
|
+
* @internal exported so the message contract — a bounded enum suffix and nothing
|
|
2888
|
+
* else — is pinned without a 60s wait (#5606).
|
|
2889
|
+
*/
|
|
2890
|
+
export function transitionClaimTimeoutForTests(
|
|
2891
|
+
transitionDir: string,
|
|
2892
|
+
refusal: TransitionReclaimRefusal | undefined,
|
|
2893
|
+
): SessionStateLockUnavailableError {
|
|
2894
|
+
return transitionClaimTimeout(transitionDir, lockRetryBudget(), new Error("EEXIST"), refusal);
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2756
2897
|
/**
|
|
2757
2898
|
* Decide what actually occupies the lock path, without following it.
|
|
2758
2899
|
*
|