@gajae-code/coding-agent 0.17.2 → 0.17.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +267 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +1049 -3
- package/dist/types/config/file-lock.d.ts +13 -3
- package/dist/types/config/model-profile-activation.d.ts +45 -1
- package/dist/types/config/model-registry.d.ts +14 -3
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
- package/dist/types/harness-control-plane/finalize.d.ts +14 -3
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +23 -1
- package/dist/types/modes/acp/acp-agent.d.ts +55 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +69 -6
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/broker/broker.d.ts +17 -4
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/identity.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
- package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +3 -3
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +175 -23
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tools/bash-interactive.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/fetch.d.ts +10 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/tools/subagent.d.ts +8 -1
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +22 -1
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +541 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +10 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli/stats-cli.ts +11 -1
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +175 -117
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +543 -3
- package/src/config/file-lock.ts +206 -65
- package/src/config/model-discovery-manager.ts +4 -4
- package/src/config/model-profile-activation.ts +417 -66
- package/src/config/model-registry.ts +254 -36
- package/src/config/model-resolver.ts +6 -1
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +179 -9
- package/src/coordinator-mcp/server.ts +521 -87
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/exec/bash-executor.ts +3 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/state-writer.ts +317 -6
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/harness-control-plane/control-endpoint.ts +24 -6
- package/src/harness-control-plane/finalize.ts +312 -25
- package/src/harness-control-plane/owner.ts +323 -89
- package/src/internal-urls/docs-index.generated.ts +23 -20
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +192 -74
- package/src/modes/acp/acp-agent.ts +973 -56
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/gjc-bundle-settings.ts +21 -1
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/tool-status-header.ts +72 -6
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +161 -15
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/loader.ts +6 -3
- package/src/runtime-mcp/manager.ts +845 -109
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +27 -8
- package/src/sdk/broker/broker.ts +1431 -198
- package/src/sdk/broker/ensure.ts +21 -3
- package/src/sdk/broker/identity.ts +22 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/managed-task-dag.ts +1927 -0
- package/src/sdk/broker/managed-task-verification.ts +642 -0
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +175 -14
- package/src/sdk/broker/startup-failure.ts +80 -1
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +707 -81
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +705 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +184 -2
- package/src/sdk/host/session-runtime.ts +322 -101
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/lifecycle/service.ts +2 -0
- package/src/sdk/prompt-deadline-flush.ts +356 -0
- package/src/sdk/prompt-deadline-manager.ts +133 -1
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/session-router.ts +9 -1
- package/src/sdk/session.ts +763 -116
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +397 -42
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2472 -407
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +68 -18
- package/src/session/internal/managed-session-storage.ts +334 -12
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +259 -15
- package/src/task/executor.ts +46 -17
- package/src/task/index.ts +14 -2
- package/src/task/types.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -0
- package/src/tools/bash.ts +118 -78
- package/src/tools/fetch.ts +140 -25
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/subagent-render.ts +24 -1
- package/src/tools/subagent.ts +27 -1
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf.ts +136 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
|
@@ -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,
|
|
@@ -516,6 +519,11 @@ const OBSERVED_BROKER_FAILURE_CODES = new Set([
|
|
|
516
519
|
const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
|
|
517
520
|
const PROMPT_ACK_TIMEOUT_REASON = "runtime_prompt_ack_timeout";
|
|
518
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
|
+
}
|
|
519
527
|
/**
|
|
520
528
|
* Pagination window for `readCompleteQ12Snapshot`: it stops starting pages once
|
|
521
529
|
* this much time has passed and gives each page only the remainder as its reply
|
|
@@ -527,6 +535,26 @@ const Q12_SNAPSHOT_BUDGET_MS = 5_000;
|
|
|
527
535
|
const MAX_RUNTIME_SESSIONS_PER_WATCH_PASS = 4;
|
|
528
536
|
const MAX_Q12_ATTEMPTS_PER_WATCH_PASS = 2;
|
|
529
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
|
+
}
|
|
530
558
|
const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
|
|
531
559
|
const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
|
|
532
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;
|
|
@@ -741,7 +769,8 @@ function toolSchema(
|
|
|
741
769
|
if (name === "gjc_coordinator_await_turn") {
|
|
742
770
|
return {
|
|
743
771
|
name,
|
|
744
|
-
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.",
|
|
745
774
|
inputSchema: {
|
|
746
775
|
type: "object",
|
|
747
776
|
properties: {
|
|
@@ -1314,6 +1343,11 @@ interface CoordinatorToolIdempotencyRecord {
|
|
|
1314
1343
|
request_digest: string;
|
|
1315
1344
|
state: "in_progress" | "completed";
|
|
1316
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;
|
|
1317
1351
|
admission?: DelegateAdmissionCheckpoint;
|
|
1318
1352
|
delegate_response_pin?: DelegateResponsePinV1;
|
|
1319
1353
|
created_at: string;
|
|
@@ -1393,7 +1427,24 @@ function boundedPublicValue(value: unknown, budget: { remaining: number }, depth
|
|
|
1393
1427
|
: undefined;
|
|
1394
1428
|
const code =
|
|
1395
1429
|
typeof rawCode === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, rawCode) ? rawCode : "unavailable";
|
|
1396
|
-
|
|
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
|
+
};
|
|
1397
1448
|
} else {
|
|
1398
1449
|
output[key] = sensitivePublicField(key) ? "[redacted]" : boundedPublicValue(field, budget, depth + 1);
|
|
1399
1450
|
}
|
|
@@ -3855,7 +3906,25 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
3855
3906
|
): Promise<Map<string, string>> {
|
|
3856
3907
|
const authorized = new Map<string, string>();
|
|
3857
3908
|
for (const sessionId of new Set(sessionIds)) {
|
|
3858
|
-
|
|
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
|
+
}
|
|
3859
3928
|
if (!transaction) {
|
|
3860
3929
|
if (scopedSessionId === sessionId && !allowMissingSessionIds.has(sessionId))
|
|
3861
3930
|
throw new Error("resource_gone");
|
|
@@ -4454,6 +4523,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4454
4523
|
questionId: string;
|
|
4455
4524
|
eventId: string;
|
|
4456
4525
|
}> = [];
|
|
4526
|
+
let pendingGateCount = 0;
|
|
4457
4527
|
let q12Admitted = true;
|
|
4458
4528
|
let liveInFlightAskAdmitted = false;
|
|
4459
4529
|
let liveInFlightCandidate = false;
|
|
@@ -4543,10 +4613,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4543
4613
|
byRuntimeTurn.set(runtimeTurnId, owners);
|
|
4544
4614
|
}
|
|
4545
4615
|
for (const row of items as WorkflowGateQueryRecord[]) {
|
|
4546
|
-
if (!row || typeof row !== "object" ||
|
|
4616
|
+
if (!row || typeof row !== "object" || typeof row.gate_id !== "string") {
|
|
4617
|
+
diagnostic("invalid_gate_row");
|
|
4618
|
+
continue;
|
|
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") {
|
|
4547
4625
|
diagnostic("invalid_gate_row");
|
|
4548
4626
|
continue;
|
|
4549
4627
|
}
|
|
4628
|
+
pendingGateCount++;
|
|
4550
4629
|
const gate = row as WorkflowGateQueryRecord & WorkflowGate;
|
|
4551
4630
|
const authorityId = createHash("sha256")
|
|
4552
4631
|
.update(
|
|
@@ -4792,7 +4871,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4792
4871
|
// even while an authenticated, acknowledged turn is still running.
|
|
4793
4872
|
// Nonempty snapshots still require an admitted ask; all writer, waiting
|
|
4794
4873
|
// token and terminal fences above remain authoritative.
|
|
4795
|
-
if (!q12Admitted || (liveInFlightCandidate &&
|
|
4874
|
+
if (!q12Admitted || (liveInFlightCandidate && pendingGateCount > 0 && !liveInFlightAskAdmitted))
|
|
4796
4875
|
return {
|
|
4797
4876
|
ok: true,
|
|
4798
4877
|
schema_version: 1,
|
|
@@ -5137,6 +5216,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5137
5216
|
return asRecord(response.error)?.code === "ambiguous";
|
|
5138
5217
|
}
|
|
5139
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
|
+
|
|
5140
5224
|
function publicErrorCode(code: unknown): string {
|
|
5141
5225
|
return typeof code === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, code) ? code : "unavailable";
|
|
5142
5226
|
}
|
|
@@ -5147,7 +5231,22 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5147
5231
|
const messageCode =
|
|
5148
5232
|
error instanceof Error && Object.hasOwn(PUBLIC_ERROR_MESSAGES, error.message) ? error.message : undefined;
|
|
5149
5233
|
const code = publicErrorCode(directCode ?? messageCode);
|
|
5150
|
-
|
|
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
|
+
};
|
|
5151
5250
|
}
|
|
5152
5251
|
function sdkError(error: unknown): Record<string, unknown> {
|
|
5153
5252
|
return publicError(error);
|
|
@@ -5728,10 +5827,55 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5728
5827
|
);
|
|
5729
5828
|
}
|
|
5730
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
|
+
|
|
5731
5865
|
type BrokerSessionAuthority = {
|
|
5732
5866
|
workspace: string;
|
|
5733
5867
|
endpointGeneration: number;
|
|
5734
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;
|
|
5735
5879
|
};
|
|
5736
5880
|
|
|
5737
5881
|
async function exactBrokerSessionAuthority(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
|
|
@@ -5751,17 +5895,35 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5751
5895
|
if (sameCanonicalPath(canonicalWorkspace, workspace, platform))
|
|
5752
5896
|
matches.push({ session, workspace: canonicalWorkspace });
|
|
5753
5897
|
}
|
|
5754
|
-
if (matches.length
|
|
5898
|
+
if (matches.length === 0)
|
|
5755
5899
|
throw new SdkClientError(
|
|
5756
5900
|
"not_found",
|
|
5757
5901
|
"Session is not uniquely indexed in the requested coordinator workspace.",
|
|
5758
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
|
+
);
|
|
5759
5908
|
const match = matches[0]!;
|
|
5760
5909
|
const endpointGeneration = brokerEndpointGeneration(match.session);
|
|
5761
5910
|
const endpointIncarnation = brokerEndpointIncarnation(match.session, sessionId);
|
|
5762
5911
|
if (endpointGeneration === null || endpointIncarnation === null)
|
|
5763
5912
|
throw new SdkClientError("endpoint_stale", "Broker session has no usable endpoint incarnation.");
|
|
5764
|
-
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
|
+
};
|
|
5765
5927
|
}
|
|
5766
5928
|
|
|
5767
5929
|
async function exactBrokerSessionBinding(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
|
|
@@ -5850,13 +6012,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5850
6012
|
const sessionId = brokerSessionId(session);
|
|
5851
6013
|
return sessionId !== null && registered.has(sessionId);
|
|
5852
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
|
+
}
|
|
5853
6047
|
async function removeReapedProjection(
|
|
5854
6048
|
sessionId: string,
|
|
5855
6049
|
turnIds: readonly string[],
|
|
5856
6050
|
reportIds: readonly string[],
|
|
5857
6051
|
): Promise<void> {
|
|
5858
6052
|
for (const turnId of turnIds) await fs.rm(turnFile(namespaceDir, turnId), { force: true });
|
|
5859
|
-
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 });
|
|
5860
6058
|
await fs.rm(sessionFile(sessionId), { force: true });
|
|
5861
6059
|
await fs.rm(sessionStateFile(namespaceDir, sessionId), { force: true });
|
|
5862
6060
|
await fs.rm(activeTurnFile(namespaceDir, sessionId), { force: true });
|
|
@@ -6046,19 +6244,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6046
6244
|
// manifest first, even while the session projection remains readable, so a
|
|
6047
6245
|
// verification-uncertain retry never reaches broker close again.
|
|
6048
6246
|
await ensureQuestionStateReady();
|
|
6049
|
-
let pendingDeletion = await withNamespaceRegistry(
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6247
|
+
let pendingDeletion = await withNamespaceRegistry(questionPaths, async registry => {
|
|
6248
|
+
const deletions = Object.values(registry.deletions);
|
|
6249
|
+
return (
|
|
6250
|
+
deletions
|
|
6053
6251
|
.filter(
|
|
6054
6252
|
entry =>
|
|
6055
6253
|
entry.session_id === id &&
|
|
6056
6254
|
(entry.phase === "intent" ||
|
|
6057
6255
|
entry.phase === "broker_closed" ||
|
|
6058
|
-
entry.phase === "cleanup_pending")
|
|
6256
|
+
entry.phase === "cleanup_pending") &&
|
|
6257
|
+
!isSupersededDeletionIntent(entry, deletions),
|
|
6059
6258
|
)
|
|
6060
|
-
.sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? null
|
|
6061
|
-
|
|
6259
|
+
.sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? null
|
|
6260
|
+
);
|
|
6261
|
+
});
|
|
6062
6262
|
if (pendingDeletion?.phase === "intent") {
|
|
6063
6263
|
try {
|
|
6064
6264
|
await recoverIntentDeletion(pendingDeletion);
|
|
@@ -6086,7 +6286,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6086
6286
|
const deletion = await withNamespaceRegistry(questionPaths, async registry => {
|
|
6087
6287
|
const matching = Object.values(registry.deletions).filter(entry => entry.session_id === id);
|
|
6088
6288
|
return (
|
|
6089
|
-
matching.find(entry => entry.phase !== "completed") ??
|
|
6289
|
+
matching.find(entry => entry.phase !== "completed" && !isSupersededDeletionIntent(entry, matching)) ??
|
|
6090
6290
|
matching.find(entry => entry.safe_response !== undefined) ??
|
|
6091
6291
|
null
|
|
6092
6292
|
);
|
|
@@ -6215,6 +6415,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6215
6415
|
}
|
|
6216
6416
|
const deletionId = `delete:${id}:${persistedIncarnation}`;
|
|
6217
6417
|
const deletionKey = createHash("sha256").update(deletionId).digest("hex");
|
|
6418
|
+
const projectedReportIds = (await reportProjectionFilesForSession(id)).map(file =>
|
|
6419
|
+
path.basename(file, ".json"),
|
|
6420
|
+
);
|
|
6218
6421
|
const deletionEntry = {
|
|
6219
6422
|
deletion_id: deletionId,
|
|
6220
6423
|
session_id: id,
|
|
@@ -6231,7 +6434,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6231
6434
|
session: false,
|
|
6232
6435
|
events: false,
|
|
6233
6436
|
turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
|
|
6234
|
-
report_ids:
|
|
6437
|
+
report_ids: [
|
|
6438
|
+
...new Set([
|
|
6439
|
+
...(canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : []),
|
|
6440
|
+
...projectedReportIds,
|
|
6441
|
+
]),
|
|
6442
|
+
],
|
|
6235
6443
|
},
|
|
6236
6444
|
authority_digest: deletionKey,
|
|
6237
6445
|
created_at: new Date().toISOString(),
|
|
@@ -6252,7 +6460,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6252
6460
|
}
|
|
6253
6461
|
const projectionIds = {
|
|
6254
6462
|
turnIds: Object.keys(admitted.canonical.turns),
|
|
6255
|
-
reportIds:
|
|
6463
|
+
reportIds: [
|
|
6464
|
+
...new Set([
|
|
6465
|
+
...Object.keys(admitted.canonical.reports),
|
|
6466
|
+
...(await reportProjectionFilesForSession(id)).map(file => path.basename(file, ".json")),
|
|
6467
|
+
]),
|
|
6468
|
+
],
|
|
6256
6469
|
};
|
|
6257
6470
|
await ensureQuestionStateReady();
|
|
6258
6471
|
const deletionPhase = await withNamespaceRegistry(
|
|
@@ -6409,8 +6622,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6409
6622
|
/**
|
|
6410
6623
|
* Prove a stored endpoint identity (workspace + generation + incarnation) is no
|
|
6411
6624
|
* longer current against the live broker index. Returns true only when the
|
|
6412
|
-
* broker row is absent (not_found)
|
|
6413
|
-
* 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
|
|
6414
6629
|
* returns false: we cannot prove the endpoint recovered, but we also must not
|
|
6415
6630
|
* force-evict on a flake — the normal reap path will retry.
|
|
6416
6631
|
*/
|
|
@@ -6424,6 +6639,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6424
6639
|
const workspace = await canonicalBrokerWorkspace(persistedWorkspace);
|
|
6425
6640
|
const authority = await exactBrokerSessionAuthority(id, workspace);
|
|
6426
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) ||
|
|
6427
6653
|
!sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
|
|
6428
6654
|
authority.endpointGeneration !== persistedGeneration ||
|
|
6429
6655
|
authority.endpointIncarnation !== persistedIncarnation
|
|
@@ -6535,8 +6761,50 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6535
6761
|
// projection-only delete.
|
|
6536
6762
|
return;
|
|
6537
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
|
+
}
|
|
6538
6803
|
const deletionId = `force-evict:${sessionId}:${persistedIncarnation}`;
|
|
6539
6804
|
const deletionKey = createHash("sha256").update(deletionId).digest("hex");
|
|
6805
|
+
const projectedReportIds = (await reportProjectionFilesForSession(sessionId)).map(file =>
|
|
6806
|
+
path.basename(file, ".json"),
|
|
6807
|
+
);
|
|
6540
6808
|
const now = new Date().toISOString();
|
|
6541
6809
|
const entry: NamespaceDeletionEntryV1 = {
|
|
6542
6810
|
deletion_id: deletionId,
|
|
@@ -6554,7 +6822,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6554
6822
|
session: false,
|
|
6555
6823
|
events: false,
|
|
6556
6824
|
turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
|
|
6557
|
-
report_ids:
|
|
6825
|
+
report_ids: [
|
|
6826
|
+
...new Set([
|
|
6827
|
+
...(canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : []),
|
|
6828
|
+
...projectedReportIds,
|
|
6829
|
+
]),
|
|
6830
|
+
],
|
|
6558
6831
|
},
|
|
6559
6832
|
authority_digest: deletionKey,
|
|
6560
6833
|
created_at: now,
|
|
@@ -6576,6 +6849,24 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6576
6849
|
try {
|
|
6577
6850
|
await ensureQuestionTransaction(sessionId);
|
|
6578
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
|
+
}
|
|
6579
6870
|
if (!(error instanceof Error) || error.message !== "resource_gone") throw error;
|
|
6580
6871
|
}
|
|
6581
6872
|
try {
|
|
@@ -6612,7 +6903,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6612
6903
|
},
|
|
6613
6904
|
reapSession: async (sessionId: string): Promise<void> => {
|
|
6614
6905
|
const result = await reapSession(sessionId, { reason: "idle_reaper" });
|
|
6615
|
-
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"));
|
|
6616
6915
|
},
|
|
6617
6916
|
markSessionDead: async (sessionId: string): Promise<void> => {
|
|
6618
6917
|
// Force-evict a session that cannot be reaped normally (endpoint_stale
|
|
@@ -6788,6 +7087,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6788
7087
|
return {
|
|
6789
7088
|
ok: false,
|
|
6790
7089
|
reason: "timeout",
|
|
7090
|
+
wait_expired: true,
|
|
6791
7091
|
turn: payload.turn,
|
|
6792
7092
|
advisory_status: payload.advisory_status,
|
|
6793
7093
|
session_state: payload.session_state,
|
|
@@ -7411,6 +7711,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7411
7711
|
await writeTurnRecord(namespaceDir, turnFromCanonical(turn));
|
|
7412
7712
|
for (const report of Object.values(canonical.reports))
|
|
7413
7713
|
await writeJsonFile(reportProjectionFile(namespaceDir, report.report_id), report);
|
|
7714
|
+
await removeStaleReportProjections(sessionId, new Set(Object.keys(canonical.reports)));
|
|
7414
7715
|
const activeId = canonical.queue.selected_promotion?.to_turn_id ?? canonical.queue.active_turn_id;
|
|
7415
7716
|
const active = activeId ? canonical.turns[activeId] : null;
|
|
7416
7717
|
if (active) await writeActiveTurn(namespaceDir, turnFromCanonical(active));
|
|
@@ -7489,8 +7790,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7489
7790
|
await exportRetainedDeliveries(32, options.signal);
|
|
7490
7791
|
}
|
|
7491
7792
|
|
|
7492
|
-
async function recoverCanonicalSessionProjection(
|
|
7493
|
-
|
|
7793
|
+
async function recoverCanonicalSessionProjection(
|
|
7794
|
+
sessionId: string,
|
|
7795
|
+
options: { signal?: AbortSignal } = {},
|
|
7796
|
+
): Promise<void> {
|
|
7797
|
+
const transaction = await repairLegacyReportIds(questionPaths, sessionId, options);
|
|
7494
7798
|
if (!transaction) return;
|
|
7495
7799
|
const applied = Math.min(
|
|
7496
7800
|
transaction.projection.applied_turns_revision,
|
|
@@ -7499,10 +7803,13 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7499
7803
|
transaction.projection.applied_active_revision,
|
|
7500
7804
|
transaction.projection.applied_events_revision,
|
|
7501
7805
|
);
|
|
7502
|
-
if (applied < transaction.revision) await repairCanonicalProjections(sessionId);
|
|
7806
|
+
if (applied < transaction.revision) await repairCanonicalProjections(sessionId, options);
|
|
7503
7807
|
}
|
|
7504
7808
|
|
|
7505
|
-
async function recoverCanonicalNamespaceProjections(
|
|
7809
|
+
async function recoverCanonicalNamespaceProjections(
|
|
7810
|
+
scopedSessionId: string | null = null,
|
|
7811
|
+
options: { signal?: AbortSignal } = {},
|
|
7812
|
+
): Promise<void> {
|
|
7506
7813
|
await ensureQuestionStateReady();
|
|
7507
7814
|
const roster = await readSchedulerRoster(questionPaths);
|
|
7508
7815
|
const persisted = await fs.readdir(questionPaths.sessions).catch(() => []);
|
|
@@ -7517,13 +7824,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7517
7824
|
const transaction = await readSessionTransaction(questionPaths, sessionId);
|
|
7518
7825
|
if (!transaction) continue;
|
|
7519
7826
|
await assertPersistedSessionAuthority(transaction.canonical.session);
|
|
7520
|
-
await recoverCanonicalSessionProjection(sessionId);
|
|
7827
|
+
await recoverCanonicalSessionProjection(sessionId, options);
|
|
7521
7828
|
} catch (error) {
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
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;
|
|
7527
7842
|
}
|
|
7528
7843
|
}
|
|
7529
7844
|
}
|
|
@@ -7845,12 +8160,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7845
8160
|
try {
|
|
7846
8161
|
await reconcileSessionRuntime(entry.session_id, options);
|
|
7847
8162
|
} catch (error) {
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
(error.message
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
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;
|
|
7854
8169
|
}
|
|
7855
8170
|
}
|
|
7856
8171
|
if (processedCursor && !options.signal?.aborted)
|
|
@@ -7879,7 +8194,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7879
8194
|
await reconcileSessionRuntime(sessionId, options);
|
|
7880
8195
|
await reconcileQuestions(sessionId, options);
|
|
7881
8196
|
} catch (error) {
|
|
7882
|
-
if (
|
|
8197
|
+
if (
|
|
8198
|
+
!(error instanceof Error) ||
|
|
8199
|
+
(error.message !== "resource_gone" &&
|
|
8200
|
+
error.message !== "state_corrupt" &&
|
|
8201
|
+
!isSessionAuthorityError(error))
|
|
8202
|
+
)
|
|
7883
8203
|
throw error;
|
|
7884
8204
|
}
|
|
7885
8205
|
}
|
|
@@ -7913,12 +8233,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7913
8233
|
try {
|
|
7914
8234
|
await reconcileQuestions(entry.session_id, options);
|
|
7915
8235
|
} catch (error) {
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
(error.message
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
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;
|
|
7922
8242
|
}
|
|
7923
8243
|
}
|
|
7924
8244
|
if (processedCursor && !options.signal?.aborted)
|
|
@@ -7958,14 +8278,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7958
8278
|
await assertPersistedSessionAuthority(transaction.canonical.session);
|
|
7959
8279
|
turn = await readActiveTurn(namespaceDir, entry.session_id);
|
|
7960
8280
|
} catch (error) {
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
(error.message
|
|
7964
|
-
error
|
|
7965
|
-
error.message
|
|
7966
|
-
|
|
7967
|
-
)
|
|
7968
|
-
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;
|
|
7969
8287
|
}
|
|
7970
8288
|
processedCursor = entry.session_id;
|
|
7971
8289
|
if (!turn) continue;
|
|
@@ -8054,9 +8372,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8054
8372
|
};
|
|
8055
8373
|
const binding = await exactBrokerSessionBinding(sessionId, cwd);
|
|
8056
8374
|
const priorSession = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
8375
|
+
const priorWorkspace = optionalString(priorSession?.broker_workspace);
|
|
8057
8376
|
const priorAuthority =
|
|
8058
8377
|
priorSession &&
|
|
8059
|
-
|
|
8378
|
+
priorWorkspace !== null &&
|
|
8379
|
+
sameCanonicalPath(priorWorkspace, binding.workspace, platform) &&
|
|
8060
8380
|
priorSession.endpoint_generation === binding.endpointGeneration &&
|
|
8061
8381
|
optionalString(priorSession.endpoint_incarnation) === binding.endpointIncarnation
|
|
8062
8382
|
? (priorSession.sidecar_verifier as { key_id: string; public_key: string } | undefined)
|
|
@@ -8294,18 +8614,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8294
8614
|
};
|
|
8295
8615
|
await reconcileSessionRuntime(canonicalSessionId, { observeQuestions: false });
|
|
8296
8616
|
try {
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
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,
|
|
8300
8649
|
);
|
|
8301
|
-
//
|
|
8302
|
-
//
|
|
8303
|
-
//
|
|
8304
|
-
//
|
|
8305
|
-
|
|
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) {
|
|
8306
8656
|
const listing = await paginatedBrokerSessionList(brokerWorkspace, { cwd: brokerWorkspace });
|
|
8307
|
-
indexedSession =
|
|
8308
|
-
|
|
8657
|
+
indexedSession = await findPersistedBrokerAuthority(
|
|
8658
|
+
jsonRecords(Array.isArray(listing.sessions) ? listing.sessions : []),
|
|
8659
|
+
expectedAuthority,
|
|
8309
8660
|
);
|
|
8310
8661
|
}
|
|
8311
8662
|
const sessionState = publicCoordinatorSessionState(
|
|
@@ -8314,7 +8665,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8314
8665
|
return {
|
|
8315
8666
|
ok: true,
|
|
8316
8667
|
session: publicCoordinatorStatusSession(session),
|
|
8317
|
-
|
|
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) },
|
|
8318
8672
|
session_state: sessionState,
|
|
8319
8673
|
};
|
|
8320
8674
|
} catch (error) {
|
|
@@ -8547,14 +8901,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8547
8901
|
// repair. The fresh journal snapshot below is the first public read after
|
|
8548
8902
|
// that repair/export sequence, so events are visible after the caller's
|
|
8549
8903
|
// cursor without allowing recovery to erase a terminal observation.
|
|
8550
|
-
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null);
|
|
8904
|
+
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null, reconcileOptions);
|
|
8551
8905
|
await exportRetainedDeliveries(32);
|
|
8552
8906
|
} catch (error) {
|
|
8553
8907
|
if (!(error instanceof Error && error.message === "resource_gone")) throw error;
|
|
8554
8908
|
}
|
|
8555
8909
|
} else if (!watchController.signal.aborted && Date.now() < absoluteDeadline) {
|
|
8556
|
-
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null);
|
|
8557
8910
|
try {
|
|
8911
|
+
await recoverCanonicalNamespaceProjections(prioritySessionId ?? null, reconcileOptions);
|
|
8558
8912
|
await exportRetainedDeliveries(32, watchController.signal);
|
|
8559
8913
|
await reconcileWatchAdmissions(prioritySessionId, reconcileOptions);
|
|
8560
8914
|
await reconcileActiveTurnAcknowledgements(
|
|
@@ -8873,6 +9227,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8873
9227
|
let initialDelegateRequest: CoordinatorSessionTransactionV1["recovery"]["initial_delegate_request"];
|
|
8874
9228
|
if (reusedSessionId) {
|
|
8875
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
|
+
});
|
|
8876
9238
|
const prior = await readSessionTransaction(questionPaths, sessionId);
|
|
8877
9239
|
const prompt = prior?.requests.prompts[requestKey];
|
|
8878
9240
|
delegateEffectStarted ||= Boolean(prompt && prompt.phase !== "claimed");
|
|
@@ -8904,13 +9266,58 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8904
9266
|
message: "Coordinator session is bound to another workspace.",
|
|
8905
9267
|
},
|
|
8906
9268
|
};
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
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,
|
|
8912
9304
|
platform,
|
|
8913
|
-
)
|
|
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) ||
|
|
8914
9321
|
existing.endpoint_generation !== binding.endpointGeneration ||
|
|
8915
9322
|
optionalString(existing.endpoint_incarnation) !== binding.endpointIncarnation
|
|
8916
9323
|
)
|
|
@@ -9040,8 +9447,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9040
9447
|
(await readCanonicalActiveTurn(sessionId));
|
|
9041
9448
|
const priorTransaction = await readSessionTransaction(questionPaths, sessionId);
|
|
9042
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);
|
|
9043
9459
|
if (
|
|
9044
|
-
|
|
9460
|
+
requireClaimedPrompt &&
|
|
9045
9461
|
!priorPrompt &&
|
|
9046
9462
|
!hasInitialDelegatePromptAuthority(priorTransaction, initialDelegateRequest)
|
|
9047
9463
|
)
|
|
@@ -9071,13 +9487,18 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9071
9487
|
turn_id: previousActiveTurn.turn_id,
|
|
9072
9488
|
};
|
|
9073
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
|
+
});
|
|
9074
9495
|
const promptKey = await claimCanonicalPrompt(
|
|
9075
9496
|
sessionId,
|
|
9076
9497
|
taggedPrompt,
|
|
9077
9498
|
operation,
|
|
9078
9499
|
idempotencyKey,
|
|
9079
9500
|
true,
|
|
9080
|
-
|
|
9501
|
+
requireClaimedPrompt,
|
|
9081
9502
|
initialDelegateRequest,
|
|
9082
9503
|
);
|
|
9083
9504
|
if (reserved?.terminal_fence && !(await promptReceipt(sessionId, promptKey))) {
|
|
@@ -9224,6 +9645,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9224
9645
|
true,
|
|
9225
9646
|
response =>
|
|
9226
9647
|
(!delegateResponseComplete && (creationRemoteStarted || delegateEffectStarted)) ||
|
|
9648
|
+
isTransientDelegateAuthorityFailure(response) ||
|
|
9227
9649
|
isRouterRequestAmbiguous(response),
|
|
9228
9650
|
);
|
|
9229
9651
|
}
|
|
@@ -10573,16 +10995,24 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
10573
10995
|
}
|
|
10574
10996
|
if (status !== "accepted")
|
|
10575
10997
|
throw new SdkClientError("terminal_uncertain", "Workflow gate answer was not accepted.");
|
|
10576
|
-
const
|
|
10577
|
-
typeof resolution?.resolved_at === "string"
|
|
10998
|
+
const reportedResolvedAt =
|
|
10999
|
+
typeof resolution?.resolved_at === "string" &&
|
|
11000
|
+
Number.isFinite(Date.parse(resolution.resolved_at))
|
|
11001
|
+
? resolution.resolved_at
|
|
11002
|
+
: undefined;
|
|
11003
|
+
// The gate timestamp is remote metadata; retention must age the local receipt from
|
|
11004
|
+
// the coordinator's own persistence time so a skewed or replayed response cannot
|
|
11005
|
+
// delete a fresh answer.
|
|
11006
|
+
const persistedAt = new Date().toISOString();
|
|
11007
|
+
const resolvedAt = reportedResolvedAt ?? persistedAt;
|
|
10578
11008
|
await withAdmittedSessionTransaction(questionPaths, sessionId, async transaction => {
|
|
10579
11009
|
const question = transaction.canonical.questions[questionId];
|
|
10580
11010
|
if (!question || question.claim_fence_epoch !== (claimed as { fence: number }).fence)
|
|
10581
11011
|
throw new Error("terminal_uncertain");
|
|
10582
11012
|
question.status = "answered";
|
|
10583
|
-
question.answered_at =
|
|
10584
|
-
question.updated_at =
|
|
10585
|
-
question.history.push({ at:
|
|
11013
|
+
question.answered_at = persistedAt;
|
|
11014
|
+
question.updated_at = persistedAt;
|
|
11015
|
+
question.history.push({ at: persistedAt, status: "answered", reason: null });
|
|
10586
11016
|
const authority = transaction.canonical.gate_authorities[question.authority_id];
|
|
10587
11017
|
if (authority)
|
|
10588
11018
|
authority.outcome = { state: "answered", turn_id: turnId, question_id: questionId };
|
|
@@ -10600,7 +11030,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
10600
11030
|
resolved_at: resolvedAt,
|
|
10601
11031
|
};
|
|
10602
11032
|
request.phase = "completed";
|
|
10603
|
-
request.updated_at =
|
|
11033
|
+
request.updated_at = persistedAt;
|
|
10604
11034
|
});
|
|
10605
11035
|
return {
|
|
10606
11036
|
ok: true,
|
|
@@ -11015,7 +11445,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
11015
11445
|
if (request.method === "tools/call") {
|
|
11016
11446
|
const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };
|
|
11017
11447
|
const payload = await callTool(params.name ?? "", params.arguments ?? {});
|
|
11018
|
-
return { jsonrpc: "2.0", id, result:
|
|
11448
|
+
return { jsonrpc: "2.0", id, result: coordinatorToolResult(payload) };
|
|
11019
11449
|
}
|
|
11020
11450
|
return { jsonrpc: "2.0", id, error: { code: -32601, message: `unknown_method:${request.method}` } };
|
|
11021
11451
|
}
|
|
@@ -11037,8 +11467,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
11037
11467
|
};
|
|
11038
11468
|
}
|
|
11039
11469
|
|
|
11040
|
-
function
|
|
11041
|
-
const
|
|
11470
|
+
function coordinatorToolResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
|
|
11471
|
+
const record = asRecord(payload);
|
|
11472
|
+
// The legacy ok:false means the observation window ended, not that the RPC
|
|
11473
|
+
// failed. Keep its payload contract while preventing host error/retry breakers.
|
|
11474
|
+
const observationExpired = record?.wait_expired === true && record.reason === "timeout" && !record.error;
|
|
11475
|
+
const failed = record?.ok === false && !observationExpired;
|
|
11042
11476
|
return textResult(payload, failed);
|
|
11043
11477
|
}
|
|
11044
11478
|
|
|
@@ -11083,7 +11517,7 @@ export async function handleCoordinatorMcpRequest(
|
|
|
11083
11517
|
return {
|
|
11084
11518
|
jsonrpc: "2.0",
|
|
11085
11519
|
id: request.id ?? null,
|
|
11086
|
-
result:
|
|
11520
|
+
result: coordinatorToolResult(await server.callTool(params.name ?? "", args)),
|
|
11087
11521
|
};
|
|
11088
11522
|
} finally {
|
|
11089
11523
|
await server.close();
|