@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
|
@@ -269,7 +269,7 @@ export interface CompactionCandidateFailure {
|
|
|
269
269
|
readonly message: string;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
function sanitizeCompactionCandidateFailureMessage(message: string): string {
|
|
272
|
+
export function sanitizeCompactionCandidateFailureMessage(message: string): string {
|
|
273
273
|
const cleaned = cleanReason(message) ?? "Compaction candidate failed.";
|
|
274
274
|
return cleaned.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s<>'"]+/gi, "[redacted URL]");
|
|
275
275
|
}
|
|
@@ -277,12 +277,11 @@ function sanitizeCompactionCandidateFailureMessage(message: string): string {
|
|
|
277
277
|
/**
|
|
278
278
|
* Error for an auto-compaction run whose every candidate failed.
|
|
279
279
|
*
|
|
280
|
-
* The candidate chain starts at the session model and
|
|
281
|
-
*
|
|
282
|
-
* the user
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* is the final error so callers that classify by type keep the same object.
|
|
280
|
+
* The candidate chain starts at the session model and includes only explicitly
|
|
281
|
+
* configured role candidates. The message leads with the first failure (the
|
|
282
|
+
* one the user can act on), then lists every candidate that was tried with its
|
|
283
|
+
* own error. `cause` is the final error so callers that classify by type keep
|
|
284
|
+
* the same object.
|
|
286
285
|
*/
|
|
287
286
|
export function describeCompactionCandidateFailures(
|
|
288
287
|
failures: readonly CompactionCandidateFailure[],
|
|
@@ -38,7 +38,20 @@ import foldReceiptPrompt from "../prompts/tools/fold-receipt.md" with { type: "t
|
|
|
38
38
|
export const FOLD_WAKE_MERGE_WINDOW_MS = 800;
|
|
39
39
|
|
|
40
40
|
/** The wait kinds that can be folded. `task`/`subagent` awaits are a non-goal. */
|
|
41
|
-
export type FoldWaitKind = "bash-managed" | "client-terminal" | "bash-pty";
|
|
41
|
+
export type FoldWaitKind = "bash-managed" | "client-terminal" | "bash-pty" | "job-await";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether a wait may be picked as the implicit fold target by the chord and
|
|
45
|
+
* the SDK `bash.background` control. A `job-await` watches jobs that are
|
|
46
|
+
* already in the background, so "move the foreground bash into a background
|
|
47
|
+
* job" has nothing to move; it folds only when the `job` tool's own steer
|
|
48
|
+
* watcher names it explicitly. Keeping it out of implicit targeting also stops
|
|
49
|
+
* a `job poll` registered after a foreground bash from taking the chord away
|
|
50
|
+
* from the command the user is actually watching.
|
|
51
|
+
*/
|
|
52
|
+
export function isImplicitFoldTarget(adapter: FoldAdapter): boolean {
|
|
53
|
+
return adapter.kind !== "job-await";
|
|
54
|
+
}
|
|
42
55
|
|
|
43
56
|
/** Outcome of settling the foreground caller. Exactly one party may settle it. */
|
|
44
57
|
export type ForegroundSettleOutcome = "resolved" | "already-settled";
|
|
@@ -237,15 +250,21 @@ export class FoldCoordinator {
|
|
|
237
250
|
};
|
|
238
251
|
}
|
|
239
252
|
|
|
240
|
-
/** Whether any wait is currently foldable, for the key-availability gate. */
|
|
253
|
+
/** Whether any implicitly targetable wait is currently foldable, for the key-availability gate. */
|
|
241
254
|
hasFoldableParticipant(): boolean {
|
|
242
|
-
return this
|
|
255
|
+
return this.resolveTarget() !== undefined;
|
|
243
256
|
}
|
|
244
257
|
|
|
245
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* Deterministic implicit target: newest implicitly targetable registration
|
|
260
|
+
* wins, so the chord folds what the user is watching. `job-await` waits are
|
|
261
|
+
* skipped; they fold only through an explicit adapter.
|
|
262
|
+
*/
|
|
246
263
|
resolveTarget(): FoldAdapter | undefined {
|
|
247
264
|
let target: FoldAdapter | undefined;
|
|
248
|
-
for (const adapter of this.#participants.values())
|
|
265
|
+
for (const adapter of this.#participants.values()) {
|
|
266
|
+
if (isImplicitFoldTarget(adapter)) target = adapter;
|
|
267
|
+
}
|
|
249
268
|
return target;
|
|
250
269
|
}
|
|
251
270
|
|
|
@@ -195,27 +195,31 @@ export type ManagedScopeResolution =
|
|
|
195
195
|
};
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* helpers, so a message recognized by one and not the other still surfaces to
|
|
203
|
-
* the operator as `binding_invalid`.
|
|
198
|
+
* Fixed, path-free identity tokens thrown as plain errors by the managed
|
|
199
|
+
* storage guards (`assertManagedPathIdentity`). They carry no pathname, so
|
|
200
|
+
* propagating them keeps startup redaction intact while telling the operator
|
|
201
|
+
* which invariant actually failed instead of a blanket `binding_invalid`.
|
|
204
202
|
*/
|
|
205
|
-
|
|
206
|
-
const security = managedSecurityFailureClassification(error);
|
|
207
|
-
if (security) return { classification: security };
|
|
208
|
-
return { classification: managedScopeErrorCode(error instanceof Error ? error.message : "") };
|
|
209
|
-
}
|
|
203
|
+
const managedScopeIdentityFailureCodes = new Set(["identity_mismatch", "reparse_point", "not_directory", "not_file"]);
|
|
210
204
|
|
|
211
205
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
206
|
+
* Fixed, path-free errno tokens. A managed scope that fails to prepare because
|
|
207
|
+
* another process holds the object (AV scanners, indexers, cloud-sync agents)
|
|
208
|
+
* is operationally distinct from a corrupted binding, and only the errno
|
|
209
|
+
* distinguishes them. Node places the pathname in `.message`/`.path`, never in
|
|
210
|
+
* `.code`, so the token alone is safe to surface.
|
|
215
211
|
*/
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
212
|
+
const managedScopeErrnoFailureCodes = new Set([
|
|
213
|
+
"EACCES",
|
|
214
|
+
"EPERM",
|
|
215
|
+
"EBUSY",
|
|
216
|
+
"EROFS",
|
|
217
|
+
"ENOSPC",
|
|
218
|
+
"EMFILE",
|
|
219
|
+
"ENFILE",
|
|
220
|
+
"ELOOP",
|
|
221
|
+
"ENOTDIR",
|
|
222
|
+
]);
|
|
219
223
|
|
|
220
224
|
const managedScopeFailureCodes = new Set<ManagedScopeErrorCode>([
|
|
221
225
|
"atomic_unavailable",
|
|
@@ -226,6 +230,31 @@ const managedScopeFailureCodes = new Set<ManagedScopeErrorCode>([
|
|
|
226
230
|
"capacity_exceeded",
|
|
227
231
|
]);
|
|
228
232
|
|
|
233
|
+
/**
|
|
234
|
+
* A scope whose directory outgrew a scan budget. The binding is untouched and
|
|
235
|
+
* canonical; only the surrounding entry count is over budget, so this must not
|
|
236
|
+
* be reported as binding corruption.
|
|
237
|
+
*/
|
|
238
|
+
function isManagedScopeCapacityMessage(message: string): boolean {
|
|
239
|
+
return message === "artifact_capacity_exceeded" || message === "managed_replace_cleanup_receipt_limit_exceeded";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Recover the path-free classification for a failure that carries no security
|
|
244
|
+
* classification of its own.
|
|
245
|
+
*
|
|
246
|
+
* `managedScopeFailureCodes` deliberately stays out of this: those tokens are
|
|
247
|
+
* already reported through `code`, and the `code`/`classification` split is
|
|
248
|
+
* pinned behaviour. This only rescues the identity and errno failures that
|
|
249
|
+
* otherwise reach the operator as an undifferentiated `binding_invalid`.
|
|
250
|
+
*/
|
|
251
|
+
function managedScopeResidualClassification(error: unknown): string | undefined {
|
|
252
|
+
if (!(error instanceof Error)) return undefined;
|
|
253
|
+
if (managedScopeIdentityFailureCodes.has(error.message)) return error.message;
|
|
254
|
+
const errno = (error as NodeJS.ErrnoException).code;
|
|
255
|
+
return errno !== undefined && managedScopeErrnoFailureCodes.has(errno) ? errno : undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
229
258
|
/**
|
|
230
259
|
* Preserve a recognized native/publication failure as its own error code.
|
|
231
260
|
*
|
|
@@ -240,12 +269,23 @@ function managedScopeErrorCode(message: string): ManagedScopeErrorCode {
|
|
|
240
269
|
: "binding_invalid";
|
|
241
270
|
}
|
|
242
271
|
|
|
272
|
+
function managedScopeFailureCause(error: unknown): { readonly classification: string } {
|
|
273
|
+
const security = managedSecurityFailureClassification(error);
|
|
274
|
+
if (security) return { classification: security };
|
|
275
|
+
const residual = managedScopeResidualClassification(error);
|
|
276
|
+
if (residual) return { classification: residual };
|
|
277
|
+
return {
|
|
278
|
+
classification: managedScopeErrorCode(error instanceof Error ? error.message : ""),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
243
282
|
function managedScopeFailureMessage(error: unknown, fallback: string): string {
|
|
244
283
|
const classification = managedSecurityFailureClassification(error);
|
|
245
284
|
if (classification) return classification;
|
|
246
285
|
if (!(error instanceof Error)) return fallback;
|
|
247
286
|
if (error.message === "content_too_large" || isManagedScopeCapacityMessage(error.message)) return error.message;
|
|
248
|
-
|
|
287
|
+
if (managedScopeFailureCodes.has(error.message as ManagedScopeErrorCode)) return error.message;
|
|
288
|
+
return managedScopeResidualClassification(error) ?? fallback;
|
|
249
289
|
}
|
|
250
290
|
|
|
251
291
|
export interface ManagedCandidate {
|
|
@@ -4,6 +4,7 @@ import * as fsp from "node:fs/promises";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type {
|
|
6
6
|
NativeDirectoryTreeSnapshot,
|
|
7
|
+
NativeExactFileIdentity,
|
|
7
8
|
NativeExactUnlinkResult,
|
|
8
9
|
NativeOwnerOnlySecurityResult,
|
|
9
10
|
RecoveryFsIdentity,
|
|
@@ -25,6 +26,7 @@ type NativeManagedSessionStorage = Pick<
|
|
|
25
26
|
| "exactRemoveDirectoryTree"
|
|
26
27
|
| "exactReplacePath"
|
|
27
28
|
| "exactUnlink"
|
|
29
|
+
| "exactUnlinkDirect"
|
|
28
30
|
| "linkNoReplacePath"
|
|
29
31
|
| "linkNoReplacePathAsync"
|
|
30
32
|
| "openRecoveryFsRoot"
|
|
@@ -569,6 +571,28 @@ const SCRUBBED_REMNANT_PREFIXES = [
|
|
|
569
571
|
".gjc-receipt-placeholder-remove-",
|
|
570
572
|
".gjc-replace-retry-",
|
|
571
573
|
] as const;
|
|
574
|
+
const EMPTY_FILE_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Replacement staging is named `.<destination-basename>.<uuid>.replacement` and
|
|
578
|
+
* is published by exchanging it with the destination, so the staging NAME
|
|
579
|
+
* survives publication while pointing at the retired predecessor. On a POSIX
|
|
580
|
+
* exchange that leaves the successor inode reachable under two names until the
|
|
581
|
+
* staging name is unlinked, and a crash between exchange and unlink strands it.
|
|
582
|
+
*
|
|
583
|
+
* A stranded staging name that still resolves to the LIVE transcript inode is
|
|
584
|
+
* not evidence of anything: every managed capture opens no-follow and rejects
|
|
585
|
+
* `nlink > 1` (`captureManagedFileNoFollowLimit`, `inspectManagedFileNoFollow`),
|
|
586
|
+
* so the extra name makes the transcript permanently unlistable and unopenable
|
|
587
|
+
* — `--resume <id>` reports "Session not found." and `--continue` reports
|
|
588
|
+
* "Could not inspect managed session: read-failed", while the bytes are intact
|
|
589
|
+
* and resume by explicit path still works. Reaping the surplus NAME (never the
|
|
590
|
+
* inode) restores both paths without touching transcript content.
|
|
591
|
+
*/
|
|
592
|
+
const REPLACEMENT_STAGING_NAME =
|
|
593
|
+
/^\.(?<destination>.+)\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.replacement$/;
|
|
594
|
+
const REAPER_QUARANTINE_NAME =
|
|
595
|
+
/^\.gjc-remnant-reap-(?<destination>[0-9a-f]+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
572
596
|
|
|
573
597
|
/** In-flight protocol steps complete in milliseconds; anything older is abandoned. */
|
|
574
598
|
const SCRUBBED_REMNANT_MIN_AGE_MS = 15 * 60 * 1000;
|
|
@@ -590,6 +614,237 @@ function reportScrubbedProtocolRemnantReap(reaped: number, failures: number): Sc
|
|
|
590
614
|
return { reaped, failures };
|
|
591
615
|
}
|
|
592
616
|
|
|
617
|
+
type ReplacementAliasObservation = {
|
|
618
|
+
destination: string;
|
|
619
|
+
staging: fs.BigIntStats;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
function replacementStagingDestination(name: string): string | undefined {
|
|
623
|
+
return REPLACEMENT_STAGING_NAME.exec(name)?.groups?.destination;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function remnantReapDestination(name: string): string | undefined {
|
|
627
|
+
const encoded = REAPER_QUARANTINE_NAME.exec(name)?.groups?.destination;
|
|
628
|
+
if (!encoded || encoded.length % 2 !== 0) return undefined;
|
|
629
|
+
const destination = Buffer.from(encoded, "hex").toString("utf8");
|
|
630
|
+
return destination &&
|
|
631
|
+
Buffer.from(destination).toString("hex") === encoded &&
|
|
632
|
+
path.basename(destination) === destination
|
|
633
|
+
? destination
|
|
634
|
+
: undefined;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function remnantReapQuarantineName(pathname: string): string {
|
|
638
|
+
const basename = path.basename(pathname);
|
|
639
|
+
const destination = replacementStagingDestination(basename) ?? remnantReapDestination(basename) ?? basename;
|
|
640
|
+
return `.gjc-remnant-reap-${Buffer.from(destination).toString("hex")}-${randomUUID()}`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function isRegularReaperFile(stat: fs.BigIntStats): boolean {
|
|
644
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function sameReaperFileIdentity(left: fs.BigIntStats, right: fs.BigIntStats): boolean {
|
|
648
|
+
return (
|
|
649
|
+
left.dev === right.dev &&
|
|
650
|
+
left.ino === right.ino &&
|
|
651
|
+
left.nlink === right.nlink &&
|
|
652
|
+
left.size === right.size &&
|
|
653
|
+
left.mtimeNs === right.mtimeNs &&
|
|
654
|
+
left.ctimeNs === right.ctimeNs
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* True when `name` is replacement staging whose inode is ALSO reachable under
|
|
660
|
+
* its own published destination in the same directory. Publication exchanged
|
|
661
|
+
* the two names, so the surplus staging name aliases the live successor and
|
|
662
|
+
* pins its `nlink` above one; reaping the staging name restores the
|
|
663
|
+
* single-link invariant every managed capture requires.
|
|
664
|
+
*
|
|
665
|
+
* Two distinct names resolving to one inode already prove `nlink > 1`, so
|
|
666
|
+
* identity equality is the whole test — the staging name always carries a
|
|
667
|
+
* leading dot the destination does not, and can never be its own destination.
|
|
668
|
+
*
|
|
669
|
+
* A staging name that does NOT alias its destination is left untouched: it is
|
|
670
|
+
* either an unpublished attempt or a retired predecessor, and both are
|
|
671
|
+
* recovery evidence owned by receipt reconciliation.
|
|
672
|
+
*/
|
|
673
|
+
function aliasesPublishedDestinationSync(
|
|
674
|
+
directory: string,
|
|
675
|
+
name: string,
|
|
676
|
+
destination: string,
|
|
677
|
+
): ReplacementAliasObservation | undefined {
|
|
678
|
+
try {
|
|
679
|
+
const staging = fs.lstatSync(path.join(directory, name), { bigint: true });
|
|
680
|
+
if (!isRegularReaperFile(staging) || staging.nlink < 2n) return undefined;
|
|
681
|
+
const published = fs.lstatSync(path.join(directory, destination), { bigint: true });
|
|
682
|
+
if (!isRegularReaperFile(published) || published.dev !== staging.dev || published.ino !== staging.ino)
|
|
683
|
+
return undefined;
|
|
684
|
+
return { destination, staging };
|
|
685
|
+
} catch {
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function stagingAliasesPublishedDestinationSync(
|
|
691
|
+
directory: string,
|
|
692
|
+
name: string,
|
|
693
|
+
): ReplacementAliasObservation | undefined {
|
|
694
|
+
const destination = replacementStagingDestination(name);
|
|
695
|
+
return destination ? aliasesPublishedDestinationSync(directory, name, destination) : undefined;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function remnantReapAliasesPublishedDestinationSync(
|
|
699
|
+
directory: string,
|
|
700
|
+
name: string,
|
|
701
|
+
): ReplacementAliasObservation | undefined {
|
|
702
|
+
const destination = remnantReapDestination(name);
|
|
703
|
+
return destination ? aliasesPublishedDestinationSync(directory, name, destination) : undefined;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function aliasesPublishedDestination(
|
|
707
|
+
directory: string,
|
|
708
|
+
name: string,
|
|
709
|
+
destination: string,
|
|
710
|
+
): Promise<ReplacementAliasObservation | undefined> {
|
|
711
|
+
try {
|
|
712
|
+
const staging = await fsp.lstat(path.join(directory, name), { bigint: true });
|
|
713
|
+
if (!isRegularReaperFile(staging) || staging.nlink < 2n) return undefined;
|
|
714
|
+
const published = await fsp.lstat(path.join(directory, destination), { bigint: true });
|
|
715
|
+
if (!isRegularReaperFile(published) || published.dev !== staging.dev || published.ino !== staging.ino)
|
|
716
|
+
return undefined;
|
|
717
|
+
return { destination, staging };
|
|
718
|
+
} catch {
|
|
719
|
+
return undefined;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
async function stagingAliasesPublishedDestination(
|
|
724
|
+
directory: string,
|
|
725
|
+
name: string,
|
|
726
|
+
): Promise<ReplacementAliasObservation | undefined> {
|
|
727
|
+
const destination = replacementStagingDestination(name);
|
|
728
|
+
return destination ? aliasesPublishedDestination(directory, name, destination) : undefined;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async function remnantReapAliasesPublishedDestination(
|
|
732
|
+
directory: string,
|
|
733
|
+
name: string,
|
|
734
|
+
): Promise<ReplacementAliasObservation | undefined> {
|
|
735
|
+
const destination = remnantReapDestination(name);
|
|
736
|
+
return destination ? aliasesPublishedDestination(directory, name, destination) : undefined;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function hashReaperFileSync(pathname: string, expected: fs.BigIntStats): string | undefined {
|
|
740
|
+
let descriptor: number | undefined;
|
|
741
|
+
try {
|
|
742
|
+
descriptor = fs.openSync(
|
|
743
|
+
pathname,
|
|
744
|
+
fs.constants.O_RDONLY | (fs.constants.O_NONBLOCK ?? 0) | (fs.constants.O_NOFOLLOW ?? 0),
|
|
745
|
+
);
|
|
746
|
+
const opened = fs.fstatSync(descriptor, { bigint: true });
|
|
747
|
+
if (!isRegularReaperFile(opened) || !sameReaperFileIdentity(opened, expected)) return undefined;
|
|
748
|
+
const digest = createHash("sha256");
|
|
749
|
+
const chunk = Buffer.alloc(64 * 1024);
|
|
750
|
+
for (;;) {
|
|
751
|
+
const count = fs.readSync(descriptor, chunk, 0, chunk.byteLength, null);
|
|
752
|
+
if (count === 0) break;
|
|
753
|
+
digest.update(chunk.subarray(0, count));
|
|
754
|
+
}
|
|
755
|
+
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
756
|
+
return isRegularReaperFile(after) && sameReaperFileIdentity(after, expected) ? digest.digest("hex") : undefined;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
if (isEnoent(error) || (error as NodeJS.ErrnoException).code === "ELOOP") return undefined;
|
|
759
|
+
throw error;
|
|
760
|
+
} finally {
|
|
761
|
+
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async function hashReaperFile(pathname: string, expected: fs.BigIntStats): Promise<string | undefined> {
|
|
766
|
+
let handle: fsp.FileHandle | undefined;
|
|
767
|
+
try {
|
|
768
|
+
handle = await fsp.open(
|
|
769
|
+
pathname,
|
|
770
|
+
fs.constants.O_RDONLY | (fs.constants.O_NONBLOCK ?? 0) | (fs.constants.O_NOFOLLOW ?? 0),
|
|
771
|
+
);
|
|
772
|
+
const opened = await handle.stat({ bigint: true });
|
|
773
|
+
if (!isRegularReaperFile(opened) || !sameReaperFileIdentity(opened, expected)) return undefined;
|
|
774
|
+
const digest = createHash("sha256");
|
|
775
|
+
const chunk = Buffer.alloc(64 * 1024);
|
|
776
|
+
let offset = 0;
|
|
777
|
+
for (;;) {
|
|
778
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, offset);
|
|
779
|
+
if (bytesRead === 0) break;
|
|
780
|
+
digest.update(chunk.subarray(0, bytesRead));
|
|
781
|
+
offset += bytesRead;
|
|
782
|
+
}
|
|
783
|
+
const after = await handle.stat({ bigint: true });
|
|
784
|
+
return isRegularReaperFile(after) && sameReaperFileIdentity(after, expected) ? digest.digest("hex") : undefined;
|
|
785
|
+
} catch (error) {
|
|
786
|
+
if (isEnoent(error) || (error as NodeJS.ErrnoException).code === "ELOOP") return undefined;
|
|
787
|
+
throw error;
|
|
788
|
+
} finally {
|
|
789
|
+
await handle?.close();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function exactReaperUnlinkSync(
|
|
794
|
+
pathname: string,
|
|
795
|
+
stat: fs.BigIntStats,
|
|
796
|
+
sha256: string,
|
|
797
|
+
allowHardLink: boolean,
|
|
798
|
+
): NativeExactUnlinkResult {
|
|
799
|
+
const parent = fs.lstatSync(path.dirname(pathname), { bigint: true });
|
|
800
|
+
if (!parent.isDirectory() || parent.isSymbolicLink()) return { ok: false, code: "parent_mismatch" };
|
|
801
|
+
const identity: NativeExactFileIdentity = {
|
|
802
|
+
dev: stat.dev,
|
|
803
|
+
ino: stat.ino,
|
|
804
|
+
nlink: stat.nlink,
|
|
805
|
+
parentDev: parent.dev,
|
|
806
|
+
parentIno: parent.ino,
|
|
807
|
+
size: stat.size,
|
|
808
|
+
mtimeNs: stat.mtimeNs,
|
|
809
|
+
sha256,
|
|
810
|
+
quarantineName: remnantReapQuarantineName(pathname),
|
|
811
|
+
...(allowHardLink ? { allowHardLink: true, requireHardLink: true } : {}),
|
|
812
|
+
};
|
|
813
|
+
return nativeSessionStorage().exactUnlinkDirect(pathname, identity);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async function exactReaperUnlink(
|
|
817
|
+
pathname: string,
|
|
818
|
+
stat: fs.BigIntStats,
|
|
819
|
+
sha256: string,
|
|
820
|
+
allowHardLink: boolean,
|
|
821
|
+
): Promise<NativeExactUnlinkResult> {
|
|
822
|
+
const parent = await fsp.lstat(path.dirname(pathname), { bigint: true });
|
|
823
|
+
if (!parent.isDirectory() || parent.isSymbolicLink()) return { ok: false, code: "parent_mismatch" };
|
|
824
|
+
const identity: NativeExactFileIdentity = {
|
|
825
|
+
dev: stat.dev,
|
|
826
|
+
ino: stat.ino,
|
|
827
|
+
nlink: stat.nlink,
|
|
828
|
+
parentDev: parent.dev,
|
|
829
|
+
parentIno: parent.ino,
|
|
830
|
+
size: stat.size,
|
|
831
|
+
mtimeNs: stat.mtimeNs,
|
|
832
|
+
sha256,
|
|
833
|
+
quarantineName: remnantReapQuarantineName(pathname),
|
|
834
|
+
...(allowHardLink ? { allowHardLink: true, requireHardLink: true } : {}),
|
|
835
|
+
};
|
|
836
|
+
return nativeSessionStorage().exactUnlinkDirect(pathname, identity);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function isBenignReaperRace(result: NativeExactUnlinkResult): boolean {
|
|
840
|
+
return (
|
|
841
|
+
result.code === "not_found" ||
|
|
842
|
+
result.code === "identity_mismatch" ||
|
|
843
|
+
result.code === "reparse_point" ||
|
|
844
|
+
result.code === "not_regular_file"
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
593
848
|
/**
|
|
594
849
|
* Best-effort removal of scrubbed write-protocol remnants from one managed
|
|
595
850
|
* directory. Only zero-length, single-link, non-symlink regular files whose
|
|
@@ -599,6 +854,11 @@ function reportScrubbedProtocolRemnantReap(reaped: number, failures: number): Sc
|
|
|
599
854
|
* or detached receipts retained as evidence) are never touched. A proven
|
|
600
855
|
* concurrent disappearance (ENOENT) is benign; all other I/O failures are
|
|
601
856
|
* returned and logged once without aborting scope preparation.
|
|
857
|
+
*
|
|
858
|
+
* Stranded replacement staging that aliases its own published destination is
|
|
859
|
+
* reaped by the same pass. That entry is neither zero-length nor single-link,
|
|
860
|
+
* so the terminal-remnant filters above can never reach it, yet leaving it in
|
|
861
|
+
* place bricks resume for the session it aliases.
|
|
602
862
|
*/
|
|
603
863
|
export function reapScrubbedProtocolRemnantsSync(
|
|
604
864
|
directory: string,
|
|
@@ -614,14 +874,31 @@ export function reapScrubbedProtocolRemnantsSync(
|
|
|
614
874
|
let reaped = 0;
|
|
615
875
|
let failures = 0;
|
|
616
876
|
for (const name of names) {
|
|
617
|
-
|
|
877
|
+
const terminalRemnant = SCRUBBED_REMNANT_PREFIXES.some(prefix => name.startsWith(prefix));
|
|
878
|
+
const replacementCandidate = REPLACEMENT_STAGING_NAME.test(name);
|
|
879
|
+
const quarantineCandidate = REAPER_QUARANTINE_NAME.test(name);
|
|
880
|
+
if (!terminalRemnant && !replacementCandidate && !quarantineCandidate) continue;
|
|
881
|
+
const alias = terminalRemnant
|
|
882
|
+
? undefined
|
|
883
|
+
: replacementCandidate
|
|
884
|
+
? stagingAliasesPublishedDestinationSync(directory, name)
|
|
885
|
+
: remnantReapAliasesPublishedDestinationSync(directory, name);
|
|
886
|
+
if (!terminalRemnant && !alias) continue;
|
|
618
887
|
const pathname = path.join(directory, name);
|
|
619
888
|
try {
|
|
620
|
-
|
|
621
|
-
|
|
889
|
+
// The alias probe already stat'ed the staging entry; reuse that exact
|
|
890
|
+
// observation rather than re-stating the pathname, which would only add
|
|
891
|
+
// another window. Identity is enforced at removal by the native
|
|
892
|
+
// primitive, not by how many times the name is looked up.
|
|
893
|
+
const named = terminalRemnant ? fs.lstatSync(pathname, { bigint: true }) : alias?.staging;
|
|
894
|
+
if (!named || !isRegularReaperFile(named)) continue;
|
|
895
|
+
if (terminalRemnant && (named.nlink !== 1n || named.size !== 0n)) continue;
|
|
622
896
|
if (named.mtimeMs > cutoff) continue;
|
|
623
|
-
|
|
624
|
-
|
|
897
|
+
const sha256 = terminalRemnant ? EMPTY_FILE_SHA256 : hashReaperFileSync(pathname, named);
|
|
898
|
+
if (!sha256) continue;
|
|
899
|
+
const removed = exactReaperUnlinkSync(pathname, named, sha256, !terminalRemnant);
|
|
900
|
+
if (removed.ok) reaped += 1;
|
|
901
|
+
else if (!isBenignReaperRace(removed)) failures += 1;
|
|
625
902
|
} catch (error) {
|
|
626
903
|
if (!isEnoent(error)) failures += 1;
|
|
627
904
|
}
|
|
@@ -635,7 +912,8 @@ const SCRUBBED_REMNANT_REAP_BATCH_SIZE = 256;
|
|
|
635
912
|
/**
|
|
636
913
|
* Async twin of {@link reapScrubbedProtocolRemnantsSync} with the same safety
|
|
637
914
|
* filters (terminal remnant prefix, zero-length, single-link, non-symlink,
|
|
638
|
-
* older than the age gate
|
|
915
|
+
* older than the age gate, plus stranded staging that aliases its own
|
|
916
|
+
* published destination), yielding between bounded batches. Long-lived
|
|
639
917
|
* processes reap per-session descendant directories through this path so a
|
|
640
918
|
* legacy oversized directory cannot starve timers or sibling subagents while
|
|
641
919
|
* it is being drained (issue #4394).
|
|
@@ -655,15 +933,30 @@ export async function reapScrubbedProtocolRemnants(
|
|
|
655
933
|
let failures = 0;
|
|
656
934
|
let scanned = 0;
|
|
657
935
|
for (const name of names) {
|
|
658
|
-
|
|
936
|
+
const terminalRemnant = SCRUBBED_REMNANT_PREFIXES.some(prefix => name.startsWith(prefix));
|
|
937
|
+
const replacementCandidate = REPLACEMENT_STAGING_NAME.test(name);
|
|
938
|
+
const quarantineCandidate = REAPER_QUARANTINE_NAME.test(name);
|
|
939
|
+
if (!terminalRemnant && !replacementCandidate && !quarantineCandidate) continue;
|
|
659
940
|
if (++scanned % SCRUBBED_REMNANT_REAP_BATCH_SIZE === 0) await Bun.sleep(0);
|
|
941
|
+
const alias = terminalRemnant
|
|
942
|
+
? undefined
|
|
943
|
+
: replacementCandidate
|
|
944
|
+
? await stagingAliasesPublishedDestination(directory, name)
|
|
945
|
+
: await remnantReapAliasesPublishedDestination(directory, name);
|
|
946
|
+
if (!terminalRemnant && !alias) continue;
|
|
660
947
|
const pathname = path.join(directory, name);
|
|
661
948
|
try {
|
|
662
|
-
|
|
663
|
-
|
|
949
|
+
// Same reuse as the sync twin: one probe observation, one identity-bound
|
|
950
|
+
// removal.
|
|
951
|
+
const named = terminalRemnant ? await fsp.lstat(pathname, { bigint: true }) : alias?.staging;
|
|
952
|
+
if (!named || !isRegularReaperFile(named)) continue;
|
|
953
|
+
if (terminalRemnant && (named.nlink !== 1n || named.size !== 0n)) continue;
|
|
664
954
|
if (named.mtimeMs > cutoff) continue;
|
|
665
|
-
await
|
|
666
|
-
|
|
955
|
+
const sha256 = terminalRemnant ? EMPTY_FILE_SHA256 : await hashReaperFile(pathname, named);
|
|
956
|
+
if (!sha256) continue;
|
|
957
|
+
const removed = await exactReaperUnlink(pathname, named, sha256, !terminalRemnant);
|
|
958
|
+
if (removed.ok) reaped += 1;
|
|
959
|
+
else if (!isBenignReaperRace(removed)) failures += 1;
|
|
667
960
|
} catch (error) {
|
|
668
961
|
if (!isEnoent(error)) failures += 1;
|
|
669
962
|
}
|