@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.141
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 +740 -0
- package/README.md +143 -1
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +376 -0
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +328 -18
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +1689 -545
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +52 -0
- package/dist/cli/runtime-process.js +153 -0
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +71 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +112 -0
- package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +728 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +140 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +557 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +363 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +751 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +683 -0
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +16 -0
- package/dist/harness-runtime/lib/native-children.mjs +113 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
- package/dist/harness-runtime/lib/process-json.d.mts +5 -0
- package/dist/harness-runtime/lib/process-json.mjs +201 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
- package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
- package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +28 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +250 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +25 -62
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1168 -107
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +115 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +202 -0
- package/dist/services/dashboard.js +143 -58
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +294 -153
- package/dist/services/eval-snapshots.js +1215 -116
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +308 -0
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +355 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +771 -325
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +1 -288
- package/dist/services/lifecycle-contract.js +1 -0
- package/dist/services/lifecycle-invocations.js +2007 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-lease-monitor.js +133 -0
- package/dist/services/managed-processes.js +475 -55
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +296 -0
- package/dist/services/memory-init-preflight.js +330 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +181 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +178 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +161 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +37 -17
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +385 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +560 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +165 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +1056 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +208 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1205 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-health.js +149 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +77 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +489 -0
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +1019 -247
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +210 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +193 -19
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +152 -116
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +234 -6
- package/dist/services/stage-work-graph.js +52 -0
- package/dist/services/tooling.js +41 -30
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +375 -0
- package/dist/services/vnext-code-review.js +223 -88
- package/dist/services/vnext-code.js +327 -226
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +80 -5
- package/dist/services/vnext-fanout.js +311 -19
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +279 -127
- package/dist/services/vnext-plan.js +228 -104
- package/dist/services/vnext-protocolize.js +531 -95
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1627 -199
- package/dist/services/workspace-bootstrap.js +52 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +105 -48
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +27 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +891 -39
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +89 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +43 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
2
|
+
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
3
|
+
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
4
|
+
import { operationProgressReporter } from './operation-progress.mjs';
|
|
5
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
6
|
+
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
7
|
+
import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
|
|
8
|
+
import { ToolObservations, agyToolObservation, agyTranscriptObservations } from "./tool-observations.mjs";
|
|
9
|
+
import { constants } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
12
|
+
import { appendFile, chmod, lstat, mkdir, open, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import net from "node:net";
|
|
15
|
+
import { isDeepStrictEqual } from "node:util";
|
|
16
|
+
import { ObservationClock, observedTimeout, validateObservationDuration } from "./observation-clock.mjs";
|
|
17
|
+
import { hookBudget } from "./native-hook-command.mjs";
|
|
18
|
+
import { agyWorkspaceHooks } from "./agy-hooks.mjs";
|
|
19
|
+
import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { observeModel } from "./model-observations.mjs";
|
|
22
|
+
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
|
|
23
|
+
import { settledNativeTree } from "./native-children.mjs";
|
|
24
|
+
import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
25
|
+
import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
26
|
+
|
|
27
|
+
const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
|
|
28
|
+
class DaemonError extends AgyError {}
|
|
29
|
+
export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId]) {
|
|
30
|
+
const session = { harness_id: "antigravity-cli", session_id: child.provider_session_id };
|
|
31
|
+
let handle;
|
|
32
|
+
try {
|
|
33
|
+
if (!rootSessionId || !child.provider_session_id || !ownedSessionIds.includes(child.parent_provider_session_id) || !child.log_uri || !/^[\w-]+$/.test(child.provider_session_id)) throw new Error("unbound native locator");
|
|
34
|
+
const expected = path.join(runtimeRoot, "brain", child.provider_session_id, ".system_generated", "logs", "transcript.jsonl");
|
|
35
|
+
const canonical = path.join(await realpath(runtimeRoot), "brain", child.provider_session_id, ".system_generated", "logs", "transcript.jsonl");
|
|
36
|
+
if (![expected, canonical].includes(fileURLToPath(child.log_uri)) || await realpath(expected) !== canonical) throw new Error("foreign or symlink native locator");
|
|
37
|
+
const before = await lstat(expected);
|
|
38
|
+
if (!before.isFile()) throw new Error("native locator is not a regular file");
|
|
39
|
+
handle = await open(expected, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
40
|
+
const stat = await handle.stat();
|
|
41
|
+
if (!before.isFile() || !stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino || await realpath(expected) !== canonical) throw new Error("native locator changed during open");
|
|
42
|
+
const maximum = 8 * 1024 * 1024, buffer = Buffer.alloc(Math.min(stat.size, maximum));
|
|
43
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
44
|
+
const bytes = buffer.subarray(0, bytesRead), prefix = bytes.subarray(0, bytes.lastIndexOf(10) + 1);
|
|
45
|
+
const source = { locator: child.log_uri, byte_start: 0, byte_end: prefix.length, sha256: createHash("sha256").update(prefix).digest("hex") };
|
|
46
|
+
const events = agyTranscriptObservations(prefix.toString("utf8"), child.provider_session_id, child.parent_provider_session_id ?? rootSessionId, source);
|
|
47
|
+
if (prefix.length !== bytesRead || stat.size > maximum) events.push({ session, reason: stat.size > maximum ? "agy_transcript_size_limit" : "agy_transcript_partial_tail" });
|
|
48
|
+
return events;
|
|
49
|
+
} catch (error) { return [{ session, reason: error.code === "ENOENT" ? "agy_transcript_missing" : "agy_transcript_locator_rejected" }]; }
|
|
50
|
+
finally { await handle?.close(); }
|
|
51
|
+
}
|
|
52
|
+
const absolute = (value, label) => { if (!value || !path.isAbsolute(value)) throw new DaemonError("invalid_path", `${label} must be an absolute path`); return path.resolve(value); };
|
|
53
|
+
function locations(stateDir) {
|
|
54
|
+
const dir = absolute(stateDir, "--state-dir"), digest = createHash("sha256").update(dir).digest("hex").slice(0, 22), local = path.join(dir, "daemon.sock");
|
|
55
|
+
// `tsx` creates Unix-domain sockets below TMPDIR. An evaluation path is
|
|
56
|
+
// intentionally descriptive and can exceed the platform's socket-path cap,
|
|
57
|
+
// so provider temp files must not inherit that path.
|
|
58
|
+
return { dir, socket: Buffer.byteLength(local) < 100 ? local : `/tmp/dd-agy-${digest}.sock`, state: path.join(dir, "daemon.json"), journal: path.join(dir, "events.jsonl"), log: path.join(dir, "daemon.log"), gemini: path.join(dir, "gemini"), runtime: path.join(dir, "gemini", "runtime"), config: path.join(dir, "gemini", "config"), temporary: `/tmp/dd-agy-tmp-${digest}` };
|
|
59
|
+
}
|
|
60
|
+
async function writeJson(file, value) { const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`; await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 }); await rename(temporary, file); }
|
|
61
|
+
async function readState(dir) { try { return JSON.parse(await readFile(locations(dir).state, "utf8")); } catch (error) { if (error.code === "ENOENT") return null; throw error; } }
|
|
62
|
+
async function removeSocket(paths) { try { if (!(await lstat(paths.socket)).isSocket()) throw new DaemonError("unsafe_socket", "refusing to replace a non-socket daemon path"); await unlink(paths.socket); } catch (error) { if (error.code !== "ENOENT") throw error; } }
|
|
63
|
+
const shell = value => `'${String(value).replaceAll("'", `'"'"'`)}'`;
|
|
64
|
+
export function sanitizedEnv(config) { const env = { ...process.env, TMPDIR: config.temporary, DD_AGY_HOOK_NODE: process.execPath, DD_AGY_HOOK_ENTRY: config.entryPath, DD_AGY_STATE_DIR: config.stateDir, DD_AGY_DAEMON_ID: config.daemonId }; for (const key of ["GEMINI_API_KEY","GOOGLE_API_KEY","ANTHROPIC_API_KEY","OPENAI_API_KEY"]) env[key] = undefined; return { ...env, ...managedFlowEnv(config) }; }
|
|
65
|
+
const errorPayload = errorRecord;
|
|
66
|
+
async function forwardUsage(config, sessionId, result, tools, descendants) {
|
|
67
|
+
if (!config.ddFlowBin || !config.projectRoot) return;
|
|
68
|
+
const snapshot = usageSnapshot(result, tools), hasChildren = descendants.length > 0;
|
|
69
|
+
if (snapshot.total_tokens === null) return { status: "unavailable", reason: "provider_did_not_report_total_tokens" };
|
|
70
|
+
const payload = { provider_session_id: sessionId, daemon_id: config.daemonId, observed_at: new Date().toISOString(), usage_scope: hasChildren ? "unknown" : "physical_session", completeness: hasChildren ? "partial" : "complete", usage: { inputTokens: snapshot.input_tokens, outputTokens: snapshot.output_tokens, reasoningTokens: snapshot.reasoning_tokens, cacheReadInputTokens: snapshot.cache_read_tokens, totalTokens: snapshot.total_tokens }, tool_calls: snapshot.tool_calls };
|
|
71
|
+
const target = executable(config.ddFlowBin, ["agy", "usage", "ingest", "--project-root", config.projectRoot, "--json"]);
|
|
72
|
+
await commandText(target.command, target.args, { input: `${JSON.stringify(payload)}\n`, env: { DD_FLOW_HOME: config.ddFlowHome }, phase: "usage-ingest" });
|
|
73
|
+
return { status: "ingested" };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function prepare(paths, config) {
|
|
77
|
+
config.stateDir = paths.dir;
|
|
78
|
+
config.hookBinding = "environment-v1";
|
|
79
|
+
const hooks = agyWorkspaceHooks();
|
|
80
|
+
for (const dir of [paths.dir, paths.gemini, paths.runtime, paths.config, paths.temporary]) { await mkdir(dir, { recursive: true, mode: 0o700 }); await chmod(dir, 0o700); }
|
|
81
|
+
if (config.noFlow) {
|
|
82
|
+
// Technical probes still need native settlement, but must not install
|
|
83
|
+
// workspace hooks or forward any lifecycle mutation to dd-flow.
|
|
84
|
+
await writeJson(path.join(paths.config, "hooks.json"), { "dd-flow-observer": { Stop: hooks["dd-flow"].Stop } });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Native discovery follows cwd (feature worktree/reviewer clone), while
|
|
88
|
+
// projectRoot remains the stable lifecycle identity returned to dd-flow.
|
|
89
|
+
Object.assign(config, await qualifyAgyHooks(config.cwd ?? config.projectRoot, { stateDir: paths.dir }));
|
|
90
|
+
// Workspace discovery covers root and children without global duplicates.
|
|
91
|
+
await writeJson(path.join(paths.config, "hooks.json"), {});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function qualifyAgyHooks(projectRoot, { stateDir = null, install = false } = {}) {
|
|
95
|
+
const hooks = agyWorkspaceHooks();
|
|
96
|
+
// Register in workspace discovery before creating any Session: native root
|
|
97
|
+
// and children load these named hook definitions without a global duplicate.
|
|
98
|
+
// Replace our prior handlers, including the obsolete wildcard post hook,
|
|
99
|
+
// without dropping unrelated commands in a shared matcher group.
|
|
100
|
+
const workspaceConfig = path.join(absolute(projectRoot, "--project-root"), ".agents");
|
|
101
|
+
const workspaceHooks = path.join(workspaceConfig, "hooks.json");
|
|
102
|
+
let existing = {}, existingText = "";
|
|
103
|
+
try {
|
|
104
|
+
existingText = await readFile(workspaceHooks, "utf8");
|
|
105
|
+
const parsed = JSON.parse(existingText);
|
|
106
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") throw new Error("not an object");
|
|
107
|
+
existing = parsed;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error.code !== "ENOENT") throw new DaemonError("workspace_hooks_invalid", `cannot install Antigravity lifecycle hooks: ${error.message}`);
|
|
110
|
+
if (!install) throw new DaemonError("workspace_hooks_unqualified", "Antigravity hooks must be installed during qualification before daemon start", false, { hooks_path: workspaceHooks });
|
|
111
|
+
}
|
|
112
|
+
const isManagedHandler = handler => typeof handler?.command === "string"
|
|
113
|
+
&& ((handler.command.includes("DD_AGY_HOOK_ENTRY") && handler.command.includes(" hook handle --event ") && handler.command.includes("DD_AGY_STATE_DIR")) || /(?:^|\/)dd-agy\.mjs['"]?\s+hook handle/.test(handler.command));
|
|
114
|
+
const events = ["PreToolUse", "PostToolUse", "PreInvocation", "PostInvocation", "Stop"];
|
|
115
|
+
const existingManaged = Object.entries(existing).flatMap(([name, definition]) => {
|
|
116
|
+
if (events.includes(name) && Array.isArray(definition)) return definition;
|
|
117
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) return [];
|
|
118
|
+
return events.flatMap(event => Array.isArray(definition[event]) ? definition[event] : []);
|
|
119
|
+
}).flatMap(item => Array.isArray(item?.hooks) ? item.hooks : [item]).filter(handler => isManagedHandler(handler) && handler.command.includes("--state-dir") && !handler.command.includes("DD_AGY_STATE_DIR"));
|
|
120
|
+
if (existingManaged.some(handler => !stateDir || !handler.command.includes(`--state-dir ${shell(stateDir)}`))) {
|
|
121
|
+
throw new DaemonError("workspace_hooks_drifted", "qualified Antigravity hook routes to another daemon state directory", false, { hooks_path: workspaceHooks, expected_state_dir: stateDir });
|
|
122
|
+
}
|
|
123
|
+
const retain = handlers => handlers.flatMap(item => {
|
|
124
|
+
if (!Array.isArray(item?.hooks)) return isManagedHandler(item) ? [] : [item];
|
|
125
|
+
const remaining = item.hooks.filter(handler => !isManagedHandler(handler));
|
|
126
|
+
return remaining.length ? [{ ...item, hooks: remaining }] : [];
|
|
127
|
+
});
|
|
128
|
+
const workspace = Object.create(null), legacy = Object.create(null);
|
|
129
|
+
for (const [name, definition] of Object.entries(existing)) {
|
|
130
|
+
// Migrate the old adapter's event-keyed workspace format.
|
|
131
|
+
if (events.includes(name) && Array.isArray(definition)) {
|
|
132
|
+
const remaining = retain(definition);
|
|
133
|
+
if (remaining.length) legacy[name] = remaining;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!definition || Array.isArray(definition) || typeof definition !== "object") {
|
|
137
|
+
throw new DaemonError("workspace_hooks_invalid", `invalid named hook definition: ${name}`);
|
|
138
|
+
}
|
|
139
|
+
const cleaned = { ...definition };
|
|
140
|
+
for (const event of events) if (Array.isArray(cleaned[event])) {
|
|
141
|
+
const remaining = retain(cleaned[event]);
|
|
142
|
+
if (remaining.length || cleaned[event].length === 0) cleaned[event] = remaining;
|
|
143
|
+
else delete cleaned[event];
|
|
144
|
+
}
|
|
145
|
+
workspace[name] = cleaned;
|
|
146
|
+
}
|
|
147
|
+
if (Object.keys(legacy).length) {
|
|
148
|
+
let name = "dd-flow-legacy";
|
|
149
|
+
while (Object.hasOwn(workspace, name)) name += "-retained";
|
|
150
|
+
workspace[name] = legacy;
|
|
151
|
+
}
|
|
152
|
+
const managed = workspace["dd-flow"] ?? {};
|
|
153
|
+
if (managed.enabled === false) throw new DaemonError("workspace_hooks_invalid", "dd-flow lifecycle hooks are disabled");
|
|
154
|
+
for (const [event, handlers] of Object.entries(hooks["dd-flow"])) {
|
|
155
|
+
managed[event] = [...(managed[event] ?? []), ...handlers];
|
|
156
|
+
}
|
|
157
|
+
workspace["dd-flow"] = managed;
|
|
158
|
+
const serializedWorkspace = `${JSON.stringify(workspace, null, 2)}\n`;
|
|
159
|
+
// Hook installation is a qualification/ownership boundary. Reusing the
|
|
160
|
+
// qualified command must not rewrite the project file when the pinned engine,
|
|
161
|
+
// daemon directory or workspace location changes.
|
|
162
|
+
const installed = isDeepStrictEqual(existing, JSON.parse(serializedWorkspace));
|
|
163
|
+
if (!installed && !install) throw new DaemonError("workspace_hooks_unqualified", "Antigravity hook definitions differ from the qualified contract", false, { hooks_path: workspaceHooks });
|
|
164
|
+
if (!installed) { await mkdir(workspaceConfig, { recursive: true, mode: 0o700 }); await writeFile(workspaceHooks, serializedWorkspace, { mode: 0o600 }); }
|
|
165
|
+
return { hooksSha256: createHash("sha256").update(JSON.stringify(hooks)).digest("hex"), workspaceHooksPath: workspaceHooks,
|
|
166
|
+
workspaceHooksSha256: createHash("sha256").update(installed ? existingText : serializedWorkspace).digest("hex"), workspaceHooksInstalled: installed ? "qualified_existing" : "qualified_install" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000, requestId = null) {
|
|
170
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST_SCHEMA, id: requestId ?? process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
171
|
+
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'agy', timeoutMs });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","reasoning","mode","projectRoot","ddFlowBin","ddFlowHome","resourceHome","noFlow","livenessTimeoutMs"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|
|
175
|
+
// Called only after authorizeRetainedDaemonResume verifies the old daemon,
|
|
176
|
+
// provider processes and operation ledger. Native outcomes remain historical;
|
|
177
|
+
// their old liveness flags cannot describe the replacement process tree.
|
|
178
|
+
export function retainedAgyState(previous, sessionId) {
|
|
179
|
+
if (!sessionId || !previous?.sessions?.some(session => session.provider_session_id === sessionId)) return {};
|
|
180
|
+
if (previous.shutdown_state !== "clean" || previous.active_tree !== false) return previous;
|
|
181
|
+
return {
|
|
182
|
+
...previous,
|
|
183
|
+
unclaimed_activity: false,
|
|
184
|
+
retained_tree_settlement: { daemon_id: previous.daemon_id, session_id: sessionId, turn_generation: previous.turn_generation ?? 0, evidence: "verified_clean_daemon_shutdown" },
|
|
185
|
+
session_observations: (previous.session_observations ?? []).map(([id, observation]) => [id, id === sessionId ? { ...observation, retained_stop: observation.stop ?? null, stop: null } : observation]),
|
|
186
|
+
descendants: (previous.descendants ?? []).map(child => child.tree_settled === true ? child : { ...child, tree_settled: true, settlement_evidence: "prior_clean_daemon_shutdown", settlement_daemon_id: previous.daemon_id })
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export async function startDaemon(options) {
|
|
190
|
+
const paths = locations(options.stateDir); await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
191
|
+
const previous = await readState(paths.dir), noFlow = options.noFlow === true, cwd = await realpath(absolute(options.cwd, "--cwd")), config = { cwd, journal: options.journal ? absolute(options.journal, "--journal") : paths.journal, bin: options.bin ?? process.env.DD_AGY_BIN ?? "agy", provider: String(options.provider ?? "google"), model: String(options.model ?? "gemini-3.1-pro-high"), reasoning: String(options.reasoning ?? "high"), mode: String(options.mode ?? "accept-edits"), projectRoot: noFlow ? cwd : await realpath(absolute(options.projectRoot, "--project-root")), ddFlowBin: noFlow ? null : absolute(options.ddFlowBin, "--dd-flow-bin"), ddFlowHome: noFlow ? null : absolute(options.ddFlowHome, "--dd-flow-home"), resourceHome: noFlow ? null : (process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null), env: noFlow ? {} : { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home") }, entryPath: absolute(options.entryPath, "dd-agy entry"), daemonId: randomUUID(), resumeSessionId: options.sessionId ?? null, geminiDir: paths.gemini, appDataDir: "runtime", temporary: paths.temporary, noFlow };
|
|
192
|
+
config.livenessTimeoutMs = options.livenessTimeoutMs ?? 1_800_000;
|
|
193
|
+
validateObservationDuration(config.livenessTimeoutMs);
|
|
194
|
+
await prepareRuntimeOwner(config, paths.dir);
|
|
195
|
+
assertDaemonRestartable(previous);
|
|
196
|
+
try { const status = await callDaemon(paths.dir, "daemon.status", {}, 1000); if (!sameConfig(status, config)) throw new DaemonError("daemon_config_mismatch", "a daemon is already running with different configuration"); return { ...status, already_running: true }; } catch (error) { if (!["daemon_not_running","daemon_timeout"].includes(error.code)) throw error; }
|
|
197
|
+
assertDaemonRestartable(await readState(paths.dir));
|
|
198
|
+
if (previous?.shutdown_state === "clean") await authorizeRetainedDaemonResume(paths.dir, previous, options.sessionId, config);
|
|
199
|
+
if (previous?.shutdown_state === "running" && previous.active_tree) throw new DaemonError("invalid_harness_crash", "previous Antigravity daemon died with an active or unproven tree");
|
|
200
|
+
await removeSocket(paths); await prepare(paths, config);
|
|
201
|
+
const version = (await runAgy(config.bin, ["--version"], { cwd: config.cwd, env: sanitizedEnv(config), timeoutMs: AGY_VERSION_TIMEOUT_MS })).stdout.trim();
|
|
202
|
+
const resourceProcess = await registerDaemonProcess(config, { kind: "agy-daemon", owner: `agy:${path.basename(paths.dir)}`, operation: `agy-daemon:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log });
|
|
203
|
+
const retained = retainedAgyState(previous, config.resumeSessionId);
|
|
204
|
+
await writeJson(paths.state, { schema_id: STATE_SCHEMA, daemon_id: config.daemonId, shutdown_state: "starting", active_tree: false, unclaimed_activity: retained.unclaimed_activity ?? false, retained_tree_settlement: retained.retained_tree_settlement ?? null, versions: { agy: version }, config, sessions: [], session_observations: retained.session_observations ?? [], descendants: retained.descendants ?? [], ...(Array.isArray(retained.tool_observations) ? { tool_observations: retained.tool_observations } : {}), tool_observation_gaps: retained.tool_observation_gaps ?? [], tool_calls: retained.tool_calls ?? [], completed_tool_steps: retained.completed_tool_steps ?? [], tool_identity_complete: retained.tool_identity_complete !== false, turn_generation: retained.turn_generation ?? 0, last_result: retained.last_result ?? null, resource_process: resourceProcess });
|
|
205
|
+
const log = await open(paths.log, "a", 0o600), child = spawn(process.execPath, [config.entryPath, "daemon", "serve", "--state-dir", paths.dir], { cwd: config.cwd, env: sanitizedEnv(config), detached: true, stdio: ["ignore", log.fd, log.fd] }); child.unref(); await log.close();
|
|
206
|
+
try {
|
|
207
|
+
await confirmDaemonProcess(config, resourceProcess, child);
|
|
208
|
+
const startupClock = new ObservationClock({ timeoutMs: SETUP_STARTUP_MS }); const startupProgress = startupObserver(paths.dir, config.daemonId); while (!startupClock.sample(await startupProgress())) { await new Promise(resolve => setTimeout(resolve, 100));
|
|
209
|
+
const persisted = await readState(paths.dir);
|
|
210
|
+
if (persisted?.shutdown_state === "failed") {
|
|
211
|
+
const failure = persisted.startup_error ?? {};
|
|
212
|
+
throw new DaemonError(failure.code ?? "daemon_start_failed", failure.message ?? "Antigravity daemon failed before becoming ready", failure.retryable === true, failure.details);
|
|
213
|
+
}
|
|
214
|
+
try { const status = await callDaemon(paths.dir, "daemon.status", {}, 1000); if (status.provider_ready) return status; } catch (error) { if (!["daemon_not_running","daemon_timeout"].includes(error.code)) throw error; }
|
|
215
|
+
}
|
|
216
|
+
throw Object.assign(new DaemonError("daemon_start_failed", "Antigravity daemon did not become ready", true), { details: observationDetails(startupClock, 'daemon-start', child.pid) });
|
|
217
|
+
} catch (error) {
|
|
218
|
+
await cleanupFailedDaemonStart(config, resourceProcess, child, error, () => readState(paths.dir), config.daemonId);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export const stopDaemon = async options => { const timeoutMs = options.timeoutMs ?? 30_000; return await confirmDaemonStopped(() => callDaemon(options.stateDir, "daemon.stop", { cancelTree: options.cancelTree === true }, timeoutMs), locations(options.stateDir).socket, timeoutMs, { readState: () => readState(options.stateDir) }); };
|
|
223
|
+
|
|
224
|
+
export class Runtime {
|
|
225
|
+
constructor(paths, state) { this.paths = paths; this.state = state; this.config = state.config; this.child = null; this.providerProcess = state.provider_process ?? null; this.providerFinalization = Promise.resolve(); this.buffer = ""; this.init = null; this.readyPromise = null; this.active = null; this.exited = false; this.toolObservations = new ToolObservations(); for (const reason of state.tool_observation_gaps ?? []) this.toolObservations.gaps.add(reason); for (const event of state.tool_observations ?? []) this.toolObservations.add(event); if (state.tool_calls?.length && !state.tool_observations) this.toolObservations.gaps.add("legacy_tools_not_attributed"); this.turnGeneration = state.turn_generation ?? 0; this.descendants = new Map((state.descendants ?? []).map(child => [child.provider_session_id, child])); this.lastResult = state.last_result ?? null; this.sessionObservations = new Map(state.session_observations ?? []); this.startupError = null; this.persisting = Promise.resolve(); this.draining = Promise.resolve(); this.lastActivityAt = state.last_activity_at ?? null; this.usageIngest = null; this.lastProviderEvent = null; }
|
|
226
|
+
persist(patch = {}) { Object.assign(this.state, patch, { pid: process.pid, updated_at: new Date().toISOString(), last_activity_at: this.lastActivityAt, session_observations: [...this.sessionObservations], tool_observations: [...this.toolObservations.calls.values(), ...this.toolObservations.sources.values()], tool_observation_gaps: [...this.toolObservations.gaps], turn_generation: this.turnGeneration, descendants: [...this.descendants.values()], sessions: this.init ? [{ provider_session_id: this.init.conversation_id, root_provider_session_id: this.init.conversation_id, cwd: this.config.cwd }] : [] }); const snapshot = structuredClone(this.state); const queued = performance.now(); this.persisting = this.persisting.catch(() => {}).then(async () => { const started = performance.now(); await writeJson(this.paths.state, snapshot); this.persistTiming = { queue_ms: Math.round(started - queued), write_ms: Math.round(performance.now() - started) }; }); return this.persisting; }
|
|
227
|
+
async journal(kind, payload = {}) { const line = `${JSON.stringify({ schema_id: "dd-agy/daemon-event@1", event_id: randomUUID(), observed_at: new Date().toISOString(), kind, ...payload })}\n`; for (const journal of new Set([this.paths.journal, this.config.journal].filter(Boolean))) { await mkdir(path.dirname(journal), { recursive: true, mode: 0o700 }); await appendFile(journal, line, { mode: 0o600 }); } }
|
|
228
|
+
args(sessionId) { return ["--input-format", "stream-json", "--output-format", "stream-json", "--model", this.config.model, "--effort", this.config.reasoning, "--mode", this.config.mode, "--dangerously-skip-permissions", "--print-timeout", "0", `--gemini_dir=${this.paths.gemini}`, "--app_data_dir=runtime", ...(sessionId ? ["--conversation", sessionId] : ["--new-project"])]; }
|
|
229
|
+
requireSessionIdentity(requestedId) {
|
|
230
|
+
const observedId = this.init?.conversation_id ?? null;
|
|
231
|
+
if (!requestedId || requestedId === observedId) return observedId;
|
|
232
|
+
throw new DaemonError("session_identity_mismatch", "requested Antigravity Session is not owned by this daemon", false, {
|
|
233
|
+
harness: "antigravity-cli", expected_session_id: requestedId, observed_session_id: observedId
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
markActivity() { const current = this.active; this.lastActivityAt = new Date().toISOString(); this.progress = { cursor: (this.progress?.cursor ?? 0) + 1, observed_at: Date.now() }; void current?.publishProgress?.('native_progress', { session_id: this.init?.conversation_id, observed_at: this.lastActivityAt }).catch(error => { if(this.active === current) current.reject(error); }); }
|
|
237
|
+
markTerminalActivity(result) {
|
|
238
|
+
const current = this.active;
|
|
239
|
+
if (!current) return;
|
|
240
|
+
const key = createHash("sha256").update(JSON.stringify({ conversation_id: result.conversation_id ?? this.init?.conversation_id, status: result.status, num_turns: result.num_turns, error: result.error, response: result.response ?? result.text })).digest("hex");
|
|
241
|
+
current.terminalProgress ??= new Set();
|
|
242
|
+
if (current.terminalProgress.has(key) || current.terminalProgress.size >= 1_024) return;
|
|
243
|
+
current.terminalProgress.add(key); this.markActivity();
|
|
244
|
+
}
|
|
245
|
+
observeProviderActivity(event) {
|
|
246
|
+
if (!this.active) return;
|
|
247
|
+
const root = this.init?.conversation_id, step = event.step_update;
|
|
248
|
+
const id = step?.conversation_id ?? event.result?.conversation_id ?? root;
|
|
249
|
+
let parent = id; const seen = new Set();
|
|
250
|
+
while (parent && parent !== root && !seen.has(parent)) { seen.add(parent); const child = this.descendants.get(parent); if (child?.activity_generation !== this.active.generation) return; parent = child.parent_provider_session_id; }
|
|
251
|
+
if (!root || parent !== root) return;
|
|
252
|
+
if (event.event === "result") { if (["SUCCESS", "ERROR", "CANCELLED"].includes(event.result?.status)) this.markTerminalActivity(event.result); return; }
|
|
253
|
+
if (event.event !== "step_update") return;
|
|
254
|
+
const index = step?.step_index ?? (/^\d+$/.test(step?.step_id ?? "") ? Number(step.step_id) : null);
|
|
255
|
+
if (!Number.isInteger(index)) return;
|
|
256
|
+
const floor = this.sessionObservations.get(id)?.step_floor;
|
|
257
|
+
if (floor != null && index <= floor) return;
|
|
258
|
+
const tool = step.tool_call ?? step.tool_info ?? (step.step_type === "tool" ? step.tool_name : null);
|
|
259
|
+
if (!step.content && !step.text && !step.thought && !tool && !step.error && !(step.subagent_info?.subagents?.length)) return;
|
|
260
|
+
const fingerprint = createHash("sha256").update(JSON.stringify({ content: step.content, text: step.text, thought: step.thought, tool_call: step.tool_call, tool_info: step.tool_info, tool_name: step.tool_name, ...(tool ? { state: step.state, status: step.status } : {}), error: step.error, subagent_info: step.subagent_info })).digest("hex");
|
|
261
|
+
this.progressSteps ??= new Map(); const prior = this.progressSteps.get(id);
|
|
262
|
+
if (prior && index < prior.index) return;
|
|
263
|
+
const snapshots = prior?.index === index ? prior.seen : new Set();
|
|
264
|
+
if (snapshots.has(fingerprint)) return;
|
|
265
|
+
// Native step snapshots can alternate/replay; scope retained dedup to the
|
|
266
|
+
// current step. Too many unique snapshots becomes uncertain, never a grant.
|
|
267
|
+
if (snapshots.size >= 1_024) return;
|
|
268
|
+
snapshots.add(fingerprint); this.progressSteps.set(id, { index, seen: snapshots }); this.markActivity();
|
|
269
|
+
}
|
|
270
|
+
async start(sessionId = null) {
|
|
271
|
+
if (this.drainingError) throw this.drainingError;
|
|
272
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
273
|
+
if (this.child && !this.exited) {
|
|
274
|
+
await this.readyPromise;
|
|
275
|
+
this.requireSessionIdentity(sessionId);
|
|
276
|
+
return this.init;
|
|
277
|
+
}
|
|
278
|
+
await this.providerFinalization;
|
|
279
|
+
if (this.drainingError) throw this.drainingError;
|
|
280
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
281
|
+
const target = executable(this.config.bin, this.args(sessionId)); this.exited = false; this.buffer = ""; this.init = null;
|
|
282
|
+
let readyResolve, readyReject; this.readyPromise = new Promise((resolve, reject) => { readyResolve = resolve; readyReject = reject; });
|
|
283
|
+
// The provider can reject before durable registration finishes. Keep a
|
|
284
|
+
// handler attached during that small window; `start()` still returns the
|
|
285
|
+
// original rejection below.
|
|
286
|
+
void this.readyPromise.catch(() => {});
|
|
287
|
+
this.providerProcess = await registerDaemonProcess(this.config, { kind: "agy-provider", owner: `agy:${path.basename(this.paths.dir)}`, operation: `agy-provider:${path.basename(this.paths.dir)}`, stdout: this.paths.log, stderr: this.paths.log });
|
|
288
|
+
const providerProcess = this.providerProcess;
|
|
289
|
+
try { await this.persist({ provider_process: providerProcess }); }
|
|
290
|
+
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
291
|
+
try { assertDaemonDispatch(this.paths?.dir); this.child = spawn(target.command, target.args, { cwd: this.config.cwd, env: sanitizedEnv(this.config), detached: true, stdio: ["pipe", "pipe", "pipe"] }); }
|
|
292
|
+
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
293
|
+
const providerChild = this.child;
|
|
294
|
+
let allowProviderFinalization;
|
|
295
|
+
const providerRegistered = new Promise(resolve => { allowProviderFinalization = resolve; });
|
|
296
|
+
const observationFailure = error => { this.drainingError ??= error; readyReject(error); this.active?.reject(error); };
|
|
297
|
+
this.nativeDrain = readNativeLines(this.child.stdout, line => { this.buffer = `${line}\n`; return this.draining = this.drain(readyResolve); }, observationFailure);
|
|
298
|
+
this.diagnostics = streamNativeDiagnostics(this.child.stderr, chunk => this.journal("stderr", { text: chunk }), observationFailure);
|
|
299
|
+
this.child.on("error", error => { readyReject(error); if (this.active) { this.active.reject(error); this.active = null; } });
|
|
300
|
+
// A dead leader is not yet a finalized tree: stop must also join the
|
|
301
|
+
// pending drain, process-group check and final persistence/lease writes.
|
|
302
|
+
this.providerFinalization = new Promise((resolve, reject) => {
|
|
303
|
+
this.child.on("close", (code, signal) => {
|
|
304
|
+
void Promise.all([this.nativeDrain, this.diagnostics]).then(async () => {
|
|
305
|
+
const interrupted = Boolean(this.active); this.exited = true; const beforeInit = !this.init;
|
|
306
|
+
const error = this.startupError ?? new DaemonError(beforeInit ? "agy_init_missing" : "agy_terminal_result_missing", `Antigravity exited ${beforeInit ? "before init" : "before result"} (${code ?? signal})`);
|
|
307
|
+
if (beforeInit) readyReject(error);
|
|
308
|
+
if (this.active) { this.active.reject(error); this.active = null; }
|
|
309
|
+
const patch = beforeInit ? { shutdown_state: "failed", startup_error: errorPayload(error), provider_exit: { code, signal }, active_tree: false } : { provider_exit: { code, signal }, active_tree: interrupted };
|
|
310
|
+
await providerRegistered;
|
|
311
|
+
this.retryProviderFinalization = async (graceMs = 1_000) => {
|
|
312
|
+
try {
|
|
313
|
+
await stopProcessGroup(providerChild, graceMs);
|
|
314
|
+
await this.persist(patch);
|
|
315
|
+
await finishDaemonProcess(this.config, providerProcess, beforeInit || interrupted ? "failed" : "stopped", beforeInit || interrupted ? "provider_interrupted" : "provider_stopped");
|
|
316
|
+
this.retryProviderFinalization = null;
|
|
317
|
+
} catch (cleanup) { await this.persist({ shutdown_state: "cleanup_failed", active_tree: true, cleanup_error: errorPayload(cleanup) }); throw cleanup; }
|
|
318
|
+
finally { if (beforeInit) this.server?.close(); }
|
|
319
|
+
};
|
|
320
|
+
await this.retryProviderFinalization();
|
|
321
|
+
}).then(resolve, reject);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
void this.providerFinalization.catch(() => {});
|
|
325
|
+
try { await confirmDaemonProcess(this.config, providerProcess, this.child); await this.persist({ provider_pid: this.child.pid }); scheduleDaemonProcessHeartbeat(this.config, providerProcess); }
|
|
326
|
+
catch (error) { await cleanupFailedStart(this.config, providerProcess, this.child, error); throw error; }
|
|
327
|
+
finally { allowProviderFinalization(); }
|
|
328
|
+
let readyTimer;
|
|
329
|
+
try { return await Promise.race([this.readyPromise, new Promise((_, reject) => { readyTimer = observedTimeout(() => reject(new DaemonError("agy_init_timeout", "Antigravity init timed out", true)), SETUP_STARTUP_MS, { progress: () => this.lastActivityAt }); })]); }
|
|
330
|
+
finally { clearTimeout(readyTimer); }
|
|
331
|
+
}
|
|
332
|
+
async drain(readyResolve) { let index; while ((index = this.buffer.indexOf("\n")) >= 0) { const line = this.buffer.slice(0, index).trim(); this.buffer = this.buffer.slice(index + 1); if (!line) continue; let event; try { event = JSON.parse(line); } catch { await this.journal("invalid_json", { line: line.slice(0, 1000) }); continue; }
|
|
333
|
+
// Record an early provider rejection before journalling: a short-lived
|
|
334
|
+
// child can close while the asynchronous write is still pending.
|
|
335
|
+
if (event.event === "result" && ["ERROR", "CANCELLED"].includes(event.result?.status)) {
|
|
336
|
+
this.lastTerminalFailure ??= this.state.last_terminal_failure;
|
|
337
|
+
const repeated = Boolean(this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === event.result.error
|
|
338
|
+
&& this.lastTerminalFailure.details.provider_session_id === (event.result.conversation_id || this.init?.conversation_id || null)
|
|
339
|
+
&& (this.active?.previousError === event.result.error || this.lastResult?.status === "ERROR" && this.lastResult.error === event.result.error));
|
|
340
|
+
event.terminal_metadata = repeated ? this.lastTerminalFailure.details : { observed_at: new Date().toISOString(), provider_session_id: event.result.conversation_id || this.init?.conversation_id || null,
|
|
341
|
+
operation_id: this.active?.operationId ?? null, turn_generation: this.active?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" };
|
|
342
|
+
if (!this.init) this.startupError ??= agyTerminalFailure(event.result, event.terminal_metadata);
|
|
343
|
+
this.receivedTerminalMetadata = event.terminal_metadata;
|
|
344
|
+
}
|
|
345
|
+
this.observeProviderActivity(event);
|
|
346
|
+
if (event.event === "result" && this.init && !this.startupError) { await this.finishTurn(event.result ?? {}, event.terminal_metadata, event); continue; }
|
|
347
|
+
await this.journal("provider_event", { event }); if (event.event === "init") { this.init = { conversation_id: event.conversation_id, ...(event.init ?? {}) }; if (!this.init.conversation_id) throw new DaemonError("agy_init_missing", "Antigravity init has no conversation ID"); const requested = { provider: this.config.provider, model: this.config.model, reasoning: this.config.reasoning, mode: this.config.mode, permission_mode: "always-proceed" }; this.profile = assertProfile(requested, observedProfile(this.init, requested)); await observeModel({ journal: this.config.journal, harness: "antigravity-cli", sessionId: this.init.conversation_id, requested, observed: this.profile.observed, source: "native.init" }); await this.persist({ shutdown_state: "running", active_tree: false }); readyResolve(this.init); } else if (event.event === "step_update") this.observeStep(event.step_update ?? {}); else if (event.event === "result") { if (this.startupError) throw this.startupError; await this.finishTurn(event.result ?? {}); } } }
|
|
348
|
+
observeStep(step) {
|
|
349
|
+
const sessionId = step.conversation_id ?? this.init?.conversation_id, observation = this.sessionObservations.get(sessionId) ?? {};
|
|
350
|
+
const fresh = Number.isInteger(step.step_index) && step.step_index > (observation.last_step_index ?? -1) && step.step_index > (observation.step_floor ?? -1);
|
|
351
|
+
const bound = sessionId === this.init?.conversation_id || this.descendants.get(sessionId)?.activity_generation === this.active?.generation;
|
|
352
|
+
// Tree liveness is broader than current-Turn model progress: fresh work
|
|
353
|
+
// from a retained child invalidates settlement without adopting this Turn.
|
|
354
|
+
if (fresh && (sessionId === this.init?.conversation_id || this.descendants.has(sessionId))) this.childActivity(sessionId);
|
|
355
|
+
if (Number.isInteger(step.step_index)) this.sessionObservations.set(sessionId, { ...observation, last_step_index: Math.max(observation.last_step_index ?? -1, step.step_index) });
|
|
356
|
+
this.toolObservations.add(agyToolObservation(step, this.init?.conversation_id));
|
|
357
|
+
for (const child of step.subagent_info?.subagents ?? []) if (child.conversation_id) {
|
|
358
|
+
const prior = this.descendants.get(child.conversation_id);
|
|
359
|
+
this.descendants.set(child.conversation_id, { ...prior, provider_session_id: child.conversation_id, parent_provider_session_id: sessionId ?? null, role: child.role ?? null, subagent_type: child.type_name ?? null, log_uri: child.log_uri ?? null, workspace_uris: child.workspace_uris ?? [], status: prior?.status ?? "unknown",
|
|
360
|
+
...(fresh && bound && this.active ? { activity_generation: this.active.generation } : {}) });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
toolSnapshot() { const summary = this.toolObservations.summary(); const failures_by_name = {}; for (const event of this.toolObservations.calls.values()) if (event.status === "failed") failures_by_name[event.name] = (failures_by_name[event.name] ?? 0) + 1; return { ...summary, by_name: summary.by_tool, failures_by_name }; }
|
|
364
|
+
async captureChildTools() {
|
|
365
|
+
this.capturedToolFacts ??= new Map();
|
|
366
|
+
const root = this.init?.conversation_id ?? this.state.sessions?.[0]?.provider_session_id ?? this.lastResult?.conversation_id;
|
|
367
|
+
for (const child of this.descendants.values()) {
|
|
368
|
+
const ancestors = new Set(); let parent = child.parent_provider_session_id;
|
|
369
|
+
while (parent && parent !== root && this.descendants.has(parent) && !ancestors.has(parent)) {
|
|
370
|
+
ancestors.add(parent); parent = this.descendants.get(parent).parent_provider_session_id;
|
|
371
|
+
}
|
|
372
|
+
const events = parent === root && root
|
|
373
|
+
? await captureAgyTranscript(this.paths.runtime, child, root, [root, ...ancestors])
|
|
374
|
+
: [{ session: { harness_id: "antigravity-cli", session_id: child.provider_session_id }, reason: "agy_transcript_ancestry_unproven" }];
|
|
375
|
+
for (const event of events) {
|
|
376
|
+
this.toolObservations.add(event);
|
|
377
|
+
const key = JSON.stringify([event.session, event.call_id, event.reason]), encoded = JSON.stringify(event);
|
|
378
|
+
if (this.capturedToolFacts.get(key) === encoded) continue;
|
|
379
|
+
await this.journal("tool_observation", { schema_id: "dd-flow/tool-observation@1", event });
|
|
380
|
+
this.capturedToolFacts.set(key, encoded);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async finishTurn(result, retainedMetadata = null, providerEvent = null) {
|
|
385
|
+
if (result.status === "RUNNING") { await this.persist({ active_tree: true }); return; }
|
|
386
|
+
if (!["SUCCESS", "ERROR", "CANCELLED"].includes(result.status)) throw new DaemonError("agy_terminal_result_invalid", "Antigravity result has no recognized terminal status", false, { provider_result: result });
|
|
387
|
+
if (result.conversation_id && result.conversation_id !== this.init?.conversation_id) {
|
|
388
|
+
await this.journal("foreign_terminal_observed", { conversation_id: result.conversation_id, result });
|
|
389
|
+
const child = this.descendants.get(result.conversation_id);
|
|
390
|
+
if (child) {
|
|
391
|
+
this.descendants.set(result.conversation_id, { ...child, status: result.status === "SUCCESS" ? "completed" : result.status === "ERROR" ? "failed" : "cancelled" });
|
|
392
|
+
await this.completePendingTurn();
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const current = this.active;
|
|
397
|
+
const terminalMetadata = { received_at: new Date().toISOString(), ...(retainedMetadata ?? this.receivedTerminalMetadata ?? (this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result.error
|
|
398
|
+
&& this.lastTerminalFailure.details.provider_session_id === (result.conversation_id || this.init?.conversation_id || null)
|
|
399
|
+
&& (current?.previousError === result.error || this.lastResult?.status === "ERROR" && this.lastResult.error === result.error) ? this.lastTerminalFailure.details
|
|
400
|
+
: { observed_at: new Date().toISOString(), provider_session_id: result.conversation_id || this.init?.conversation_id || null, operation_id: current?.operationId ?? null,
|
|
401
|
+
turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" })) };
|
|
402
|
+
this.receivedTerminalMetadata = null;
|
|
403
|
+
// The ordered stream returns one result per input. Model usage counters
|
|
404
|
+
// are not request IDs: native command rejections can report zero turns.
|
|
405
|
+
// prompt() drains earlier events before accepting another input.
|
|
406
|
+
if (!current && this.lastResult) { await this.journal("stale_terminal_observed", { result }); return; }
|
|
407
|
+
this.lastResult = result; this.markTerminalActivity(result);
|
|
408
|
+
const rootStop = this.sessionObservations.get(this.init?.conversation_id)?.stop;
|
|
409
|
+
const pendingTerminal = current && result.status === "SUCCESS";
|
|
410
|
+
if (current) { current.terminalResult = result; current.terminalMetadata = terminalMetadata; current.terminalPersisted = false; }
|
|
411
|
+
if (pendingTerminal && rootStop?.fullyIdle === true) for (const [id, descendant] of this.descendants) if (descendant.status === "unknown") this.descendants.set(id, { ...descendant, status: "settled_by_root", settlement_evidence: "root_fully_idle_stop" });
|
|
412
|
+
this.state.terminal_observation_pending = true;
|
|
413
|
+
try {
|
|
414
|
+
await this.persist({ active_tree: true, last_result: result, last_terminal_metadata: terminalMetadata });
|
|
415
|
+
if (current) current.terminalPersisted = true;
|
|
416
|
+
if (providerEvent) await this.journal("provider_event", { event: providerEvent });
|
|
417
|
+
await this.captureChildTools();
|
|
418
|
+
} catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
419
|
+
try { this.usageIngest = await forwardUsage(this.config, result.conversation_id ?? this.init.conversation_id, result, this.toolSnapshot(), [...this.descendants.values()]); }
|
|
420
|
+
catch (error) { this.usageIngest = { status: "failed", error: error.message ?? String(error) }; try { await this.journal("usage_ingest_failed", this.usageIngest); } catch (storage) { return this.failTerminalObservation(current, result, terminalMetadata, storage); } }
|
|
421
|
+
try { await this.persist({ terminal_observation_pending: false }); }
|
|
422
|
+
catch (error) { this.state.terminal_observation_pending = true; return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
423
|
+
this.state.terminal_observation_pending = false;
|
|
424
|
+
if (!current) return;
|
|
425
|
+
if (this.active !== current) return;
|
|
426
|
+
if (result.status === "ERROR" || result.status === "CANCELLED") {
|
|
427
|
+
this.active = null;
|
|
428
|
+
const settled = this.receipt().settled;
|
|
429
|
+
this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, native_turn_items: { observed: settled, possible_effects: true, pending: !settled }, provider_error_scope: current.previousError && current.previousError === result.error ? "repeated_conversation_error" : "terminal_result", usage_ingest: this.usageIngest });
|
|
430
|
+
try { await this.persist({ active_tree: !settled, last_terminal_failure: errorRecord(this.lastTerminalFailure) }); }
|
|
431
|
+
catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
432
|
+
return current.reject(this.lastTerminalFailure);
|
|
433
|
+
}
|
|
434
|
+
try { await this.completePendingTurn(); }
|
|
435
|
+
catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
436
|
+
}
|
|
437
|
+
async completePendingTurn() {
|
|
438
|
+
await this.observeHookFailures();
|
|
439
|
+
const current = this.active;
|
|
440
|
+
if (current?.observationError) throw current.observationError;
|
|
441
|
+
if (current?.terminalResult?.status !== "SUCCESS" || current.terminalResult !== this.lastResult) return;
|
|
442
|
+
await this.persist({ active_tree: !this.receipt().settled });
|
|
443
|
+
// Hooks can invalidate the tree while persistence is pending. Never
|
|
444
|
+
// publish a proof captured before that asynchronous boundary.
|
|
445
|
+
if (this.active !== current) return;
|
|
446
|
+
const receipt = this.receipt();
|
|
447
|
+
if (!receipt.settled) { await this.persist({ active_tree: true }); return; }
|
|
448
|
+
this.active = null;
|
|
449
|
+
current.resolve(receipt);
|
|
450
|
+
}
|
|
451
|
+
failTerminalObservation(current, result, metadata, cause) {
|
|
452
|
+
this.state.terminal_observation_pending = true;
|
|
453
|
+
const details = { ...metadata, native_outcome: result, settled: false, terminal_persisted: current?.terminalPersisted === true, observation_error: errorRecord(cause), native_turn_items: { observed: false, possible_effects: true, pending: true } };
|
|
454
|
+
const error = ["ERROR", "CANCELLED"].includes(result.status)
|
|
455
|
+
? agyTerminalFailure(result, details)
|
|
456
|
+
: new DaemonError("native_outcome_observation_failed", "Antigravity terminal was received but mandatory observation failed", false, details);
|
|
457
|
+
error.cause = cause;
|
|
458
|
+
if (result.status !== "SUCCESS") this.lastTerminalFailure = error;
|
|
459
|
+
if (current) { current.observationError = error; current.reject(error); }
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
receipt(result = this.lastResult) {
|
|
463
|
+
if (this.state.last_terminal_failure && !this.lastTerminalFailure) this.lastTerminalFailure = this.state.last_terminal_failure;
|
|
464
|
+
const terminalMetadata = this.active?.terminalResult === result ? this.active.terminalMetadata : this.state.last_result === result ? this.state.last_terminal_metadata : this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result?.error
|
|
465
|
+
&& this.lastTerminalFailure.details.provider_session_id === (result?.conversation_id || this.init?.conversation_id || null) ? this.lastTerminalFailure.details : null;
|
|
466
|
+
const assistantText = typeof result?.response === "string" ? result.response : typeof result?.text === "string" ? result.text : null;
|
|
467
|
+
const root = this.init?.conversation_id; const observation = this.sessionObservations.get(root);
|
|
468
|
+
const treeSettled = [...this.descendants.values()].every(child => child.tree_settled === true || ["completed", "failed", "cancelled", "settled_by_root"].includes(child.status));
|
|
469
|
+
// A terminal root result is the provider's authoritative end-of-turn
|
|
470
|
+
// observation when AGY does not emit a root Stop hook. A received
|
|
471
|
+
// non-idle Stop still wins, and every discovered child must be terminal.
|
|
472
|
+
const rootSettled = observation?.stop?.fullyIdle !== false;
|
|
473
|
+
const terminalPending = this.active?.terminalResult === result;
|
|
474
|
+
const settled = Boolean(!this.state.terminal_observation_pending && !this.active?.observationError && !this.state.unclaimed_activity && (!this.active || terminalPending) && result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status) && treeSettled && rootSettled);
|
|
475
|
+
return { harness: "antigravity-cli", runtime_family: "antigravity", provider_session_id: root ?? null, adapter_session_id: root ?? null, cwd: this.config.cwd, profile: this.profile, result, ...(terminalMetadata ? { terminal_metadata: terminalMetadata, ...(terminalMetadata.turn_id ? { terminal_turn_id: terminalMetadata.turn_id } : {}) } : {}), ...(assistantText === null ? {} : { assistant_text: assistantText }), usage: usageSnapshot(result ?? {}, this.toolSnapshot()), usage_ingest: this.usageIngest, descendants: [...this.descendants.values()], ...settledNativeTree(root, [...this.descendants.values()], settled, !this.state.unclaimed_activity && (!this.active || terminalPending) && rootSettled && ["SUCCESS", "ERROR", "CANCELLED"].includes(result?.status)), transcript_path: observation?.transcript_path ?? null, settled };
|
|
476
|
+
}
|
|
477
|
+
async zeroUsage() { await forwardUsage(this.config, this.init.conversation_id, { usage: {} }, this.toolSnapshot(), []); }
|
|
478
|
+
async prompt(text, assertDispatch = captureDispatchGuard(this), operationId = null, livenessTimeoutMs = 1_800_000) {
|
|
479
|
+
validateObservationDuration(livenessTimeoutMs);
|
|
480
|
+
if (!text) throw new DaemonError("prompt_required", "prompt is required");
|
|
481
|
+
await this.start(); await this.draining;
|
|
482
|
+
if (this.active || this.promptObservation) throw new DaemonError("operation_in_progress", "an Antigravity turn or its mandatory dispatch observation is already running");
|
|
483
|
+
if (this.state.last_hook_rejection && !this.receipt().settled) throw new DaemonError("tree_not_settled", "Rejected hook does not prove that the prior Antigravity tree stopped", false, { primary_error: this.state.last_hook_rejection });
|
|
484
|
+
if (this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled)) throw new DaemonError("tree_not_settled", "the prior Antigravity agent tree is not fully settled");
|
|
485
|
+
let resolveTurn, rejectTurn;
|
|
486
|
+
const reply = new Promise((resolve, reject) => { resolveTurn = resolve; rejectTurn = reject; });
|
|
487
|
+
// Cancellation can reject while admission persistence is still pending.
|
|
488
|
+
void reply.catch(() => {});
|
|
489
|
+
const current = { resolve: resolveTurn, reject: rejectTurn, generation: ++this.turnGeneration, operationId, previousError: this.lastResult?.error ?? null };
|
|
490
|
+
let resolveDispatchJournal;
|
|
491
|
+
const dispatchJournal = current.dispatchJournal = new Promise(resolve => { resolveDispatchJournal = resolve; });
|
|
492
|
+
this.promptObservation = current;
|
|
493
|
+
const finishDispatchJournal = () => {
|
|
494
|
+
current.dispatchJournalSettled = true; resolveDispatchJournal();
|
|
495
|
+
if (current.observerReturned && this.promptObservation === current) this.promptObservation = null;
|
|
496
|
+
};
|
|
497
|
+
// Reserve before persistence so different operation IDs cannot overlap.
|
|
498
|
+
current.publishProgress = operationProgressReporter();
|
|
499
|
+
this.active = current;
|
|
500
|
+
this.hookRequests = new Map();
|
|
501
|
+
const root = this.init?.conversation_id, prior = this.sessionObservations.get(root) ?? {};
|
|
502
|
+
this.sessionObservations.set(root, { ...prior, step_floor: prior.last_step_index ?? null, stop: null, last_hook: null });
|
|
503
|
+
this.lastProviderEvent = null; this.progressSteps = new Map(); this.progressHooks = new Set(); this.progress = undefined;
|
|
504
|
+
try {
|
|
505
|
+
await this.persist({ active_tree: true, last_hook_rejection: null });
|
|
506
|
+
assertDispatch();
|
|
507
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
508
|
+
if (this.active !== current) throw new DaemonError("operation_cancelled", "prompt reservation was superseded");
|
|
509
|
+
current.dispatched = true;
|
|
510
|
+
this.child.stdin.write(`${JSON.stringify({ event: "user", message: { content: text } })}\n`, error => {
|
|
511
|
+
if (error) { this.drainingError ??= nativeObservationError(error); current.dispatchObservationError = this.drainingError; current.reject(this.drainingError); finishDispatchJournal(); }
|
|
512
|
+
else void Promise.resolve().then(() => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: this.init?.conversation_id, native_request_id: current.generation })).then(finishDispatchJournal, cause => {
|
|
513
|
+
this.drainingError ??= nativeObservationError(cause); current.dispatchObservationError = this.drainingError;
|
|
514
|
+
current.reject(this.drainingError); finishDispatchJournal();
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
} catch (error) {
|
|
518
|
+
if (current.dispatched) { this.drainingError ??= nativeObservationError(error); error = this.drainingError; }
|
|
519
|
+
else if (this.active === current) this.active = null;
|
|
520
|
+
current.reject(error); finishDispatchJournal();
|
|
521
|
+
}
|
|
522
|
+
const clock = new ObservationClock({ timeoutMs: livenessTimeoutMs, onGap: gap => { void this.journal("observation_gap", { gap, operation_id: operationId }).catch(() => {}); } });
|
|
523
|
+
let checking = false;
|
|
524
|
+
const timer = setInterval(() => {
|
|
525
|
+
if (this.active !== current || checking || !clock.sample(this.progress)) return;
|
|
526
|
+
checking = true; const cursor = this.progress?.cursor;
|
|
527
|
+
void this.completePendingTurn().then(() => {
|
|
528
|
+
if (this.active !== current || this.progress?.cursor !== cursor || !clock.sample(this.progress)) return;
|
|
529
|
+
if (current.terminalResult) return this.failTerminalObservation(current, current.terminalResult, current.terminalMetadata, new Error("Native tree settlement was not confirmed within the productive silence window"));
|
|
530
|
+
current.reject(new DaemonError("operation_observation_lost", "Antigravity native outcome is unknown after the productive silence window", true, { operation_id: operationId, turn_generation: current.generation, native_outcome: "unknown", last_activity_at: this.lastActivityAt ?? null }));
|
|
531
|
+
}).catch(error => {
|
|
532
|
+
if (this.active === current && this.progress?.cursor === cursor) {
|
|
533
|
+
if (current.observationError) current.reject(current.observationError);
|
|
534
|
+
else if (current.terminalResult) { try { this.failTerminalObservation(current, current.terminalResult, current.terminalMetadata, error); } catch { /* reply owns the typed failure */ } }
|
|
535
|
+
else current.reject(new DaemonError("operation_observation_lost", "Antigravity quiet reconciliation could not confirm the native outcome", true, { operation_id: operationId, turn_generation: current.generation, native_outcome: "unknown", observation_error: errorRecord(error) }));
|
|
536
|
+
}
|
|
537
|
+
}).finally(() => { checking = false; });
|
|
538
|
+
}, Math.max(10, Math.min(1_000, livenessTimeoutMs / 4)));
|
|
539
|
+
try {
|
|
540
|
+
const result = await reply;
|
|
541
|
+
await dispatchJournal;
|
|
542
|
+
if (current.dispatchObservationError) {
|
|
543
|
+
const error = current.dispatchObservationError;
|
|
544
|
+
if (error.code !== "operation_observation_lost") throw error;
|
|
545
|
+
throw Object.assign(new Error("Antigravity completed natively but its dispatch journal could not be saved", { cause: error.cause ?? error }), {
|
|
546
|
+
code: "native_outcome_observation_failed", details: { native_outcome: result, settled: false, observation_error: errorRecord(error) }
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return result;
|
|
550
|
+
} finally {
|
|
551
|
+
clearInterval(timer); current.observerReturned = true;
|
|
552
|
+
if (current.dispatchJournalSettled && this.promptObservation === current) this.promptObservation = null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
childActivity(sessionId) {
|
|
556
|
+
if (sessionId === this.init?.conversation_id && !this.active) this.state.unclaimed_activity = true;
|
|
557
|
+
const child = this.descendants.get(sessionId);
|
|
558
|
+
if (!child) return;
|
|
559
|
+
this.descendants.set(sessionId, { ...child, previous_terminal_status: child.status === "unknown" ? child.previous_terminal_status : child.status, status: "unknown", tree_settled: false, settlement_evidence: null, settlement_daemon_id: null });
|
|
560
|
+
}
|
|
561
|
+
async childForHook(conversationId) {
|
|
562
|
+
if (conversationId === this.init?.conversation_id) return null;
|
|
563
|
+
const deadline = Date.now() + 1_000;
|
|
564
|
+
while (!this.descendants.has(conversationId) && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 10));
|
|
565
|
+
const child = this.descendants.get(conversationId);
|
|
566
|
+
if (!child) throw new DaemonError("agy_child_identity_unconfirmed", "Antigravity hook has no confirmed native parent", true, { conversation_id: conversationId });
|
|
567
|
+
if (child.parent_provider_session_id !== this.init?.conversation_id) throw new DaemonError("agy_child_parent_unqualified", "Nested Antigravity worker is not a direct child of the controlled Session", false, { conversation_id: conversationId, parent_conversation_id: child.parent_provider_session_id });
|
|
568
|
+
return child;
|
|
569
|
+
}
|
|
570
|
+
async rejectHook(error, event, payload, current = this.active) {
|
|
571
|
+
if (current && this.active !== current) return;
|
|
572
|
+
const correctable = current && event === "PreToolUse" && isProvenShellCorrection(error);
|
|
573
|
+
if (correctable && current.hookCorrectionGeneration !== current.generation) {
|
|
574
|
+
// The request was correlated with this still-active Turn by
|
|
575
|
+
// observeHookFailures. Persist before allowing native same-Turn recovery.
|
|
576
|
+
const correction = { code: error.code, message: error.message, details: error.details, event, conversation_id: payload.conversationId ?? null,
|
|
577
|
+
daemon_id: this.config.daemonId, operation_id: current.operationId, turn_generation: current.generation };
|
|
578
|
+
try {
|
|
579
|
+
await hookBudget(1_000, null).run(async () => { await this.journal("hook_correction", { correction }); await this.persist({ last_hook_correction: correction }); }, "correction_persistence");
|
|
580
|
+
current.hookCorrectionGeneration = current.generation;
|
|
581
|
+
return;
|
|
582
|
+
} catch (storage) { error = Object.assign(new Error("Could not persist native hook correction"), { code: "native_hook_receipt_invalid", details: { effect: "unknown", primary_error: correction, receipt_error: storage.message } }); }
|
|
583
|
+
}
|
|
584
|
+
const rejection = this.state.last_hook_rejection && this.state.last_hook_rejection.turn_generation === current?.generation ? this.state.last_hook_rejection : { code: error.code ?? "agy_hook_rejected", message: error.message ?? String(error), details: error.details, event, conversation_id: payload.conversationId ?? null, tool: payload.toolCall?.name ?? null, daemon_id: this.config.daemonId, operation_id: current?.operationId ?? null, turn_generation: current?.generation ?? null };
|
|
585
|
+
this.state.last_hook_rejection = rejection;
|
|
586
|
+
if (current && this.active === current) { this.active = null; current.reject(new DaemonError(rejection.code, rejection.message, false, rejection)); }
|
|
587
|
+
try { await hookBudget(1_000, null).run(async () => { await this.journal("hook_rejected", { rejection }); await this.persist({ last_hook_rejection: rejection }); }, "failure_persistence"); }
|
|
588
|
+
catch (storageError) { rejection.receipt_error = { code: storageError.code, message: storageError.message }; }
|
|
589
|
+
}
|
|
590
|
+
async observeHookFailures() {
|
|
591
|
+
for (const [requestId, binding] of this.hookRequests ?? []) {
|
|
592
|
+
if (binding.current !== this.active || !this.active) continue;
|
|
593
|
+
let failure;
|
|
594
|
+
try { failure = JSON.parse(await readFile(path.join(this.paths.dir, "hook-failures", `${requestId}.json`), "utf8")); }
|
|
595
|
+
catch (error) { if (error.code === "ENOENT") continue; failure = { code: "native_hook_receipt_invalid", message: error.message }; }
|
|
596
|
+
if (failure.request_id !== undefined && failure.request_id !== requestId) continue;
|
|
597
|
+
this.hookRequests.delete(requestId);
|
|
598
|
+
await this.rejectHook(Object.assign(new Error(failure.message ?? "Native hook failed"), { code: failure.code, details: failure.details }), binding.event, binding.payload, binding.current);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async observeHook(event, payload, budget = null) { const conversationId = payload.conversationId ?? this.init?.conversation_id; const child = await this.childForHook(conversationId); budget?.remaining("identity"); const prior = this.sessionObservations.get(conversationId) ?? {}; const execution = Number.isInteger(payload.executionNum) ? payload.executionNum : null; const step = Number.isInteger(payload.stepIdx) ? payload.stepIdx : null; const floor = prior.step_floor; const stale = step !== null && ((Number.isInteger(floor) && step <= floor) || (child && Number.isInteger(prior.last_step_index) && step < prior.last_step_index)); if (stale) { await this.journal("stale_hook", { hook_event: event, conversation_id: conversationId, step_index: step }); return { stale: true }; } const fingerprint = createHash("sha256").update(JSON.stringify({ event, payload })).digest("hex"); this.progressHooks ??= new Set(); const scoped = this.active && (!child || child.activity_generation === this.active.generation); const progressKey = JSON.stringify([conversationId, step, execution, event, payload.toolCall ?? null, payload.fullyIdle ?? null]); if (scoped && step !== null && (["PreToolUse", "PostToolUse"].includes(event) || event === "Stop" && payload.fullyIdle === true) && !this.progressHooks.has(progressKey) && this.progressHooks.size < 1_024) { this.progressHooks.add(progressKey); this.markActivity(); } if (conversationId && !stale) this.sessionObservations.set(conversationId, { ...prior, ...(step !== null ? { last_step_index: Math.max(prior.last_step_index ?? -1, step) } : {}), last_hook: fingerprint, ...(event === "PreToolUse" ? { last_pre_hook: fingerprint } : {}), ...(payload.transcriptPath ? { transcript_path: payload.transcriptPath } : {}), ...(event === "Stop" ? { stop: { fullyIdle: payload.fullyIdle === true, terminationReason: payload.terminationReason ?? null, executionNum: payload.executionNum ?? null } } : {}) }); if (!stale && event === "PreToolUse" && prior.last_pre_hook !== fingerprint) this.childActivity(conversationId); if (child && !stale && event === "Stop" && payload.fullyIdle === true) this.descendants.set(conversationId, { ...this.descendants.get(conversationId), tree_settled: true, settlement_evidence: "child_fully_idle_stop" }); const terminalPending = this.active?.terminalResult === this.lastResult; if (!child && !stale && event === "Stop" && payload.fullyIdle === true && (!this.active || terminalPending) && ["SUCCESS", "ERROR", "CANCELLED"].includes(this.lastResult?.status)) for (const [id, descendant] of this.descendants) if (descendant.status === "unknown") this.descendants.set(id, { ...descendant, status: "settled_by_root", settlement_evidence: "root_fully_idle_stop" }); const parent = child?.parent_provider_session_id ?? null;
|
|
602
|
+
const observed = payload.model ? observedProfile(payload) : {};
|
|
603
|
+
await observeModel({ journal: this.config.journal, harness: "antigravity-cli", sessionId: conversationId, parentSessionId: parent, requested: this.config, observed, source: { channel: "native.hook", scope: "configured", ...(!payload.model ? { non_asserting: true } : {}) }, evidence: payload.model ? "configured" : "unavailable", reason: payload.model ? null : "hook_omits_current_model" });
|
|
604
|
+
const eventId = createHash("sha256").update(JSON.stringify({ daemon: this.config.daemonId, event, conversationId, fully_idle: payload.fullyIdle ?? null, termination_reason: payload.terminationReason ?? null, execution: payload.executionNum ?? null, step: payload.stepIdx ?? null, tool: payload.toolCall?.name ?? null, args: payload.toolCall?.args ?? null })).digest("hex"); await this.persist(); await this.journal("hook", { stale, hook_event: event, event_id: eventId, conversation_id: conversationId, turn_generation: this.turnGeneration, execution_num: execution, step_index: step, termination_reason: payload.terminationReason ?? null, fully_idle: payload.fullyIdle ?? null, persistence_timing: this.persistTiming ?? null }); await this.completePendingTurn(); return { event_id: eventId, daemon_id: this.config.daemonId, launch_cwd: this.config.cwd, parent_conversation_id: parent, profile: observed, requested_profile: { provider: this.config.provider, model: this.config.model, reasoning: this.config.reasoning, mode: this.config.mode }, project_root: this.config.projectRoot, dd_flow_bin: this.config.ddFlowBin, dd_flow_home: this.config.ddFlowHome };
|
|
605
|
+
}
|
|
606
|
+
async finalizeProviderForControl() {
|
|
607
|
+
const pending = this.providerFinalization;
|
|
608
|
+
try {
|
|
609
|
+
try { await pending; }
|
|
610
|
+
catch (error) {
|
|
611
|
+
// Control retries only retained physical/resource cleanup, never a
|
|
612
|
+
// missing mandatory stream record or a productive provider request.
|
|
613
|
+
if (this.providerFinalization !== pending) await this.providerFinalization;
|
|
614
|
+
else {
|
|
615
|
+
if (!this.retryProviderFinalization) throw error;
|
|
616
|
+
this.providerFinalization = this.retryProviderFinalization(5_000);
|
|
617
|
+
await this.providerFinalization;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
await this.promptObservation?.dispatchJournal;
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (!this.drainingError) throw error;
|
|
623
|
+
this.drainingError.cleanup_error = errorRecord(error);
|
|
624
|
+
this.drainingError.details = { ...this.drainingError.details, cleanup_error: this.drainingError.cleanup_error };
|
|
625
|
+
throw this.drainingError;
|
|
626
|
+
}
|
|
627
|
+
if (this.drainingError) throw this.drainingError;
|
|
628
|
+
}
|
|
629
|
+
async prepareStop(cancelTree) {
|
|
630
|
+
const current = this.active, priorResult = this.lastResult;
|
|
631
|
+
const emptyTree = !current && !this.state.active_tree && !this.state.unclaimed_activity && !priorResult && !this.descendants.size;
|
|
632
|
+
if (cancelTree) await this.cancel();
|
|
633
|
+
else if (this.active || this.state.active_tree || this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled)) throw new DaemonError("tree_not_settled", "Antigravity root or child tree is still running");
|
|
634
|
+
// A physical kill is settlement, not a native clean terminal receipt.
|
|
635
|
+
// In particular, the previous Turn's SUCCESS cannot certify a cancelled
|
|
636
|
+
// in-flight Turn that exited without its own result.
|
|
637
|
+
const currentResult = !current || current.terminalResult === this.lastResult || this.lastResult !== priorResult;
|
|
638
|
+
const clean = emptyTree || (currentResult && this.receipt().settled);
|
|
639
|
+
return { clean, ...(clean ? {} : { native_settlement: { confirmed: false, reason: "native_terminal_unconfirmed" } }) };
|
|
640
|
+
}
|
|
641
|
+
async cancel() { cancelPendingDispatch(this); if (!this.child) return { ...this.receipt(), settled: Boolean(this.exited) }; if (!this.exited) { try { process.kill(-this.child.pid, "SIGINT"); } catch (error) { if (error.code !== "ESRCH") throw error; } const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) await stopProcessGroup(this.child, 5_000); } await this.finalizeProviderForControl(); await this.persist({ active_tree: false }); return { ...this.receipt(), cancelled: true, settled: Boolean(this.exited) };
|
|
642
|
+
}
|
|
643
|
+
async close(cancelTree) { if (cancelTree) cancelPendingDispatch(this); if (!cancelTree && !this.exited && (this.active || this.state.active_tree || this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled))) throw new DaemonError("tree_not_settled", "Antigravity root or child tree is still running"); if (cancelTree) await this.cancel(); else if (this.child && !this.exited) this.child.stdin.end(); const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) await this.cancel(); await this.finalizeProviderForControl(); }
|
|
644
|
+
async abortProvider(reason) {
|
|
645
|
+
const child = this.child, providerProcess = this.providerProcess;
|
|
646
|
+
await stopProcessGroup(child);
|
|
647
|
+
await this.finalizeProviderForControl();
|
|
648
|
+
await finishDaemonProcess(this.config, providerProcess, "failed", reason);
|
|
649
|
+
}
|
|
650
|
+
async finishResource(state = "stopped", reason = "daemon_stopped") { await this.captureChildTools(); await finishDaemonProcess(this.config, this.state.resource_process, state, reason); }
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export async function serveDaemon(stateDir) {
|
|
654
|
+
const paths = locations(stateDir), state = await readState(paths.dir); if (!state?.config) throw new DaemonError("daemon_state_missing", "Antigravity daemon state is missing");
|
|
655
|
+
scheduleDaemonProcessHeartbeat(state.config, state.resource_process);
|
|
656
|
+
const runtime = new Runtime(paths, state); await removeSocket(paths);
|
|
657
|
+
const server = net.createServer(socket => {
|
|
658
|
+
let buffer = "", consumed = false; socket.setEncoding("utf8");
|
|
659
|
+
socket.on("data", chunk => {
|
|
660
|
+
if (consumed) return;
|
|
661
|
+
buffer += chunk; if (!admitNativeRequestBuffer(buffer, socket, RESPONSE_SCHEMA)) { consumed = true; return; } const at = buffer.indexOf("\n"); if (at < 0) return; consumed = true;
|
|
662
|
+
void (async () => {
|
|
663
|
+
let request;
|
|
664
|
+
try {
|
|
665
|
+
request = JSON.parse(buffer.slice(0, at));
|
|
666
|
+
const result = await durableDaemonDispatch(paths.dir, request, () => dispatch(runtime, request), () => dispatch(runtime, { operation: "session.inspect", params: request.params ?? {} }));
|
|
667
|
+
if (result._shutdown) retireDaemonEndpoint(server);
|
|
668
|
+
socket.end(`${JSON.stringify({ schema_id: RESPONSE_SCHEMA, id: request.id, ok: true, result: { ...result, _shutdown: undefined } })}\n`);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
await retireFailedDaemon(server, state, error, patch => runtime.persist(patch));
|
|
671
|
+
socket.end(`${JSON.stringify({ schema_id: RESPONSE_SCHEMA, id: request?.id ?? null, ok: false, error: errorPayload(error) })}\n`);
|
|
672
|
+
}
|
|
673
|
+
})();
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
trackDaemonConnections(server); runtime.server = server;
|
|
677
|
+
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(paths.socket, resolve); }); await chmod(paths.socket, 0o600);
|
|
678
|
+
try { await runtime.start(state.config.resumeSessionId); runtime.requireSessionIdentity(state.config.resumeSessionId); }
|
|
679
|
+
catch (error) {
|
|
680
|
+
try { await runtime.abortProvider("daemon_start_failed"); }
|
|
681
|
+
catch (cleanup) { error.details = { ...error.details, cleanup_failed: errorRecord(cleanup) }; }
|
|
682
|
+
try { if (!error.details?.cleanup_failed) await runtime.finishResource("failed", "daemon_start_failed"); }
|
|
683
|
+
catch (cleanup) { error.details = { ...error.details, resource_cleanup_failed: errorRecord(cleanup) }; }
|
|
684
|
+
Object.assign(state, { shutdown_state: error.details?.cleanup_failed || error.details?.resource_cleanup_failed ? "cleanup_failed" : "failed", active_tree: Boolean(error.details?.cleanup_failed) });
|
|
685
|
+
await retainStartupFailure({ state, publish: snapshot => runtime.persist(snapshot), log: paths.log, error });
|
|
686
|
+
await new Promise(resolve => server.close(resolve));
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
await new Promise(resolve => server.once("close", resolve));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function dispatch(runtime, request) {
|
|
693
|
+
if (request.schema_id !== REQUEST_SCHEMA) throw new DaemonError("daemon_protocol_mismatch", "unsupported dd-agy request schema"); const params = request.params ?? {};
|
|
694
|
+
if (["session.create", "session.prompt", "session.start", "session.fork", "session.resume"].includes(request.operation)) assertDaemonDispatch(runtime.paths.dir);
|
|
695
|
+
if (request.operation === "daemon.status") { await runtime.observeHookFailures(); return { daemon_id: runtime.config.daemonId, shutdown_state: runtime.state.shutdown_state, active_tree: runtime.state.active_tree, active_operation: runtime.active?.operationId ?? null, last_hook_rejection: runtime.state.last_hook_rejection ?? null, last_activity_at: runtime.lastActivityAt, provider_ready: Boolean(runtime.init && !runtime.exited), pid: process.pid, provider_pid: runtime.child?.pid ?? null, versions: runtime.state.versions, config: runtime.config, sessions: runtime.state.sessions ?? [], receipt: runtime.receipt() }; }
|
|
696
|
+
if (request.operation === "session.create") { if (params.sessionId) throw new DaemonError("invalid_create", "create does not accept a Session ID"); if (params.prompt) throw new DaemonError("invalid_create", "create does not execute a prompt; use session.prompt"); await runtime.start(); await runtime.zeroUsage(); return runtime.receipt(); }
|
|
697
|
+
if (request.operation === "session.prompt") { const assertDispatch = captureDispatchGuard(runtime); await runtime.start(params.sessionId ?? null); runtime.requireSessionIdentity(params.sessionId); return await runtime.prompt(params.prompt, assertDispatch, request.id, params.livenessTimeoutMs ?? runtime.config.livenessTimeoutMs ?? 1_800_000); }
|
|
698
|
+
if (request.operation === "session.inspect") { runtime.requireSessionIdentity(params.sessionId); await runtime.captureChildTools(); return runtime.receipt(); }
|
|
699
|
+
if (request.operation === "session.resume") { await runtime.start(params.sessionId ?? runtime.init?.conversation_id ?? null); runtime.requireSessionIdentity(params.sessionId); return runtime.receipt(); }
|
|
700
|
+
if (request.operation === "session.cancel") { runtime.requireSessionIdentity(params.sessionId); return await runtime.cancel(); }
|
|
701
|
+
if (request.operation === "hook.observe") {
|
|
702
|
+
// Check the process binding before correlating a request with an active
|
|
703
|
+
// turn. A stale/foreign daemon must not poison an unrelated operation.
|
|
704
|
+
if (runtime.config.hookBinding === "environment-v1" && params.daemonId !== runtime.config.daemonId) throw new DaemonError("agy_hook_binding_invalid", "Antigravity hook binding does not name this daemon", false, { expected_daemon_id: runtime.config.daemonId, observed_daemon_id: params.daemonId ?? null });
|
|
705
|
+
const current = runtime.active, budget = hookBudget(25_000, params.deadlineAt);
|
|
706
|
+
runtime.hookRequests ??= new Map();
|
|
707
|
+
if (/^[A-Za-z0-9-]{1,128}$/.test(request.id)) runtime.hookRequests.set(request.id, { current, event: params.event, payload: { conversationId: params.payload?.conversationId, toolCall: { name: params.payload?.toolCall?.name } } });
|
|
708
|
+
try {
|
|
709
|
+
const result = await budget.run(async () => {
|
|
710
|
+
if (runtime.hookRequests.has(request.id) && current?.operationId) {
|
|
711
|
+
const directory = path.join(runtime.paths.dir, "operations", createHash("sha256").update(current.operationId).digest("hex"), "hooks");
|
|
712
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
713
|
+
await writeJson(path.join(directory, `${request.id}.json`), { schema_id: "dd-flow/hook-request@1", request_id: request.id, daemon_id: runtime.config.daemonId, operation_id: current.operationId, turn_generation: current.generation, event: params.event, conversation_id: params.payload?.conversationId ?? null, tool: params.payload?.toolCall?.name ?? null });
|
|
714
|
+
}
|
|
715
|
+
budget.remaining("observation");
|
|
716
|
+
return await runtime.observeHook(params.event, params.payload ?? {}, budget);
|
|
717
|
+
}, "observation");
|
|
718
|
+
await runtime.observeHookFailures(); budget.remaining("observation");
|
|
719
|
+
if (current && runtime.state.last_hook_rejection?.turn_generation === current.generation) throw Object.assign(new Error(runtime.state.last_hook_rejection.message), runtime.state.last_hook_rejection);
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
catch (error) { await runtime.rejectHook(error, params.event, params.payload ?? {}, current); throw error; }
|
|
723
|
+
}
|
|
724
|
+
if (request.operation === "daemon.stop") return await completeDaemonShutdown(runtime.paths.dir, runtime.state, patch => runtime.persist(patch), () => runtime.prepareStop(params.cancelTree === true), [["provider", () => runtime.close(false)], ["daemon_resource", () => runtime.finishResource()]]);
|
|
725
|
+
throw new DaemonError("unknown_operation", `unknown dd-agy daemon operation: ${request.operation}`);
|
|
726
|
+
}
|
|
727
|
+
import { authorizeRetainedDaemonResume } from "./driver-recovery.mjs";
|
|
728
|
+
import { commandText } from "./process-json.mjs";
|