@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.150
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 +895 -0
- package/README.md +140 -6
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +386 -0
- package/dist/cli/command-metadata.js +232 -0
- package/dist/cli/command-option-descriptions.js +192 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -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 +396 -19
- package/dist/cli/hook-ingress.js +102 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +50 -0
- package/dist/cli/memory-init-invocation.js +85 -0
- package/dist/cli/memory-init.js +249 -0
- package/dist/cli/memory-storage-admission.js +54 -0
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +90 -0
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +72 -0
- package/dist/cli/run-cli.js +1734 -550
- 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 +150 -0
- package/dist/cli.js +89 -2
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -0
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +28 -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-error-boundary.mjs +58 -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/agy-native-inspection.mjs +216 -0
- package/dist/harness-runtime/lib/agy-native_pb.js +117 -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 +2 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1096 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +622 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1175 -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 +140 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -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 +698 -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 +23 -0
- package/dist/harness-runtime/lib/native-children.mjs +219 -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 +95 -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 +15 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +151 -0
- package/dist/harness-runtime/lib/runner-events.mjs +202 -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 +53 -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-decision.schema.json +9 -0
- 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/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +211 -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 +29 -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/mb-upgrade-stage-result.schema.json +863 -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 +56 -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 +12 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
- package/dist/schemas/release-impact.schema.json +3 -3
- 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/stage-start-response.schema.json +4 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/accepted-intent.js +139 -0
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +54 -15
- package/dist/services/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +562 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +26 -63
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1398 -119
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +16 -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 +94 -0
- package/dist/services/controller-fanout.js +226 -0
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +49 -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 +107 -0
- package/dist/services/descriptor-run-start.js +475 -0
- package/dist/services/descriptor-stage-coordinator.js +250 -0
- package/dist/services/descriptor-stage-settlement.js +223 -0
- package/dist/services/descriptor-stage-start.js +175 -0
- package/dist/services/diagnostic-evidence.js +73 -0
- package/dist/services/diagnostic-report-publication.js +72 -0
- package/dist/services/diagnostic-report.js +67 -0
- package/dist/services/diagnostic-run-bundle.js +74 -0
- package/dist/services/diagnostic-snapshot.js +159 -0
- package/dist/services/diagnostic-stage-handlers.js +108 -0
- package/dist/services/diagnostic-stage-results.js +132 -0
- package/dist/services/diagnostic-start.js +84 -0
- package/dist/services/diagnostic-work-batch.js +34 -0
- package/dist/services/diagnostic-work-collection.js +97 -0
- package/dist/services/diagnostic-work-registration.js +101 -0
- package/dist/services/diagnostic-work-results.js +93 -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 +354 -163
- package/dist/services/eval-snapshots.js +1316 -150
- package/dist/services/execution-bundles.js +275 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +310 -0
- package/dist/services/flow-catalog.js +30 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +387 -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/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +791 -326
- package/dist/services/hybrid-contracts.js +474 -0
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +226 -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 +2148 -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 +449 -69
- package/dist/services/memory-accepted-composition.js +78 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +328 -0
- package/dist/services/memory-compose-handler.js +400 -0
- package/dist/services/memory-composition-acceptance.js +375 -0
- package/dist/services/memory-composition-graph.js +165 -0
- package/dist/services/memory-composition-registration.js +161 -0
- package/dist/services/memory-composition-work-custody.js +156 -0
- package/dist/services/memory-composition-work-results.js +108 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +37 -0
- package/dist/services/memory-corrected-review-findings.js +108 -0
- package/dist/services/memory-corrected-review-inputs.js +215 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +178 -0
- package/dist/services/memory-corrected-review-registration.js +192 -0
- package/dist/services/memory-correction-check-receipt.js +235 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +457 -0
- package/dist/services/memory-finish-handler.js +229 -0
- package/dist/services/memory-finish-integration.js +450 -0
- package/dist/services/memory-finish-readiness.js +169 -0
- package/dist/services/memory-git-integration.js +220 -0
- package/dist/services/memory-historical-corrected-review.js +212 -0
- package/dist/services/memory-historical-original-review.js +143 -0
- package/dist/services/memory-historical-repair-registration.js +105 -0
- package/dist/services/memory-historical-second-review.js +147 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +131 -0
- package/dist/services/memory-init-controller-start.js +103 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +165 -0
- package/dist/services/memory-init-lint-capability.js +203 -0
- package/dist/services/memory-init-native-capacity.js +309 -0
- package/dist/services/memory-init-preflight.js +334 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +213 -0
- package/dist/services/memory-init-request-input.js +82 -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 +135 -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 +91 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +174 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +221 -0
- package/dist/services/memory-lint-config.js +194 -0
- package/dist/services/memory-native-review-copy.js +103 -0
- package/dist/services/memory-normal-flow-entry.js +87 -0
- package/dist/services/memory-ordinary-defaults.js +129 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +274 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +488 -0
- package/dist/services/memory-repair-history.js +496 -0
- package/dist/services/memory-repair-physical-binding.js +194 -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 +162 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +795 -0
- package/dist/services/memory-repair-registration.js +240 -0
- package/dist/services/memory-repair-work-baseline.js +415 -0
- package/dist/services/memory-repair-work-completion.js +117 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +108 -0
- package/dist/services/memory-repair-workspace.js +261 -0
- package/dist/services/memory-repair-writer-copy.js +736 -0
- package/dist/services/memory-result-binding.js +239 -0
- package/dist/services/memory-retained-review-authority.js +138 -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 +162 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +206 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +246 -0
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +117 -0
- package/dist/services/memory-review-work-custody.js +126 -0
- package/dist/services/memory-review-work-input.js +91 -0
- package/dist/services/memory-review-work-results.js +95 -0
- package/dist/services/memory-second-corrected-review-inputs.js +167 -0
- package/dist/services/memory-second-correction-plan.js +77 -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 +65 -0
- package/dist/services/memory-stage-results.js +290 -0
- package/dist/services/memory-tool-probe.js +25 -0
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +156 -0
- package/dist/services/memory-verification-checks.js +317 -0
- package/dist/services/memory-verification-receipt.js +160 -0
- package/dist/services/memory-verify-handler.js +137 -0
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +64 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +163 -35
- package/dist/services/migrations.js +164 -114
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +50 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +17 -87
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +455 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +588 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +150 -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 +31 -0
- package/dist/services/repair-receipt.js +13 -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 +428 -0
- package/dist/services/run-control.js +1092 -0
- package/dist/services/run-controller-adapter.js +400 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +205 -0
- package/dist/services/run-controller-recovery.js +295 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1326 -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 +107 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +456 -0
- package/dist/services/run-start-custody.js +189 -0
- package/dist/services/runs.js +1023 -246
- package/dist/services/runtime-budget.js +446 -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 +605 -0
- package/dist/services/runtime-scope-stop.js +211 -0
- package/dist/services/runtime-scope-worker.js +314 -0
- package/dist/services/runtime-service.js +19 -14
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +36 -91
- package/dist/services/setup.js +427 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +193 -19
- package/dist/services/stage-handlers.js +89 -0
- package/dist/services/stage-lifecycle.js +152 -116
- package/dist/services/stage-pause.js +202 -91
- 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 +393 -0
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code-review.js +235 -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 +355 -21
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +287 -132
- package/dist/services/vnext-plan.js +228 -104
- package/dist/services/vnext-protocolize.js +533 -95
- package/dist/services/vnext-specify.js +123 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1706 -200
- package/dist/services/workspace-bootstrap.js +53 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +105 -48
- package/dist/shared/entity-references.js +1 -0
- package/dist/shared/errors.js +27 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +930 -40
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +159 -0
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paired-writer-migration.js +424 -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 +96 -0
- package/dist/storage/writer-migration.js +346 -0
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +50 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/probe-codex-history.mjs +37 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,1096 @@
|
|
|
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 { inspectAgyNative, stopAgyNativeTree, ownedAgyEndpoint, readAgyNativeQualification, AGY_NATIVE_INSPECTION_CONTRACT } from './agy-native-inspection.mjs';
|
|
7
|
+
import { processSnapshot } from './process-snapshot.mjs';
|
|
8
|
+
import { streamRequestKey } from './provider-overload-policy.mjs';
|
|
9
|
+
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
10
|
+
import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
|
|
11
|
+
import { ToolObservations, agyToolObservation, agyTranscriptObservations } from "./tool-observations.mjs";
|
|
12
|
+
import { constants } from "node:fs";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
15
|
+
import { appendFile, chmod, lstat, mkdir, open, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import net from "node:net";
|
|
18
|
+
import { isDeepStrictEqual } from "node:util";
|
|
19
|
+
import { ObservationClock, observedTimeout, validateObservationDuration } from "./observation-clock.mjs";
|
|
20
|
+
import { hookBudget } from "./native-hook-command.mjs";
|
|
21
|
+
import { agyWorkspaceHooks } from "./agy-hooks.mjs";
|
|
22
|
+
import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { observeModel } from "./model-observations.mjs";
|
|
25
|
+
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot, managedAgyEnv } from "./dd-agy.mjs";
|
|
26
|
+
import { recoveredAgyErrorBoundary } from "./agy-error-boundary.mjs";
|
|
27
|
+
import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
|
|
28
|
+
import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
29
|
+
import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
30
|
+
|
|
31
|
+
const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
|
|
32
|
+
class DaemonError extends AgyError {}
|
|
33
|
+
// Only unambiguously correlated native tool results can introduce children or
|
|
34
|
+
// classify services. This transcript format has no tool-call IDs: parallel
|
|
35
|
+
// calls therefore retain unknown resources, never FIFO-derived authority.
|
|
36
|
+
export function agyTranscriptFacts(text, source, sessionId) {
|
|
37
|
+
const children = [], resources = new Map(), calls = [];
|
|
38
|
+
const result = () => ({ children, resources: [...resources.values()] });
|
|
39
|
+
if (!source?.locator || !source?.sha256 || !sessionId) return result();
|
|
40
|
+
const lines = text.split('\n');
|
|
41
|
+
for (let index = 0; index < lines.length; index++) {
|
|
42
|
+
const line = lines[index];
|
|
43
|
+
if (!line && index === lines.length - 1) continue;
|
|
44
|
+
let event;
|
|
45
|
+
try { event = JSON.parse(line); } catch {
|
|
46
|
+
// A writer may have appended only part of the last record. Retain the
|
|
47
|
+
// valid prefix; a malformed newline-terminated record is a real gap.
|
|
48
|
+
return index === lines.length - 1 ? result() : { ...result(), gap: 'agy_transcript_invalid_record' };
|
|
49
|
+
}
|
|
50
|
+
if (!event || typeof event !== 'object' || Array.isArray(event)) return { ...result(), gap: 'agy_transcript_invalid_record' };
|
|
51
|
+
const content = typeof event.content === 'string' ? event.content : '';
|
|
52
|
+
const native_source = { ...source, step_index: event.step_index };
|
|
53
|
+
if (event.source === 'SYSTEM' && event.type === 'SYSTEM_MESSAGE' && event.status === 'DONE') {
|
|
54
|
+
const terminal = content.match(/(?:^|\n)\[Message\] timestamp=[^\n]+ sender=([\w-]+\/task-\d+) priority=MESSAGE_PRIORITY_(?:HIGH|LOW) content=Task id "([\w-]+\/task-\d+)" (finished with result:\nCommand exited with code (-?\d+)\.|was canceled with result:\nTool execution was canceled)(?:\n|$)/);
|
|
55
|
+
if (terminal && terminal[1] === terminal[2]) {
|
|
56
|
+
const prior = resources.get(terminal[1]);
|
|
57
|
+
if (prior) resources.set(prior.resource_id, mergeAgyResource(prior, { ...prior, state: 'stopped', native_outcome: terminal[4] === undefined ? 'cancelled' : Number(terminal[4]) === 0 ? 'completed' : 'failed', terminal_source: native_source }));
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (event.source !== 'MODEL') continue;
|
|
62
|
+
if (event.type === 'PLANNER_RESPONSE') { calls.length = 0; if (Array.isArray(event.tool_calls) && event.tool_calls.length === 1) calls.push(event.tool_calls[0]); continue; }
|
|
63
|
+
if (event.type !== 'GENERIC') continue;
|
|
64
|
+
const call = calls.shift();
|
|
65
|
+
if (call?.name === 'manage_task' && event.status === 'DONE') {
|
|
66
|
+
const decode = value => { try { return JSON.parse(value); } catch { return value; } };
|
|
67
|
+
const id = decode(call.args?.TaskId), action = decode(call.args?.Action);
|
|
68
|
+
const prior = resources.get(id);
|
|
69
|
+
if (prior && action === 'kill' && content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nTask "([\w-]+\/task-\d+)" cancelled\.(?:\n|$)/)?.[1] === id)
|
|
70
|
+
resources.set(id, mergeAgyResource(prior, { ...prior, state: 'stopped', native_outcome: 'cancelled', terminal_source: native_source }));
|
|
71
|
+
const status = action === 'status' && content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nTask: ([\w-]+\/task-\d+)\nStatus: (COMPLETED|FAILED|CANCELLED)\n/);
|
|
72
|
+
if (prior && status?.[1] === id) resources.set(id, mergeAgyResource(prior, { ...prior, state: 'stopped', native_outcome: status[2].toLowerCase(), terminal_source: native_source }));
|
|
73
|
+
}
|
|
74
|
+
if (call?.name === 'invoke_subagent' && event.status === 'DONE') {
|
|
75
|
+
const match = content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nCreated the following subagent:\n(\{[\s\S]*?\})\nThe subagent will send you a message/);
|
|
76
|
+
if (!match) continue;
|
|
77
|
+
try {
|
|
78
|
+
const child = JSON.parse(match[1]);
|
|
79
|
+
if (/^[\w-]+$/.test(child.conversationId) && typeof child.logAbsoluteUri === 'string') children.push({ provider_session_id: child.conversationId, parent_provider_session_id: sessionId, log_uri: child.logAbsoluteUri, workspace_uris: child.workspaceUris ?? [], status: 'unknown', native_source });
|
|
80
|
+
} catch { /* No partial/guessed native edge. */ }
|
|
81
|
+
}
|
|
82
|
+
if (event.status !== 'RUNNING') continue;
|
|
83
|
+
const task = content.match(/^Created At: [^\n]+\nTool is running as a background task with task id: ([\w-]+\/task-\d+)\n/);
|
|
84
|
+
if (!task || !task[1].startsWith(`${sessionId}/`)) continue;
|
|
85
|
+
let daemon = call?.name === 'run_command' ? call.args?.IsDaemon : null;
|
|
86
|
+
if (typeof daemon === 'string') { try { daemon = JSON.parse(daemon); } catch { daemon = null; } }
|
|
87
|
+
const resource = { resource_id: task[1], provider_session_id: sessionId, classification: daemon === true ? 'support_service' : daemon === false ? 'required_work' : 'unknown', state: 'unknown', native_source,
|
|
88
|
+
...(typeof daemon === 'boolean' ? { native_classification: { tool: 'run_command', IsDaemon: daemon } } : {}) };
|
|
89
|
+
resources.set(resource.resource_id, mergeAgyResource(resources.get(resource.resource_id), resource));
|
|
90
|
+
}
|
|
91
|
+
return result();
|
|
92
|
+
}
|
|
93
|
+
export function mergeAgyResource(prior, next) {
|
|
94
|
+
if (!prior) return next;
|
|
95
|
+
if (prior.provider_session_id !== next.provider_session_id || prior.native_source?.locator !== next.native_source?.locator)
|
|
96
|
+
return { ...prior, state: 'unknown', observation_conflict: true };
|
|
97
|
+
if (prior.native_source?.step_index !== next.native_source?.step_index)
|
|
98
|
+
return { ...prior, state: 'unknown', observation_conflict: true };
|
|
99
|
+
if (prior.observation_conflict || next.observation_conflict || prior.native_outcome && next.native_outcome && prior.native_outcome !== next.native_outcome)
|
|
100
|
+
return { ...prior, state: 'unknown', observation_conflict: true };
|
|
101
|
+
if (next.native_outcome && (!Number.isSafeInteger(next.native_source?.step_index) || !Number.isSafeInteger(next.terminal_source?.step_index) || next.terminal_source.step_index <= next.native_source.step_index))
|
|
102
|
+
return { ...prior, state: 'unknown', observation_conflict: true };
|
|
103
|
+
if (prior.state === 'stopped' && next.state !== 'stopped') {
|
|
104
|
+
if (next.native_source?.step_index > (prior.terminal_source?.step_index ?? prior.native_source?.step_index))
|
|
105
|
+
return { ...prior, state: 'unknown', observation_conflict: true };
|
|
106
|
+
return prior;
|
|
107
|
+
}
|
|
108
|
+
return { ...prior, ...next };
|
|
109
|
+
}
|
|
110
|
+
export const agyTranscriptResources = (text, source, sessionId) => agyTranscriptFacts(text, source, sessionId).resources;
|
|
111
|
+
export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId], inspect = null) {
|
|
112
|
+
const session = { harness_id: "antigravity-cli", session_id: child.provider_session_id };
|
|
113
|
+
let handle;
|
|
114
|
+
try {
|
|
115
|
+
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");
|
|
116
|
+
const expected = path.join(runtimeRoot, "brain", child.provider_session_id, ".system_generated", "logs", "transcript.jsonl");
|
|
117
|
+
const canonical = path.join(await realpath(runtimeRoot), "brain", child.provider_session_id, ".system_generated", "logs", "transcript.jsonl");
|
|
118
|
+
if (![expected, canonical].includes(fileURLToPath(child.log_uri)) || await realpath(expected) !== canonical) throw new Error("foreign or symlink native locator");
|
|
119
|
+
const before = await lstat(expected);
|
|
120
|
+
if (!before.isFile()) throw new Error("native locator is not a regular file");
|
|
121
|
+
handle = await open(expected, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
122
|
+
const stat = await handle.stat();
|
|
123
|
+
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");
|
|
124
|
+
const maximum = 8 * 1024 * 1024, buffer = Buffer.alloc(Math.min(stat.size, maximum));
|
|
125
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
126
|
+
const bytes = buffer.subarray(0, bytesRead), prefix = bytes.subarray(0, bytes.lastIndexOf(10) + 1);
|
|
127
|
+
const source = { locator: child.log_uri, byte_start: 0, byte_end: prefix.length, sha256: createHash("sha256").update(prefix).digest("hex") };
|
|
128
|
+
if (prefix.length === bytesRead && stat.size <= maximum) inspect?.(prefix.toString("utf8"), source);
|
|
129
|
+
const events = agyTranscriptObservations(prefix.toString("utf8"), child.provider_session_id, child.parent_provider_session_id ?? rootSessionId, source);
|
|
130
|
+
if (prefix.length !== bytesRead || stat.size > maximum) events.push({ session, reason: stat.size > maximum ? "agy_transcript_size_limit" : "agy_transcript_partial_tail" });
|
|
131
|
+
return events;
|
|
132
|
+
} catch (error) { return [{ session, reason: error.code === "ENOENT" ? "agy_transcript_missing" : "agy_transcript_locator_rejected" }]; }
|
|
133
|
+
finally { await handle?.close(); }
|
|
134
|
+
}
|
|
135
|
+
const absolute = (value, label) => { if (!value || !path.isAbsolute(value)) throw new DaemonError("invalid_path", `${label} must be an absolute path`); return path.resolve(value); };
|
|
136
|
+
function locations(stateDir) {
|
|
137
|
+
const dir = absolute(stateDir, "--state-dir"), digest = createHash("sha256").update(dir).digest("hex").slice(0, 22), local = path.join(dir, "daemon.sock");
|
|
138
|
+
// `tsx` creates Unix-domain sockets below TMPDIR. An evaluation path is
|
|
139
|
+
// intentionally descriptive and can exceed the platform's socket-path cap,
|
|
140
|
+
// so provider temp files must not inherit that path.
|
|
141
|
+
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}` };
|
|
142
|
+
}
|
|
143
|
+
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); }
|
|
144
|
+
async function readState(dir) { try { return JSON.parse(await readFile(locations(dir).state, "utf8")); } catch (error) { if (error.code === "ENOENT") return null; throw error; } }
|
|
145
|
+
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; } }
|
|
146
|
+
const shell = value => `'${String(value).replaceAll("'", `'"'"'`)}'`;
|
|
147
|
+
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 managedAgyEnv({ ...env, ...managedFlowEnv(config) }); }
|
|
148
|
+
const errorPayload = errorRecord;
|
|
149
|
+
async function forwardUsage(config, sessionId, result, tools, descendants) {
|
|
150
|
+
if (!config.ddFlowBin || !config.projectRoot) return;
|
|
151
|
+
const snapshot = usageSnapshot(result, tools), hasChildren = descendants.length > 0;
|
|
152
|
+
if (snapshot.total_tokens === null) return { status: "unavailable", reason: "provider_did_not_report_total_tokens" };
|
|
153
|
+
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 };
|
|
154
|
+
const target = executable(config.ddFlowBin, ["agy", "usage", "ingest", "--project-root", config.projectRoot, "--json"]);
|
|
155
|
+
await commandText(target.command, target.args, { input: `${JSON.stringify(payload)}\n`, env: { DD_FLOW_HOME: config.ddFlowHome }, phase: "usage-ingest" });
|
|
156
|
+
return { status: "ingested" };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function prepare(paths, config) {
|
|
160
|
+
config.stateDir = paths.dir;
|
|
161
|
+
config.hookBinding = "environment-v1";
|
|
162
|
+
const hooks = agyWorkspaceHooks();
|
|
163
|
+
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); }
|
|
164
|
+
if (config.noFlow) {
|
|
165
|
+
// Technical probes still need native settlement, but must not install
|
|
166
|
+
// workspace hooks or forward any lifecycle mutation to dd-flow.
|
|
167
|
+
await writeJson(path.join(paths.config, "hooks.json"), { "dd-flow-observer": { Stop: hooks["dd-flow"].Stop } });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Native discovery follows cwd (feature worktree/reviewer clone), while
|
|
171
|
+
// projectRoot remains the stable lifecycle identity returned to dd-flow.
|
|
172
|
+
Object.assign(config, await qualifyAgyHooks(config.cwd ?? config.projectRoot, { stateDir: paths.dir }));
|
|
173
|
+
// Workspace discovery covers root and children without global duplicates.
|
|
174
|
+
await writeJson(path.join(paths.config, "hooks.json"), {});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function qualifyAgyHooks(projectRoot, { stateDir = null, install = false } = {}) {
|
|
178
|
+
const hooks = agyWorkspaceHooks();
|
|
179
|
+
// Register in workspace discovery before creating any Session: native root
|
|
180
|
+
// and children load these named hook definitions without a global duplicate.
|
|
181
|
+
// Replace our prior handlers, including the obsolete wildcard post hook,
|
|
182
|
+
// without dropping unrelated commands in a shared matcher group.
|
|
183
|
+
const workspaceConfig = path.join(absolute(projectRoot, "--project-root"), ".agents");
|
|
184
|
+
const workspaceHooks = path.join(workspaceConfig, "hooks.json");
|
|
185
|
+
let existing = {}, existingText = "";
|
|
186
|
+
try {
|
|
187
|
+
existingText = await readFile(workspaceHooks, "utf8");
|
|
188
|
+
const parsed = JSON.parse(existingText);
|
|
189
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") throw new Error("not an object");
|
|
190
|
+
existing = parsed;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error.code !== "ENOENT") throw new DaemonError("workspace_hooks_invalid", `cannot install Antigravity lifecycle hooks: ${error.message}`);
|
|
193
|
+
if (!install) throw new DaemonError("workspace_hooks_unqualified", "Antigravity hooks must be installed during qualification before daemon start", false, { hooks_path: workspaceHooks });
|
|
194
|
+
}
|
|
195
|
+
const isManagedHandler = handler => typeof handler?.command === "string"
|
|
196
|
+
&& ((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));
|
|
197
|
+
const events = ["PreToolUse", "PostToolUse", "PreInvocation", "PostInvocation", "Stop"];
|
|
198
|
+
const existingManaged = Object.entries(existing).flatMap(([name, definition]) => {
|
|
199
|
+
if (events.includes(name) && Array.isArray(definition)) return definition;
|
|
200
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) return [];
|
|
201
|
+
return events.flatMap(event => Array.isArray(definition[event]) ? definition[event] : []);
|
|
202
|
+
}).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"));
|
|
203
|
+
if (existingManaged.some(handler => !stateDir || !handler.command.includes(`--state-dir ${shell(stateDir)}`))) {
|
|
204
|
+
throw new DaemonError("workspace_hooks_drifted", "qualified Antigravity hook routes to another daemon state directory", false, { hooks_path: workspaceHooks, expected_state_dir: stateDir });
|
|
205
|
+
}
|
|
206
|
+
const retain = handlers => handlers.flatMap(item => {
|
|
207
|
+
if (!Array.isArray(item?.hooks)) return isManagedHandler(item) ? [] : [item];
|
|
208
|
+
const remaining = item.hooks.filter(handler => !isManagedHandler(handler));
|
|
209
|
+
return remaining.length ? [{ ...item, hooks: remaining }] : [];
|
|
210
|
+
});
|
|
211
|
+
const workspace = Object.create(null), legacy = Object.create(null);
|
|
212
|
+
for (const [name, definition] of Object.entries(existing)) {
|
|
213
|
+
// Migrate the old adapter's event-keyed workspace format.
|
|
214
|
+
if (events.includes(name) && Array.isArray(definition)) {
|
|
215
|
+
const remaining = retain(definition);
|
|
216
|
+
if (remaining.length) legacy[name] = remaining;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (!definition || Array.isArray(definition) || typeof definition !== "object") {
|
|
220
|
+
throw new DaemonError("workspace_hooks_invalid", `invalid named hook definition: ${name}`);
|
|
221
|
+
}
|
|
222
|
+
const cleaned = { ...definition };
|
|
223
|
+
for (const event of events) if (Array.isArray(cleaned[event])) {
|
|
224
|
+
const remaining = retain(cleaned[event]);
|
|
225
|
+
if (remaining.length || cleaned[event].length === 0) cleaned[event] = remaining;
|
|
226
|
+
else delete cleaned[event];
|
|
227
|
+
}
|
|
228
|
+
workspace[name] = cleaned;
|
|
229
|
+
}
|
|
230
|
+
if (Object.keys(legacy).length) {
|
|
231
|
+
let name = "dd-flow-legacy";
|
|
232
|
+
while (Object.hasOwn(workspace, name)) name += "-retained";
|
|
233
|
+
workspace[name] = legacy;
|
|
234
|
+
}
|
|
235
|
+
const managed = workspace["dd-flow"] ?? {};
|
|
236
|
+
if (managed.enabled === false) throw new DaemonError("workspace_hooks_invalid", "dd-flow lifecycle hooks are disabled");
|
|
237
|
+
for (const [event, handlers] of Object.entries(hooks["dd-flow"])) {
|
|
238
|
+
managed[event] = [...(managed[event] ?? []), ...handlers];
|
|
239
|
+
}
|
|
240
|
+
workspace["dd-flow"] = managed;
|
|
241
|
+
const serializedWorkspace = `${JSON.stringify(workspace, null, 2)}\n`;
|
|
242
|
+
// Hook installation is a qualification/ownership boundary. Reusing the
|
|
243
|
+
// qualified command must not rewrite the project file when the pinned engine,
|
|
244
|
+
// daemon directory or workspace location changes.
|
|
245
|
+
const installed = isDeepStrictEqual(existing, JSON.parse(serializedWorkspace));
|
|
246
|
+
if (!installed && !install) throw new DaemonError("workspace_hooks_unqualified", "Antigravity hook definitions differ from the qualified contract", false, { hooks_path: workspaceHooks });
|
|
247
|
+
if (!installed) { await mkdir(workspaceConfig, { recursive: true, mode: 0o700 }); await writeFile(workspaceHooks, serializedWorkspace, { mode: 0o600 }); }
|
|
248
|
+
return { hooksSha256: createHash("sha256").update(JSON.stringify(hooks)).digest("hex"), workspaceHooksPath: workspaceHooks,
|
|
249
|
+
workspaceHooksSha256: createHash("sha256").update(installed ? existingText : serializedWorkspace).digest("hex"), workspaceHooksInstalled: installed ? "qualified_existing" : "qualified_install" };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000, requestId = null) {
|
|
253
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST_SCHEMA, id: requestId ?? process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
254
|
+
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'agy', timeoutMs });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","reasoning","mode","projectRoot","ddFlowBin","ddFlowHome","resourceHome","noFlow","livenessTimeoutMs","nativeQualificationPath"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|
|
258
|
+
// Called only after authorizeRetainedDaemonResume verifies the old daemon,
|
|
259
|
+
// provider processes and operation ledger. Native outcomes remain historical;
|
|
260
|
+
// their old liveness flags cannot describe the replacement process tree.
|
|
261
|
+
export function retainedAgyState(previous, sessionId) {
|
|
262
|
+
if (!sessionId || !previous?.sessions?.some(session => session.provider_session_id === sessionId)) return {};
|
|
263
|
+
if (previous.shutdown_state !== "clean" || previous.active_tree !== false) return previous;
|
|
264
|
+
return {
|
|
265
|
+
...previous,
|
|
266
|
+
unclaimed_activity: false,
|
|
267
|
+
native_resources_entries: [],
|
|
268
|
+
native_topology_error: null,
|
|
269
|
+
retained_tree_settlement: { daemon_id: previous.daemon_id, session_id: sessionId, turn_generation: previous.turn_generation ?? 0, evidence: "verified_clean_daemon_shutdown" },
|
|
270
|
+
session_observations: (previous.session_observations ?? []).map(([id, observation]) => [id, id === sessionId ? { ...observation, retained_stop: observation.stop ?? null, stop: null } : observation]),
|
|
271
|
+
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 })
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
export async function startDaemon(options) {
|
|
275
|
+
const paths = locations(options.stateDir); await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
276
|
+
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 };
|
|
277
|
+
config.livenessTimeoutMs = options.livenessTimeoutMs ?? 1_800_000;
|
|
278
|
+
config.nativeQualificationPath = process.env.DD_AGY_NATIVE_QUALIFICATION ? absolute(process.env.DD_AGY_NATIVE_QUALIFICATION, 'DD_AGY_NATIVE_QUALIFICATION') : null;
|
|
279
|
+
validateObservationDuration(config.livenessTimeoutMs);
|
|
280
|
+
await prepareRuntimeOwner(config, paths.dir);
|
|
281
|
+
assertDaemonRestartable(previous);
|
|
282
|
+
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; }
|
|
283
|
+
assertDaemonRestartable(await readState(paths.dir));
|
|
284
|
+
if (previous?.shutdown_state === "clean") await authorizeRetainedDaemonResume(paths.dir, previous, options.sessionId, config);
|
|
285
|
+
if (previous?.shutdown_state === "running" && previous.active_tree) throw new DaemonError("invalid_harness_crash", "previous Antigravity daemon died with an active or unproven tree");
|
|
286
|
+
await removeSocket(paths); await prepare(paths, config);
|
|
287
|
+
const version = (await runAgy(config.bin, ["--version"], { cwd: config.cwd, env: sanitizedEnv(config), timeoutMs: AGY_VERSION_TIMEOUT_MS })).stdout.trim();
|
|
288
|
+
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 });
|
|
289
|
+
const retained = retainedAgyState(previous, config.resumeSessionId);
|
|
290
|
+
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 ?? [], native_resources_entries: retained.native_resources_entries ?? [], native_topology_error: retained.native_topology_error ?? null, ...(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 });
|
|
291
|
+
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();
|
|
292
|
+
try {
|
|
293
|
+
await confirmDaemonProcess(config, resourceProcess, child);
|
|
294
|
+
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));
|
|
295
|
+
const persisted = await readState(paths.dir);
|
|
296
|
+
if (persisted?.shutdown_state === "failed") {
|
|
297
|
+
const failure = persisted.startup_error ?? {};
|
|
298
|
+
throw new DaemonError(failure.code ?? "daemon_start_failed", failure.message ?? "Antigravity daemon failed before becoming ready", failure.retryable === true, failure.details);
|
|
299
|
+
}
|
|
300
|
+
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; }
|
|
301
|
+
}
|
|
302
|
+
throw Object.assign(new DaemonError("daemon_start_failed", "Antigravity daemon did not become ready", true), { details: observationDetails(startupClock, 'daemon-start', child.pid) });
|
|
303
|
+
} catch (error) {
|
|
304
|
+
await cleanupFailedDaemonStart(config, resourceProcess, child, error, () => readState(paths.dir), config.daemonId);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
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) }); };
|
|
309
|
+
|
|
310
|
+
export class Runtime {
|
|
311
|
+
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;
|
|
312
|
+
const saved = state.native_observation_episode;
|
|
313
|
+
if (saved?.clock) {
|
|
314
|
+
this.observationEpisode = { operationId: saved.operation_id, generation: saved.turn_generation, connectionId: saved.connection_id, terminalMetadata: state.last_terminal_metadata,
|
|
315
|
+
clock: new ObservationClock({ timeoutMs: saved.clock.timeout_ms, saved: saved.clock }) };
|
|
316
|
+
this.connectionId = saved.connection_id;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
persist(patch = {}) { Object.assign(this.state, patch, { native_observation_episode: this.episodeSnapshot(), 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; }
|
|
320
|
+
async journal(kind, payload = {}, reservedPosition = null) { const position = reservedPosition ?? (this.journalPosition = (this.journalPosition ?? 0) + 1); const line = `${JSON.stringify({ schema_id: "dd-agy/daemon-event@1", event_id: randomUUID(), position, 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 }); } return position; }
|
|
321
|
+
args(sessionId) { return ["--input-format", "stream-json", "--output-format", "stream-json", ...agyModelArgs(this.config.model, this.config.reasoning), "--mode", this.config.mode, "--dangerously-skip-permissions", "--print-timeout", "0", ...(this.nativeLogFile ? ['--log-file', this.nativeLogFile] : []), ...(this.nativeCsrfToken ? [`--csrf_token=${this.nativeCsrfToken}`] : []), `--gemini_dir=${this.paths.gemini}`, "--app_data_dir=runtime", ...(sessionId ? ["--conversation", sessionId] : ["--new-project"])]; }
|
|
322
|
+
requireSessionIdentity(requestedId) {
|
|
323
|
+
const observedId = this.init?.conversation_id ?? null;
|
|
324
|
+
if (!requestedId || requestedId === observedId) return observedId;
|
|
325
|
+
throw new DaemonError("session_identity_mismatch", "requested Antigravity Session is not owned by this daemon", false, {
|
|
326
|
+
harness: "antigravity-cli", expected_session_id: requestedId, observed_session_id: observedId
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
markActivity() { const current = this.active ?? this.observationEpisode; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.state.native_execution_revision = this.nativeRevision; this.nativeProof = null; this.nativeAbort?.abort(); this.lastActivityAt = new Date().toISOString(); this.progress = { cursor: (this.progress?.cursor ?? 0) + 1, observed_at: Date.now() }; current?.clock?.sample(this.progress); void current?.publishProgress?.('native_progress', { session_id: this.init?.conversation_id, observed_at: this.lastActivityAt }).catch(error => { if(this.active === current) current.reject(error); }); }
|
|
330
|
+
markTerminalActivity(result) {
|
|
331
|
+
const current = this.active;
|
|
332
|
+
if (!current) return;
|
|
333
|
+
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");
|
|
334
|
+
current.terminalProgress ??= new Set();
|
|
335
|
+
if (current.terminalProgress.has(key) || current.terminalProgress.size >= 1_024) return;
|
|
336
|
+
current.terminalProgress.add(key); this.markActivity();
|
|
337
|
+
}
|
|
338
|
+
observeProviderActivity(event) {
|
|
339
|
+
const owner = this.active ?? this.observationEpisode;
|
|
340
|
+
if (!owner || owner.connectionId !== this.connectionId || owner.generation !== this.turnGeneration) return;
|
|
341
|
+
const root = this.init?.conversation_id, step = event.step_update;
|
|
342
|
+
const id = step?.conversation_id ?? event.result?.conversation_id ?? root;
|
|
343
|
+
let parent = id; const seen = new Set();
|
|
344
|
+
while (parent && parent !== root && !seen.has(parent)) { seen.add(parent); const child = this.descendants.get(parent); if (child?.activity_generation !== owner.generation) return; parent = child.parent_provider_session_id; }
|
|
345
|
+
if (!root || parent !== root) return;
|
|
346
|
+
if (event.event === "result") { if (["SUCCESS", "ERROR", "CANCELLED"].includes(event.result?.status)) this.markTerminalActivity(event.result); return; }
|
|
347
|
+
if (event.event !== "step_update") return;
|
|
348
|
+
const index = step?.step_index ?? (/^\d+$/.test(step?.step_id ?? "") ? Number(step.step_id) : null);
|
|
349
|
+
if (!Number.isInteger(index)) return;
|
|
350
|
+
const floor = this.sessionObservations.get(id)?.step_floor;
|
|
351
|
+
if (floor != null && index <= floor) return;
|
|
352
|
+
const tool = step.tool_call ?? step.tool_info ?? (step.step_type === "tool" ? step.tool_name : null);
|
|
353
|
+
if (!step.content && !step.text && !step.thought && !tool && !step.error && !(step.subagent_info?.subagents?.length)) return;
|
|
354
|
+
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");
|
|
355
|
+
this.progressSteps ??= new Map(); const prior = this.progressSteps.get(id);
|
|
356
|
+
if (prior && index < prior.index) return;
|
|
357
|
+
const snapshots = prior?.index === index ? prior.seen : new Set();
|
|
358
|
+
if (snapshots.has(fingerprint)) return;
|
|
359
|
+
// Native step snapshots can alternate/replay; scope retained dedup to the
|
|
360
|
+
// current step. Too many unique snapshots becomes uncertain, never a grant.
|
|
361
|
+
if (snapshots.size >= 1_024) return;
|
|
362
|
+
snapshots.add(fingerprint); this.progressSteps.set(id, { index, seen: snapshots }); this.invalidateSettlement(id); this.markActivity();
|
|
363
|
+
}
|
|
364
|
+
async start(sessionId = null) {
|
|
365
|
+
if (this.drainingError) throw this.drainingError;
|
|
366
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
367
|
+
if (this.child && !this.exited) {
|
|
368
|
+
await this.readyPromise;
|
|
369
|
+
this.requireSessionIdentity(sessionId);
|
|
370
|
+
return this.init;
|
|
371
|
+
}
|
|
372
|
+
await this.providerFinalization;
|
|
373
|
+
if (this.drainingError) throw this.drainingError;
|
|
374
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
375
|
+
this.nativeAbort?.abort(); this.nativeProof = null;
|
|
376
|
+
this.nativeLogFile = path.join(this.paths.dir, `native-${randomUUID()}.log`);
|
|
377
|
+
this.nativeCsrfToken = randomUUID();
|
|
378
|
+
this.admissionClock = null;
|
|
379
|
+
this.nativeQualification = await readAgyNativeQualification(this.paths.dir, this.config.bin, this.config.nativeQualificationPath);
|
|
380
|
+
const target = executable(this.config.bin, this.args(sessionId)); this.exited = false; this.buffer = ""; this.init = null;
|
|
381
|
+
let readyResolve, readyReject; this.readyPromise = new Promise((resolve, reject) => { readyResolve = resolve; readyReject = reject; });
|
|
382
|
+
// The provider can reject before durable registration finishes. Keep a
|
|
383
|
+
// handler attached during that small window; `start()` still returns the
|
|
384
|
+
// original rejection below.
|
|
385
|
+
void this.readyPromise.catch(() => {});
|
|
386
|
+
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 });
|
|
387
|
+
const providerProcess = this.providerProcess;
|
|
388
|
+
try { await this.persist({ provider_process: providerProcess }); }
|
|
389
|
+
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
390
|
+
try { assertDaemonDispatch(this.paths?.dir); this.connectionId = randomUUID(); this.child = spawn(target.command, target.args, { cwd: this.config.cwd, env: sanitizedEnv(this.config), detached: true, stdio: ["pipe", "pipe", "pipe"] }); }
|
|
391
|
+
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
392
|
+
const providerChild = this.child;
|
|
393
|
+
this.nativeLaunch = { logFile: this.nativeLogFile, providerPid: providerChild.pid, providerBirth: (await processSnapshot()).find(item => item.pid === providerChild.pid)?.started, connectionId: this.connectionId };
|
|
394
|
+
let allowProviderFinalization;
|
|
395
|
+
const providerRegistered = new Promise(resolve => { allowProviderFinalization = resolve; });
|
|
396
|
+
const observationFailure = error => { this.drainingError ??= error; readyReject(error); this.active?.reject(error); };
|
|
397
|
+
this.nativeDrain = readNativeLines(this.child.stdout, line => { this.buffer = `${line}\n`; return this.draining = this.drain(readyResolve, readyReject); }, observationFailure);
|
|
398
|
+
this.diagnostics = streamNativeDiagnostics(this.child.stderr, chunk => this.journal("stderr", { text: chunk }), observationFailure);
|
|
399
|
+
this.child.on("error", error => { readyReject(error); if (this.active) { this.active.reject(error); this.active = null; } });
|
|
400
|
+
// A dead leader is not yet a finalized tree: stop must also join the
|
|
401
|
+
// pending drain, process-group check and final persistence/lease writes.
|
|
402
|
+
this.providerFinalization = new Promise((resolve, reject) => {
|
|
403
|
+
this.child.on("close", (code, signal) => {
|
|
404
|
+
void Promise.all([this.nativeDrain, this.diagnostics]).then(async () => {
|
|
405
|
+
const interrupted = Boolean(this.active); this.exited = true; const beforeInit = !this.init;
|
|
406
|
+
const error = this.startupError ?? new DaemonError(beforeInit ? "agy_init_missing" : "agy_terminal_result_missing", `Antigravity exited ${beforeInit ? "before init" : "before result"} (${code ?? signal})`);
|
|
407
|
+
if (beforeInit) readyReject(error);
|
|
408
|
+
if (this.active) { this.active.reject(error); this.active = null; }
|
|
409
|
+
const patch = beforeInit ? { shutdown_state: "failed", startup_error: errorPayload(error), provider_exit: { code, signal }, active_tree: false } : { provider_exit: { code, signal }, active_tree: interrupted };
|
|
410
|
+
await providerRegistered;
|
|
411
|
+
this.retryProviderFinalization = async (graceMs = 1_000) => {
|
|
412
|
+
try {
|
|
413
|
+
await stopProcessGroup(providerChild, graceMs);
|
|
414
|
+
await this.persist(patch);
|
|
415
|
+
await finishDaemonProcess(this.config, providerProcess, beforeInit || interrupted ? "failed" : "stopped", beforeInit || interrupted ? "provider_interrupted" : "provider_stopped");
|
|
416
|
+
this.retryProviderFinalization = null;
|
|
417
|
+
} catch (cleanup) { await this.persist({ shutdown_state: "cleanup_failed", active_tree: true, cleanup_error: errorPayload(cleanup) }); throw cleanup; }
|
|
418
|
+
finally { if (beforeInit) this.server?.close(); }
|
|
419
|
+
};
|
|
420
|
+
await this.retryProviderFinalization();
|
|
421
|
+
}).then(resolve, reject);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
void this.providerFinalization.catch(() => {});
|
|
425
|
+
try { await confirmDaemonProcess(this.config, providerProcess, this.child); await this.persist({ provider_pid: this.child.pid }); scheduleDaemonProcessHeartbeat(this.config, providerProcess); }
|
|
426
|
+
catch (error) { await cleanupFailedStart(this.config, providerProcess, this.child, error); throw error; }
|
|
427
|
+
finally { allowProviderFinalization(); }
|
|
428
|
+
let readyTimer;
|
|
429
|
+
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 }); })]); }
|
|
430
|
+
finally { clearTimeout(readyTimer); }
|
|
431
|
+
}
|
|
432
|
+
async drain(readyResolve, readyReject) { 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; }
|
|
433
|
+
// Record an early provider rejection before journalling: a short-lived
|
|
434
|
+
// child can close while the asynchronous write is still pending.
|
|
435
|
+
if (event.event === "result" && ["ERROR", "CANCELLED"].includes(event.result?.status)) {
|
|
436
|
+
this.lastTerminalFailure ??= this.state.last_terminal_failure;
|
|
437
|
+
event.terminal_metadata = { observed_at: new Date().toISOString(), provider_session_id: event.result.conversation_id || this.init?.conversation_id || null,
|
|
438
|
+
operation_id: this.active?.operationId ?? null, turn_generation: this.active?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" };
|
|
439
|
+
if (!this.init) this.startupError ??= agyTerminalFailure(event.result, event.terminal_metadata);
|
|
440
|
+
this.receivedTerminalMetadata = event.terminal_metadata;
|
|
441
|
+
}
|
|
442
|
+
this.observeProviderActivity(event);
|
|
443
|
+
if (event.event === "result" && this.init && !this.startupError) { await this.finishTurn(event.result ?? {}, event.terminal_metadata, event); continue; }
|
|
444
|
+
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") {
|
|
445
|
+
// A successfully read/journalled provider refusal rejects readiness, not
|
|
446
|
+
// the stream drain that physical cleanup must still join.
|
|
447
|
+
if (this.startupError) { readyReject(this.startupError); continue; }
|
|
448
|
+
await this.finishTurn(event.result ?? {});
|
|
449
|
+
} } }
|
|
450
|
+
observeStep(step) {
|
|
451
|
+
const sessionId = step.conversation_id ?? this.init?.conversation_id, observation = this.sessionObservations.get(sessionId) ?? {};
|
|
452
|
+
const fresh = Number.isInteger(step.step_index) && step.step_index > (observation.last_step_index ?? -1) && step.step_index > (observation.step_floor ?? -1);
|
|
453
|
+
const bound = sessionId === this.init?.conversation_id || this.descendants.get(sessionId)?.activity_generation === this.active?.generation;
|
|
454
|
+
// Tree liveness is broader than current-Turn model progress: fresh work
|
|
455
|
+
// from a retained child invalidates settlement without adopting this Turn.
|
|
456
|
+
if (fresh && (sessionId === this.init?.conversation_id || this.descendants.has(sessionId))) this.childActivity(sessionId);
|
|
457
|
+
if (Number.isInteger(step.step_index)) this.sessionObservations.set(sessionId, { ...this.sessionObservations.get(sessionId), ...(step.step_index > (observation.last_step_index ?? -1) ? { last_execution_num: null } : {}), last_step_index: Math.max(observation.last_step_index ?? -1, step.step_index) });
|
|
458
|
+
this.toolObservations.add(agyToolObservation(step, this.init?.conversation_id));
|
|
459
|
+
for (const child of step.subagent_info?.subagents ?? []) if (child.conversation_id) {
|
|
460
|
+
const prior = this.descendants.get(child.conversation_id);
|
|
461
|
+
if (child.conversation_id === this.init?.conversation_id || prior && prior.parent_provider_session_id !== sessionId) {
|
|
462
|
+
this.state.native_topology_error = 'agy_native_ancestry_contradiction'; this.invalidateSettlement(sessionId); continue;
|
|
463
|
+
}
|
|
464
|
+
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",
|
|
465
|
+
...(bound && Number.isInteger(step.step_index) ? { native_source: { kind: 'agy.step_update.subagent_info', parent_provider_session_id: sessionId, step_index: step.step_index } } : {}),
|
|
466
|
+
...(fresh && bound && this.active ? { activity_generation: this.active.generation } : {}) });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
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 }; }
|
|
470
|
+
async captureChildTools() {
|
|
471
|
+
this.capturedToolFacts ??= new Map();
|
|
472
|
+
this.nativeResources ??= new Map(this.state.native_resources_entries?.map(item => [item.resource_id, item]) ?? []);
|
|
473
|
+
const root = this.init?.conversation_id ?? this.state.sessions?.[0]?.provider_session_id ?? this.lastResult?.conversation_id;
|
|
474
|
+
const queue = root && this.paths.runtime ? [{ provider_session_id: root, parent_provider_session_id: root, log_uri: pathToFileURL(path.join(this.paths.runtime, 'brain', root, '.system_generated', 'logs', 'transcript.jsonl')).href }, ...this.descendants.values()] : [...this.descendants.values()];
|
|
475
|
+
const visited = new Set();
|
|
476
|
+
for (let index = 0; index < queue.length && index < 256; index++) {
|
|
477
|
+
const child = queue[index];
|
|
478
|
+
if (visited.has(child.provider_session_id)) continue;
|
|
479
|
+
visited.add(child.provider_session_id);
|
|
480
|
+
const ancestors = new Set(); let parent = child.parent_provider_session_id;
|
|
481
|
+
while (parent && parent !== root && this.descendants.has(parent) && !ancestors.has(parent)) {
|
|
482
|
+
ancestors.add(parent); parent = this.descendants.get(parent).parent_provider_session_id;
|
|
483
|
+
}
|
|
484
|
+
const events = parent === root && root
|
|
485
|
+
? await captureAgyTranscript(this.paths.runtime, child, root, [root, ...ancestors], (text, source) => {
|
|
486
|
+
const facts = agyTranscriptFacts(text, source, child.provider_session_id);
|
|
487
|
+
for (const resource of facts.resources) this.nativeResources.set(resource.resource_id, mergeAgyResource(this.nativeResources.get(resource.resource_id), resource));
|
|
488
|
+
if (facts.gap) { this.state.native_topology_error = facts.gap; this.invalidateSettlement(root); }
|
|
489
|
+
for (const discovered of facts.children) {
|
|
490
|
+
const expected = path.join(this.paths.runtime, 'brain', discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
|
|
491
|
+
const sourceBrain = path.resolve(path.dirname(fileURLToPath(source.locator)), '../../..');
|
|
492
|
+
const sibling = path.join(sourceBrain, discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
|
|
493
|
+
let locator;
|
|
494
|
+
try { locator = fileURLToPath(discovered.log_uri); } catch { locator = null; }
|
|
495
|
+
if (locator !== expected && locator !== sibling) { this.state.native_topology_error = 'agy_native_locator_unbound'; this.invalidateSettlement(root); continue; }
|
|
496
|
+
const prior = this.descendants.get(discovered.provider_session_id);
|
|
497
|
+
if (discovered.provider_session_id === root || prior && (prior.parent_provider_session_id !== discovered.parent_provider_session_id || prior.log_uri && prior.log_uri !== discovered.log_uri)) {
|
|
498
|
+
this.state.native_topology_error = 'agy_native_ancestry_contradiction'; this.invalidateSettlement(root); continue;
|
|
499
|
+
}
|
|
500
|
+
const value = { ...discovered, ...prior, native_source: discovered.native_source };
|
|
501
|
+
this.descendants.set(value.provider_session_id, value);
|
|
502
|
+
queue.push(value);
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
: [{ session: { harness_id: "antigravity-cli", session_id: child.provider_session_id }, reason: "agy_transcript_ancestry_unproven" }];
|
|
506
|
+
for (const event of events) {
|
|
507
|
+
this.toolObservations.add(event);
|
|
508
|
+
const key = JSON.stringify([event.session, event.call_id, event.reason]), encoded = JSON.stringify(event);
|
|
509
|
+
if (this.capturedToolFacts.get(key) === encoded) continue;
|
|
510
|
+
await this.journal("tool_observation", { schema_id: "dd-flow/tool-observation@1", event });
|
|
511
|
+
this.capturedToolFacts.set(key, encoded);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (queue.length > 256 && visited.size < new Set(queue.map(item => item.provider_session_id)).size) { this.state.native_topology_error = 'agy_native_inventory_limit'; this.invalidateSettlement(root); }
|
|
515
|
+
this.state.native_resources_entries = [...this.nativeResources.values()];
|
|
516
|
+
}
|
|
517
|
+
async finishTurn(result, retainedMetadata = null, providerEvent = null) {
|
|
518
|
+
if (result.status === "RUNNING") { await this.persist({ active_tree: true }); return; }
|
|
519
|
+
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 });
|
|
520
|
+
if (result.conversation_id && result.conversation_id !== this.init?.conversation_id) {
|
|
521
|
+
await this.journal("foreign_terminal_observed", { conversation_id: result.conversation_id, result });
|
|
522
|
+
const child = this.descendants.get(result.conversation_id);
|
|
523
|
+
if (child) {
|
|
524
|
+
this.descendants.set(result.conversation_id, { ...child, status: result.status === "SUCCESS" ? "completed" : result.status === "ERROR" ? "failed" : "cancelled" });
|
|
525
|
+
await this.completePendingTurn();
|
|
526
|
+
}
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const current = this.active;
|
|
530
|
+
if (current?.dispatched === false) {
|
|
531
|
+
const error = new DaemonError("operation_observation_lost", "Unexpected native terminal before this stream request was dispatched", false, { operation_id: current.operationId, turn_generation: current.generation, native_outcome: "unknown" });
|
|
532
|
+
this.active = null; current.reject(error);
|
|
533
|
+
await this.journal("unexpected_terminal_before_dispatch", { result, operation_id: current.operationId });
|
|
534
|
+
await this.persist({ unclaimed_activity: true, active_tree: true });
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const terminalMetadata = { received_at: new Date().toISOString(), ...(retainedMetadata ?? this.receivedTerminalMetadata ?? { observed_at: new Date().toISOString(), provider_session_id: result.conversation_id || this.init?.conversation_id || null, operation_id: current?.operationId ?? null,
|
|
538
|
+
turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" }) };
|
|
539
|
+
this.receivedTerminalMetadata = null;
|
|
540
|
+
// The ordered stream returns one result per input. Model usage counters
|
|
541
|
+
// are not request IDs: native command rejections can report zero turns.
|
|
542
|
+
// prompt() drains earlier events before accepting another input.
|
|
543
|
+
if (!current && this.lastResult) {
|
|
544
|
+
await this.journal("stale_terminal_observed", { result });
|
|
545
|
+
if (!isDeepStrictEqual(result, this.lastResult)) await this.persist({ unclaimed_activity: true, active_tree: true });
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.lastResult = result; this.markTerminalActivity(result);
|
|
549
|
+
const rootStop = this.sessionObservations.get(this.init?.conversation_id)?.stop;
|
|
550
|
+
const pendingTerminal = current && result.status === "SUCCESS";
|
|
551
|
+
if (current) { current.terminalResult = result; current.terminalMetadata = terminalMetadata; current.terminalPersisted = false; }
|
|
552
|
+
if (pendingTerminal && rootStop?.source_bound === true && 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" });
|
|
553
|
+
this.state.terminal_observation_pending = true;
|
|
554
|
+
try {
|
|
555
|
+
await this.persist({ active_tree: true, last_result: result, last_terminal_metadata: terminalMetadata });
|
|
556
|
+
if (current) current.terminalPersisted = true;
|
|
557
|
+
const terminalPosition = await this.journal("provider_event", { event: providerEvent ?? { event: "result", result } });
|
|
558
|
+
terminalMetadata.stream_terminal_position = terminalPosition;
|
|
559
|
+
if (current?.outboundPosition && current.dispatchJournalSettled && !current.dispatchObservationError && this.config.daemonId && current.connectionId && current.operationId) terminalMetadata.request_proof = {
|
|
560
|
+
kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
|
|
561
|
+
turn_generation: current.generation, session_id: this.init.conversation_id, outbound_position: current.outboundPosition, terminal_position: terminalPosition
|
|
562
|
+
};
|
|
563
|
+
await this.captureChildTools();
|
|
564
|
+
} catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
565
|
+
try { this.usageIngest = await forwardUsage(this.config, result.conversation_id ?? this.init.conversation_id, result, this.toolSnapshot(), [...this.descendants.values()]); }
|
|
566
|
+
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); } }
|
|
567
|
+
try { await this.persist({ terminal_observation_pending: false }); }
|
|
568
|
+
catch (error) { this.state.terminal_observation_pending = true; return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
569
|
+
this.state.terminal_observation_pending = false;
|
|
570
|
+
this.queueNativeInspection();
|
|
571
|
+
if (!current) return;
|
|
572
|
+
if (this.active !== current) return;
|
|
573
|
+
if (result.status === "ERROR" || result.status === "CANCELLED") {
|
|
574
|
+
await this.observeHookFailures();
|
|
575
|
+
if (this.active !== current) return;
|
|
576
|
+
this.active = null;
|
|
577
|
+
const settled = this.receipt().settled;
|
|
578
|
+
if (result.status === "ERROR" && settled && rootStop?.fullyIdle === true && !this.state.last_hook_rejection && current.operationId && this.paths.runtime) {
|
|
579
|
+
const root = this.init.conversation_id, transcript = path.join(this.paths.runtime, "brain", root, ".system_generated", "logs", "transcript.jsonl");
|
|
580
|
+
await captureAgyTranscript(this.paths.runtime, { provider_session_id: root, parent_provider_session_id: root, log_uri: new URL(`file://${transcript}`).href }, root, [root], (text, source) => {
|
|
581
|
+
const boundary = recoveredAgyErrorBoundary(text, result.error, current.stepFloor);
|
|
582
|
+
const hook = current.nativeHookEvents?.get(boundary?.boundary_step);
|
|
583
|
+
if (boundary && hook) terminalMetadata.recovered_error_boundary = { ...boundary, receipt: { ...boundary.receipt, native_hook_event_key: hook, daemon_instance: this.config.daemonId }, source };
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, native_turn_items: { observed: settled, possible_effects: true, pending: !settled }, provider_error_scope: "terminal_result", usage_ingest: this.usageIngest });
|
|
587
|
+
try { await this.persist({ active_tree: !settled, last_terminal_failure: errorRecord(this.lastTerminalFailure) }); }
|
|
588
|
+
catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
589
|
+
return current.reject(this.lastTerminalFailure);
|
|
590
|
+
}
|
|
591
|
+
try { await this.completePendingTurn(); }
|
|
592
|
+
catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
593
|
+
}
|
|
594
|
+
async completePendingTurn() {
|
|
595
|
+
await this.observeHookFailures();
|
|
596
|
+
const current = this.active;
|
|
597
|
+
if (current?.observationError) throw current.observationError;
|
|
598
|
+
if (current?.terminalResult?.status !== "SUCCESS" || current.terminalResult !== this.lastResult) return;
|
|
599
|
+
this.queueNativeInspection();
|
|
600
|
+
await this.persist({ active_tree: !this.receipt().settled });
|
|
601
|
+
// Hooks can invalidate the tree while persistence is pending. Never
|
|
602
|
+
// publish a proof captured before that asynchronous boundary.
|
|
603
|
+
if (this.active !== current) return;
|
|
604
|
+
const receipt = this.receipt();
|
|
605
|
+
if (!receipt.settled) { await this.persist({ active_tree: true }); return; }
|
|
606
|
+
this.active = null;
|
|
607
|
+
current.resolve(receipt);
|
|
608
|
+
}
|
|
609
|
+
failTerminalObservation(current, result, metadata, cause) {
|
|
610
|
+
this.state.terminal_observation_pending = true;
|
|
611
|
+
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 } };
|
|
612
|
+
const error = ["ERROR", "CANCELLED"].includes(result.status)
|
|
613
|
+
? agyTerminalFailure(result, details)
|
|
614
|
+
: new DaemonError("native_outcome_observation_failed", "Antigravity terminal was received but mandatory observation failed", false, details);
|
|
615
|
+
error.cause = cause;
|
|
616
|
+
if (result.status !== "SUCCESS") this.lastTerminalFailure = error;
|
|
617
|
+
if (current) { current.observationError = error; current.reject(error); }
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
620
|
+
queueNativeInspection() {
|
|
621
|
+
const owner = this.active ?? this.observationEpisode, proof = owner?.terminalMetadata?.request_proof;
|
|
622
|
+
if (!owner?.clock || !proof || this.state.terminal_observation_pending || this.nativeFlight || this.nativeProof || this.exited) return;
|
|
623
|
+
if (proof.connection_id !== this.connectionId || proof.daemon_instance !== this.config.daemonId || proof.turn_generation !== this.turnGeneration || proof.operation_id !== owner.operationId || proof.session_id !== this.init?.conversation_id) { this.nativeInspection = { state: 'unproven', reason: 'native_owner_binding_changed' }; return; }
|
|
624
|
+
if (owner.clock.sample(this.progress)) { this.nativeInspection = { state: 'unproven', reason: 'observation_budget_exhausted' }; return; }
|
|
625
|
+
// The observer's raw wire result is never authority before P3 qualification.
|
|
626
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified) { this.nativeInspection = { state: 'unproven', reason: 'native_inspection_not_qualified' }; return; }
|
|
627
|
+
const revision = this.nativeRevision ?? 0, connection = this.connectionId;
|
|
628
|
+
const controller = this.nativeAbort = new AbortController();
|
|
629
|
+
let recording = false;
|
|
630
|
+
this.nativeInspection = { state: 'inspection_pending', reason: 'native_idle_barrier_pending' };
|
|
631
|
+
this.nativeFlight = Promise.resolve().then(async () => {
|
|
632
|
+
await owner.dispatchJournal;
|
|
633
|
+
const clock = owner.clock.state(), remainingMs = Math.max(0, clock.timeout_ms - clock.elapsed_ms);
|
|
634
|
+
if (!remainingMs || owner.clock.sample(this.progress)) throw new Error('observation_budget_exhausted');
|
|
635
|
+
const endpoint = this.nativeObserver ? null : await ownedAgyEndpoint(this.nativeLaunch);
|
|
636
|
+
const observed = await (this.nativeObserver ?? inspectAgyNative)({ endpoint, sessionId: proof.session_id, cwd: this.config.cwd, remainingMs, signal: controller.signal, rootRelation: this.nativeQualification?.rootRelation, csrfToken: this.nativeCsrfToken });
|
|
637
|
+
if (!this.nativeObserver && JSON.stringify(await ownedAgyEndpoint(this.nativeLaunch)) !== JSON.stringify(endpoint)) throw new Error('native_endpoint_changed');
|
|
638
|
+
const current = () => !controller.signal.aborted && this.connectionId === connection && (this.nativeRevision ?? 0) === revision && this.turnGeneration === owner.generation && this.observationEpisode === owner && !this.exited && !owner.clock.sample(this.progress);
|
|
639
|
+
if (!current()) return;
|
|
640
|
+
if (!observed.idle) { this.nativeInspection = { state: 'inspection_pending', reason: observed.reason ?? 'native_wait_pending' }; return; }
|
|
641
|
+
const candidate = { daemon_instance: this.config.daemonId, connection_id: connection, operation_id: owner.operationId, turn_generation: owner.generation, request_proof: proof, revision, method: observed.method, source: 'native_idle_barrier' };
|
|
642
|
+
recording = true;
|
|
643
|
+
await this.journal('native_tree_settlement', { proof: candidate });
|
|
644
|
+
if (!current()) return;
|
|
645
|
+
// Persist conservatively before publishing. A crash may require another
|
|
646
|
+
// read-only observation; no concurrent reader gets an uncommitted grant.
|
|
647
|
+
await this.persist({ active_tree: true });
|
|
648
|
+
if (!current()) return;
|
|
649
|
+
this.nativeProof = candidate;
|
|
650
|
+
this.state.native_execution_revision = revision;
|
|
651
|
+
this.state.unclaimed_activity = false;
|
|
652
|
+
this.projectNativeIdleChildren();
|
|
653
|
+
this.nativeInspection = { state: 'ready', reason: 'native_wait_fully_idle' };
|
|
654
|
+
this.state.active_tree = !this.receipt().settled;
|
|
655
|
+
await this.completePendingTurn();
|
|
656
|
+
}).catch(error => {
|
|
657
|
+
if (!controller.signal.aborted) {
|
|
658
|
+
this.nativeProof = null;
|
|
659
|
+
const transient = !recording && ['grpc_4', 'grpc_14'].includes(error.details?.reason) && !owner.clock.sample(this.progress);
|
|
660
|
+
this.nativeInspection = { state: transient ? 'inspection_pending' : 'unproven', reason: error.details?.reason ?? error.message ?? 'native_inspection_failed' };
|
|
661
|
+
if (recording && this.active === owner && owner.terminalResult) {
|
|
662
|
+
try { this.failTerminalObservation(owner, owner.terminalResult, owner.terminalMetadata, error); } catch { /* original prompt owns the typed failure */ }
|
|
663
|
+
}
|
|
664
|
+
if (recording) this.state.terminal_observation_pending = true;
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
.finally(() => { this.nativeFlight = null; });
|
|
668
|
+
}
|
|
669
|
+
projectNativeIdleChildren() {
|
|
670
|
+
for (const [id, child] of this.descendants) this.descendants.set(id, { ...child, ...(!['completed', 'failed', 'cancelled'].includes(child.status) ? { previous_native_status: child.status, status: 'settled_by_root' } : {}), ...(child.native_turn_items ? { native_turn_items: { ...child.native_turn_items, observed: true, pending: false } } : {}), tree_settled: true, settlement_evidence: 'native_idle_barrier' });
|
|
671
|
+
}
|
|
672
|
+
async reconcileRetainedAdmission() {
|
|
673
|
+
const retained = this.state.retained_tree_settlement;
|
|
674
|
+
const fresh = !this.lastResult;
|
|
675
|
+
if (this.active || this.receipt().settled || !fresh && (retained?.evidence !== 'verified_clean_daemon_shutdown' || retained.session_id !== this.init?.conversation_id)) return;
|
|
676
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified) return;
|
|
677
|
+
if (this.admissionFlight) return await this.admissionFlight;
|
|
678
|
+
const clock = this.admissionClock ??= new ObservationClock({ timeoutMs: SETUP_STARTUP_MS });
|
|
679
|
+
if (!clock || clock.sample()) throw new DaemonError('agy_native_inspection_unavailable', 'Retained AGY native admission budget exhausted');
|
|
680
|
+
const connection = this.connectionId, revision = this.nativeRevision ?? 0, generation = this.turnGeneration;
|
|
681
|
+
const controller = this.nativeAbort = new AbortController();
|
|
682
|
+
this.admissionFlight = (async () => {
|
|
683
|
+
const endpoint = this.nativeObserver ? null : await ownedAgyEndpoint(this.nativeLaunch);
|
|
684
|
+
const observed = await (this.nativeObserver ?? inspectAgyNative)({ endpoint, sessionId: this.init.conversation_id, cwd: this.config.cwd, remainingMs: Math.max(1, clock.timeoutMs - clock.elapsed), signal: controller.signal, rootRelation: this.nativeQualification?.rootRelation, csrfToken: this.nativeCsrfToken });
|
|
685
|
+
if (!this.nativeObserver && JSON.stringify(await ownedAgyEndpoint(this.nativeLaunch)) !== JSON.stringify(endpoint)) throw new DaemonError('agy_native_inspection_unavailable', 'AGY admission endpoint changed');
|
|
686
|
+
const current = () => !controller.signal.aborted && !this.exited && !this.active && !clock.sample() && this.connectionId === connection && this.turnGeneration === generation && (this.nativeRevision ?? 0) === revision;
|
|
687
|
+
if (!observed.idle || !current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY fresh native admission is unproven');
|
|
688
|
+
// Admission is not the old stream request: no invented operation/Turn or
|
|
689
|
+
// request proof. The old failed result remains historical diagnostics.
|
|
690
|
+
const candidate = { daemon_instance: this.config.daemonId, connection_id: connection, turn_generation: generation, revision, method: observed.method, source: 'native_idle_admission' };
|
|
691
|
+
await this.journal('native_idle_admission', { proof: candidate });
|
|
692
|
+
if (!current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY native admission changed during observation persistence');
|
|
693
|
+
await this.persist({ active_tree: true });
|
|
694
|
+
if (!current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY native admission changed during observation persistence');
|
|
695
|
+
this.nativeProof = candidate;
|
|
696
|
+
this.state.native_execution_revision = revision;
|
|
697
|
+
this.state.unclaimed_activity = false; this.projectNativeIdleChildren();
|
|
698
|
+
this.state.active_tree = !this.receipt().settled;
|
|
699
|
+
})().catch(error => { this.nativeProof = null; this.nativeInspection = { state: 'unproven', reason: error.details?.reason ?? error.code ?? 'native_admission_unproven' }; throw error; }).finally(() => { this.admissionFlight = null; });
|
|
700
|
+
return await this.admissionFlight;
|
|
701
|
+
}
|
|
702
|
+
episodeSnapshot() {
|
|
703
|
+
const owner = this.observationEpisode;
|
|
704
|
+
return owner?.clock ? { operation_id: owner.operationId, turn_generation: owner.generation, connection_id: owner.connectionId, clock: owner.clock.state() } : null;
|
|
705
|
+
}
|
|
706
|
+
receipt(result = this.lastResult) {
|
|
707
|
+
if (this.state.last_terminal_failure && !this.lastTerminalFailure) this.lastTerminalFailure = this.state.last_terminal_failure;
|
|
708
|
+
const metadata = 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
|
|
709
|
+
&& this.lastTerminalFailure.details.provider_session_id === (result?.conversation_id || this.init?.conversation_id || null) ? this.lastTerminalFailure.details : null;
|
|
710
|
+
const terminalMetadata = metadata?.request_proof && (metadata.request_proof.connection_id !== this.connectionId || metadata.request_proof.daemon_instance !== this.config.daemonId)
|
|
711
|
+
? { ...metadata, request_proof: undefined } : metadata;
|
|
712
|
+
const assistantText = typeof result?.response === "string" ? result.response : typeof result?.text === "string" ? result.text : null;
|
|
713
|
+
const root = this.init?.conversation_id; const observation = this.sessionObservations.get(root);
|
|
714
|
+
const treeSettled = [...this.descendants.values()].every(child => child.tree_settled === true || ["completed", "failed", "cancelled", "settled_by_root"].includes(child.status));
|
|
715
|
+
// SUCCESS can coexist with background tasks. Only a current native idle
|
|
716
|
+
// proof (or verified prior clean retirement before new activity) closes it.
|
|
717
|
+
const retained = this.state.retained_tree_settlement;
|
|
718
|
+
const rootSettled = this.nativeProof?.turn_generation === this.turnGeneration && this.nativeProof.connection_id === this.connectionId && this.nativeProof.revision === (this.nativeRevision ?? 0)
|
|
719
|
+
|| observation?.stop?.source_bound === true && observation.stop?.fullyIdle === true && observation.stop.generation === this.turnGeneration
|
|
720
|
+
|| retained?.evidence === "verified_clean_daemon_shutdown" && retained.session_id === root && retained.turn_generation === this.turnGeneration && !this.child;
|
|
721
|
+
const terminalPending = this.active?.terminalResult === result;
|
|
722
|
+
// A positively inspected owned scope before its first Turn has no model
|
|
723
|
+
// result to manufacture. Native admission alone proves this empty execution.
|
|
724
|
+
const emptyExecution = !result && this.turnGeneration === 0 && !this.active && !this.descendants.size && this.nativeProof?.source === 'native_idle_admission';
|
|
725
|
+
const executionTerminal = emptyExecution || result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status);
|
|
726
|
+
const settled = Boolean(!this.state.native_topology_error && !this.state.terminal_observation_pending && !this.active?.observationError && !this.state.unclaimed_activity && (!this.active || terminalPending) && executionTerminal && treeSettled && rootSettled);
|
|
727
|
+
const clock = this.observationEpisode?.clock;
|
|
728
|
+
if (clock) clock.sample(this.progress);
|
|
729
|
+
const exhausted = clock && (clock.observationLost || clock.elapsed >= clock.timeoutMs);
|
|
730
|
+
const nativeSettlement = { confirmed: Boolean(settled), state: settled ? 'ready' : exhausted ? 'unproven' : this.nativeInspection?.state ?? 'unproven', reason: settled ? 'current_root_idle_proven' : exhausted ? 'observation_budget_exhausted' : this.nativeInspection?.reason ?? 'current_root_idle_proof_missing', capability: AGY_NATIVE_INSPECTION_CONTRACT, operation_id: terminalMetadata?.request_proof?.operation_id, request_key: streamRequestKey(terminalMetadata?.request_proof), provider_session_id: root, daemon_instance: this.config.daemonId, connection_epoch: this.connectionId, generation: this.turnGeneration, ...(this.nativeProof ? { source: this.nativeProof.source, method: this.nativeProof.method, proof_sha256: createHash('sha256').update(JSON.stringify(this.nativeProof)).digest('hex') } : {}), ...(clock ? { observation_clock: clock.state() } : {}) };
|
|
731
|
+
return nativeExecutionReceipt({ 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)), native_turn_items: { observed: Boolean(terminalMetadata?.request_proof), possible_effects: true, pending: !settled, settled, ...(terminalMetadata?.request_proof ? { request_proof: terminalMetadata.request_proof } : {}) }, transcript_path: observation?.transcript_path ?? null, settled, native_settled: settled, physical_stopped: this.exited === true, native_settlement: nativeSettlement, }, { daemonId: this.config.daemonId, generation: this.turnGeneration, revision: this.nativeRevision ?? 0, resources: { coverage: "partial", entries: [...(this.nativeResources?.values() ?? this.state.native_resources_entries ?? [])] } });
|
|
732
|
+
}
|
|
733
|
+
async zeroUsage() { await forwardUsage(this.config, this.init.conversation_id, { usage: {} }, this.toolSnapshot(), []); }
|
|
734
|
+
async prompt(text, assertDispatch = captureDispatchGuard(this), operationId = null, livenessTimeoutMs = 1_800_000) {
|
|
735
|
+
validateObservationDuration(livenessTimeoutMs);
|
|
736
|
+
if (!text) throw new DaemonError("prompt_required", "prompt is required");
|
|
737
|
+
await this.start(); await this.draining;
|
|
738
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified && this.config.bin && this.paths.dir) this.nativeQualification = await readAgyNativeQualification(this.paths.dir, this.config.bin, this.config.nativeQualificationPath);
|
|
739
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified) throw new DaemonError('agy_native_inspection_unqualified', 'AGY whole-tree inspection requires a matching native qualification receipt before productive dispatch', false, { capability: AGY_NATIVE_INSPECTION_CONTRACT });
|
|
740
|
+
await this.reconcileRetainedAdmission();
|
|
741
|
+
if (this.active || this.promptObservation) throw new DaemonError("operation_in_progress", "an Antigravity turn or its mandatory dispatch observation is already running");
|
|
742
|
+
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 });
|
|
743
|
+
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");
|
|
744
|
+
let resolveTurn, rejectTurn;
|
|
745
|
+
const reply = new Promise((resolve, reject) => { resolveTurn = resolve; rejectTurn = reject; });
|
|
746
|
+
// Cancellation can reject while admission persistence is still pending.
|
|
747
|
+
void reply.catch(() => {});
|
|
748
|
+
this.connectionId ??= randomUUID();
|
|
749
|
+
const current = { resolve: resolveTurn, reject: rejectTurn, generation: ++this.turnGeneration, operationId, connectionId: this.connectionId, dispatched: false };
|
|
750
|
+
this.nativeAbort?.abort(); this.nativeProof = null; this.nativeInspection = null;
|
|
751
|
+
current.clock = new ObservationClock({ timeoutMs: livenessTimeoutMs, onGap: gap => { void this.journal('observation_gap', { gap, operation_id: operationId }).catch(() => {}); } });
|
|
752
|
+
this.observationEpisode = current;
|
|
753
|
+
let resolveDispatchJournal;
|
|
754
|
+
const dispatchJournal = current.dispatchJournal = new Promise(resolve => { resolveDispatchJournal = resolve; });
|
|
755
|
+
this.promptObservation = current;
|
|
756
|
+
const finishDispatchJournal = () => {
|
|
757
|
+
current.dispatchJournalSettled = true; resolveDispatchJournal();
|
|
758
|
+
if (current.observerReturned && this.promptObservation === current) this.promptObservation = null;
|
|
759
|
+
};
|
|
760
|
+
// Reserve before persistence so different operation IDs cannot overlap.
|
|
761
|
+
current.publishProgress = operationProgressReporter();
|
|
762
|
+
this.active = current;
|
|
763
|
+
this.hookRequests = new Map();
|
|
764
|
+
const root = this.init?.conversation_id, prior = this.sessionObservations.get(root) ?? {};
|
|
765
|
+
current.stepFloor = prior.last_step_index ?? -1;
|
|
766
|
+
this.sessionObservations.set(root, { ...prior, step_floor: prior.last_step_index ?? null, stop: null, last_hook: null });
|
|
767
|
+
this.lastProviderEvent = null; this.progressSteps = new Map(); this.progressHooks = new Set(); this.progress = undefined;
|
|
768
|
+
try {
|
|
769
|
+
await this.persist({ active_tree: true, last_hook_rejection: null });
|
|
770
|
+
assertDispatch();
|
|
771
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
772
|
+
if (this.active !== current) throw new DaemonError("operation_cancelled", "prompt reservation was superseded");
|
|
773
|
+
current.outboundPosition = this.journalPosition = (this.journalPosition ?? 0) + 1;
|
|
774
|
+
current.dispatched = true;
|
|
775
|
+
this.child.stdin.write(`${JSON.stringify({ event: "user", message: { content: text } })}\n`, error => {
|
|
776
|
+
if (error) { this.drainingError ??= nativeObservationError(error); current.dispatchObservationError = this.drainingError; current.reject(this.drainingError); finishDispatchJournal(); }
|
|
777
|
+
else void Promise.resolve().then(() => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: this.init?.conversation_id, turn_generation: current.generation, connection_id: current.connectionId }, current.outboundPosition)).then(finishDispatchJournal, cause => {
|
|
778
|
+
this.drainingError ??= nativeObservationError(cause); current.dispatchObservationError = this.drainingError;
|
|
779
|
+
current.reject(this.drainingError); finishDispatchJournal();
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (current.dispatched) { this.drainingError ??= nativeObservationError(error); error = this.drainingError; }
|
|
784
|
+
else if (this.active === current) this.active = null;
|
|
785
|
+
current.reject(error); finishDispatchJournal();
|
|
786
|
+
}
|
|
787
|
+
const clock = current.clock;
|
|
788
|
+
let checking = false;
|
|
789
|
+
const timer = setInterval(() => {
|
|
790
|
+
if (this.active === current && current.terminalResult && this.nativeInspection?.state === 'inspection_pending' && !this.nativeFlight) this.queueNativeInspection();
|
|
791
|
+
if (this.active !== current || checking || !clock.sample(this.progress)) return;
|
|
792
|
+
checking = true; const cursor = this.progress?.cursor;
|
|
793
|
+
void this.completePendingTurn().then(() => {
|
|
794
|
+
if (this.active !== current || this.progress?.cursor !== cursor || !clock.sample(this.progress)) return;
|
|
795
|
+
if (current.terminalResult) return this.failTerminalObservation(current, current.terminalResult, current.terminalMetadata, new Error("Native tree settlement was not confirmed within the productive silence window"));
|
|
796
|
+
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 }));
|
|
797
|
+
}).catch(error => {
|
|
798
|
+
if (this.active === current && this.progress?.cursor === cursor) {
|
|
799
|
+
if (current.observationError) current.reject(current.observationError);
|
|
800
|
+
else if (current.terminalResult) { try { this.failTerminalObservation(current, current.terminalResult, current.terminalMetadata, error); } catch { /* reply owns the typed failure */ } }
|
|
801
|
+
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) }));
|
|
802
|
+
}
|
|
803
|
+
}).finally(() => { checking = false; });
|
|
804
|
+
}, Math.max(10, Math.min(1_000, livenessTimeoutMs / 4)));
|
|
805
|
+
try {
|
|
806
|
+
const result = await reply;
|
|
807
|
+
await dispatchJournal;
|
|
808
|
+
if (current.dispatchObservationError) {
|
|
809
|
+
const error = current.dispatchObservationError;
|
|
810
|
+
if (error.code !== "operation_observation_lost") throw error;
|
|
811
|
+
throw Object.assign(new Error("Antigravity completed natively but its dispatch journal could not be saved", { cause: error.cause ?? error }), {
|
|
812
|
+
code: "native_outcome_observation_failed", details: { native_outcome: result, settled: false, observation_error: errorRecord(error) }
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
return result;
|
|
816
|
+
} catch (error) {
|
|
817
|
+
await dispatchJournal;
|
|
818
|
+
if (error.code === "native_outcome_observation_failed") throw error;
|
|
819
|
+
if (current.dispatchObservationError) {
|
|
820
|
+
if (!current.terminalResult) throw current.dispatchObservationError;
|
|
821
|
+
const cause = current.dispatchObservationError;
|
|
822
|
+
const failed = current.terminalResult.status === 'SUCCESS'
|
|
823
|
+
? new DaemonError('native_outcome_observation_failed', 'Antigravity completed natively but its dispatch journal could not be saved', false, { native_outcome: this.receipt(current.terminalResult), settled: false, observation_error: errorRecord(cause) })
|
|
824
|
+
: agyTerminalFailure(current.terminalResult, { ...current.terminalMetadata, native_outcome: current.terminalResult, settled: false, native_turn_items: { observed: false, possible_effects: true, pending: true }, observation_error: errorRecord(cause) });
|
|
825
|
+
failed.cause = cause.cause ?? cause;
|
|
826
|
+
current.observationError = failed; current.dispatchObservationError = failed;
|
|
827
|
+
this.drainingError = failed; this.state.terminal_observation_pending = true;
|
|
828
|
+
if (current.terminalResult.status !== 'SUCCESS') this.lastTerminalFailure = failed;
|
|
829
|
+
throw failed;
|
|
830
|
+
}
|
|
831
|
+
const metadata = current.terminalMetadata;
|
|
832
|
+
if (error === this.lastTerminalFailure && metadata && !metadata.request_proof && current.outboundPosition && metadata.stream_terminal_position && this.config.daemonId && current.operationId) {
|
|
833
|
+
metadata.request_proof = { kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
|
|
834
|
+
turn_generation: current.generation, session_id: this.init.conversation_id, outbound_position: current.outboundPosition, terminal_position: metadata.stream_terminal_position };
|
|
835
|
+
const proven = agyTerminalFailure(current.terminalResult, { ...error.details, ...metadata });
|
|
836
|
+
this.lastTerminalFailure = proven;
|
|
837
|
+
await this.persist({ last_terminal_metadata: metadata, last_terminal_failure: errorRecord(proven) });
|
|
838
|
+
throw proven;
|
|
839
|
+
}
|
|
840
|
+
throw error;
|
|
841
|
+
} finally {
|
|
842
|
+
clearInterval(timer); current.observerReturned = true;
|
|
843
|
+
if (current.dispatchJournalSettled && this.promptObservation === current) this.promptObservation = null;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
childActivity(sessionId) {
|
|
847
|
+
this.invalidateSettlement(sessionId);
|
|
848
|
+
if (sessionId === this.init?.conversation_id && !this.active) this.state.unclaimed_activity = true;
|
|
849
|
+
const child = this.descendants.get(sessionId);
|
|
850
|
+
if (!child) return;
|
|
851
|
+
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 });
|
|
852
|
+
}
|
|
853
|
+
invalidateSettlement(sessionId) {
|
|
854
|
+
this.nativeProof = null; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.nativeAbort?.abort();
|
|
855
|
+
this.state.native_execution_revision = this.nativeRevision;
|
|
856
|
+
if (this.init) this.state.active_tree = true;
|
|
857
|
+
this.state.retained_tree_settlement = null;
|
|
858
|
+
for (const id of new Set([this.init?.conversation_id, sessionId])) {
|
|
859
|
+
const observation = this.sessionObservations.get(id);
|
|
860
|
+
if (observation?.stop) this.sessionObservations.set(id, { ...observation, stop: null });
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async childForHook(conversationId) {
|
|
864
|
+
if (conversationId === this.init?.conversation_id) return null;
|
|
865
|
+
await this.captureChildTools();
|
|
866
|
+
const deadline = Date.now() + 1_000;
|
|
867
|
+
while (!this.descendants.has(conversationId) && Date.now() < deadline) { await new Promise(resolve => setTimeout(resolve, 25)); await this.captureChildTools(); }
|
|
868
|
+
const child = this.descendants.get(conversationId);
|
|
869
|
+
if (!child) throw new DaemonError("agy_child_identity_unconfirmed", "Antigravity hook has no confirmed native parent", true, { conversation_id: conversationId });
|
|
870
|
+
const seen = new Set([conversationId]); let current = child;
|
|
871
|
+
while (current.parent_provider_session_id !== this.init?.conversation_id) {
|
|
872
|
+
const parent = this.descendants.get(current.parent_provider_session_id);
|
|
873
|
+
if (this.state.native_topology_error || !current.native_source || !parent || seen.has(parent.provider_session_id)) throw new DaemonError("agy_child_parent_unqualified", "Antigravity worker has no source-bound ancestry to the controlled Session", false, { conversation_id: conversationId, parent_conversation_id: current.parent_provider_session_id });
|
|
874
|
+
seen.add(parent.provider_session_id); current = parent;
|
|
875
|
+
}
|
|
876
|
+
if (this.state.native_topology_error) throw new DaemonError("agy_child_parent_unqualified", "Antigravity native ancestry is contradictory");
|
|
877
|
+
return child;
|
|
878
|
+
}
|
|
879
|
+
async rejectHook(error, event, payload, current = this.active) {
|
|
880
|
+
if (current && this.active !== current) return;
|
|
881
|
+
const correctable = current && event === "PreToolUse" && isProvenShellCorrection(error);
|
|
882
|
+
if (correctable && current.hookCorrectionGeneration !== current.generation) {
|
|
883
|
+
// The request was correlated with this still-active Turn by
|
|
884
|
+
// observeHookFailures. Persist before allowing native same-Turn recovery.
|
|
885
|
+
const correction = { code: error.code, message: error.message, details: error.details, event, conversation_id: payload.conversationId ?? null,
|
|
886
|
+
daemon_id: this.config.daemonId, operation_id: current.operationId, turn_generation: current.generation };
|
|
887
|
+
try {
|
|
888
|
+
await hookBudget(1_000, null).run(async () => { await this.journal("hook_correction", { correction }); await this.persist({ last_hook_correction: correction }); }, "correction_persistence");
|
|
889
|
+
current.hookCorrectionGeneration = current.generation;
|
|
890
|
+
return;
|
|
891
|
+
} 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 } }); }
|
|
892
|
+
}
|
|
893
|
+
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 };
|
|
894
|
+
this.state.last_hook_rejection = rejection;
|
|
895
|
+
if (current && this.active === current) { this.active = null; current.reject(new DaemonError(rejection.code, rejection.message, false, rejection)); }
|
|
896
|
+
try { await hookBudget(1_000, null).run(async () => { await this.journal("hook_rejected", { rejection }); await this.persist({ last_hook_rejection: rejection }); }, "failure_persistence"); }
|
|
897
|
+
catch (storageError) { rejection.receipt_error = { code: storageError.code, message: storageError.message }; }
|
|
898
|
+
}
|
|
899
|
+
async observeHookFailures() {
|
|
900
|
+
for (const [requestId, binding] of this.hookRequests ?? []) {
|
|
901
|
+
if (binding.current !== this.active || !this.active) continue;
|
|
902
|
+
let failure;
|
|
903
|
+
try { failure = JSON.parse(await readFile(path.join(this.paths.dir, "hook-failures", `${requestId}.json`), "utf8")); }
|
|
904
|
+
catch (error) { if (error.code === "ENOENT") continue; failure = { code: "native_hook_receipt_invalid", message: error.message }; }
|
|
905
|
+
if (failure.request_id !== undefined && failure.request_id !== requestId) continue;
|
|
906
|
+
this.hookRequests.delete(requestId);
|
|
907
|
+
await this.rejectHook(Object.assign(new Error(failure.message ?? "Native hook failed"), { code: failure.code, details: failure.details }), binding.event, binding.payload, binding.current);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async observeHook(event, payload, budget = null) {
|
|
911
|
+
const conversationId = payload.conversationId ?? this.init?.conversation_id;
|
|
912
|
+
const child = await this.childForHook(conversationId); budget?.remaining("identity");
|
|
913
|
+
const prior = this.sessionObservations.get(conversationId) ?? {};
|
|
914
|
+
const execution = Number.isInteger(payload.executionNum) ? payload.executionNum : null;
|
|
915
|
+
const step = Number.isInteger(payload.stepIdx) ? payload.stepIdx : null;
|
|
916
|
+
const stale = step !== null && (Number.isInteger(prior.step_floor) && step <= prior.step_floor
|
|
917
|
+
|| Number.isInteger(prior.last_step_index) && step < prior.last_step_index
|
|
918
|
+
|| step === prior.last_step_index && execution !== null && Number.isInteger(prior.last_execution_num) && execution < prior.last_execution_num);
|
|
919
|
+
if (stale) { await this.journal("stale_hook", { hook_event: event, conversation_id: conversationId, step_index: step }); return { stale: true }; }
|
|
920
|
+
const fingerprint = createHash("sha256").update(JSON.stringify({ event, payload })).digest("hex");
|
|
921
|
+
if (event === "Stop" && prior.last_stop_hook === fingerprint && prior.last_stop_generation === this.turnGeneration) return { stale: true };
|
|
922
|
+
this.progressHooks ??= new Set();
|
|
923
|
+
const scoped = this.active && (!child || child.activity_generation === this.active.generation);
|
|
924
|
+
const sourceBound = Boolean(scoped && step !== null && step > (this.active.stepFloor ?? -1)
|
|
925
|
+
&& this.progressSteps?.get(conversationId)?.index === step);
|
|
926
|
+
const progressKey = JSON.stringify([conversationId, step, execution, event, payload.toolCall ?? null, payload.fullyIdle ?? null]);
|
|
927
|
+
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(); }
|
|
928
|
+
if (conversationId) this.sessionObservations.set(conversationId, { ...prior,
|
|
929
|
+
...(step !== null ? { last_step_index: step, last_execution_num: execution ?? (step === prior.last_step_index ? prior.last_execution_num : null) } : {}),
|
|
930
|
+
last_hook: fingerprint, ...(event === "PreToolUse" ? { last_pre_hook: fingerprint } : {}),
|
|
931
|
+
...(payload.transcriptPath ? { transcript_path: payload.transcriptPath } : {}),
|
|
932
|
+
...(event === "Stop" ? { last_stop_hook: fingerprint, last_stop_generation: this.turnGeneration, stop: { fullyIdle: payload.fullyIdle === true, source_bound: sourceBound, terminationReason: payload.terminationReason ?? null, executionNum: execution } } : {}) });
|
|
933
|
+
if (event === "PreToolUse" && prior.last_pre_hook !== fingerprint) this.childActivity(conversationId);
|
|
934
|
+
if (child && event === "Stop" && sourceBound && payload.fullyIdle === true) this.descendants.set(conversationId, { ...this.descendants.get(conversationId), tree_settled: true, settlement_evidence: "child_fully_idle_stop" });
|
|
935
|
+
const terminalPending = this.active?.terminalResult === this.lastResult;
|
|
936
|
+
if (!child && event === "Stop" && sourceBound && 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" });
|
|
937
|
+
const parent = child?.parent_provider_session_id ?? null;
|
|
938
|
+
if (event === "Stop" && !stale) {
|
|
939
|
+
const observation = this.sessionObservations.get(conversationId);
|
|
940
|
+
this.sessionObservations.set(conversationId, { ...observation, stop: { ...observation.stop, generation: this.turnGeneration } });
|
|
941
|
+
}
|
|
942
|
+
const observed = payload.model ? observedProfile(payload) : {};
|
|
943
|
+
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" });
|
|
944
|
+
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");
|
|
945
|
+
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 });
|
|
946
|
+
if (!stale && !child && event === "PreToolUse" && Number.isInteger(step) && this.active) { this.active.nativeHookEvents ??= new Map(); this.active.nativeHookEvents.set(step, `${eventId}:before`); }
|
|
947
|
+
this.queueNativeInspection();
|
|
948
|
+
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 };
|
|
949
|
+
}
|
|
950
|
+
async finalizeProviderForControl() {
|
|
951
|
+
const pending = this.providerFinalization;
|
|
952
|
+
try {
|
|
953
|
+
try { await pending; }
|
|
954
|
+
catch (error) {
|
|
955
|
+
// Control retries only retained physical/resource cleanup, never a
|
|
956
|
+
// missing mandatory stream record or a productive provider request.
|
|
957
|
+
if (this.providerFinalization !== pending) await this.providerFinalization;
|
|
958
|
+
else {
|
|
959
|
+
if (!this.retryProviderFinalization) throw error;
|
|
960
|
+
this.providerFinalization = this.retryProviderFinalization(5_000);
|
|
961
|
+
await this.providerFinalization;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
await this.promptObservation?.dispatchJournal;
|
|
965
|
+
} catch (error) {
|
|
966
|
+
if (!this.drainingError) throw error;
|
|
967
|
+
this.drainingError.cleanup_error = errorRecord(error);
|
|
968
|
+
this.drainingError.details = { ...this.drainingError.details, cleanup_error: this.drainingError.cleanup_error };
|
|
969
|
+
throw this.drainingError;
|
|
970
|
+
}
|
|
971
|
+
if (this.drainingError) throw this.drainingError;
|
|
972
|
+
}
|
|
973
|
+
async prepareStop(cancelTree) {
|
|
974
|
+
const current = this.active, priorResult = this.lastResult;
|
|
975
|
+
if (!cancelTree && !current && !priorResult) await this.reconcileRetainedAdmission();
|
|
976
|
+
if (cancelTree && (this.active || !this.receipt().settled)) await this.cancel();
|
|
977
|
+
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");
|
|
978
|
+
// A physical kill is settlement, not a native clean terminal receipt.
|
|
979
|
+
// In particular, the previous Turn's SUCCESS cannot certify a cancelled
|
|
980
|
+
// in-flight Turn that exited without its own result.
|
|
981
|
+
const currentResult = !current || current.terminalResult === this.lastResult || this.lastResult !== priorResult;
|
|
982
|
+
if (currentResult && this.receipt().settled) await this.retireNativeServices();
|
|
983
|
+
const clean = currentResult && this.receipt().settled;
|
|
984
|
+
return { ...this.receipt(), clean, settled: Boolean(clean), native_settled: Boolean(clean), ...(clean ? {} : { native_settlement: { confirmed: false, reason: "native_terminal_unconfirmed" } }) };
|
|
985
|
+
}
|
|
986
|
+
async retireNativeServices() {
|
|
987
|
+
const services = [...(this.nativeResources?.values() ?? [])].filter(item => item.classification === 'support_service' && item.state !== 'stopped');
|
|
988
|
+
// Unknown classification/unsupported binaries keep the existing cleanup
|
|
989
|
+
// blocker. A fixture PID printed to stdout is never a production mapping.
|
|
990
|
+
if (!services.length || !this.nativeQualification?.qualified) return;
|
|
991
|
+
const revision = this.nativeRevision ?? 0;
|
|
992
|
+
const endpoint = this.nativeStopper ? null : await ownedAgyEndpoint(this.nativeLaunch);
|
|
993
|
+
const input = { endpoint, sessionId: this.init.conversation_id, cwd: this.config.cwd,
|
|
994
|
+
csrfToken: this.nativeCsrfToken, remainingMs: 10_000, rootRelation: this.nativeQualification.rootRelation };
|
|
995
|
+
const stopped = await (this.nativeStopper ?? stopAgyNativeTree)(input, [...this.descendants.keys()]);
|
|
996
|
+
const observed = await (this.nativeObserver ?? inspectAgyNative)(input);
|
|
997
|
+
if (!this.nativeStopper && !isDeepStrictEqual(await ownedAgyEndpoint(this.nativeLaunch), endpoint)
|
|
998
|
+
|| !observed.idle || revision !== (this.nativeRevision ?? 0) || !this.receipt().settled)
|
|
999
|
+
throw new DaemonError('native_resources_unproven', 'AGY native service retirement lost its owner/execution fence');
|
|
1000
|
+
// This is native handle retirement, not physical cleanup. Shared shutdown
|
|
1001
|
+
// still re-observes the retained local PID/birth inventory after close.
|
|
1002
|
+
for (const service of services) this.nativeResources.set(service.resource_id, { ...service, state: 'stopped',
|
|
1003
|
+
native_retirement: { method: stopped.method, stopped_session_ids: stopped.stopped_session_ids, revision } });
|
|
1004
|
+
await this.persist({ native_resources_entries: [...this.nativeResources.values()] });
|
|
1005
|
+
}
|
|
1006
|
+
async cancel() { if (!this.active && this.receipt().settled) return { ...this.receipt(), cancelled: true }; this.nativeAbort?.abort(); this.nativeProof = null; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.state.native_execution_revision = this.nativeRevision; cancelPendingDispatch(this); if (!this.child) return this.receipt(); 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 };
|
|
1007
|
+
}
|
|
1008
|
+
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) { if (!this.active && this.receipt().settled) await stopProcessGroup(this.child, 5_000); else await this.cancel(); } await this.finalizeProviderForControl(); }
|
|
1009
|
+
async abortProvider(reason) {
|
|
1010
|
+
const child = this.child, providerProcess = this.providerProcess;
|
|
1011
|
+
await stopProcessGroup(child);
|
|
1012
|
+
await this.finalizeProviderForControl();
|
|
1013
|
+
await finishDaemonProcess(this.config, providerProcess, "failed", reason);
|
|
1014
|
+
}
|
|
1015
|
+
async finishResource(state = "stopped", reason = "daemon_stopped") { await this.captureChildTools(); await finishDaemonProcess(this.config, this.state.resource_process, state, reason); }
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export async function serveDaemon(stateDir) {
|
|
1019
|
+
const paths = locations(stateDir), state = await readState(paths.dir); if (!state?.config) throw new DaemonError("daemon_state_missing", "Antigravity daemon state is missing");
|
|
1020
|
+
scheduleDaemonProcessHeartbeat(state.config, state.resource_process);
|
|
1021
|
+
const runtime = new Runtime(paths, state); await removeSocket(paths);
|
|
1022
|
+
const server = net.createServer(socket => {
|
|
1023
|
+
let buffer = "", consumed = false; socket.setEncoding("utf8");
|
|
1024
|
+
socket.on("data", chunk => {
|
|
1025
|
+
if (consumed) return;
|
|
1026
|
+
buffer += chunk; if (!admitNativeRequestBuffer(buffer, socket, RESPONSE_SCHEMA)) { consumed = true; return; } const at = buffer.indexOf("\n"); if (at < 0) return; consumed = true;
|
|
1027
|
+
void (async () => {
|
|
1028
|
+
let request;
|
|
1029
|
+
try {
|
|
1030
|
+
request = JSON.parse(buffer.slice(0, at));
|
|
1031
|
+
const result = await durableDaemonDispatch(paths.dir, request, () => dispatch(runtime, request), () => dispatch(runtime, { operation: "session.inspect", params: request.params ?? {} }));
|
|
1032
|
+
if (result._shutdown) retireDaemonEndpoint(server);
|
|
1033
|
+
socket.end(`${JSON.stringify({ schema_id: RESPONSE_SCHEMA, id: request.id, ok: true, result: { ...result, _shutdown: undefined } })}\n`);
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
await retireFailedDaemon(server, state, error, patch => runtime.persist(patch));
|
|
1036
|
+
socket.end(`${JSON.stringify({ schema_id: RESPONSE_SCHEMA, id: request?.id ?? null, ok: false, error: errorPayload(error) })}\n`);
|
|
1037
|
+
}
|
|
1038
|
+
})();
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
trackDaemonConnections(server); runtime.server = server;
|
|
1042
|
+
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(paths.socket, resolve); }); await chmod(paths.socket, 0o600);
|
|
1043
|
+
try { await runtime.start(state.config.resumeSessionId); runtime.requireSessionIdentity(state.config.resumeSessionId); }
|
|
1044
|
+
catch (error) {
|
|
1045
|
+
try { await runtime.abortProvider("daemon_start_failed"); }
|
|
1046
|
+
catch (cleanup) { error.details = { ...error.details, cleanup_failed: errorRecord(cleanup) }; }
|
|
1047
|
+
try { if (!error.details?.cleanup_failed) await runtime.finishResource("failed", "daemon_start_failed"); }
|
|
1048
|
+
catch (cleanup) { error.details = { ...error.details, resource_cleanup_failed: errorRecord(cleanup) }; }
|
|
1049
|
+
Object.assign(state, { shutdown_state: error.details?.cleanup_failed || error.details?.resource_cleanup_failed ? "cleanup_failed" : "failed", active_tree: Boolean(error.details?.cleanup_failed) });
|
|
1050
|
+
await retainStartupFailure({ state, publish: snapshot => runtime.persist(snapshot), log: paths.log, error });
|
|
1051
|
+
await new Promise(resolve => server.close(resolve));
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
await new Promise(resolve => server.once("close", resolve));
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
async function dispatch(runtime, request) {
|
|
1058
|
+
if (request.schema_id !== REQUEST_SCHEMA) throw new DaemonError("daemon_protocol_mismatch", "unsupported dd-agy request schema"); const params = request.params ?? {};
|
|
1059
|
+
if (["session.create", "session.prompt", "session.start", "session.fork", "session.resume"].includes(request.operation)) assertDaemonDispatch(runtime.paths.dir);
|
|
1060
|
+
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() }; }
|
|
1061
|
+
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(); }
|
|
1062
|
+
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); }
|
|
1063
|
+
if (request.operation === "session.inspect") { runtime.requireSessionIdentity(params.sessionId); await runtime.captureChildTools(); void runtime.reconcileRetainedAdmission().catch(error => { runtime.nativeInspection = { state: 'unproven', reason: error.details?.reason ?? error.code ?? 'native_admission_unproven' }; }); runtime.queueNativeInspection(); await runtime.persist({ native_observation_episode: runtime.episodeSnapshot() }); return runtime.receipt(); }
|
|
1064
|
+
if (request.operation === "session.resume") { await runtime.start(params.sessionId ?? runtime.init?.conversation_id ?? null); runtime.requireSessionIdentity(params.sessionId); return runtime.receipt(); }
|
|
1065
|
+
if (request.operation === "session.cancel") { runtime.requireSessionIdentity(params.sessionId); return await runtime.cancel(); }
|
|
1066
|
+
if (request.operation === "hook.observe") {
|
|
1067
|
+
// Check the process binding before correlating a request with an active
|
|
1068
|
+
// turn. A stale/foreign daemon must not poison an unrelated operation.
|
|
1069
|
+
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 });
|
|
1070
|
+
const current = runtime.active, budget = hookBudget(25_000, params.deadlineAt);
|
|
1071
|
+
runtime.hookRequests ??= new Map();
|
|
1072
|
+
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 } } });
|
|
1073
|
+
try {
|
|
1074
|
+
const result = await budget.run(async () => {
|
|
1075
|
+
if (runtime.hookRequests.has(request.id) && current?.operationId) {
|
|
1076
|
+
const directory = path.join(runtime.paths.dir, "operations", createHash("sha256").update(current.operationId).digest("hex"), "hooks");
|
|
1077
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
1078
|
+
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 });
|
|
1079
|
+
}
|
|
1080
|
+
budget.remaining("observation");
|
|
1081
|
+
return await runtime.observeHook(params.event, params.payload ?? {}, budget);
|
|
1082
|
+
}, "observation");
|
|
1083
|
+
await runtime.observeHookFailures(); budget.remaining("observation");
|
|
1084
|
+
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);
|
|
1085
|
+
return result;
|
|
1086
|
+
}
|
|
1087
|
+
catch (error) { await runtime.rejectHook(error, params.event, params.payload ?? {}, current); throw error; }
|
|
1088
|
+
}
|
|
1089
|
+
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()]], async () => {
|
|
1090
|
+
await runtime.captureChildTools();
|
|
1091
|
+
return runtime.receipt().native_resources;
|
|
1092
|
+
});
|
|
1093
|
+
throw new DaemonError("unknown_operation", `unknown dd-agy daemon operation: ${request.operation}`);
|
|
1094
|
+
}
|
|
1095
|
+
import { authorizeRetainedDaemonResume } from "./driver-recovery.mjs";
|
|
1096
|
+
import { commandText } from "./process-json.mjs";
|