@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
|
@@ -4,7 +4,7 @@ import * as fs from "node:fs/promises";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { getAgentDir, isKnownSinkPeerClosedError, logger } from "@gajae-code/utils";
|
|
6
6
|
import { normalizePathForComparison, VERSION } from "@gajae-code/utils/dirs";
|
|
7
|
-
import { withFileLock } from "../config/file-lock";
|
|
7
|
+
import { isFileLockAcquireTimeout, withFileLock } from "../config/file-lock";
|
|
8
8
|
import {
|
|
9
9
|
COORDINATOR_MCP_PROTOCOL_VERSION,
|
|
10
10
|
COORDINATOR_MCP_SERVER_NAME,
|
|
@@ -27,6 +27,7 @@ import type { WorkflowGate, WorkflowGateQueryRecord } from "../modes/shared/agen
|
|
|
27
27
|
import type { BrokerDiscovery } from "../sdk/broker/discovery";
|
|
28
28
|
import { endpointIncarnation } from "../sdk/broker/endpoint-authority";
|
|
29
29
|
import { type EnsureBrokerSettings, ensureBroker } from "../sdk/broker/ensure";
|
|
30
|
+
import { DEAD_HOST_HEARTBEAT_SILENCE_MS } from "../sdk/broker/session-index";
|
|
30
31
|
import { lifecycleRequestTimeoutMs } from "../sdk/broker/startup-budget";
|
|
31
32
|
import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
|
|
32
33
|
import { SdkClient, SdkClientError } from "../sdk/client/client";
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
SessionActivationError,
|
|
40
41
|
} from "../sdk/session-activation";
|
|
41
42
|
import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../sdk/session-list";
|
|
43
|
+
import { sanitizeSdkStartupMessage } from "../sdk/startup-capability";
|
|
42
44
|
import {
|
|
43
45
|
ackCodexWakeEvent,
|
|
44
46
|
bindDelegateCodexHandoff,
|
|
@@ -144,6 +146,7 @@ import {
|
|
|
144
146
|
recoverExpiredPublicDelivery,
|
|
145
147
|
releasePublicDeliveryClaim,
|
|
146
148
|
removeSessionTransaction,
|
|
149
|
+
repairLegacyReportIds,
|
|
147
150
|
repairProjections,
|
|
148
151
|
replaceCreationRetirementIntent,
|
|
149
152
|
rotateClaimedCreationVerifier,
|
|
@@ -307,6 +310,9 @@ interface CoordinatorServices {
|
|
|
307
310
|
canonicalizePath?: (value: string) => Promise<string>;
|
|
308
311
|
codexTransportFactory?: CodexTransportFactory;
|
|
309
312
|
eventWebhookDelivery?: WebhookDelivery;
|
|
313
|
+
/** Observable transitions for consumers coordinating with the filesystem-backed event wait. */
|
|
314
|
+
onCoordinatorEventWatchReady?: () => void;
|
|
315
|
+
onCoordinatorEventWake?: () => void;
|
|
310
316
|
/** Test barrier invoked after an accepted prompt receipt is durable and before turn finalization. */
|
|
311
317
|
afterPromptReceiptPersisted?: (sessionId: string) => void | Promise<void>;
|
|
312
318
|
/** Test barrier invoked after an answer dispatch is claimed and before its final admission check. */
|
|
@@ -513,6 +519,11 @@ const OBSERVED_BROKER_FAILURE_CODES = new Set([
|
|
|
513
519
|
const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
|
|
514
520
|
const PROMPT_ACK_TIMEOUT_REASON = "runtime_prompt_ack_timeout";
|
|
515
521
|
const DEFAULT_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 10_000;
|
|
522
|
+
function coordinatorKnownSecrets(): string[] {
|
|
523
|
+
return Object.entries(process.env)
|
|
524
|
+
.filter(([name, value]) => value && /(?:token|secret|password|credential|api[_-]?key|auth)/iu.test(name))
|
|
525
|
+
.map(([, value]) => value!);
|
|
526
|
+
}
|
|
516
527
|
/**
|
|
517
528
|
* Pagination window for `readCompleteQ12Snapshot`: it stops starting pages once
|
|
518
529
|
* this much time has passed and gives each page only the remainder as its reply
|
|
@@ -524,6 +535,26 @@ const Q12_SNAPSHOT_BUDGET_MS = 5_000;
|
|
|
524
535
|
const MAX_RUNTIME_SESSIONS_PER_WATCH_PASS = 4;
|
|
525
536
|
const MAX_Q12_ATTEMPTS_PER_WATCH_PASS = 2;
|
|
526
537
|
const MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
538
|
+
/**
|
|
539
|
+
* A delete intent is superseded once another deletion for the same session and
|
|
540
|
+
* endpoint incarnation has completed: force eviction retires a stale endpoint under
|
|
541
|
+
* its own record and leaves behind the intent an earlier idle reap admitted.
|
|
542
|
+
*/
|
|
543
|
+
function isSupersededDeletionIntent(
|
|
544
|
+
entry: NamespaceDeletionEntryV1,
|
|
545
|
+
deletions: readonly NamespaceDeletionEntryV1[],
|
|
546
|
+
): boolean {
|
|
547
|
+
return (
|
|
548
|
+
entry.phase === "intent" &&
|
|
549
|
+
deletions.some(
|
|
550
|
+
other =>
|
|
551
|
+
other !== entry &&
|
|
552
|
+
other.session_id === entry.session_id &&
|
|
553
|
+
other.endpoint_incarnation === entry.endpoint_incarnation &&
|
|
554
|
+
other.phase === "completed",
|
|
555
|
+
)
|
|
556
|
+
);
|
|
557
|
+
}
|
|
527
558
|
const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
|
|
528
559
|
const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
|
|
529
560
|
const TURN_ID_PATTERN = /^turn-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -738,7 +769,8 @@ function toolSchema(
|
|
|
738
769
|
if (name === "gjc_coordinator_await_turn") {
|
|
739
770
|
return {
|
|
740
771
|
name,
|
|
741
|
-
description:
|
|
772
|
+
description:
|
|
773
|
+
"Poll a durable turn for a bounded time. Observation expiry returns wait_expired:true (legacy ok:false, reason:timeout) without an MCP error. Inspect turn.status for completion; await again without resending the prompt.",
|
|
742
774
|
inputSchema: {
|
|
743
775
|
type: "object",
|
|
744
776
|
properties: {
|
|
@@ -1311,6 +1343,11 @@ interface CoordinatorToolIdempotencyRecord {
|
|
|
1311
1343
|
request_digest: string;
|
|
1312
1344
|
state: "in_progress" | "completed";
|
|
1313
1345
|
response?: Record<string, unknown>;
|
|
1346
|
+
/** Reused-session delegate prompt-claim provenance, written under the key lock:
|
|
1347
|
+
* `false` when the attempt starts, `true` immediately before the canonical prompt
|
|
1348
|
+
* claim. A same-key retry may claim afresh only on an explicit `false`; `true` or
|
|
1349
|
+
* an absent marker (unknown provenance) keeps missing live state fail-closed. */
|
|
1350
|
+
delegate_prompt_claim_started?: boolean;
|
|
1314
1351
|
admission?: DelegateAdmissionCheckpoint;
|
|
1315
1352
|
delegate_response_pin?: DelegateResponsePinV1;
|
|
1316
1353
|
created_at: string;
|
|
@@ -1390,7 +1427,24 @@ function boundedPublicValue(value: unknown, budget: { remaining: number }, depth
|
|
|
1390
1427
|
: undefined;
|
|
1391
1428
|
const code =
|
|
1392
1429
|
typeof rawCode === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, rawCode) ? rawCode : "unavailable";
|
|
1393
|
-
|
|
1430
|
+
const rawDiagnostic =
|
|
1431
|
+
field && typeof field === "object" && !Array.isArray(field)
|
|
1432
|
+
? (field as Record<string, unknown>).diagnostic
|
|
1433
|
+
: undefined;
|
|
1434
|
+
const boundedDiagnostic =
|
|
1435
|
+
typeof rawDiagnostic === "string" ? boundedPublicValue(rawDiagnostic, budget, depth + 1) : undefined;
|
|
1436
|
+
const rawMessage =
|
|
1437
|
+
field && typeof field === "object" && !Array.isArray(field)
|
|
1438
|
+
? (field as Record<string, unknown>).message
|
|
1439
|
+
: undefined;
|
|
1440
|
+
output[key] = {
|
|
1441
|
+
code,
|
|
1442
|
+
message:
|
|
1443
|
+
code === "spawn_failed" && typeof rawMessage === "string" && boundedDiagnostic !== undefined
|
|
1444
|
+
? boundedPublicValue(rawMessage, budget, depth + 1)
|
|
1445
|
+
: PUBLIC_ERROR_MESSAGES[code],
|
|
1446
|
+
...(boundedDiagnostic === undefined ? {} : { diagnostic: boundedDiagnostic }),
|
|
1447
|
+
};
|
|
1394
1448
|
} else {
|
|
1395
1449
|
output[key] = sensitivePublicField(key) ? "[redacted]" : boundedPublicValue(field, budget, depth + 1);
|
|
1396
1450
|
}
|
|
@@ -3188,18 +3242,24 @@ function waitForTurnStateChange(namespaceDir: string, turn: TurnRecord, timeoutM
|
|
|
3188
3242
|
return deferred.promise;
|
|
3189
3243
|
}
|
|
3190
3244
|
|
|
3191
|
-
async function waitForCoordinatorEvents(
|
|
3245
|
+
async function waitForCoordinatorEvents(
|
|
3246
|
+
namespaceDir: string,
|
|
3247
|
+
timeoutMs: number,
|
|
3248
|
+
onWatchReady?: () => void,
|
|
3249
|
+
onWake?: () => void,
|
|
3250
|
+
): Promise<void> {
|
|
3192
3251
|
const deferred = Promise.withResolvers<void>();
|
|
3193
3252
|
const watchers: nodeFs.FSWatcher[] = [];
|
|
3194
3253
|
let settled = false;
|
|
3195
|
-
const finish = () => {
|
|
3254
|
+
const finish = (woke: boolean) => {
|
|
3196
3255
|
if (settled) return;
|
|
3197
3256
|
settled = true;
|
|
3198
3257
|
for (const watcher of watchers) watcher.close();
|
|
3199
3258
|
clearTimeout(timer);
|
|
3259
|
+
if (woke) onWake?.();
|
|
3200
3260
|
deferred.resolve();
|
|
3201
3261
|
};
|
|
3202
|
-
const timer = setTimeout(finish, Math.max(timeoutMs, 0));
|
|
3262
|
+
const timer = setTimeout(() => finish(false), Math.max(timeoutMs, 0));
|
|
3203
3263
|
timer.unref?.();
|
|
3204
3264
|
const eventDir = eventsDir(namespaceDir);
|
|
3205
3265
|
const watchedDirs = [
|
|
@@ -3213,16 +3273,17 @@ async function waitForCoordinatorEvents(namespaceDir: string, timeoutMs: number)
|
|
|
3213
3273
|
try {
|
|
3214
3274
|
const watcher = nodeFs.watch(dir, (_eventType, filename) => {
|
|
3215
3275
|
if (dir === eventDir) {
|
|
3216
|
-
if (filename === "event-journal.jsonl" || filename === "latest-seq.json") finish();
|
|
3276
|
+
if (filename === "event-journal.jsonl" || filename === "latest-seq.json") finish(true);
|
|
3217
3277
|
return;
|
|
3218
3278
|
}
|
|
3219
|
-
if (typeof filename === "string" && filename.endsWith(".json")) finish();
|
|
3279
|
+
if (typeof filename === "string" && filename.endsWith(".json")) finish(true);
|
|
3220
3280
|
});
|
|
3221
3281
|
watchers.push(watcher);
|
|
3222
3282
|
} catch {
|
|
3223
3283
|
// Directory may not be watchable on this platform; the timeout remains a bounded fallback.
|
|
3224
3284
|
}
|
|
3225
3285
|
}
|
|
3286
|
+
onWatchReady?.();
|
|
3226
3287
|
return deferred.promise;
|
|
3227
3288
|
}
|
|
3228
3289
|
|
|
@@ -3845,7 +3906,25 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
3845
3906
|
): Promise<Map<string, string>> {
|
|
3846
3907
|
const authorized = new Map<string, string>();
|
|
3847
3908
|
for (const sessionId of new Set(sessionIds)) {
|
|
3848
|
-
|
|
3909
|
+
// A corrupt or unreadable peer session must never abort authorization for
|
|
3910
|
+
// every other session. Only the explicitly scoped session may propagate,
|
|
3911
|
+
// otherwise one bad WAL record makes namespace-wide reads permanently
|
|
3912
|
+
// unavailable instead of degrading to the sessions that are still sound.
|
|
3913
|
+
let transaction: CoordinatorSessionTransactionV1 | null;
|
|
3914
|
+
try {
|
|
3915
|
+
transaction = await readSessionTransaction(questionPaths, sessionId);
|
|
3916
|
+
} catch (error) {
|
|
3917
|
+
logger.warn("Coordinator authorization skipped unreadable session", {
|
|
3918
|
+
sessionId,
|
|
3919
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3920
|
+
});
|
|
3921
|
+
// Only a structurally corrupt WAL is degradable for an unscoped
|
|
3922
|
+
// namespace sweep. Cancellation, I/O, and unexpected invariant errors
|
|
3923
|
+
// must remain visible instead of silently shrinking authorization.
|
|
3924
|
+
if (scopedSessionId === sessionId || !(error instanceof Error && error.message === "state_corrupt"))
|
|
3925
|
+
throw error;
|
|
3926
|
+
continue;
|
|
3927
|
+
}
|
|
3849
3928
|
if (!transaction) {
|
|
3850
3929
|
if (scopedSessionId === sessionId && !allowMissingSessionIds.has(sessionId))
|
|
3851
3930
|
throw new Error("resource_gone");
|
|
@@ -4444,6 +4523,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4444
4523
|
questionId: string;
|
|
4445
4524
|
eventId: string;
|
|
4446
4525
|
}> = [];
|
|
4526
|
+
let pendingGateCount = 0;
|
|
4447
4527
|
let q12Admitted = true;
|
|
4448
4528
|
let liveInFlightAskAdmitted = false;
|
|
4449
4529
|
let liveInFlightCandidate = false;
|
|
@@ -4533,10 +4613,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4533
4613
|
byRuntimeTurn.set(runtimeTurnId, owners);
|
|
4534
4614
|
}
|
|
4535
4615
|
for (const row of items as WorkflowGateQueryRecord[]) {
|
|
4536
|
-
if (!row || typeof row !== "object" ||
|
|
4616
|
+
if (!row || typeof row !== "object" || typeof row.gate_id !== "string") {
|
|
4537
4617
|
diagnostic("invalid_gate_row");
|
|
4538
4618
|
continue;
|
|
4539
4619
|
}
|
|
4620
|
+
// Accepted and quarantined rows are durable diagnostics, not actionable
|
|
4621
|
+
// questions. They remain in Q12 so SDK clients can explain a completed or
|
|
4622
|
+
// lost gate, but reconciliation must not classify them as malformed.
|
|
4623
|
+
if (row.tag === "accepted" || row.tag === "quarantined") continue;
|
|
4624
|
+
if (row.tag !== "pending") {
|
|
4625
|
+
diagnostic("invalid_gate_row");
|
|
4626
|
+
continue;
|
|
4627
|
+
}
|
|
4628
|
+
pendingGateCount++;
|
|
4540
4629
|
const gate = row as WorkflowGateQueryRecord & WorkflowGate;
|
|
4541
4630
|
const authorityId = createHash("sha256")
|
|
4542
4631
|
.update(
|
|
@@ -4782,7 +4871,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4782
4871
|
// even while an authenticated, acknowledged turn is still running.
|
|
4783
4872
|
// Nonempty snapshots still require an admitted ask; all writer, waiting
|
|
4784
4873
|
// token and terminal fences above remain authoritative.
|
|
4785
|
-
if (!q12Admitted || (liveInFlightCandidate &&
|
|
4874
|
+
if (!q12Admitted || (liveInFlightCandidate && pendingGateCount > 0 && !liveInFlightAskAdmitted))
|
|
4786
4875
|
return {
|
|
4787
4876
|
ok: true,
|
|
4788
4877
|
schema_version: 1,
|
|
@@ -5127,6 +5216,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5127
5216
|
return asRecord(response.error)?.code === "ambiguous";
|
|
5128
5217
|
}
|
|
5129
5218
|
|
|
5219
|
+
function isTransientDelegateAuthorityFailure(response: Record<string, unknown>): boolean {
|
|
5220
|
+
if (response.ok !== false) return false;
|
|
5221
|
+
return asRecord(response.error)?.code === "unavailable";
|
|
5222
|
+
}
|
|
5223
|
+
|
|
5130
5224
|
function publicErrorCode(code: unknown): string {
|
|
5131
5225
|
return typeof code === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, code) ? code : "unavailable";
|
|
5132
5226
|
}
|
|
@@ -5137,7 +5231,22 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5137
5231
|
const messageCode =
|
|
5138
5232
|
error instanceof Error && Object.hasOwn(PUBLIC_ERROR_MESSAGES, error.message) ? error.message : undefined;
|
|
5139
5233
|
const code = publicErrorCode(directCode ?? messageCode);
|
|
5140
|
-
|
|
5234
|
+
const diagnostic =
|
|
5235
|
+
error instanceof SdkClientError && code === "spawn_failed"
|
|
5236
|
+
? sanitizeSdkStartupMessage(error.message, coordinatorKnownSecrets())
|
|
5237
|
+
: undefined;
|
|
5238
|
+
const message =
|
|
5239
|
+
diagnostic && diagnostic !== PUBLIC_ERROR_MESSAGES[code]
|
|
5240
|
+
? `${PUBLIC_ERROR_MESSAGES[code]} Cause: ${diagnostic}`
|
|
5241
|
+
: PUBLIC_ERROR_MESSAGES[code];
|
|
5242
|
+
return {
|
|
5243
|
+
ok: false,
|
|
5244
|
+
error: {
|
|
5245
|
+
code,
|
|
5246
|
+
message,
|
|
5247
|
+
...(diagnostic && diagnostic !== PUBLIC_ERROR_MESSAGES[code] ? { diagnostic } : {}),
|
|
5248
|
+
},
|
|
5249
|
+
};
|
|
5141
5250
|
}
|
|
5142
5251
|
function sdkError(error: unknown): Record<string, unknown> {
|
|
5143
5252
|
return publicError(error);
|
|
@@ -5718,10 +5827,55 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5718
5827
|
);
|
|
5719
5828
|
}
|
|
5720
5829
|
|
|
5830
|
+
async function findPersistedBrokerAuthority(
|
|
5831
|
+
sessions: Array<Record<string, unknown>>,
|
|
5832
|
+
expected: {
|
|
5833
|
+
sessionId: string;
|
|
5834
|
+
workspace: string;
|
|
5835
|
+
endpointGeneration: number | null;
|
|
5836
|
+
endpointIncarnation: string | null;
|
|
5837
|
+
},
|
|
5838
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
5839
|
+
if (expected.endpointGeneration === null || expected.endpointIncarnation === null) return undefined;
|
|
5840
|
+
let canonicalExpectedWorkspace: string;
|
|
5841
|
+
try {
|
|
5842
|
+
canonicalExpectedWorkspace = await canonicalBrokerWorkspace(expected.workspace);
|
|
5843
|
+
} catch {
|
|
5844
|
+
return undefined;
|
|
5845
|
+
}
|
|
5846
|
+
for (const session of sessions) {
|
|
5847
|
+
if (brokerSessionId(session) !== expected.sessionId) continue;
|
|
5848
|
+
if (
|
|
5849
|
+
brokerEndpointGeneration(session) !== expected.endpointGeneration ||
|
|
5850
|
+
brokerEndpointIncarnation(session, expected.sessionId) !== expected.endpointIncarnation
|
|
5851
|
+
)
|
|
5852
|
+
continue;
|
|
5853
|
+
const declaredWorkspace = brokerSessionScope(session);
|
|
5854
|
+
if (declaredWorkspace === null) continue;
|
|
5855
|
+
try {
|
|
5856
|
+
const canonicalDeclaredWorkspace = await canonicalBrokerWorkspace(declaredWorkspace);
|
|
5857
|
+
if (sameCanonicalPath(canonicalDeclaredWorkspace, canonicalExpectedWorkspace, platform)) return session;
|
|
5858
|
+
} catch {
|
|
5859
|
+
// A missing or unreadable locator is not authority for this session.
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
return undefined;
|
|
5863
|
+
}
|
|
5864
|
+
|
|
5721
5865
|
type BrokerSessionAuthority = {
|
|
5722
5866
|
workspace: string;
|
|
5723
5867
|
endpointGeneration: number;
|
|
5724
5868
|
endpointIncarnation: string;
|
|
5869
|
+
/** Host liveness the broker reports for the indexed row; absent when unreported. */
|
|
5870
|
+
live?: boolean;
|
|
5871
|
+
/** Last host heartbeat the broker recorded for the row (epoch ms); absent when unreported. */
|
|
5872
|
+
lastHeartbeatAt?: number;
|
|
5873
|
+
/** True when more than one unresolved state-root authority claims this session. */
|
|
5874
|
+
ambiguous?: boolean;
|
|
5875
|
+
/** True when teardown ownership is unresolved and must not be force-retired. */
|
|
5876
|
+
terminalUncertain?: boolean;
|
|
5877
|
+
/** True when the exact broker row is already terminal. */
|
|
5878
|
+
terminal?: boolean;
|
|
5725
5879
|
};
|
|
5726
5880
|
|
|
5727
5881
|
async function exactBrokerSessionAuthority(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
|
|
@@ -5741,17 +5895,35 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5741
5895
|
if (sameCanonicalPath(canonicalWorkspace, workspace, platform))
|
|
5742
5896
|
matches.push({ session, workspace: canonicalWorkspace });
|
|
5743
5897
|
}
|
|
5744
|
-
if (matches.length
|
|
5898
|
+
if (matches.length === 0)
|
|
5745
5899
|
throw new SdkClientError(
|
|
5746
5900
|
"not_found",
|
|
5747
5901
|
"Session is not uniquely indexed in the requested coordinator workspace.",
|
|
5748
5902
|
);
|
|
5903
|
+
if (matches.length !== 1)
|
|
5904
|
+
throw new SdkClientError(
|
|
5905
|
+
"ambiguous",
|
|
5906
|
+
"Session has multiple broker authority rows in the requested coordinator workspace.",
|
|
5907
|
+
);
|
|
5749
5908
|
const match = matches[0]!;
|
|
5750
5909
|
const endpointGeneration = brokerEndpointGeneration(match.session);
|
|
5751
5910
|
const endpointIncarnation = brokerEndpointIncarnation(match.session, sessionId);
|
|
5752
5911
|
if (endpointGeneration === null || endpointIncarnation === null)
|
|
5753
5912
|
throw new SdkClientError("endpoint_stale", "Broker session has no usable endpoint incarnation.");
|
|
5754
|
-
return {
|
|
5913
|
+
return {
|
|
5914
|
+
workspace: match.workspace,
|
|
5915
|
+
endpointGeneration,
|
|
5916
|
+
endpointIncarnation,
|
|
5917
|
+
...(typeof match.session.live === "boolean" ? { live: match.session.live } : {}),
|
|
5918
|
+
...(typeof match.session.lastHeartbeatAt === "number"
|
|
5919
|
+
? { lastHeartbeatAt: match.session.lastHeartbeatAt }
|
|
5920
|
+
: {}),
|
|
5921
|
+
...(typeof match.session.ambiguous === "boolean" ? { ambiguous: match.session.ambiguous } : {}),
|
|
5922
|
+
...(typeof match.session.terminalUncertain === "boolean"
|
|
5923
|
+
? { terminalUncertain: match.session.terminalUncertain }
|
|
5924
|
+
: {}),
|
|
5925
|
+
...(typeof match.session.terminal === "boolean" ? { terminal: match.session.terminal } : {}),
|
|
5926
|
+
};
|
|
5755
5927
|
}
|
|
5756
5928
|
|
|
5757
5929
|
async function exactBrokerSessionBinding(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
|
|
@@ -5840,13 +6012,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5840
6012
|
const sessionId = brokerSessionId(session);
|
|
5841
6013
|
return sessionId !== null && registered.has(sessionId);
|
|
5842
6014
|
}
|
|
6015
|
+
async function reportProjectionFilesForSession(sessionId: string): Promise<string[]> {
|
|
6016
|
+
const reportsDirectory = path.join(namespaceDir, "reports");
|
|
6017
|
+
const entries = await fs.readdir(reportsDirectory, { withFileTypes: true }).catch(error => {
|
|
6018
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
6019
|
+
throw error;
|
|
6020
|
+
});
|
|
6021
|
+
const files: string[] = [];
|
|
6022
|
+
for (const entry of entries) {
|
|
6023
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
6024
|
+
const file = path.join(reportsDirectory, entry.name);
|
|
6025
|
+
try {
|
|
6026
|
+
const report = asRecord(await readJsonFile(file));
|
|
6027
|
+
if (report?.session_id === sessionId) files.push(file);
|
|
6028
|
+
} catch (error) {
|
|
6029
|
+
logger.warn("Coordinator report projection scan skipped unreadable file", {
|
|
6030
|
+
file,
|
|
6031
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
return files;
|
|
6036
|
+
}
|
|
6037
|
+
async function removeStaleReportProjections(
|
|
6038
|
+
sessionId: string,
|
|
6039
|
+
retainedReportIds: ReadonlySet<string>,
|
|
6040
|
+
): Promise<void> {
|
|
6041
|
+
for (const file of await reportProjectionFilesForSession(sessionId)) {
|
|
6042
|
+
const reportId = path.basename(file, ".json");
|
|
6043
|
+
if (retainedReportIds.has(reportId)) continue;
|
|
6044
|
+
await fs.rm(file, { force: true });
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
5843
6047
|
async function removeReapedProjection(
|
|
5844
6048
|
sessionId: string,
|
|
5845
6049
|
turnIds: readonly string[],
|
|
5846
6050
|
reportIds: readonly string[],
|
|
5847
6051
|
): Promise<void> {
|
|
5848
6052
|
for (const turnId of turnIds) await fs.rm(turnFile(namespaceDir, turnId), { force: true });
|
|
5849
|
-
for (const reportId of reportIds)
|
|
6053
|
+
for (const reportId of reportIds) {
|
|
6054
|
+
if (COORDINATOR_REPORT_ID_PATTERN.test(reportId))
|
|
6055
|
+
await fs.rm(reportProjectionFile(namespaceDir, reportId), { force: true });
|
|
6056
|
+
}
|
|
6057
|
+
for (const file of await reportProjectionFilesForSession(sessionId)) await fs.rm(file, { force: true });
|
|
5850
6058
|
await fs.rm(sessionFile(sessionId), { force: true });
|
|
5851
6059
|
await fs.rm(sessionStateFile(namespaceDir, sessionId), { force: true });
|
|
5852
6060
|
await fs.rm(activeTurnFile(namespaceDir, sessionId), { force: true });
|
|
@@ -6036,19 +6244,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6036
6244
|
// manifest first, even while the session projection remains readable, so a
|
|
6037
6245
|
// verification-uncertain retry never reaches broker close again.
|
|
6038
6246
|
await ensureQuestionStateReady();
|
|
6039
|
-
let pendingDeletion = await withNamespaceRegistry(
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6247
|
+
let pendingDeletion = await withNamespaceRegistry(questionPaths, async registry => {
|
|
6248
|
+
const deletions = Object.values(registry.deletions);
|
|
6249
|
+
return (
|
|
6250
|
+
deletions
|
|
6043
6251
|
.filter(
|
|
6044
6252
|
entry =>
|
|
6045
6253
|
entry.session_id === id &&
|
|
6046
6254
|
(entry.phase === "intent" ||
|
|
6047
6255
|
entry.phase === "broker_closed" ||
|
|
6048
|
-
entry.phase === "cleanup_pending")
|
|
6256
|
+
entry.phase === "cleanup_pending") &&
|
|
6257
|
+
!isSupersededDeletionIntent(entry, deletions),
|
|
6049
6258
|
)
|
|
6050
|
-
.sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? null
|
|
6051
|
-
|
|
6259
|
+
.sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? null
|
|
6260
|
+
);
|
|
6261
|
+
});
|
|
6052
6262
|
if (pendingDeletion?.phase === "intent") {
|
|
6053
6263
|
try {
|
|
6054
6264
|
await recoverIntentDeletion(pendingDeletion);
|
|
@@ -6076,7 +6286,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6076
6286
|
const deletion = await withNamespaceRegistry(questionPaths, async registry => {
|
|
6077
6287
|
const matching = Object.values(registry.deletions).filter(entry => entry.session_id === id);
|
|
6078
6288
|
return (
|
|
6079
|
-
matching.find(entry => entry.phase !== "completed") ??
|
|
6289
|
+
matching.find(entry => entry.phase !== "completed" && !isSupersededDeletionIntent(entry, matching)) ??
|
|
6080
6290
|
matching.find(entry => entry.safe_response !== undefined) ??
|
|
6081
6291
|
null
|
|
6082
6292
|
);
|
|
@@ -6205,6 +6415,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6205
6415
|
}
|
|
6206
6416
|
const deletionId = `delete:${id}:${persistedIncarnation}`;
|
|
6207
6417
|
const deletionKey = createHash("sha256").update(deletionId).digest("hex");
|
|
6418
|
+
const projectedReportIds = (await reportProjectionFilesForSession(id)).map(file =>
|
|
6419
|
+
path.basename(file, ".json"),
|
|
6420
|
+
);
|
|
6208
6421
|
const deletionEntry = {
|
|
6209
6422
|
deletion_id: deletionId,
|
|
6210
6423
|
session_id: id,
|
|
@@ -6221,7 +6434,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6221
6434
|
session: false,
|
|
6222
6435
|
events: false,
|
|
6223
6436
|
turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
|
|
6224
|
-
report_ids:
|
|
6437
|
+
report_ids: [
|
|
6438
|
+
...new Set([
|
|
6439
|
+
...(canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : []),
|
|
6440
|
+
...projectedReportIds,
|
|
6441
|
+
]),
|
|
6442
|
+
],
|
|
6225
6443
|
},
|
|
6226
6444
|
authority_digest: deletionKey,
|
|
6227
6445
|
created_at: new Date().toISOString(),
|
|
@@ -6242,7 +6460,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6242
6460
|
}
|
|
6243
6461
|
const projectionIds = {
|
|
6244
6462
|
turnIds: Object.keys(admitted.canonical.turns),
|
|
6245
|
-
reportIds:
|
|
6463
|
+
reportIds: [
|
|
6464
|
+
...new Set([
|
|
6465
|
+
...Object.keys(admitted.canonical.reports),
|
|
6466
|
+
...(await reportProjectionFilesForSession(id)).map(file => path.basename(file, ".json")),
|
|
6467
|
+
]),
|
|
6468
|
+
],
|
|
6246
6469
|
};
|
|
6247
6470
|
await ensureQuestionStateReady();
|
|
6248
6471
|
const deletionPhase = await withNamespaceRegistry(
|
|
@@ -6399,8 +6622,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6399
6622
|
/**
|
|
6400
6623
|
* Prove a stored endpoint identity (workspace + generation + incarnation) is no
|
|
6401
6624
|
* longer current against the live broker index. Returns true only when the
|
|
6402
|
-
* broker row is absent (not_found)
|
|
6403
|
-
* rotated away from the stored triple
|
|
6625
|
+
* broker row is absent (not_found), its generation/incarnation/workspace has
|
|
6626
|
+
* rotated away from the stored triple, or the broker reports the row not live
|
|
6627
|
+
* and its host has been silent for DEAD_HOST_HEARTBEAT_SILENCE_MS. A transient
|
|
6628
|
+
* authority-read failure
|
|
6404
6629
|
* returns false: we cannot prove the endpoint recovered, but we also must not
|
|
6405
6630
|
* force-evict on a flake — the normal reap path will retry.
|
|
6406
6631
|
*/
|
|
@@ -6414,6 +6639,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6414
6639
|
const workspace = await canonicalBrokerWorkspace(persistedWorkspace);
|
|
6415
6640
|
const authority = await exactBrokerSessionAuthority(id, workspace);
|
|
6416
6641
|
return (
|
|
6642
|
+
// The broker can keep indexing a session's exact identity after its host is
|
|
6643
|
+
// gone; it then reports the row as not live and answers close with
|
|
6644
|
+
// endpoint_stale, so an identity match alone does not prove a live endpoint.
|
|
6645
|
+
// `live` alone is heartbeat-derived and flips after a short gap, so also
|
|
6646
|
+
// require the host to have been silent far beyond the broker's freshness
|
|
6647
|
+
// window before treating the row as dead.
|
|
6648
|
+
(authority.ambiguous !== true &&
|
|
6649
|
+
authority.terminalUncertain !== true &&
|
|
6650
|
+
authority.live === false &&
|
|
6651
|
+
authority.lastHeartbeatAt !== undefined &&
|
|
6652
|
+
Date.now() - authority.lastHeartbeatAt >= DEAD_HOST_HEARTBEAT_SILENCE_MS) ||
|
|
6417
6653
|
!sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
|
|
6418
6654
|
authority.endpointGeneration !== persistedGeneration ||
|
|
6419
6655
|
authority.endpointIncarnation !== persistedIncarnation
|
|
@@ -6525,8 +6761,50 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6525
6761
|
// projection-only delete.
|
|
6526
6762
|
return;
|
|
6527
6763
|
}
|
|
6764
|
+
const brokerSessionSnapshot = canonicalTransaction.canonical.session.broker;
|
|
6765
|
+
const brokerWorkspace = optionalString(brokerSessionSnapshot.workspace);
|
|
6766
|
+
const brokerGeneration =
|
|
6767
|
+
typeof brokerSessionSnapshot.endpoint_generation === "number" &&
|
|
6768
|
+
Number.isSafeInteger(brokerSessionSnapshot.endpoint_generation) &&
|
|
6769
|
+
brokerSessionSnapshot.endpoint_generation > 0
|
|
6770
|
+
? brokerSessionSnapshot.endpoint_generation
|
|
6771
|
+
: null;
|
|
6772
|
+
if (
|
|
6773
|
+
!brokerWorkspace ||
|
|
6774
|
+
brokerGeneration === null ||
|
|
6775
|
+
brokerSessionSnapshot.endpoint_incarnation !== persistedIncarnation
|
|
6776
|
+
)
|
|
6777
|
+
return;
|
|
6778
|
+
// The stale proof above is a read. Before deleting coordinator state, ask the
|
|
6779
|
+
// broker to atomically fence the exact generation/incarnation under its index
|
|
6780
|
+
// lock. A successor, fresh heartbeat, ambiguous row, or terminal-uncertain
|
|
6781
|
+
// authority refuses this request. A missing row is already fenced and is safe
|
|
6782
|
+
// to treat as retired: the preceding reap close observed the same endpoint.
|
|
6783
|
+
try {
|
|
6784
|
+
const fence = strictBrokerSessionClose(
|
|
6785
|
+
await brokerSession(
|
|
6786
|
+
brokerWorkspace,
|
|
6787
|
+
"session.close",
|
|
6788
|
+
{
|
|
6789
|
+
cwd: brokerWorkspace,
|
|
6790
|
+
sessionId,
|
|
6791
|
+
endpointGeneration: brokerGeneration,
|
|
6792
|
+
endpointIncarnation: persistedIncarnation,
|
|
6793
|
+
forceRetireStale: true,
|
|
6794
|
+
},
|
|
6795
|
+
`coordinator-reaper-fence:${sessionId}:${persistedIncarnation}`,
|
|
6796
|
+
),
|
|
6797
|
+
sessionId,
|
|
6798
|
+
);
|
|
6799
|
+
if (fence.retired !== true) return;
|
|
6800
|
+
} catch (error) {
|
|
6801
|
+
if (!(error instanceof SdkClientError) || error.code !== "not_found") return;
|
|
6802
|
+
}
|
|
6528
6803
|
const deletionId = `force-evict:${sessionId}:${persistedIncarnation}`;
|
|
6529
6804
|
const deletionKey = createHash("sha256").update(deletionId).digest("hex");
|
|
6805
|
+
const projectedReportIds = (await reportProjectionFilesForSession(sessionId)).map(file =>
|
|
6806
|
+
path.basename(file, ".json"),
|
|
6807
|
+
);
|
|
6530
6808
|
const now = new Date().toISOString();
|
|
6531
6809
|
const entry: NamespaceDeletionEntryV1 = {
|
|
6532
6810
|
deletion_id: deletionId,
|
|
@@ -6544,7 +6822,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6544
6822
|
session: false,
|
|
6545
6823
|
events: false,
|
|
6546
6824
|
turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
|
|
6547
|
-
report_ids:
|
|
6825
|
+
report_ids: [
|
|
6826
|
+
...new Set([
|
|
6827
|
+
...(canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : []),
|
|
6828
|
+
...projectedReportIds,
|
|
6829
|
+
]),
|
|
6830
|
+
],
|
|
6548
6831
|
},
|
|
6549
6832
|
authority_digest: deletionKey,
|
|
6550
6833
|
created_at: now,
|
|
@@ -6566,6 +6849,24 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6566
6849
|
try {
|
|
6567
6850
|
await ensureQuestionTransaction(sessionId);
|
|
6568
6851
|
} catch (error) {
|
|
6852
|
+
// A session whose persisted location the current policy no longer authorizes
|
|
6853
|
+
// must not be reaped (that would mutate an unauthorized location), but it must
|
|
6854
|
+
// not abort enumeration either: rethrowing refuses the whole sweep, so one such
|
|
6855
|
+
// session would stop reaping for every other session in the namespace. An empty
|
|
6856
|
+
// root list is a namespace-wide misconfiguration, not a per-session defect, so it
|
|
6857
|
+
// still refuses the sweep once instead of warning for every session.
|
|
6858
|
+
if (
|
|
6859
|
+
error instanceof Error &&
|
|
6860
|
+
isSessionAuthorityError(error) &&
|
|
6861
|
+
error.message !== "coordinator_workdir_roots_required"
|
|
6862
|
+
) {
|
|
6863
|
+
logger.warn("Coordinator session reaper skipped an unauthorized session", {
|
|
6864
|
+
sessionId,
|
|
6865
|
+
reason: error.message,
|
|
6866
|
+
detail: error.cause instanceof Error ? error.cause.message : undefined,
|
|
6867
|
+
});
|
|
6868
|
+
continue;
|
|
6869
|
+
}
|
|
6569
6870
|
if (!(error instanceof Error) || error.message !== "resource_gone") throw error;
|
|
6570
6871
|
}
|
|
6571
6872
|
try {
|
|
@@ -6602,7 +6903,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6602
6903
|
},
|
|
6603
6904
|
reapSession: async (sessionId: string): Promise<void> => {
|
|
6604
6905
|
const result = await reapSession(sessionId, { reason: "idle_reaper" });
|
|
6605
|
-
if (
|
|
6906
|
+
if (result.ok) return;
|
|
6907
|
+
// reapSession reports a broker close the endpoint could not serve as
|
|
6908
|
+
// close_failed with the broker code in `detail`. The reaper only counts
|
|
6909
|
+
// endpoint_stale toward force eviction, so pass that code through;
|
|
6910
|
+
// markSessionDead still re-proves staleness under the session lock.
|
|
6911
|
+
const staleClose =
|
|
6912
|
+
result.reason === "close_failed" &&
|
|
6913
|
+
(result.detail === "endpoint_stale" || result.detail === "not_found");
|
|
6914
|
+
throw new Error(staleClose ? "endpoint_stale" : (result.reason ?? "session_reap_failed"));
|
|
6606
6915
|
},
|
|
6607
6916
|
markSessionDead: async (sessionId: string): Promise<void> => {
|
|
6608
6917
|
// Force-evict a session that cannot be reaped normally (endpoint_stale
|
|
@@ -6778,6 +7087,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6778
7087
|
return {
|
|
6779
7088
|
ok: false,
|
|
6780
7089
|
reason: "timeout",
|
|
7090
|
+
wait_expired: true,
|
|
6781
7091
|
turn: payload.turn,
|
|
6782
7092
|
advisory_status: payload.advisory_status,
|
|
6783
7093
|
session_state: payload.session_state,
|
|
@@ -7401,6 +7711,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7401
7711
|
await writeTurnRecord(namespaceDir, turnFromCanonical(turn));
|
|
7402
7712
|
for (const report of Object.values(canonical.reports))
|
|
7403
7713
|
await writeJsonFile(reportProjectionFile(namespaceDir, report.report_id), report);
|
|
7714
|
+
await removeStaleReportProjections(sessionId, new Set(Object.keys(canonical.reports)));
|
|
7404
7715
|
const activeId = canonical.queue.selected_promotion?.to_turn_id ?? canonical.queue.active_turn_id;
|
|
7405
7716
|
const active = activeId ? canonical.turns[activeId] : null;
|
|
7406
7717
|
if (active) await writeActiveTurn(namespaceDir, turnFromCanonical(active));
|
|
@@ -7479,8 +7790,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7479
7790
|
await exportRetainedDeliveries(32, options.signal);
|
|
7480
7791
|
}
|
|
7481
7792
|
|
|
7482
|
-
async function recoverCanonicalSessionProjection(
|
|
7483
|
-
|
|
7793
|
+
async function recoverCanonicalSessionProjection(
|
|
7794
|
+
sessionId: string,
|
|
7795
|
+
options: { signal?: AbortSignal } = {},
|
|
7796
|
+
): Promise<void> {
|
|
7797
|
+
const transaction = await repairLegacyReportIds(questionPaths, sessionId, options);
|
|
7484
7798
|
if (!transaction) return;
|
|
7485
7799
|
const applied = Math.min(
|
|
7486
7800
|
transaction.projection.applied_turns_revision,
|
|
@@ -7489,10 +7803,13 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7489
7803
|
transaction.projection.applied_active_revision,
|
|
7490
7804
|
transaction.projection.applied_events_revision,
|
|
7491
7805
|
);
|
|
7492
|
-
if (applied < transaction.revision) await repairCanonicalProjections(sessionId);
|
|
7806
|
+
if (applied < transaction.revision) await repairCanonicalProjections(sessionId, options);
|
|
7493
7807
|
}
|
|
7494
7808
|
|
|
7495
|
-
async function recoverCanonicalNamespaceProjections(
|
|
7809
|
+
async function recoverCanonicalNamespaceProjections(
|
|
7810
|
+
scopedSessionId: string | null = null,
|
|
7811
|
+
options: { signal?: AbortSignal } = {},
|
|
7812
|
+
): Promise<void> {
|
|
7496
7813
|
await ensureQuestionStateReady();
|
|
7497
7814
|
const roster = await readSchedulerRoster(questionPaths);
|
|
7498
7815
|
const persisted = await fs.readdir(questionPaths.sessions).catch(() => []);
|
|
@@ -7507,13 +7824,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7507
7824
|
const transaction = await readSessionTransaction(questionPaths, sessionId);
|
|
7508
7825
|
if (!transaction) continue;
|
|
7509
7826
|
await assertPersistedSessionAuthority(transaction.canonical.session);
|
|
7510
|
-
await recoverCanonicalSessionProjection(sessionId);
|
|
7827
|
+
await recoverCanonicalSessionProjection(sessionId, options);
|
|
7511
7828
|
} catch (error) {
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7829
|
+
// Degrade per session instead of aborting the namespace sweep: one
|
|
7830
|
+
// unreadable record must not make every coordination read fail.
|
|
7831
|
+
// Log before classifying so the skipped session is always nameable
|
|
7832
|
+
// from the outside — state_corrupt is precisely the shape the two
|
|
7833
|
+
// WAL defects surface as, and silencing it hides the cause.
|
|
7834
|
+
logger.warn("Coordinator projection recovery skipped session", {
|
|
7835
|
+
sessionId,
|
|
7836
|
+
error: error instanceof Error ? error.message : String(error),
|
|
7837
|
+
});
|
|
7838
|
+
const isCorruptWal = error instanceof Error && error.message === "state_corrupt";
|
|
7839
|
+
const isAuthorityFailure = isSessionAuthorityError(error);
|
|
7840
|
+
if (!isCorruptWal && !isAuthorityFailure && !isFileLockAcquireTimeout(error)) throw error;
|
|
7841
|
+
if (scopedSessionId === sessionId && !isCorruptWal) throw error;
|
|
7517
7842
|
}
|
|
7518
7843
|
}
|
|
7519
7844
|
}
|
|
@@ -7835,12 +8160,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7835
8160
|
try {
|
|
7836
8161
|
await reconcileSessionRuntime(entry.session_id, options);
|
|
7837
8162
|
} catch (error) {
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
(error.message
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
8163
|
+
const degradable =
|
|
8164
|
+
error instanceof Error &&
|
|
8165
|
+
(error.message === "resource_gone" ||
|
|
8166
|
+
isSessionAuthorityError(error) ||
|
|
8167
|
+
(error.message === "state_corrupt" && prioritySessionId === undefined));
|
|
8168
|
+
if (!degradable) throw error;
|
|
7844
8169
|
}
|
|
7845
8170
|
}
|
|
7846
8171
|
if (processedCursor && !options.signal?.aborted)
|
|
@@ -7869,7 +8194,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7869
8194
|
await reconcileSessionRuntime(sessionId, options);
|
|
7870
8195
|
await reconcileQuestions(sessionId, options);
|
|
7871
8196
|
} catch (error) {
|
|
7872
|
-
if (
|
|
8197
|
+
if (
|
|
8198
|
+
!(error instanceof Error) ||
|
|
8199
|
+
(error.message !== "resource_gone" &&
|
|
8200
|
+
error.message !== "state_corrupt" &&
|
|
8201
|
+
!isSessionAuthorityError(error))
|
|
8202
|
+
)
|
|
7873
8203
|
throw error;
|
|
7874
8204
|
}
|
|
7875
8205
|
}
|
|
@@ -7903,12 +8233,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7903
8233
|
try {
|
|
7904
8234
|
await reconcileQuestions(entry.session_id, options);
|
|
7905
8235
|
} catch (error) {
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
(error.message
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8236
|
+
const degradable =
|
|
8237
|
+
error instanceof Error &&
|
|
8238
|
+
(error.message === "resource_gone" ||
|
|
8239
|
+
isSessionAuthorityError(error) ||
|
|
8240
|
+
(error.message === "state_corrupt" && prioritySessionId === undefined));
|
|
8241
|
+
if (!degradable) throw error;
|
|
7912
8242
|
}
|
|
7913
8243
|
}
|
|
7914
8244
|
if (processedCursor && !options.signal?.aborted)
|
|
@@ -7948,14 +8278,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7948
8278
|
await assertPersistedSessionAuthority(transaction.canonical.session);
|
|
7949
8279
|
turn = await readActiveTurn(namespaceDir, entry.session_id);
|
|
7950
8280
|
} catch (error) {
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
(error.message
|
|
7954
|
-
error
|
|
7955
|
-
error.message
|
|
7956
|
-
|
|
7957
|
-
)
|
|
7958
|
-
throw error;
|
|
8281
|
+
const degradable =
|
|
8282
|
+
error instanceof Error &&
|
|
8283
|
+
(error.message === "resource_gone" ||
|
|
8284
|
+
isSessionAuthorityError(error) ||
|
|
8285
|
+
(error.message === "state_corrupt" && prioritySessionId === undefined));
|
|
8286
|
+
if (!degradable) throw error;
|
|
7959
8287
|
}
|
|
7960
8288
|
processedCursor = entry.session_id;
|
|
7961
8289
|
if (!turn) continue;
|
|
@@ -8044,9 +8372,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8044
8372
|
};
|
|
8045
8373
|
const binding = await exactBrokerSessionBinding(sessionId, cwd);
|
|
8046
8374
|
const priorSession = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
8375
|
+
const priorWorkspace = optionalString(priorSession?.broker_workspace);
|
|
8047
8376
|
const priorAuthority =
|
|
8048
8377
|
priorSession &&
|
|
8049
|
-
|
|
8378
|
+
priorWorkspace !== null &&
|
|
8379
|
+
sameCanonicalPath(priorWorkspace, binding.workspace, platform) &&
|
|
8050
8380
|
priorSession.endpoint_generation === binding.endpointGeneration &&
|
|
8051
8381
|
optionalString(priorSession.endpoint_incarnation) === binding.endpointIncarnation
|
|
8052
8382
|
? (priorSession.sidecar_verifier as { key_id: string; public_key: string } | undefined)
|
|
@@ -8284,18 +8614,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8284
8614
|
};
|
|
8285
8615
|
await reconcileSessionRuntime(canonicalSessionId, { observeQuestions: false });
|
|
8286
8616
|
try {
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8617
|
+
// A projection can be partially written after its canonical WAL commit.
|
|
8618
|
+
// Recover the durable broker identity before proving the live endpoint so
|
|
8619
|
+
// a missing projection field does not turn an indexed session into a
|
|
8620
|
+
// false `not_indexed` result.
|
|
8621
|
+
const canonicalBroker = (await readSessionTransaction(questionPaths, canonicalSessionId))?.canonical
|
|
8622
|
+
.session.broker;
|
|
8623
|
+
const brokerWorkspace =
|
|
8624
|
+
optionalString(session.broker_workspace) ?? optionalString(canonicalBroker?.workspace) ?? cwd;
|
|
8625
|
+
const persistedEndpointGeneration =
|
|
8626
|
+
typeof session.endpoint_generation === "number" &&
|
|
8627
|
+
Number.isSafeInteger(session.endpoint_generation) &&
|
|
8628
|
+
session.endpoint_generation > 0
|
|
8629
|
+
? session.endpoint_generation
|
|
8630
|
+
: typeof canonicalBroker?.endpoint_generation === "number" &&
|
|
8631
|
+
Number.isSafeInteger(canonicalBroker.endpoint_generation) &&
|
|
8632
|
+
canonicalBroker.endpoint_generation > 0
|
|
8633
|
+
? canonicalBroker.endpoint_generation
|
|
8634
|
+
: null;
|
|
8635
|
+
const persistedEndpointIncarnation =
|
|
8636
|
+
optionalString(session.endpoint_incarnation) ??
|
|
8637
|
+
optionalString(canonicalBroker?.endpoint_incarnation);
|
|
8638
|
+
const expectedAuthority = {
|
|
8639
|
+
sessionId: canonicalSessionId,
|
|
8640
|
+
workspace: brokerWorkspace,
|
|
8641
|
+
endpointGeneration: persistedEndpointGeneration,
|
|
8642
|
+
endpointIncarnation: persistedEndpointIncarnation,
|
|
8643
|
+
};
|
|
8644
|
+
// A matching session id is not enough: the broker row must still
|
|
8645
|
+
// prove the projection's exact worktree endpoint authority.
|
|
8646
|
+
let indexedSession = await findPersistedBrokerAuthority(
|
|
8647
|
+
await listSessions(brokerWorkspace),
|
|
8648
|
+
expectedAuthority,
|
|
8290
8649
|
);
|
|
8291
|
-
//
|
|
8292
|
-
//
|
|
8293
|
-
//
|
|
8294
|
-
//
|
|
8295
|
-
|
|
8650
|
+
// A broker locator can retain a valid worktree under a spelling that
|
|
8651
|
+
// the platform-local scope filter cannot normalize (for example a
|
|
8652
|
+
// symlinked worktree or a Windows alias). Retry the unfiltered page,
|
|
8653
|
+
// but keep canonical workspace and endpoint authority checks so this
|
|
8654
|
+
// cannot turn a foreign or rotated row into an indexed session.
|
|
8655
|
+
if (!indexedSession) {
|
|
8296
8656
|
const listing = await paginatedBrokerSessionList(brokerWorkspace, { cwd: brokerWorkspace });
|
|
8297
|
-
indexedSession =
|
|
8298
|
-
|
|
8657
|
+
indexedSession = await findPersistedBrokerAuthority(
|
|
8658
|
+
jsonRecords(Array.isArray(listing.sessions) ? listing.sessions : []),
|
|
8659
|
+
expectedAuthority,
|
|
8299
8660
|
);
|
|
8300
8661
|
}
|
|
8301
8662
|
const sessionState = publicCoordinatorSessionState(
|
|
@@ -8304,7 +8665,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8304
8665
|
return {
|
|
8305
8666
|
ok: true,
|
|
8306
8667
|
session: publicCoordinatorStatusSession(session),
|
|
8307
|
-
|
|
8668
|
+
// Broker indexing is the authority for endpoint liveness. A runtime
|
|
8669
|
+
// sidecar can retain a stale `live:true` projection after its
|
|
8670
|
+
// endpoint incarnation rotates, so it must not mask `not_indexed`.
|
|
8671
|
+
status: { ...(sessionState ?? {}), ...brokerLiveness(indexedSession ?? null) },
|
|
8308
8672
|
session_state: sessionState,
|
|
8309
8673
|
};
|
|
8310
8674
|
} catch (error) {
|
|
@@ -8537,14 +8901,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8537
8901
|
// repair. The fresh journal snapshot below is the first public read after
|
|
8538
8902
|
// that repair/export sequence, so events are visible after the caller's
|
|
8539
8903
|
// cursor without allowing recovery to erase a terminal observation.
|
|
8540
|
-
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null);
|
|
8904
|
+
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null, reconcileOptions);
|
|
8541
8905
|
await exportRetainedDeliveries(32);
|
|
8542
8906
|
} catch (error) {
|
|
8543
8907
|
if (!(error instanceof Error && error.message === "resource_gone")) throw error;
|
|
8544
8908
|
}
|
|
8545
8909
|
} else if (!watchController.signal.aborted && Date.now() < absoluteDeadline) {
|
|
8546
|
-
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null);
|
|
8547
8910
|
try {
|
|
8911
|
+
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null, reconcileOptions);
|
|
8548
8912
|
await exportRetainedDeliveries(32, watchController.signal);
|
|
8549
8913
|
await reconcileWatchAdmissions(prioritySessionId, reconcileOptions);
|
|
8550
8914
|
await reconcileActiveTurnAcknowledgements(
|
|
@@ -8645,7 +9009,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8645
9009
|
if (matched.length === 0 && timeoutMs > 0) {
|
|
8646
9010
|
const deadline = absoluteDeadline;
|
|
8647
9011
|
while (matched.length === 0 && Date.now() < deadline && !watchController.signal.aborted) {
|
|
8648
|
-
await waitForCoordinatorEvents(
|
|
9012
|
+
await waitForCoordinatorEvents(
|
|
9013
|
+
namespaceDir,
|
|
9014
|
+
Math.min(50, Math.max(1, deadline - Date.now())),
|
|
9015
|
+
services.onCoordinatorEventWatchReady,
|
|
9016
|
+
services.onCoordinatorEventWake,
|
|
9017
|
+
);
|
|
8649
9018
|
if (Date.now() >= deadline || watchController.signal.aborted) break;
|
|
8650
9019
|
try {
|
|
8651
9020
|
await exportRetainedDeliveries(32, watchController.signal);
|
|
@@ -8858,6 +9227,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8858
9227
|
let initialDelegateRequest: CoordinatorSessionTransactionV1["recovery"]["initial_delegate_request"];
|
|
8859
9228
|
if (reusedSessionId) {
|
|
8860
9229
|
sessionId = reusedSessionId;
|
|
9230
|
+
// Record that this attempt has not claimed a prompt before any step
|
|
9231
|
+
// that can fail transiently. Only this explicit marker lets a same-key
|
|
9232
|
+
// retry claim afresh; a receipt without it has unknown provenance.
|
|
9233
|
+
if (!recovering && outer.value.delegate_prompt_claim_started === undefined)
|
|
9234
|
+
await writeCoordinatorIdempotencyFile(idempotencyFile(idempotencyKey), {
|
|
9235
|
+
...(outer.value as unknown as CoordinatorToolIdempotencyRecord),
|
|
9236
|
+
delegate_prompt_claim_started: false,
|
|
9237
|
+
});
|
|
8861
9238
|
const prior = await readSessionTransaction(questionPaths, sessionId);
|
|
8862
9239
|
const prompt = prior?.requests.prompts[requestKey];
|
|
8863
9240
|
delegateEffectStarted ||= Boolean(prompt && prompt.phase !== "claimed");
|
|
@@ -8889,13 +9266,58 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8889
9266
|
message: "Coordinator session is bound to another workspace.",
|
|
8890
9267
|
},
|
|
8891
9268
|
};
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
9269
|
+
// Delegate-created managed-worktree sessions retain the caller's
|
|
9270
|
+
// repository cwd separately from the broker's execution worktree.
|
|
9271
|
+
// Resolve endpoint authority in that persisted broker workspace;
|
|
9272
|
+
// using canonicalCwd here searches the parent repository and makes
|
|
9273
|
+
// every follow-up appear unindexed even while its endpoint is live.
|
|
9274
|
+
const persistedBrokerWorkspace = optionalString(existing.broker_workspace);
|
|
9275
|
+
if (!persistedBrokerWorkspace)
|
|
9276
|
+
return {
|
|
9277
|
+
ok: false,
|
|
9278
|
+
error: {
|
|
9279
|
+
code: "endpoint_stale",
|
|
9280
|
+
message: "Coordinator session endpoint authority is stale.",
|
|
9281
|
+
},
|
|
9282
|
+
};
|
|
9283
|
+
let bindingWorkspace: string;
|
|
9284
|
+
try {
|
|
9285
|
+
bindingWorkspace = await canonicalBrokerWorkspace(persistedBrokerWorkspace);
|
|
9286
|
+
} catch (error) {
|
|
9287
|
+
if (!(error instanceof SdkClientError) || error.code !== "not_found") throw error;
|
|
9288
|
+
return {
|
|
9289
|
+
ok: false,
|
|
9290
|
+
error: {
|
|
9291
|
+
code: "endpoint_stale",
|
|
9292
|
+
message: "Coordinator session endpoint authority is stale.",
|
|
9293
|
+
},
|
|
9294
|
+
};
|
|
9295
|
+
}
|
|
9296
|
+
// The caller cwd guard only proves args.cwd is inside the current roots.
|
|
9297
|
+
// The persisted pair is what the delegate actually dispatches through, so
|
|
9298
|
+
// reauthorize it against the current policy before it becomes binding scope:
|
|
9299
|
+
// a narrowed managed-worktree root or a redirected projection must not let
|
|
9300
|
+
// a reused delegate reach a workspace the coordinator no longer owns.
|
|
9301
|
+
try {
|
|
9302
|
+
await assertCoordinatorSessionLocations(config, existingCwd, bindingWorkspace, {
|
|
9303
|
+
canonicalizePath: services.canonicalizePath,
|
|
8897
9304
|
platform,
|
|
8898
|
-
)
|
|
9305
|
+
});
|
|
9306
|
+
} catch (error) {
|
|
9307
|
+
if (!isSessionAuthorityError(error)) throw error;
|
|
9308
|
+
return {
|
|
9309
|
+
ok: false,
|
|
9310
|
+
error: {
|
|
9311
|
+
code: "workspace_mismatch",
|
|
9312
|
+
message: "Coordinator session workspace is outside the allowed roots.",
|
|
9313
|
+
},
|
|
9314
|
+
};
|
|
9315
|
+
}
|
|
9316
|
+
const binding = await exactBrokerSessionBinding(sessionId, bindingWorkspace);
|
|
9317
|
+
if (
|
|
9318
|
+
// The caller cwd guard above prevents cross-workspace reuse; this
|
|
9319
|
+
// comparison fences the persisted endpoint identity itself.
|
|
9320
|
+
!sameCanonicalPath(binding.workspace, bindingWorkspace, platform) ||
|
|
8899
9321
|
existing.endpoint_generation !== binding.endpointGeneration ||
|
|
8900
9322
|
optionalString(existing.endpoint_incarnation) !== binding.endpointIncarnation
|
|
8901
9323
|
)
|
|
@@ -9025,8 +9447,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9025
9447
|
(await readCanonicalActiveTurn(sessionId));
|
|
9026
9448
|
const priorTransaction = await readSessionTransaction(questionPaths, sessionId);
|
|
9027
9449
|
const priorPrompt = priorTransaction?.requests.prompts[requestKey];
|
|
9450
|
+
// A reused session has no creation authority to recover through, so a
|
|
9451
|
+
// same-key retry after a pre-admission failure (for example a transient
|
|
9452
|
+
// workspace canonicalization error) would otherwise seal as uncertain. The
|
|
9453
|
+
// outer receipt records whether a prompt claim began: an explicit `false`
|
|
9454
|
+
// proves none was claimed and this retry may claim afresh. `true` or an
|
|
9455
|
+
// absent marker means missing live state is still not proof that nothing
|
|
9456
|
+
// was dispatched.
|
|
9457
|
+
const requireClaimedPrompt =
|
|
9458
|
+
recovering && (!reusedSessionId || outer.value.delegate_prompt_claim_started !== false);
|
|
9028
9459
|
if (
|
|
9029
|
-
|
|
9460
|
+
requireClaimedPrompt &&
|
|
9030
9461
|
!priorPrompt &&
|
|
9031
9462
|
!hasInitialDelegatePromptAuthority(priorTransaction, initialDelegateRequest)
|
|
9032
9463
|
)
|
|
@@ -9056,13 +9487,18 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9056
9487
|
turn_id: previousActiveTurn.turn_id,
|
|
9057
9488
|
};
|
|
9058
9489
|
}
|
|
9490
|
+
if (outer.value.delegate_prompt_claim_started !== true)
|
|
9491
|
+
await writeCoordinatorIdempotencyFile(idempotencyFile(idempotencyKey), {
|
|
9492
|
+
...(outer.value as unknown as CoordinatorToolIdempotencyRecord),
|
|
9493
|
+
delegate_prompt_claim_started: true,
|
|
9494
|
+
});
|
|
9059
9495
|
const promptKey = await claimCanonicalPrompt(
|
|
9060
9496
|
sessionId,
|
|
9061
9497
|
taggedPrompt,
|
|
9062
9498
|
operation,
|
|
9063
9499
|
idempotencyKey,
|
|
9064
9500
|
true,
|
|
9065
|
-
|
|
9501
|
+
requireClaimedPrompt,
|
|
9066
9502
|
initialDelegateRequest,
|
|
9067
9503
|
);
|
|
9068
9504
|
if (reserved?.terminal_fence && !(await promptReceipt(sessionId, promptKey))) {
|
|
@@ -9209,6 +9645,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9209
9645
|
true,
|
|
9210
9646
|
response =>
|
|
9211
9647
|
(!delegateResponseComplete && (creationRemoteStarted || delegateEffectStarted)) ||
|
|
9648
|
+
isTransientDelegateAuthorityFailure(response) ||
|
|
9212
9649
|
isRouterRequestAmbiguous(response),
|
|
9213
9650
|
);
|
|
9214
9651
|
}
|
|
@@ -11000,7 +11437,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
11000
11437
|
if (request.method === "tools/call") {
|
|
11001
11438
|
const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };
|
|
11002
11439
|
const payload = await callTool(params.name ?? "", params.arguments ?? {});
|
|
11003
|
-
return { jsonrpc: "2.0", id, result:
|
|
11440
|
+
return { jsonrpc: "2.0", id, result: coordinatorToolResult(payload) };
|
|
11004
11441
|
}
|
|
11005
11442
|
return { jsonrpc: "2.0", id, error: { code: -32601, message: `unknown_method:${request.method}` } };
|
|
11006
11443
|
}
|
|
@@ -11022,8 +11459,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
11022
11459
|
};
|
|
11023
11460
|
}
|
|
11024
11461
|
|
|
11025
|
-
function
|
|
11026
|
-
const
|
|
11462
|
+
function coordinatorToolResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
|
|
11463
|
+
const record = asRecord(payload);
|
|
11464
|
+
// The legacy ok:false means the observation window ended, not that the RPC
|
|
11465
|
+
// failed. Keep its payload contract while preventing host error/retry breakers.
|
|
11466
|
+
const observationExpired = record?.wait_expired === true && record.reason === "timeout" && !record.error;
|
|
11467
|
+
const failed = record?.ok === false && !observationExpired;
|
|
11027
11468
|
return textResult(payload, failed);
|
|
11028
11469
|
}
|
|
11029
11470
|
|
|
@@ -11068,7 +11509,7 @@ export async function handleCoordinatorMcpRequest(
|
|
|
11068
11509
|
return {
|
|
11069
11510
|
jsonrpc: "2.0",
|
|
11070
11511
|
id: request.id ?? null,
|
|
11071
|
-
result:
|
|
11512
|
+
result: coordinatorToolResult(await server.callTool(params.name ?? "", args)),
|
|
11072
11513
|
};
|
|
11073
11514
|
} finally {
|
|
11074
11515
|
await server.close();
|