@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
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { markNonDispatchedToolEvent } from "@gajae-code/agent-core";
|
|
6
7
|
import { logger } from "@gajae-code/utils";
|
|
7
8
|
import { AsyncJobManager } from "../../async";
|
|
8
9
|
import type { Settings } from "../../config/settings";
|
|
@@ -18,6 +19,7 @@ import { createKindAwareReconciliation } from "../bus/kind-aware-reconciliation"
|
|
|
18
19
|
import { createPromptReconciliation } from "../bus/prompt-reconciliation";
|
|
19
20
|
import { createReconciliationStore } from "../bus/reconciliation-store";
|
|
20
21
|
import { BROKER_RUNTIME_ABORT_CAPABILITY_FIELD, setBrokerRuntimeAbortCapabilityForTest } from "./control/runtime-gate";
|
|
22
|
+
import { SESSION_HOST_OBSERVER_CAPABILITY, TURN_STREAM_CAPABILITY } from "./host";
|
|
21
23
|
import { CursorRegistry, QueryHandlers, type QueryResponse, RevisionStore } from "./query";
|
|
22
24
|
import {
|
|
23
25
|
createInvocationReconciliation,
|
|
@@ -25,6 +27,7 @@ import {
|
|
|
25
27
|
createSdkSurfaceFactory,
|
|
26
28
|
type SdkOnlyInvocationRecord,
|
|
27
29
|
type SdkOnlyReconciliationStore,
|
|
30
|
+
type SdkOnlyTerminalAbortSeams,
|
|
28
31
|
SessionSdkSessionRuntime,
|
|
29
32
|
type SessionSdkTransport,
|
|
30
33
|
} from "./session-runtime";
|
|
@@ -1259,6 +1262,26 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
1259
1262
|
);
|
|
1260
1263
|
await runtime.stop();
|
|
1261
1264
|
});
|
|
1265
|
+
|
|
1266
|
+
test("uses a custom capability provider for live delivery without transport negotiation hooks", async () => {
|
|
1267
|
+
const transport = memoryTransport();
|
|
1268
|
+
const runtime = new SessionSdkSessionRuntime({
|
|
1269
|
+
transport,
|
|
1270
|
+
connectionCapabilities: connectionId =>
|
|
1271
|
+
connectionId === "observer"
|
|
1272
|
+
? new Set([TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY])
|
|
1273
|
+
: new Set(),
|
|
1274
|
+
});
|
|
1275
|
+
await runtime.start();
|
|
1276
|
+
transport.feed("observer", { type: "event_replay", id: "replay", sinceSeq: 0 });
|
|
1277
|
+
runtime.sendFrameToCapabilities([TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY], {
|
|
1278
|
+
type: "event",
|
|
1279
|
+
kind: "message_update",
|
|
1280
|
+
payload: {},
|
|
1281
|
+
});
|
|
1282
|
+
expect(transport.sent).toContainEqual({ type: "event", kind: "message_update", payload: {} });
|
|
1283
|
+
await runtime.stop();
|
|
1284
|
+
});
|
|
1262
1285
|
test("SDK-only host publishes one replayable bash_folded frame per fold and drops the subscription on shutdown", async () => {
|
|
1263
1286
|
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-bash-folded-"));
|
|
1264
1287
|
const handlers = new Map<string, (event: unknown, ctx: ExtensionContext) => Promise<void> | void>();
|
|
@@ -3774,11 +3797,17 @@ async function invocationHarness(
|
|
|
3774
3797
|
persistInterceptor?: (transition: { type: string }) => void;
|
|
3775
3798
|
/** Hold one matching durable transition until the test releases it. */
|
|
3776
3799
|
persistHold?: { type: string; onEntered: () => void; release: Promise<void> };
|
|
3800
|
+
/** Hold successive matching durable transitions, consumed in arrival order. */
|
|
3801
|
+
persistHolds?: Array<{ type: string; onEntered: () => void; release: Promise<void> }>;
|
|
3802
|
+
/** Throw to inject a publication failure for a matching broadcast frame. */
|
|
3803
|
+
broadcastInterceptor?: (frame: SdkFrame) => void;
|
|
3777
3804
|
onLifecycleDrainTimeout?: () => void;
|
|
3778
3805
|
onFailureDiagnosticKeyCount?: (count: number) => void;
|
|
3779
3806
|
agentFailedWriteFailures?: number;
|
|
3780
3807
|
branch?: unknown[];
|
|
3781
3808
|
onInvocationCompletionReconciled?: (kind: string, correlation: { commandId: string; turnId: string }) => void;
|
|
3809
|
+
/** Override/extend the INTERNAL terminal-abort seams the runtime is threaded. */
|
|
3810
|
+
terminalAbortSeams?: Partial<SdkOnlyTerminalAbortSeams>;
|
|
3782
3811
|
},
|
|
3783
3812
|
): Promise<InvocationHarness> {
|
|
3784
3813
|
const waiters = new Map<string, (frame: ResponseFrame) => void>();
|
|
@@ -3801,20 +3830,21 @@ async function invocationHarness(
|
|
|
3801
3830
|
? createInterceptorReconciliationStore(
|
|
3802
3831
|
hooks.persistInterceptor,
|
|
3803
3832
|
hooks.agentFailedWriteFailures,
|
|
3804
|
-
hooks.persistHold,
|
|
3833
|
+
hooks.persistHolds ?? (hooks.persistHold ? [hooks.persistHold] : undefined),
|
|
3805
3834
|
)
|
|
3806
3835
|
: undefined;
|
|
3807
3836
|
createSdkSessionRuntimeExtension(api, {
|
|
3808
3837
|
agentDir: cwd,
|
|
3809
3838
|
...(hooks.onLifecycleDrainTimeout ? { onLifecycleDrainTimeoutForTests: hooks.onLifecycleDrainTimeout } : {}),
|
|
3810
|
-
...(interceptorStore
|
|
3839
|
+
...(interceptorStore || hooks.terminalAbortSeams
|
|
3811
3840
|
? {
|
|
3812
3841
|
terminalAbortSeams: {
|
|
3813
|
-
getReconciliationStore: () => interceptorStore,
|
|
3814
3842
|
getTerminalTurnEpoch: () => undefined,
|
|
3815
3843
|
getActivePromptHandle: () => undefined,
|
|
3816
3844
|
cancelPendingPreflightForTerminalAbort: () => {},
|
|
3817
3845
|
abortPromptAndWaitWithTerminal: async () => ({ status: "settled", terminalScope: {} }),
|
|
3846
|
+
...(interceptorStore ? { getReconciliationStore: () => interceptorStore } : {}),
|
|
3847
|
+
...hooks.terminalAbortSeams,
|
|
3818
3848
|
},
|
|
3819
3849
|
}
|
|
3820
3850
|
: {}),
|
|
@@ -3845,6 +3875,9 @@ async function invocationHarness(
|
|
|
3845
3875
|
if (typeof response.id === "string") waiters.get(response.id)?.(response);
|
|
3846
3876
|
},
|
|
3847
3877
|
broadcastFrame(frame) {
|
|
3878
|
+
// Interception precedes recording: a frame whose publication throws never
|
|
3879
|
+
// reached the wire, so it must not appear in the observed broadcasts.
|
|
3880
|
+
hooks.broadcastInterceptor?.(frame);
|
|
3848
3881
|
broadcasts.push(frame);
|
|
3849
3882
|
},
|
|
3850
3883
|
start: async () => ({ url: "ws://127.0.0.1:1" }),
|
|
@@ -3931,10 +3964,10 @@ async function invocationHarness(
|
|
|
3931
3964
|
function createInterceptorReconciliationStore(
|
|
3932
3965
|
interceptor: (transition: { type: string }) => void,
|
|
3933
3966
|
agentFailedWriteFailures = 1,
|
|
3934
|
-
|
|
3967
|
+
persistHolds?: Array<{ type: string; onEntered: () => void; release: Promise<void> }>,
|
|
3935
3968
|
): SdkOnlyReconciliationStore {
|
|
3936
3969
|
const failureCounts = new Map<string, number>();
|
|
3937
|
-
let
|
|
3970
|
+
let nextHold = 0;
|
|
3938
3971
|
const backing = createInterceptorBackingStore();
|
|
3939
3972
|
return {
|
|
3940
3973
|
path: null,
|
|
@@ -3966,10 +3999,11 @@ function createInterceptorReconciliationStore(
|
|
|
3966
3999
|
interceptor({ type: "agent_end" });
|
|
3967
4000
|
}
|
|
3968
4001
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
4002
|
+
const hold = persistHolds?.[nextHold];
|
|
4003
|
+
if (hold && hold.type === transitionType) {
|
|
4004
|
+
nextHold += 1;
|
|
4005
|
+
hold.onEntered();
|
|
4006
|
+
await hold.release;
|
|
3973
4007
|
}
|
|
3974
4008
|
backing.records = records;
|
|
3975
4009
|
},
|
|
@@ -5656,6 +5690,599 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
5656
5690
|
}
|
|
5657
5691
|
});
|
|
5658
5692
|
|
|
5693
|
+
test("a running prompt deadline publishes one terminal frame pair and keeps normal terminals working", async () => {
|
|
5694
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-terminal-frame-"));
|
|
5695
|
+
try {
|
|
5696
|
+
const harness = await invocationHarness("deadline-terminal-frame", cwd, {
|
|
5697
|
+
settings: zeroProgressSettings,
|
|
5698
|
+
sendUserMessage: async (_content, options) => {
|
|
5699
|
+
await options?.onPreflightAcceptCommit?.();
|
|
5700
|
+
await neverSettlingPromise();
|
|
5701
|
+
},
|
|
5702
|
+
});
|
|
5703
|
+
const accepted = await harness.control("turn.prompt", { text: "deadline" });
|
|
5704
|
+
expect(accepted.ok).toBe(true);
|
|
5705
|
+
const correlation = {
|
|
5706
|
+
commandId: accepted.result?.commandId,
|
|
5707
|
+
turnId: accepted.result?.turnId,
|
|
5708
|
+
};
|
|
5709
|
+
await harness.emit("agent_start");
|
|
5710
|
+
|
|
5711
|
+
// An abort can synthesize pairing-only tool boundaries for a call that
|
|
5712
|
+
// never ran. Those are cleanup, not fresh progress that may renew the
|
|
5713
|
+
// deadline currently terminating this prompt.
|
|
5714
|
+
const syntheticStart = {
|
|
5715
|
+
type: "tool_execution_start",
|
|
5716
|
+
toolCallId: "deadline-cleanup",
|
|
5717
|
+
toolName: "read",
|
|
5718
|
+
args: {},
|
|
5719
|
+
};
|
|
5720
|
+
const syntheticEnd = {
|
|
5721
|
+
type: "tool_execution_end",
|
|
5722
|
+
toolCallId: "deadline-cleanup",
|
|
5723
|
+
toolName: "read",
|
|
5724
|
+
result: { content: "aborted" },
|
|
5725
|
+
isError: true,
|
|
5726
|
+
};
|
|
5727
|
+
markNonDispatchedToolEvent(syntheticStart);
|
|
5728
|
+
markNonDispatchedToolEvent(syntheticEnd);
|
|
5729
|
+
await harness.emit("tool_execution_start", syntheticStart);
|
|
5730
|
+
await harness.emit("tool_execution_end", syntheticEnd);
|
|
5731
|
+
|
|
5732
|
+
const settled = await settledStatus(harness, "turn.prompt_status", correlation);
|
|
5733
|
+
expect(settled).toMatchObject({
|
|
5734
|
+
status: "failed",
|
|
5735
|
+
error: { code: "prompt_deadline_exceeded" },
|
|
5736
|
+
outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
|
|
5737
|
+
});
|
|
5738
|
+
const correlated = () =>
|
|
5739
|
+
harness.broadcasts.filter(frame => {
|
|
5740
|
+
const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
|
|
5741
|
+
return payload?.commandId === correlation.commandId && payload?.turnId === correlation.turnId;
|
|
5742
|
+
});
|
|
5743
|
+
// onExpired publishes the pair only after the durable finalize resolves, so
|
|
5744
|
+
// the terminal status can be observable a few microtasks before the frames
|
|
5745
|
+
// are. Wait for them within a bounded horizon rather than sampling once;
|
|
5746
|
+
// the deep-equality assertions below still pin "exactly one of each".
|
|
5747
|
+
const framesDeadline = Date.now() + 10_000;
|
|
5748
|
+
while (
|
|
5749
|
+
Date.now() < framesDeadline &&
|
|
5750
|
+
!(
|
|
5751
|
+
correlated().some(frame => frame.kind === "agent_failed") &&
|
|
5752
|
+
correlated().some(frame => frame.kind === "agent_end")
|
|
5753
|
+
)
|
|
5754
|
+
)
|
|
5755
|
+
await Bun.sleep(5);
|
|
5756
|
+
expect(correlated().filter(frame => frame.kind === "agent_failed")).toEqual([
|
|
5757
|
+
expect.objectContaining({
|
|
5758
|
+
payload: expect.objectContaining({
|
|
5759
|
+
error: expect.objectContaining({ code: "prompt_deadline_exceeded" }),
|
|
5760
|
+
}),
|
|
5761
|
+
}),
|
|
5762
|
+
]);
|
|
5763
|
+
expect(correlated().filter(frame => frame.kind === "agent_end")).toEqual([
|
|
5764
|
+
expect.objectContaining({
|
|
5765
|
+
payload: expect.objectContaining({
|
|
5766
|
+
outcome: expect.objectContaining({
|
|
5767
|
+
kind: "failed",
|
|
5768
|
+
code: "prompt_deadline_exceeded",
|
|
5769
|
+
provenance: "deadline",
|
|
5770
|
+
}),
|
|
5771
|
+
}),
|
|
5772
|
+
}),
|
|
5773
|
+
]);
|
|
5774
|
+
|
|
5775
|
+
// A late real boundary is idempotent for the retired correlation.
|
|
5776
|
+
await harness.emit("agent_end", { messages: [] });
|
|
5777
|
+
expect(correlated().filter(frame => frame.kind === "agent_failed")).toHaveLength(1);
|
|
5778
|
+
expect(correlated().filter(frame => frame.kind === "agent_end")).toHaveLength(1);
|
|
5779
|
+
expect(await harness.query("turn.prompt_status", correlation)).toMatchObject({ result: settled });
|
|
5780
|
+
|
|
5781
|
+
// Control: the ordinary agent-produced terminal path remains live.
|
|
5782
|
+
const control = await harness.control("turn.prompt", { text: "normal terminal" });
|
|
5783
|
+
const controlCorrelation = {
|
|
5784
|
+
commandId: control.result?.commandId,
|
|
5785
|
+
turnId: control.result?.turnId,
|
|
5786
|
+
};
|
|
5787
|
+
await harness.emit("agent_start");
|
|
5788
|
+
await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
|
|
5789
|
+
expect(await settledStatus(harness, "turn.prompt_status", controlCorrelation)).toMatchObject({
|
|
5790
|
+
status: "terminal_ok",
|
|
5791
|
+
});
|
|
5792
|
+
const controlTerminals = harness.broadcasts.filter(frame => {
|
|
5793
|
+
const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
|
|
5794
|
+
return (
|
|
5795
|
+
frame.kind === "agent_end" &&
|
|
5796
|
+
payload?.commandId === controlCorrelation.commandId &&
|
|
5797
|
+
payload?.turnId === controlCorrelation.turnId
|
|
5798
|
+
);
|
|
5799
|
+
});
|
|
5800
|
+
expect(controlTerminals).toHaveLength(1);
|
|
5801
|
+
await harness.stop();
|
|
5802
|
+
} finally {
|
|
5803
|
+
await Bun.sleep(50);
|
|
5804
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5805
|
+
}
|
|
5806
|
+
});
|
|
5807
|
+
|
|
5808
|
+
/** Frames broadcast for one correlation, in publication order. */
|
|
5809
|
+
const correlatedFrames = (harness: InvocationHarness, correlation: { commandId?: string; turnId?: string }) =>
|
|
5810
|
+
harness.broadcasts.filter(frame => {
|
|
5811
|
+
const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
|
|
5812
|
+
return payload?.commandId === correlation.commandId && payload?.turnId === correlation.turnId;
|
|
5813
|
+
});
|
|
5814
|
+
|
|
5815
|
+
/** Waits for a bounded horizon until the correlated frames satisfy `ready`. */
|
|
5816
|
+
const awaitCorrelatedFrames = async (
|
|
5817
|
+
harness: InvocationHarness,
|
|
5818
|
+
correlation: { commandId?: string; turnId?: string },
|
|
5819
|
+
ready: (frames: SdkFrame[]) => boolean,
|
|
5820
|
+
): Promise<void> => {
|
|
5821
|
+
// Terminal frames are published only after the durable finalize resolves, so a
|
|
5822
|
+
// single sample can precede them. Poll for the observable condition within a
|
|
5823
|
+
// bounded horizon; the callers' deep-equality assertions still pin "exactly one".
|
|
5824
|
+
const budgetEndsAt = Date.now() + 10_000;
|
|
5825
|
+
while (Date.now() < budgetEndsAt && !ready(correlatedFrames(harness, correlation))) await Bun.sleep(5);
|
|
5826
|
+
};
|
|
5827
|
+
|
|
5828
|
+
test("an SDK-only prompt deadline publishes its terminal frames without aborting the run", async () => {
|
|
5829
|
+
// #5637 review finding 1. The bus route's deadline fences the run through
|
|
5830
|
+
// `abortPromptAndWaitWithTerminal`, which aborts the cancellation domain
|
|
5831
|
+
// BEFORE it waits for settlement — that is the mid-tool kill the boundary
|
|
5832
|
+
// wait exists to prevent, and it is why the bus consumes the ledger seam.
|
|
5833
|
+
//
|
|
5834
|
+
// This route is architecturally different: its `PromptDeadlineManager.onExpired`
|
|
5835
|
+
// publishes `agent_failed` + `agent_end` and cleans up lifecycle references,
|
|
5836
|
+
// and aborts NOTHING. That is precisely why no boundary wait is required
|
|
5837
|
+
// here, and it is the fact this case pins. If someone later wires an abort
|
|
5838
|
+
// into this path, the `abortPromptAndWaitWithTerminal` assertion below fails
|
|
5839
|
+
// loudly — and whoever does it must add a boundary wait, for which the
|
|
5840
|
+
// `pendingToolExecutions` seam is now threaded (both hosts expose one
|
|
5841
|
+
// contract; only the bus consumes it today).
|
|
5842
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-only-deadline-no-abort-"));
|
|
5843
|
+
const abortCalls: Array<{ handle: string; graceMs: number }> = [];
|
|
5844
|
+
const pendingLedgerReads: string[] = [];
|
|
5845
|
+
try {
|
|
5846
|
+
const harness = await invocationHarness("sdk-only-deadline-no-abort", cwd, {
|
|
5847
|
+
settings: zeroProgressSettings,
|
|
5848
|
+
sendUserMessage: async (_content, options) => {
|
|
5849
|
+
await options?.onPreflightAcceptCommit?.();
|
|
5850
|
+
await neverSettlingPromise();
|
|
5851
|
+
},
|
|
5852
|
+
terminalAbortSeams: {
|
|
5853
|
+
getActivePromptHandle: () => "sdk-only-deadline-handle",
|
|
5854
|
+
abortPromptAndWaitWithTerminal: async (handle, seamOptions) => {
|
|
5855
|
+
abortCalls.push({ handle, graceMs: seamOptions.graceMs });
|
|
5856
|
+
return { status: "settled", terminalScope: {} };
|
|
5857
|
+
},
|
|
5858
|
+
// Threaded through `SdkOnlyTerminalAbortSeams` (change 1): the seam
|
|
5859
|
+
// must type-check and the extension must construct with it present.
|
|
5860
|
+
// Reads are recorded rather than asserted non-empty — nothing on this
|
|
5861
|
+
// route consults it yet, which is the honest state of the contract.
|
|
5862
|
+
pendingToolExecutions: handle => {
|
|
5863
|
+
pendingLedgerReads.push(handle);
|
|
5864
|
+
return ["sdk-only-mutating-tool"];
|
|
5865
|
+
},
|
|
5866
|
+
},
|
|
5867
|
+
});
|
|
5868
|
+
const accepted = await harness.control("turn.prompt", { text: "sdk-only deadline" });
|
|
5869
|
+
expect(accepted.ok).toBe(true);
|
|
5870
|
+
const correlation = {
|
|
5871
|
+
commandId: accepted.result?.commandId,
|
|
5872
|
+
turnId: accepted.result?.turnId,
|
|
5873
|
+
};
|
|
5874
|
+
await harness.emit("agent_start");
|
|
5875
|
+
// A dispatched tool is running when the deadline expires: its start has
|
|
5876
|
+
// been delivered and no end ever follows.
|
|
5877
|
+
await harness.emit("tool_execution_start", {
|
|
5878
|
+
type: "tool_execution_start",
|
|
5879
|
+
toolCallId: "sdk-only-mutating-tool",
|
|
5880
|
+
toolName: "apply_patch",
|
|
5881
|
+
args: {},
|
|
5882
|
+
});
|
|
5883
|
+
|
|
5884
|
+
expect(await settledStatus(harness, "turn.prompt_status", correlation)).toMatchObject({
|
|
5885
|
+
status: "failed",
|
|
5886
|
+
error: { code: "prompt_deadline_exceeded" },
|
|
5887
|
+
outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
|
|
5888
|
+
});
|
|
5889
|
+
await awaitCorrelatedFrames(
|
|
5890
|
+
harness,
|
|
5891
|
+
correlation,
|
|
5892
|
+
frames =>
|
|
5893
|
+
frames.some(frame => frame.kind === "agent_failed") && frames.some(frame => frame.kind === "agent_end"),
|
|
5894
|
+
);
|
|
5895
|
+
const frames = correlatedFrames(harness, correlation);
|
|
5896
|
+
expect(frames.filter(frame => frame.kind === "agent_failed")).toEqual([
|
|
5897
|
+
expect.objectContaining({
|
|
5898
|
+
payload: expect.objectContaining({
|
|
5899
|
+
error: expect.objectContaining({ code: "prompt_deadline_exceeded" }),
|
|
5900
|
+
}),
|
|
5901
|
+
}),
|
|
5902
|
+
]);
|
|
5903
|
+
expect(frames.filter(frame => frame.kind === "agent_end")).toEqual([
|
|
5904
|
+
expect.objectContaining({
|
|
5905
|
+
payload: expect.objectContaining({
|
|
5906
|
+
outcome: expect.objectContaining({
|
|
5907
|
+
kind: "failed",
|
|
5908
|
+
code: "prompt_deadline_exceeded",
|
|
5909
|
+
provenance: "deadline",
|
|
5910
|
+
}),
|
|
5911
|
+
}),
|
|
5912
|
+
}),
|
|
5913
|
+
]);
|
|
5914
|
+
|
|
5915
|
+
// THE point of the case: the terminal was published, and the run was never
|
|
5916
|
+
// aborted — so there is no mid-tool kill to prevent on this route.
|
|
5917
|
+
expect(abortCalls).toEqual([]);
|
|
5918
|
+
// A late real boundary for the still-"running" tool changes nothing.
|
|
5919
|
+
await harness.emit("tool_execution_end", {
|
|
5920
|
+
type: "tool_execution_end",
|
|
5921
|
+
toolCallId: "sdk-only-mutating-tool",
|
|
5922
|
+
toolName: "apply_patch",
|
|
5923
|
+
isError: false,
|
|
5924
|
+
});
|
|
5925
|
+
await Bun.sleep(50);
|
|
5926
|
+
expect(abortCalls).toEqual([]);
|
|
5927
|
+
expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_end")).toHaveLength(1);
|
|
5928
|
+
await harness.stop();
|
|
5929
|
+
} finally {
|
|
5930
|
+
await Bun.sleep(50);
|
|
5931
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5932
|
+
}
|
|
5933
|
+
});
|
|
5934
|
+
|
|
5935
|
+
test("the deadline/agent_end overlap publishes exactly one correlated terminal boundary", async () => {
|
|
5936
|
+
// Review P1: the two terminal publishers genuinely overlap. The provider
|
|
5937
|
+
// agent_end handler captures its transitions synchronously and then awaits
|
|
5938
|
+
// durable writes; the deadline callback fires only after its own claim/finalize
|
|
5939
|
+
// awaits. Either can reach the wire first, neither can be stopped by removing
|
|
5940
|
+
// lifecycle references, and noteTransition is a no-op once the record is
|
|
5941
|
+
// terminal — so without a shared claim durable state records one outcome while
|
|
5942
|
+
// clients receive two boundaries. Sweeping the real agent_end across the lease
|
|
5943
|
+
// instant exercises BOTH orderings; whichever publisher loses must stay silent.
|
|
5944
|
+
//
|
|
5945
|
+
// The Bun.sleep below is the RACE DRIVER, not the sampling mechanism: every
|
|
5946
|
+
// observation is a bounded poll on the durable settle plus a quiet window that
|
|
5947
|
+
// proves no second frame follows.
|
|
5948
|
+
const ATTEMPTS = 20;
|
|
5949
|
+
const deadlineWins: number[] = [];
|
|
5950
|
+
const providerWins: number[] = [];
|
|
5951
|
+
for (let attempt = 0; attempt < ATTEMPTS; attempt += 1) {
|
|
5952
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-overlap-"));
|
|
5953
|
+
try {
|
|
5954
|
+
const harness = await invocationHarness(`deadline-overlap-${attempt}`, cwd, {
|
|
5955
|
+
settings: zeroProgressSettings,
|
|
5956
|
+
sendUserMessage: async (_content, options) => {
|
|
5957
|
+
await options?.onPreflightAcceptCommit?.();
|
|
5958
|
+
await neverSettlingPromise();
|
|
5959
|
+
},
|
|
5960
|
+
});
|
|
5961
|
+
const accepted = await harness.control("turn.prompt", { text: "overlap" });
|
|
5962
|
+
expect(accepted.ok).toBe(true);
|
|
5963
|
+
const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
|
|
5964
|
+
await harness.emit("agent_start");
|
|
5965
|
+
// zeroProgressSettings leases 25ms; straddle that instant.
|
|
5966
|
+
await Bun.sleep(24 + attempt * 0.25);
|
|
5967
|
+
await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
|
|
5968
|
+
// Both publishers have run once the durable record is terminal and a
|
|
5969
|
+
// correlated boundary is on the wire; the quiet window then proves that
|
|
5970
|
+
// the loser did not publish a second one behind it.
|
|
5971
|
+
const settled = await settledStatus(harness, "turn.prompt_status", correlation);
|
|
5972
|
+
await awaitCorrelatedFrames(harness, correlation, frames =>
|
|
5973
|
+
frames.some(frame => frame.kind === "agent_end"),
|
|
5974
|
+
);
|
|
5975
|
+
await Bun.sleep(25);
|
|
5976
|
+
const correlated = correlatedFrames(harness, correlation);
|
|
5977
|
+
const boundaries = correlated.filter(frame => frame.kind === "agent_end");
|
|
5978
|
+
const diagnostics = correlated.filter(frame => frame.kind === "agent_failed");
|
|
5979
|
+
expect({ attempt, boundaries: boundaries.length }).toEqual({ attempt, boundaries: 1 });
|
|
5980
|
+
const outcome = (boundaries[0]?.payload as { outcome?: { provenance?: string } } | undefined)?.outcome;
|
|
5981
|
+
if (outcome?.provenance === "deadline") {
|
|
5982
|
+
// The deadline owns the pair atomically: its correlated diagnostic and
|
|
5983
|
+
// its correlated boundary reach the wire together, exactly once, and
|
|
5984
|
+
// the correlation still reconciles to a single durable outcome.
|
|
5985
|
+
deadlineWins.push(attempt);
|
|
5986
|
+
expect({ attempt, diagnostics: diagnostics.length }).toEqual({ attempt, diagnostics: 1 });
|
|
5987
|
+
expect(settled).toMatchObject({ outcome: expect.objectContaining({ kind: expect.any(String) }) });
|
|
5988
|
+
} else {
|
|
5989
|
+
// Control within the sweep: the ordinary provider terminal path still
|
|
5990
|
+
// publishes its one boundary, and no synthetic pair trails it.
|
|
5991
|
+
providerWins.push(attempt);
|
|
5992
|
+
expect({ attempt, diagnostics: diagnostics.length }).toEqual({ attempt, diagnostics: 0 });
|
|
5993
|
+
}
|
|
5994
|
+
await harness.stop();
|
|
5995
|
+
} finally {
|
|
5996
|
+
await Bun.sleep(10);
|
|
5997
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
// A sweep that never reached the lease instant would prove nothing.
|
|
6001
|
+
expect(deadlineWins.length).toBeGreaterThan(0);
|
|
6002
|
+
expect(deadlineWins.length + providerWins.length).toBe(ATTEMPTS);
|
|
6003
|
+
});
|
|
6004
|
+
|
|
6005
|
+
test("the deadline publishes its correlated diagnostic before its correlated boundary", async () => {
|
|
6006
|
+
// Ordering is part of the pair's contract: a client that matches the boundary and
|
|
6007
|
+
// stops reading must never have the failure reason arrive behind it.
|
|
6008
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-pair-order-"));
|
|
6009
|
+
try {
|
|
6010
|
+
const harness = await invocationHarness("deadline-pair-order", cwd, {
|
|
6011
|
+
settings: zeroProgressSettings,
|
|
6012
|
+
sendUserMessage: async (_content, options) => {
|
|
6013
|
+
await options?.onPreflightAcceptCommit?.();
|
|
6014
|
+
await neverSettlingPromise();
|
|
6015
|
+
},
|
|
6016
|
+
});
|
|
6017
|
+
const accepted = await harness.control("turn.prompt", { text: "order" });
|
|
6018
|
+
expect(accepted.ok).toBe(true);
|
|
6019
|
+
const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
|
|
6020
|
+
await harness.emit("agent_start");
|
|
6021
|
+
await awaitCorrelatedFrames(
|
|
6022
|
+
harness,
|
|
6023
|
+
correlation,
|
|
6024
|
+
frames =>
|
|
6025
|
+
frames.some(frame => frame.kind === "agent_failed") && frames.some(frame => frame.kind === "agent_end"),
|
|
6026
|
+
);
|
|
6027
|
+
const kinds = correlatedFrames(harness, correlation)
|
|
6028
|
+
.map(frame => frame.kind)
|
|
6029
|
+
.filter(kind => kind === "agent_failed" || kind === "agent_end");
|
|
6030
|
+
// One assertion over the indices, so an out-of-order pair cannot pass by
|
|
6031
|
+
// satisfying two independent presence checks.
|
|
6032
|
+
expect({
|
|
6033
|
+
failedAt: kinds.indexOf("agent_failed"),
|
|
6034
|
+
endAt: kinds.indexOf("agent_end"),
|
|
6035
|
+
failedCount: kinds.filter(kind => kind === "agent_failed").length,
|
|
6036
|
+
endCount: kinds.filter(kind => kind === "agent_end").length,
|
|
6037
|
+
}).toEqual({ failedAt: 0, endAt: 1, failedCount: 1, endCount: 1 });
|
|
6038
|
+
|
|
6039
|
+
// Control: the ordinary terminal path still publishes exactly one boundary.
|
|
6040
|
+
const control = await harness.control("turn.prompt", { text: "normal terminal" });
|
|
6041
|
+
const controlCorrelation = { commandId: control.result?.commandId, turnId: control.result?.turnId };
|
|
6042
|
+
await harness.emit("agent_start");
|
|
6043
|
+
await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
|
|
6044
|
+
expect(await settledStatus(harness, "turn.prompt_status", controlCorrelation)).toMatchObject({
|
|
6045
|
+
status: "terminal_ok",
|
|
6046
|
+
});
|
|
6047
|
+
expect(correlatedFrames(harness, controlCorrelation).filter(frame => frame.kind === "agent_end")).toHaveLength(
|
|
6048
|
+
1,
|
|
6049
|
+
);
|
|
6050
|
+
await harness.stop();
|
|
6051
|
+
} finally {
|
|
6052
|
+
await Bun.sleep(50);
|
|
6053
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6054
|
+
}
|
|
6055
|
+
});
|
|
6056
|
+
|
|
6057
|
+
test.each([
|
|
6058
|
+
{ failing: "agent_failed" as const, surviving: "agent_end" as const },
|
|
6059
|
+
{ failing: "agent_end" as const, surviving: "agent_failed" as const },
|
|
6060
|
+
])("a deadline frame that fails to publish never suppresses the other ($failing)", async ({
|
|
6061
|
+
failing,
|
|
6062
|
+
surviving,
|
|
6063
|
+
}) => {
|
|
6064
|
+
// Review P2: the deadline's two frames were emitted as unguarded sequential
|
|
6065
|
+
// calls, so a throwing agent_failed skipped the agent_end while reconciliation
|
|
6066
|
+
// and the lifecycle cleanup still ran -- leaving the ACP request with NO
|
|
6067
|
+
// terminal boundary, the exact failure this path exists to prevent. Each frame
|
|
6068
|
+
// is now published independently, and neither failure may skip the cleanup.
|
|
6069
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-frame-failure-"));
|
|
6070
|
+
const warnings: Array<{ message: string; data: Record<string, unknown> | undefined }> = [];
|
|
6071
|
+
const warn = spyOn(logger, "warn").mockImplementation((message, data) => {
|
|
6072
|
+
warnings.push({ message: String(message), data: data as Record<string, unknown> | undefined });
|
|
6073
|
+
});
|
|
6074
|
+
const errors: Array<{ message: string; data: Record<string, unknown> | undefined }> = [];
|
|
6075
|
+
const error = spyOn(logger, "error").mockImplementation((message, data) => {
|
|
6076
|
+
errors.push({ message: String(message), data: data as Record<string, unknown> | undefined });
|
|
6077
|
+
});
|
|
6078
|
+
try {
|
|
6079
|
+
let failCorrelation: { commandId?: string; turnId?: string } = {};
|
|
6080
|
+
const harness = await invocationHarness(`deadline-frame-failure-${failing}`, cwd, {
|
|
6081
|
+
settings: zeroProgressSettings,
|
|
6082
|
+
sendUserMessage: async (_content, options) => {
|
|
6083
|
+
await options?.onPreflightAcceptCommit?.();
|
|
6084
|
+
await neverSettlingPromise();
|
|
6085
|
+
},
|
|
6086
|
+
broadcastInterceptor: frame => {
|
|
6087
|
+
const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
|
|
6088
|
+
if (
|
|
6089
|
+
frame.kind === failing &&
|
|
6090
|
+
payload?.commandId === failCorrelation.commandId &&
|
|
6091
|
+
payload?.turnId === failCorrelation.turnId
|
|
6092
|
+
)
|
|
6093
|
+
throw Object.assign(new Error("injected publication failure"), { code: "io_error" });
|
|
6094
|
+
},
|
|
6095
|
+
});
|
|
6096
|
+
const accepted = await harness.control("turn.prompt", { text: "frame failure" });
|
|
6097
|
+
expect(accepted.ok).toBe(true);
|
|
6098
|
+
const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
|
|
6099
|
+
failCorrelation = correlation;
|
|
6100
|
+
await harness.emit("agent_start");
|
|
6101
|
+
await awaitCorrelatedFrames(harness, correlation, frames => frames.some(frame => frame.kind === surviving));
|
|
6102
|
+
const correlated = correlatedFrames(harness, correlation);
|
|
6103
|
+
// The frame whose publication threw never reached the wire; the other one
|
|
6104
|
+
// still did, carrying the deadline's terminal identity.
|
|
6105
|
+
expect({
|
|
6106
|
+
failing: correlated.filter(frame => frame.kind === failing).length,
|
|
6107
|
+
surviving: correlated.filter(frame => frame.kind === surviving).length,
|
|
6108
|
+
}).toEqual({ failing: 0, surviving: 1 });
|
|
6109
|
+
if (surviving === "agent_end")
|
|
6110
|
+
expect(correlated.find(frame => frame.kind === "agent_end")?.payload).toMatchObject({
|
|
6111
|
+
outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
|
|
6112
|
+
});
|
|
6113
|
+
|
|
6114
|
+
// The failure is reported, with the correlation and without provider text.
|
|
6115
|
+
expect([...warnings, ...errors]).toContainEqual(
|
|
6116
|
+
expect.objectContaining({
|
|
6117
|
+
message: expect.stringContaining("deadline correlated"),
|
|
6118
|
+
data: expect.objectContaining({ commandId: correlation.commandId, turnId: correlation.turnId }),
|
|
6119
|
+
}),
|
|
6120
|
+
);
|
|
6121
|
+
|
|
6122
|
+
// The cleanup after the publication still ran: the durable record is settled,
|
|
6123
|
+
// a late real boundary stays idempotent for the retired correlation, and the
|
|
6124
|
+
// session still terminalizes a fresh turn.
|
|
6125
|
+
expect(await settledStatus(harness, "turn.prompt_status", correlation)).toMatchObject({
|
|
6126
|
+
status: "failed",
|
|
6127
|
+
error: { code: "prompt_deadline_exceeded" },
|
|
6128
|
+
});
|
|
6129
|
+
await harness.emit("agent_end", { messages: [] });
|
|
6130
|
+
expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_end")).toHaveLength(
|
|
6131
|
+
surviving === "agent_end" ? 1 : 0,
|
|
6132
|
+
);
|
|
6133
|
+
const control = await harness.control("turn.prompt", { text: "after frame failure" });
|
|
6134
|
+
const controlIds = { commandId: control.result?.commandId, turnId: control.result?.turnId };
|
|
6135
|
+
await harness.emit("agent_start");
|
|
6136
|
+
await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
|
|
6137
|
+
expect(await settledStatus(harness, "turn.prompt_status", controlIds)).toMatchObject({
|
|
6138
|
+
status: "terminal_ok",
|
|
6139
|
+
});
|
|
6140
|
+
expect(correlatedFrames(harness, controlIds).filter(frame => frame.kind === "agent_end")).toHaveLength(1);
|
|
6141
|
+
await harness.stop();
|
|
6142
|
+
} finally {
|
|
6143
|
+
warn.mockRestore();
|
|
6144
|
+
error.mockRestore();
|
|
6145
|
+
await Bun.sleep(50);
|
|
6146
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6147
|
+
}
|
|
6148
|
+
});
|
|
6149
|
+
|
|
6150
|
+
test("boundary-claim eviction never lets a parked publisher republish a claimed boundary", async () => {
|
|
6151
|
+
// Review P1: the claim registry is FIFO-bounded, while a lifecycle handler
|
|
6152
|
+
// captures its transitions, awaits its durable writes, and only claims
|
|
6153
|
+
// immediately before the emit. Parked on those awaits, its key can be evicted
|
|
6154
|
+
// by later correlations, and its delayed agent_end then publishes a SECOND
|
|
6155
|
+
// correlated boundary for a correlation the deadline already terminalized.
|
|
6156
|
+
//
|
|
6157
|
+
// The interleaving is CONSTRUCTED, not sampled. The deadline's finalize write
|
|
6158
|
+
// is held so the real agent_end lands inside the finalization window and takes
|
|
6159
|
+
// the upgrade path; releasing it lets the deadline publish and claim the pair
|
|
6160
|
+
// while the handler parks on the NEXT invocation's write, before its claim
|
|
6161
|
+
// loop. The pressure then runs in a second session because one session's
|
|
6162
|
+
// durable writes are serialized -- a held write blocks that session entirely.
|
|
6163
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-claim-eviction-"));
|
|
6164
|
+
try {
|
|
6165
|
+
const finalizeHold = { entered: Promise.withResolvers<void>(), release: Promise.withResolvers<void>() };
|
|
6166
|
+
const parkHold = { entered: Promise.withResolvers<void>(), release: Promise.withResolvers<void>() };
|
|
6167
|
+
// Read at each lease arming, so only the `expiring` prompt is short-leased.
|
|
6168
|
+
let promptDeadlineMs = 600_000;
|
|
6169
|
+
const settings = {
|
|
6170
|
+
get: (key: string) =>
|
|
6171
|
+
key === "sdk.promptDeadlineMs"
|
|
6172
|
+
? promptDeadlineMs
|
|
6173
|
+
: key === "sdk.promptMaxRuntimeMs"
|
|
6174
|
+
? 600_000
|
|
6175
|
+
: undefined,
|
|
6176
|
+
} as unknown as Settings;
|
|
6177
|
+
const promoted: Array<((promotion: { startsOwnRun: boolean }) => void) | undefined> = [];
|
|
6178
|
+
const harness = await invocationHarness("claim-eviction", cwd, {
|
|
6179
|
+
settings,
|
|
6180
|
+
sendUserMessage: async (_content, options) => {
|
|
6181
|
+
await options?.onPreflightAcceptCommit?.();
|
|
6182
|
+
if ((options as { deliverAs?: string } | undefined)?.deliverAs === "followUp") {
|
|
6183
|
+
promoted.push(
|
|
6184
|
+
(options as { onQueuedPromoted?: (promotion: { startsOwnRun: boolean }) => void } | undefined)
|
|
6185
|
+
?.onQueuedPromoted,
|
|
6186
|
+
);
|
|
6187
|
+
return;
|
|
6188
|
+
}
|
|
6189
|
+
await neverSettlingPromise();
|
|
6190
|
+
},
|
|
6191
|
+
persistInterceptor: () => {},
|
|
6192
|
+
agentFailedWriteFailures: 0,
|
|
6193
|
+
persistHolds: [
|
|
6194
|
+
{
|
|
6195
|
+
type: "agent_end",
|
|
6196
|
+
onEntered: () => finalizeHold.entered.resolve(),
|
|
6197
|
+
release: finalizeHold.release.promise,
|
|
6198
|
+
},
|
|
6199
|
+
{ type: "agent_end", onEntered: () => parkHold.entered.resolve(), release: parkHold.release.promise },
|
|
6200
|
+
],
|
|
6201
|
+
onLifecycleDrainTimeout: () => {},
|
|
6202
|
+
});
|
|
6203
|
+
// A never-settling head turn keeps the session streaming so the two
|
|
6204
|
+
// follow-ups queue and promote into ONE run, giving the terminal handler a
|
|
6205
|
+
// second invocation to park on after the deadline publishes the first.
|
|
6206
|
+
expect((await harness.control("turn.prompt", { text: "head" })).ok).toBe(true);
|
|
6207
|
+
await harness.emit("agent_start");
|
|
6208
|
+
// `leading` is drained FIRST, so the terminal handler's first durable write
|
|
6209
|
+
// is its own -- not the deadline's pending finalization -- and parking on it
|
|
6210
|
+
// leaves `expiring` untouched for the deadline to publish.
|
|
6211
|
+
const leading = await harness.control("turn.follow_up", { text: "leading" });
|
|
6212
|
+
promoted[0]?.({ startsOwnRun: true });
|
|
6213
|
+
promptDeadlineMs = 1_500;
|
|
6214
|
+
const expiring = await harness.control("turn.follow_up", { text: "expiring" });
|
|
6215
|
+
promoted[1]?.({ startsOwnRun: true });
|
|
6216
|
+
promptDeadlineMs = 600_000;
|
|
6217
|
+
const expiringIds = { commandId: expiring.result?.commandId, turnId: expiring.result?.turnId };
|
|
6218
|
+
const leadingIds = { commandId: leading.result?.commandId, turnId: leading.result?.turnId };
|
|
6219
|
+
await harness.emit("agent_start");
|
|
6220
|
+
await awaitCorrelatedFrames(harness, expiringIds, frames =>
|
|
6221
|
+
frames.some(frame => frame.kind === "agent_start"),
|
|
6222
|
+
);
|
|
6223
|
+
await awaitCorrelatedFrames(harness, leadingIds, frames => frames.some(frame => frame.kind === "agent_start"));
|
|
6224
|
+
// Both must be in the drained batch: a lease that fired before the drain
|
|
6225
|
+
// would leave the handler with nothing to park on and prove nothing.
|
|
6226
|
+
expect({
|
|
6227
|
+
expiring: correlatedFrames(harness, expiringIds).filter(frame => frame.kind === "agent_start").length,
|
|
6228
|
+
leading: correlatedFrames(harness, leadingIds).filter(frame => frame.kind === "agent_start").length,
|
|
6229
|
+
}).toEqual({ expiring: 1, leading: 1 });
|
|
6230
|
+
|
|
6231
|
+
await finalizeHold.entered.promise;
|
|
6232
|
+
// The real boundary lands while the deadline's finalization is pending, so
|
|
6233
|
+
// the handler captures BOTH transitions and waits on the finalize commit.
|
|
6234
|
+
const parkedEnd = harness.emit("agent_end", {
|
|
6235
|
+
sdkRunToken: `${leadingIds.commandId}:${leadingIds.turnId}`,
|
|
6236
|
+
messages: [{ role: "assistant", content: "done" }],
|
|
6237
|
+
});
|
|
6238
|
+
finalizeHold.release.resolve();
|
|
6239
|
+
// The deadline now publishes and claims the pair; the handler parks on the
|
|
6240
|
+
// leading invocation's durable write, before its own claim loop.
|
|
6241
|
+
await parkHold.entered.promise;
|
|
6242
|
+
await awaitCorrelatedFrames(harness, expiringIds, frames => frames.some(frame => frame.kind === "agent_end"));
|
|
6243
|
+
const deadlineBoundary = correlatedFrames(harness, expiringIds).find(frame => frame.kind === "agent_end");
|
|
6244
|
+
expect(
|
|
6245
|
+
(deadlineBoundary?.payload as { outcome?: { provenance?: string } } | undefined)?.outcome?.provenance,
|
|
6246
|
+
).toBe("deadline");
|
|
6247
|
+
|
|
6248
|
+
// Eviction pressure: more than MAX_PUBLISHED_TERMINAL_BOUNDARIES (1024)
|
|
6249
|
+
// other correlations claim their own boundary while the publisher is parked.
|
|
6250
|
+
await harness.switchSession("claim-eviction-pressure");
|
|
6251
|
+
const PRESSURE = 1_100;
|
|
6252
|
+
for (let index = 0; index < PRESSURE; index += 1) {
|
|
6253
|
+
const accepted = await harness.control("turn.prompt", { text: `pressure-${index}` });
|
|
6254
|
+
expect(accepted.ok).toBe(true);
|
|
6255
|
+
const token = `${accepted.result?.commandId}:${accepted.result?.turnId}`;
|
|
6256
|
+
await harness.emit("agent_start", { sdkRunToken: token });
|
|
6257
|
+
await harness.emit("agent_end", { sdkRunToken: token, messages: [{ role: "assistant", content: "ok" }] });
|
|
6258
|
+
}
|
|
6259
|
+
const claimedCorrelations = new Set(
|
|
6260
|
+
harness.broadcasts
|
|
6261
|
+
.filter(frame => frame.kind === "agent_end")
|
|
6262
|
+
.map(frame => {
|
|
6263
|
+
const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
|
|
6264
|
+
return `${payload?.commandId}:${payload?.turnId}`;
|
|
6265
|
+
}),
|
|
6266
|
+
);
|
|
6267
|
+
// A sweep that never passed the bound would evict nothing and prove nothing.
|
|
6268
|
+
expect(claimedCorrelations.size).toBeGreaterThan(1_024);
|
|
6269
|
+
|
|
6270
|
+
parkHold.release.resolve();
|
|
6271
|
+
await parkedEnd;
|
|
6272
|
+
await Bun.sleep(25);
|
|
6273
|
+
// The parked publisher lost its claim to the deadline before the pressure
|
|
6274
|
+
// ran, and must still lose it after: exactly one boundary on the wire.
|
|
6275
|
+
expect({
|
|
6276
|
+
expiring: correlatedFrames(harness, expiringIds).filter(frame => frame.kind === "agent_end").length,
|
|
6277
|
+
leading: correlatedFrames(harness, leadingIds).filter(frame => frame.kind === "agent_end").length,
|
|
6278
|
+
}).toEqual({ expiring: 1, leading: 1 });
|
|
6279
|
+
await harness.stop();
|
|
6280
|
+
} finally {
|
|
6281
|
+
await Bun.sleep(50);
|
|
6282
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6283
|
+
}
|
|
6284
|
+
});
|
|
6285
|
+
|
|
5659
6286
|
test("an abort_and_prompt replacement of a zero-execution turn is also bounded", async () => {
|
|
5660
6287
|
// The issue observed both the original turn.prompt AND the replacement
|
|
5661
6288
|
// turn.abort_and_prompt accepted with permanently zero activity. Both the
|