@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149

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.
Files changed (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
@@ -3,11 +3,14 @@ import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-p
3
3
  import { callDaemonObserved } from './daemon-observation.mjs';
4
4
  import { operationProgressReporter } from './operation-progress.mjs';
5
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';
6
9
  import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
7
10
  import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
8
11
  import { ToolObservations, agyToolObservation, agyTranscriptObservations } from "./tool-observations.mjs";
9
12
  import { constants } from "node:fs";
10
- import { fileURLToPath } from "node:url";
13
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
14
  import { createHash, randomUUID } from "node:crypto";
12
15
  import { appendFile, chmod, lstat, mkdir, open, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
13
16
  import { spawn } from "node:child_process";
@@ -19,14 +22,48 @@ import { agyWorkspaceHooks } from "./agy-hooks.mjs";
19
22
  import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
20
23
  import path from "node:path";
21
24
  import { observeModel } from "./model-observations.mjs";
22
- import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
23
- import { settledNativeTree } from "./native-children.mjs";
25
+ import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
26
+ import { recoveredAgyErrorBoundary } from "./agy-error-boundary.mjs";
27
+ import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
24
28
  import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
25
29
  import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
26
30
 
27
31
  const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
28
32
  class DaemonError extends AgyError {}
29
- export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId]) {
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 = [], calls = [];
38
+ if (!source?.locator || !source?.sha256 || !sessionId) return { children, resources };
39
+ for (const line of text.trim().split('\n')) {
40
+ let event;
41
+ try { event = JSON.parse(line); } catch { return { children: [], resources: [] }; }
42
+ if (event.source !== 'MODEL') continue;
43
+ 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; }
44
+ if (event.type !== 'GENERIC') continue;
45
+ const call = calls.shift(), content = typeof event.content === 'string' ? event.content : '';
46
+ const native_source = { ...source, step_index: event.step_index };
47
+ if (call?.name === 'invoke_subagent' && event.status === 'DONE') {
48
+ const match = content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nCreated the following subagent:\n(\{[\s\S]*?\})\nThe subagent will send you a message/);
49
+ if (!match) continue;
50
+ try {
51
+ const child = JSON.parse(match[1]);
52
+ 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 });
53
+ } catch { /* No partial/guessed native edge. */ }
54
+ }
55
+ if (event.status !== 'RUNNING') continue;
56
+ const task = content.match(/^Created At: [^\n]+\nTool is running as a background task with task id: ([\w-]+\/task-\d+)\n/);
57
+ if (!task || !task[1].startsWith(`${sessionId}/`)) continue;
58
+ let daemon = call?.name === 'run_command' ? call.args?.IsDaemon : null;
59
+ if (typeof daemon === 'string') { try { daemon = JSON.parse(daemon); } catch { daemon = null; } }
60
+ resources.push({ resource_id: task[1], provider_session_id: sessionId, classification: daemon === true ? 'support_service' : daemon === false ? 'required_work' : 'unknown', state: 'unknown', native_source,
61
+ ...(typeof daemon === 'boolean' ? { native_classification: { tool: 'run_command', IsDaemon: daemon } } : {}) });
62
+ }
63
+ return { children, resources };
64
+ }
65
+ export const agyTranscriptResources = (text, source, sessionId) => agyTranscriptFacts(text, source, sessionId).resources;
66
+ export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId], inspect = null) {
30
67
  const session = { harness_id: "antigravity-cli", session_id: child.provider_session_id };
31
68
  let handle;
32
69
  try {
@@ -43,6 +80,7 @@ export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ow
43
80
  const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
44
81
  const bytes = buffer.subarray(0, bytesRead), prefix = bytes.subarray(0, bytes.lastIndexOf(10) + 1);
45
82
  const source = { locator: child.log_uri, byte_start: 0, byte_end: prefix.length, sha256: createHash("sha256").update(prefix).digest("hex") };
83
+ if (prefix.length === bytesRead && stat.size <= maximum) inspect?.(prefix.toString("utf8"), source);
46
84
  const events = agyTranscriptObservations(prefix.toString("utf8"), child.provider_session_id, child.parent_provider_session_id ?? rootSessionId, source);
47
85
  if (prefix.length !== bytesRead || stat.size > maximum) events.push({ session, reason: stat.size > maximum ? "agy_transcript_size_limit" : "agy_transcript_partial_tail" });
48
86
  return events;
@@ -171,7 +209,7 @@ export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 3
171
209
  return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'agy', timeoutMs });
172
210
  }
173
211
 
174
- function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","reasoning","mode","projectRoot","ddFlowBin","ddFlowHome","resourceHome","noFlow","livenessTimeoutMs"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
212
+ 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)); }
175
213
  // Called only after authorizeRetainedDaemonResume verifies the old daemon,
176
214
  // provider processes and operation ledger. Native outcomes remain historical;
177
215
  // their old liveness flags cannot describe the replacement process tree.
@@ -181,6 +219,8 @@ export function retainedAgyState(previous, sessionId) {
181
219
  return {
182
220
  ...previous,
183
221
  unclaimed_activity: false,
222
+ native_resources_entries: [],
223
+ native_topology_error: null,
184
224
  retained_tree_settlement: { daemon_id: previous.daemon_id, session_id: sessionId, turn_generation: previous.turn_generation ?? 0, evidence: "verified_clean_daemon_shutdown" },
185
225
  session_observations: (previous.session_observations ?? []).map(([id, observation]) => [id, id === sessionId ? { ...observation, retained_stop: observation.stop ?? null, stop: null } : observation]),
186
226
  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 })
@@ -190,6 +230,7 @@ export async function startDaemon(options) {
190
230
  const paths = locations(options.stateDir); await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
191
231
  const previous = await readState(paths.dir), noFlow = options.noFlow === true, cwd = await realpath(absolute(options.cwd, "--cwd")), config = { cwd, journal: options.journal ? absolute(options.journal, "--journal") : paths.journal, bin: options.bin ?? process.env.DD_AGY_BIN ?? "agy", provider: String(options.provider ?? "google"), model: String(options.model ?? "gemini-3.1-pro-high"), reasoning: String(options.reasoning ?? "high"), mode: String(options.mode ?? "accept-edits"), projectRoot: noFlow ? cwd : await realpath(absolute(options.projectRoot, "--project-root")), ddFlowBin: noFlow ? null : absolute(options.ddFlowBin, "--dd-flow-bin"), ddFlowHome: noFlow ? null : absolute(options.ddFlowHome, "--dd-flow-home"), resourceHome: noFlow ? null : (process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null), env: noFlow ? {} : { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home") }, entryPath: absolute(options.entryPath, "dd-agy entry"), daemonId: randomUUID(), resumeSessionId: options.sessionId ?? null, geminiDir: paths.gemini, appDataDir: "runtime", temporary: paths.temporary, noFlow };
192
232
  config.livenessTimeoutMs = options.livenessTimeoutMs ?? 1_800_000;
233
+ config.nativeQualificationPath = process.env.DD_AGY_NATIVE_QUALIFICATION ? absolute(process.env.DD_AGY_NATIVE_QUALIFICATION, 'DD_AGY_NATIVE_QUALIFICATION') : null;
193
234
  validateObservationDuration(config.livenessTimeoutMs);
194
235
  await prepareRuntimeOwner(config, paths.dir);
195
236
  assertDaemonRestartable(previous);
@@ -201,7 +242,7 @@ export async function startDaemon(options) {
201
242
  const version = (await runAgy(config.bin, ["--version"], { cwd: config.cwd, env: sanitizedEnv(config), timeoutMs: AGY_VERSION_TIMEOUT_MS })).stdout.trim();
202
243
  const resourceProcess = await registerDaemonProcess(config, { kind: "agy-daemon", owner: `agy:${path.basename(paths.dir)}`, operation: `agy-daemon:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log });
203
244
  const retained = retainedAgyState(previous, config.resumeSessionId);
204
- await writeJson(paths.state, { schema_id: STATE_SCHEMA, daemon_id: config.daemonId, shutdown_state: "starting", active_tree: false, unclaimed_activity: retained.unclaimed_activity ?? false, retained_tree_settlement: retained.retained_tree_settlement ?? null, versions: { agy: version }, config, sessions: [], session_observations: retained.session_observations ?? [], descendants: retained.descendants ?? [], ...(Array.isArray(retained.tool_observations) ? { tool_observations: retained.tool_observations } : {}), tool_observation_gaps: retained.tool_observation_gaps ?? [], tool_calls: retained.tool_calls ?? [], completed_tool_steps: retained.completed_tool_steps ?? [], tool_identity_complete: retained.tool_identity_complete !== false, turn_generation: retained.turn_generation ?? 0, last_result: retained.last_result ?? null, resource_process: resourceProcess });
245
+ 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 });
205
246
  const log = await open(paths.log, "a", 0o600), child = spawn(process.execPath, [config.entryPath, "daemon", "serve", "--state-dir", paths.dir], { cwd: config.cwd, env: sanitizedEnv(config), detached: true, stdio: ["ignore", log.fd, log.fd] }); child.unref(); await log.close();
206
247
  try {
207
248
  await confirmDaemonProcess(config, resourceProcess, child);
@@ -222,10 +263,17 @@ export async function startDaemon(options) {
222
263
  export const stopDaemon = async options => { const timeoutMs = options.timeoutMs ?? 30_000; return await confirmDaemonStopped(() => callDaemon(options.stateDir, "daemon.stop", { cancelTree: options.cancelTree === true }, timeoutMs), locations(options.stateDir).socket, timeoutMs, { readState: () => readState(options.stateDir) }); };
223
264
 
224
265
  export class Runtime {
225
- constructor(paths, state) { this.paths = paths; this.state = state; this.config = state.config; this.child = null; this.providerProcess = state.provider_process ?? null; this.providerFinalization = Promise.resolve(); this.buffer = ""; this.init = null; this.readyPromise = null; this.active = null; this.exited = false; this.toolObservations = new ToolObservations(); for (const reason of state.tool_observation_gaps ?? []) this.toolObservations.gaps.add(reason); for (const event of state.tool_observations ?? []) this.toolObservations.add(event); if (state.tool_calls?.length && !state.tool_observations) this.toolObservations.gaps.add("legacy_tools_not_attributed"); this.turnGeneration = state.turn_generation ?? 0; this.descendants = new Map((state.descendants ?? []).map(child => [child.provider_session_id, child])); this.lastResult = state.last_result ?? null; this.sessionObservations = new Map(state.session_observations ?? []); this.startupError = null; this.persisting = Promise.resolve(); this.draining = Promise.resolve(); this.lastActivityAt = state.last_activity_at ?? null; this.usageIngest = null; this.lastProviderEvent = null; }
226
- persist(patch = {}) { Object.assign(this.state, patch, { pid: process.pid, updated_at: new Date().toISOString(), last_activity_at: this.lastActivityAt, session_observations: [...this.sessionObservations], tool_observations: [...this.toolObservations.calls.values(), ...this.toolObservations.sources.values()], tool_observation_gaps: [...this.toolObservations.gaps], turn_generation: this.turnGeneration, descendants: [...this.descendants.values()], sessions: this.init ? [{ provider_session_id: this.init.conversation_id, root_provider_session_id: this.init.conversation_id, cwd: this.config.cwd }] : [] }); const snapshot = structuredClone(this.state); const queued = performance.now(); this.persisting = this.persisting.catch(() => {}).then(async () => { const started = performance.now(); await writeJson(this.paths.state, snapshot); this.persistTiming = { queue_ms: Math.round(started - queued), write_ms: Math.round(performance.now() - started) }; }); return this.persisting; }
227
- async journal(kind, payload = {}) { const line = `${JSON.stringify({ schema_id: "dd-agy/daemon-event@1", event_id: randomUUID(), observed_at: new Date().toISOString(), kind, ...payload })}\n`; for (const journal of new Set([this.paths.journal, this.config.journal].filter(Boolean))) { await mkdir(path.dirname(journal), { recursive: true, mode: 0o700 }); await appendFile(journal, line, { mode: 0o600 }); } }
228
- args(sessionId) { return ["--input-format", "stream-json", "--output-format", "stream-json", "--model", this.config.model, "--effort", this.config.reasoning, "--mode", this.config.mode, "--dangerously-skip-permissions", "--print-timeout", "0", `--gemini_dir=${this.paths.gemini}`, "--app_data_dir=runtime", ...(sessionId ? ["--conversation", sessionId] : ["--new-project"])]; }
266
+ 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;
267
+ const saved = state.native_observation_episode;
268
+ if (saved?.clock) {
269
+ this.observationEpisode = { operationId: saved.operation_id, generation: saved.turn_generation, connectionId: saved.connection_id, terminalMetadata: state.last_terminal_metadata,
270
+ clock: new ObservationClock({ timeoutMs: saved.clock.timeout_ms, saved: saved.clock }) };
271
+ this.connectionId = saved.connection_id;
272
+ }
273
+ }
274
+ 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; }
275
+ 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; }
276
+ 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"])]; }
229
277
  requireSessionIdentity(requestedId) {
230
278
  const observedId = this.init?.conversation_id ?? null;
231
279
  if (!requestedId || requestedId === observedId) return observedId;
@@ -233,7 +281,7 @@ export class Runtime {
233
281
  harness: "antigravity-cli", expected_session_id: requestedId, observed_session_id: observedId
234
282
  });
235
283
  }
236
- markActivity() { const current = this.active; this.lastActivityAt = new Date().toISOString(); this.progress = { cursor: (this.progress?.cursor ?? 0) + 1, observed_at: Date.now() }; void current?.publishProgress?.('native_progress', { session_id: this.init?.conversation_id, observed_at: this.lastActivityAt }).catch(error => { if(this.active === current) current.reject(error); }); }
284
+ 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); }); }
237
285
  markTerminalActivity(result) {
238
286
  const current = this.active;
239
287
  if (!current) return;
@@ -243,11 +291,12 @@ export class Runtime {
243
291
  current.terminalProgress.add(key); this.markActivity();
244
292
  }
245
293
  observeProviderActivity(event) {
246
- if (!this.active) return;
294
+ const owner = this.active ?? this.observationEpisode;
295
+ if (!owner || owner.connectionId !== this.connectionId || owner.generation !== this.turnGeneration) return;
247
296
  const root = this.init?.conversation_id, step = event.step_update;
248
297
  const id = step?.conversation_id ?? event.result?.conversation_id ?? root;
249
298
  let parent = id; const seen = new Set();
250
- while (parent && parent !== root && !seen.has(parent)) { seen.add(parent); const child = this.descendants.get(parent); if (child?.activity_generation !== this.active.generation) return; parent = child.parent_provider_session_id; }
299
+ 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; }
251
300
  if (!root || parent !== root) return;
252
301
  if (event.event === "result") { if (["SUCCESS", "ERROR", "CANCELLED"].includes(event.result?.status)) this.markTerminalActivity(event.result); return; }
253
302
  if (event.event !== "step_update") return;
@@ -265,7 +314,7 @@ export class Runtime {
265
314
  // Native step snapshots can alternate/replay; scope retained dedup to the
266
315
  // current step. Too many unique snapshots becomes uncertain, never a grant.
267
316
  if (snapshots.size >= 1_024) return;
268
- snapshots.add(fingerprint); this.progressSteps.set(id, { index, seen: snapshots }); this.markActivity();
317
+ snapshots.add(fingerprint); this.progressSteps.set(id, { index, seen: snapshots }); this.invalidateSettlement(id); this.markActivity();
269
318
  }
270
319
  async start(sessionId = null) {
271
320
  if (this.drainingError) throw this.drainingError;
@@ -278,6 +327,11 @@ export class Runtime {
278
327
  await this.providerFinalization;
279
328
  if (this.drainingError) throw this.drainingError;
280
329
  assertDaemonDispatch(this.paths?.dir);
330
+ this.nativeAbort?.abort(); this.nativeProof = null;
331
+ this.nativeLogFile = path.join(this.paths.dir, `native-${randomUUID()}.log`);
332
+ this.nativeCsrfToken = randomUUID();
333
+ this.admissionClock = null;
334
+ this.nativeQualification = await readAgyNativeQualification(this.paths.dir, this.config.bin, this.config.nativeQualificationPath);
281
335
  const target = executable(this.config.bin, this.args(sessionId)); this.exited = false; this.buffer = ""; this.init = null;
282
336
  let readyResolve, readyReject; this.readyPromise = new Promise((resolve, reject) => { readyResolve = resolve; readyReject = reject; });
283
337
  // The provider can reject before durable registration finishes. Keep a
@@ -288,9 +342,10 @@ export class Runtime {
288
342
  const providerProcess = this.providerProcess;
289
343
  try { await this.persist({ provider_process: providerProcess }); }
290
344
  catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
291
- try { assertDaemonDispatch(this.paths?.dir); this.child = spawn(target.command, target.args, { cwd: this.config.cwd, env: sanitizedEnv(this.config), detached: true, stdio: ["pipe", "pipe", "pipe"] }); }
345
+ 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"] }); }
292
346
  catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
293
347
  const providerChild = this.child;
348
+ this.nativeLaunch = { logFile: this.nativeLogFile, providerPid: providerChild.pid, providerBirth: (await processSnapshot()).find(item => item.pid === providerChild.pid)?.started, connectionId: this.connectionId };
294
349
  let allowProviderFinalization;
295
350
  const providerRegistered = new Promise(resolve => { allowProviderFinalization = resolve; });
296
351
  const observationFailure = error => { this.drainingError ??= error; readyReject(error); this.active?.reject(error); };
@@ -334,10 +389,7 @@ export class Runtime {
334
389
  // child can close while the asynchronous write is still pending.
335
390
  if (event.event === "result" && ["ERROR", "CANCELLED"].includes(event.result?.status)) {
336
391
  this.lastTerminalFailure ??= this.state.last_terminal_failure;
337
- const repeated = Boolean(this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === event.result.error
338
- && this.lastTerminalFailure.details.provider_session_id === (event.result.conversation_id || this.init?.conversation_id || null)
339
- && (this.active?.previousError === event.result.error || this.lastResult?.status === "ERROR" && this.lastResult.error === event.result.error));
340
- event.terminal_metadata = repeated ? this.lastTerminalFailure.details : { observed_at: new Date().toISOString(), provider_session_id: event.result.conversation_id || this.init?.conversation_id || null,
392
+ event.terminal_metadata = { observed_at: new Date().toISOString(), provider_session_id: event.result.conversation_id || this.init?.conversation_id || null,
341
393
  operation_id: this.active?.operationId ?? null, turn_generation: this.active?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" };
342
394
  if (!this.init) this.startupError ??= agyTerminalFailure(event.result, event.terminal_metadata);
343
395
  this.receivedTerminalMetadata = event.terminal_metadata;
@@ -357,25 +409,53 @@ export class Runtime {
357
409
  // Tree liveness is broader than current-Turn model progress: fresh work
358
410
  // from a retained child invalidates settlement without adopting this Turn.
359
411
  if (fresh && (sessionId === this.init?.conversation_id || this.descendants.has(sessionId))) this.childActivity(sessionId);
360
- if (Number.isInteger(step.step_index)) this.sessionObservations.set(sessionId, { ...observation, last_step_index: Math.max(observation.last_step_index ?? -1, step.step_index) });
412
+ 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) });
361
413
  this.toolObservations.add(agyToolObservation(step, this.init?.conversation_id));
362
414
  for (const child of step.subagent_info?.subagents ?? []) if (child.conversation_id) {
363
415
  const prior = this.descendants.get(child.conversation_id);
416
+ if (child.conversation_id === this.init?.conversation_id || prior && prior.parent_provider_session_id !== sessionId) {
417
+ this.state.native_topology_error = 'agy_native_ancestry_contradiction'; this.invalidateSettlement(sessionId); continue;
418
+ }
364
419
  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",
420
+ ...(bound && Number.isInteger(step.step_index) ? { native_source: { kind: 'agy.step_update.subagent_info', parent_provider_session_id: sessionId, step_index: step.step_index } } : {}),
365
421
  ...(fresh && bound && this.active ? { activity_generation: this.active.generation } : {}) });
366
422
  }
367
423
  }
368
424
  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 }; }
369
425
  async captureChildTools() {
370
426
  this.capturedToolFacts ??= new Map();
427
+ this.nativeResources ??= new Map(this.state.native_resources_entries?.map(item => [item.resource_id, item]) ?? []);
371
428
  const root = this.init?.conversation_id ?? this.state.sessions?.[0]?.provider_session_id ?? this.lastResult?.conversation_id;
372
- for (const child of this.descendants.values()) {
429
+ 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()];
430
+ const visited = new Set();
431
+ for (let index = 0; index < queue.length && index < 256; index++) {
432
+ const child = queue[index];
433
+ if (visited.has(child.provider_session_id)) continue;
434
+ visited.add(child.provider_session_id);
373
435
  const ancestors = new Set(); let parent = child.parent_provider_session_id;
374
436
  while (parent && parent !== root && this.descendants.has(parent) && !ancestors.has(parent)) {
375
437
  ancestors.add(parent); parent = this.descendants.get(parent).parent_provider_session_id;
376
438
  }
377
439
  const events = parent === root && root
378
- ? await captureAgyTranscript(this.paths.runtime, child, root, [root, ...ancestors])
440
+ ? await captureAgyTranscript(this.paths.runtime, child, root, [root, ...ancestors], (text, source) => {
441
+ const facts = agyTranscriptFacts(text, source, child.provider_session_id);
442
+ for (const resource of facts.resources) this.nativeResources.set(resource.resource_id, resource);
443
+ for (const discovered of facts.children) {
444
+ const expected = path.join(this.paths.runtime, 'brain', discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
445
+ const sourceBrain = path.resolve(path.dirname(fileURLToPath(source.locator)), '../../..');
446
+ const sibling = path.join(sourceBrain, discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
447
+ let locator;
448
+ try { locator = fileURLToPath(discovered.log_uri); } catch { locator = null; }
449
+ if (locator !== expected && locator !== sibling) { this.state.native_topology_error = 'agy_native_locator_unbound'; this.invalidateSettlement(root); continue; }
450
+ const prior = this.descendants.get(discovered.provider_session_id);
451
+ 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)) {
452
+ this.state.native_topology_error = 'agy_native_ancestry_contradiction'; this.invalidateSettlement(root); continue;
453
+ }
454
+ const value = { ...discovered, ...prior, native_source: discovered.native_source };
455
+ this.descendants.set(value.provider_session_id, value);
456
+ queue.push(value);
457
+ }
458
+ })
379
459
  : [{ session: { harness_id: "antigravity-cli", session_id: child.provider_session_id }, reason: "agy_transcript_ancestry_unproven" }];
380
460
  for (const event of events) {
381
461
  this.toolObservations.add(event);
@@ -385,6 +465,8 @@ export class Runtime {
385
465
  this.capturedToolFacts.set(key, encoded);
386
466
  }
387
467
  }
468
+ 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); }
469
+ this.state.native_resources_entries = [...this.nativeResources.values()];
388
470
  }
389
471
  async finishTurn(result, retainedMetadata = null, providerEvent = null) {
390
472
  if (result.status === "RUNNING") { await this.persist({ active_tree: true }); return; }
@@ -399,26 +481,39 @@ export class Runtime {
399
481
  return;
400
482
  }
401
483
  const current = this.active;
402
- const terminalMetadata = { received_at: new Date().toISOString(), ...(retainedMetadata ?? this.receivedTerminalMetadata ?? (this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result.error
403
- && this.lastTerminalFailure.details.provider_session_id === (result.conversation_id || this.init?.conversation_id || null)
404
- && (current?.previousError === result.error || this.lastResult?.status === "ERROR" && this.lastResult.error === result.error) ? this.lastTerminalFailure.details
405
- : { observed_at: new Date().toISOString(), provider_session_id: result.conversation_id || this.init?.conversation_id || null, operation_id: current?.operationId ?? null,
406
- turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" })) };
484
+ if (current?.dispatched === false) {
485
+ 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" });
486
+ this.active = null; current.reject(error);
487
+ await this.journal("unexpected_terminal_before_dispatch", { result, operation_id: current.operationId });
488
+ await this.persist({ unclaimed_activity: true, active_tree: true });
489
+ return;
490
+ }
491
+ 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,
492
+ turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" }) };
407
493
  this.receivedTerminalMetadata = null;
408
494
  // The ordered stream returns one result per input. Model usage counters
409
495
  // are not request IDs: native command rejections can report zero turns.
410
496
  // prompt() drains earlier events before accepting another input.
411
- if (!current && this.lastResult) { await this.journal("stale_terminal_observed", { result }); return; }
497
+ if (!current && this.lastResult) {
498
+ await this.journal("stale_terminal_observed", { result });
499
+ if (!isDeepStrictEqual(result, this.lastResult)) await this.persist({ unclaimed_activity: true, active_tree: true });
500
+ return;
501
+ }
412
502
  this.lastResult = result; this.markTerminalActivity(result);
413
503
  const rootStop = this.sessionObservations.get(this.init?.conversation_id)?.stop;
414
504
  const pendingTerminal = current && result.status === "SUCCESS";
415
505
  if (current) { current.terminalResult = result; current.terminalMetadata = terminalMetadata; current.terminalPersisted = false; }
416
- if (pendingTerminal && rootStop?.fullyIdle === true) for (const [id, descendant] of this.descendants) if (descendant.status === "unknown") this.descendants.set(id, { ...descendant, status: "settled_by_root", settlement_evidence: "root_fully_idle_stop" });
506
+ 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" });
417
507
  this.state.terminal_observation_pending = true;
418
508
  try {
419
509
  await this.persist({ active_tree: true, last_result: result, last_terminal_metadata: terminalMetadata });
420
510
  if (current) current.terminalPersisted = true;
421
- if (providerEvent) await this.journal("provider_event", { event: providerEvent });
511
+ const terminalPosition = await this.journal("provider_event", { event: providerEvent ?? { event: "result", result } });
512
+ terminalMetadata.stream_terminal_position = terminalPosition;
513
+ if (current?.outboundPosition && current.dispatchJournalSettled && !current.dispatchObservationError && this.config.daemonId && current.connectionId && current.operationId) terminalMetadata.request_proof = {
514
+ kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
515
+ turn_generation: current.generation, session_id: this.init.conversation_id, outbound_position: current.outboundPosition, terminal_position: terminalPosition
516
+ };
422
517
  await this.captureChildTools();
423
518
  } catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
424
519
  try { this.usageIngest = await forwardUsage(this.config, result.conversation_id ?? this.init.conversation_id, result, this.toolSnapshot(), [...this.descendants.values()]); }
@@ -426,12 +521,23 @@ export class Runtime {
426
521
  try { await this.persist({ terminal_observation_pending: false }); }
427
522
  catch (error) { this.state.terminal_observation_pending = true; return this.failTerminalObservation(current, result, terminalMetadata, error); }
428
523
  this.state.terminal_observation_pending = false;
524
+ this.queueNativeInspection();
429
525
  if (!current) return;
430
526
  if (this.active !== current) return;
431
527
  if (result.status === "ERROR" || result.status === "CANCELLED") {
528
+ await this.observeHookFailures();
529
+ if (this.active !== current) return;
432
530
  this.active = null;
433
531
  const settled = this.receipt().settled;
434
- this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, native_turn_items: { observed: settled, possible_effects: true, pending: !settled }, provider_error_scope: current.previousError && current.previousError === result.error ? "repeated_conversation_error" : "terminal_result", usage_ingest: this.usageIngest });
532
+ if (result.status === "ERROR" && settled && rootStop?.fullyIdle === true && !this.state.last_hook_rejection && current.operationId && this.paths.runtime) {
533
+ const root = this.init.conversation_id, transcript = path.join(this.paths.runtime, "brain", root, ".system_generated", "logs", "transcript.jsonl");
534
+ 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) => {
535
+ const boundary = recoveredAgyErrorBoundary(text, result.error, current.stepFloor);
536
+ const hook = current.nativeHookEvents?.get(boundary?.boundary_step);
537
+ if (boundary && hook) terminalMetadata.recovered_error_boundary = { ...boundary, receipt: { ...boundary.receipt, native_hook_event_key: hook, daemon_instance: this.config.daemonId }, source };
538
+ });
539
+ }
540
+ this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, native_turn_items: { observed: settled, possible_effects: true, pending: !settled }, provider_error_scope: "terminal_result", usage_ingest: this.usageIngest });
435
541
  try { await this.persist({ active_tree: !settled, last_terminal_failure: errorRecord(this.lastTerminalFailure) }); }
436
542
  catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
437
543
  return current.reject(this.lastTerminalFailure);
@@ -444,6 +550,7 @@ export class Runtime {
444
550
  const current = this.active;
445
551
  if (current?.observationError) throw current.observationError;
446
552
  if (current?.terminalResult?.status !== "SUCCESS" || current.terminalResult !== this.lastResult) return;
553
+ this.queueNativeInspection();
447
554
  await this.persist({ active_tree: !this.receipt().settled });
448
555
  // Hooks can invalidate the tree while persistence is pending. Never
449
556
  // publish a proof captured before that asynchronous boundary.
@@ -464,26 +571,127 @@ export class Runtime {
464
571
  if (current) { current.observationError = error; current.reject(error); }
465
572
  throw error;
466
573
  }
574
+ queueNativeInspection() {
575
+ const owner = this.active ?? this.observationEpisode, proof = owner?.terminalMetadata?.request_proof;
576
+ if (!owner?.clock || !proof || this.state.terminal_observation_pending || this.nativeFlight || this.nativeProof || this.exited) return;
577
+ 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; }
578
+ if (owner.clock.sample(this.progress)) { this.nativeInspection = { state: 'unproven', reason: 'observation_budget_exhausted' }; return; }
579
+ // The observer's raw wire result is never authority before P3 qualification.
580
+ if (!this.nativeObserver && !this.nativeQualification?.qualified) { this.nativeInspection = { state: 'unproven', reason: 'native_inspection_not_qualified' }; return; }
581
+ const revision = this.nativeRevision ?? 0, connection = this.connectionId;
582
+ const controller = this.nativeAbort = new AbortController();
583
+ let recording = false;
584
+ this.nativeInspection = { state: 'inspection_pending', reason: 'native_idle_barrier_pending' };
585
+ this.nativeFlight = Promise.resolve().then(async () => {
586
+ await owner.dispatchJournal;
587
+ const clock = owner.clock.state(), remainingMs = Math.max(0, clock.timeout_ms - clock.elapsed_ms);
588
+ if (!remainingMs || owner.clock.sample(this.progress)) throw new Error('observation_budget_exhausted');
589
+ const endpoint = this.nativeObserver ? null : await ownedAgyEndpoint(this.nativeLaunch);
590
+ 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 });
591
+ if (!this.nativeObserver && JSON.stringify(await ownedAgyEndpoint(this.nativeLaunch)) !== JSON.stringify(endpoint)) throw new Error('native_endpoint_changed');
592
+ 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);
593
+ if (!current()) return;
594
+ if (!observed.idle) { this.nativeInspection = { state: 'inspection_pending', reason: observed.reason ?? 'native_wait_pending' }; return; }
595
+ 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' };
596
+ recording = true;
597
+ await this.journal('native_tree_settlement', { proof: candidate });
598
+ if (!current()) return;
599
+ // Persist conservatively before publishing. A crash may require another
600
+ // read-only observation; no concurrent reader gets an uncommitted grant.
601
+ await this.persist({ active_tree: true });
602
+ if (!current()) return;
603
+ this.nativeProof = candidate;
604
+ this.state.native_execution_revision = revision;
605
+ this.state.unclaimed_activity = false;
606
+ this.projectNativeIdleChildren();
607
+ this.nativeInspection = { state: 'ready', reason: 'native_wait_fully_idle' };
608
+ this.state.active_tree = !this.receipt().settled;
609
+ await this.completePendingTurn();
610
+ }).catch(error => {
611
+ if (!controller.signal.aborted) {
612
+ this.nativeProof = null;
613
+ const transient = !recording && ['grpc_4', 'grpc_14'].includes(error.details?.reason) && !owner.clock.sample(this.progress);
614
+ this.nativeInspection = { state: transient ? 'inspection_pending' : 'unproven', reason: error.details?.reason ?? error.message ?? 'native_inspection_failed' };
615
+ if (recording && this.active === owner && owner.terminalResult) {
616
+ try { this.failTerminalObservation(owner, owner.terminalResult, owner.terminalMetadata, error); } catch { /* original prompt owns the typed failure */ }
617
+ }
618
+ if (recording) this.state.terminal_observation_pending = true;
619
+ }
620
+ })
621
+ .finally(() => { this.nativeFlight = null; });
622
+ }
623
+ projectNativeIdleChildren() {
624
+ 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' });
625
+ }
626
+ async reconcileRetainedAdmission() {
627
+ const retained = this.state.retained_tree_settlement;
628
+ const fresh = !this.lastResult;
629
+ if (this.active || this.receipt().settled || !fresh && (retained?.evidence !== 'verified_clean_daemon_shutdown' || retained.session_id !== this.init?.conversation_id)) return;
630
+ if (!this.nativeObserver && !this.nativeQualification?.qualified) return;
631
+ if (this.admissionFlight) return await this.admissionFlight;
632
+ const clock = this.admissionClock ??= new ObservationClock({ timeoutMs: SETUP_STARTUP_MS });
633
+ if (!clock || clock.sample()) throw new DaemonError('agy_native_inspection_unavailable', 'Retained AGY native admission budget exhausted');
634
+ const connection = this.connectionId, revision = this.nativeRevision ?? 0, generation = this.turnGeneration;
635
+ const controller = this.nativeAbort = new AbortController();
636
+ this.admissionFlight = (async () => {
637
+ const endpoint = this.nativeObserver ? null : await ownedAgyEndpoint(this.nativeLaunch);
638
+ 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 });
639
+ if (!this.nativeObserver && JSON.stringify(await ownedAgyEndpoint(this.nativeLaunch)) !== JSON.stringify(endpoint)) throw new DaemonError('agy_native_inspection_unavailable', 'AGY admission endpoint changed');
640
+ const current = () => !controller.signal.aborted && !this.exited && !this.active && !clock.sample() && this.connectionId === connection && this.turnGeneration === generation && (this.nativeRevision ?? 0) === revision;
641
+ if (!observed.idle || !current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY fresh native admission is unproven');
642
+ // Admission is not the old stream request: no invented operation/Turn or
643
+ // request proof. The old failed result remains historical diagnostics.
644
+ const candidate = { daemon_instance: this.config.daemonId, connection_id: connection, turn_generation: generation, revision, method: observed.method, source: 'native_idle_admission' };
645
+ await this.journal('native_idle_admission', { proof: candidate });
646
+ if (!current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY native admission changed during observation persistence');
647
+ await this.persist({ active_tree: true });
648
+ if (!current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY native admission changed during observation persistence');
649
+ this.nativeProof = candidate;
650
+ this.state.native_execution_revision = revision;
651
+ this.state.unclaimed_activity = false; this.projectNativeIdleChildren();
652
+ this.state.active_tree = !this.receipt().settled;
653
+ })().catch(error => { this.nativeProof = null; this.nativeInspection = { state: 'unproven', reason: error.details?.reason ?? error.code ?? 'native_admission_unproven' }; throw error; }).finally(() => { this.admissionFlight = null; });
654
+ return await this.admissionFlight;
655
+ }
656
+ episodeSnapshot() {
657
+ const owner = this.observationEpisode;
658
+ return owner?.clock ? { operation_id: owner.operationId, turn_generation: owner.generation, connection_id: owner.connectionId, clock: owner.clock.state() } : null;
659
+ }
467
660
  receipt(result = this.lastResult) {
468
661
  if (this.state.last_terminal_failure && !this.lastTerminalFailure) this.lastTerminalFailure = this.state.last_terminal_failure;
469
- const terminalMetadata = this.active?.terminalResult === result ? this.active.terminalMetadata : this.state.last_result === result ? this.state.last_terminal_metadata : this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result?.error
662
+ 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
470
663
  && this.lastTerminalFailure.details.provider_session_id === (result?.conversation_id || this.init?.conversation_id || null) ? this.lastTerminalFailure.details : null;
664
+ const terminalMetadata = metadata?.request_proof && (metadata.request_proof.connection_id !== this.connectionId || metadata.request_proof.daemon_instance !== this.config.daemonId)
665
+ ? { ...metadata, request_proof: undefined } : metadata;
471
666
  const assistantText = typeof result?.response === "string" ? result.response : typeof result?.text === "string" ? result.text : null;
472
667
  const root = this.init?.conversation_id; const observation = this.sessionObservations.get(root);
473
668
  const treeSettled = [...this.descendants.values()].every(child => child.tree_settled === true || ["completed", "failed", "cancelled", "settled_by_root"].includes(child.status));
474
- // A terminal root result is the provider's authoritative end-of-turn
475
- // observation when AGY does not emit a root Stop hook. A received
476
- // non-idle Stop still wins, and every discovered child must be terminal.
477
- const rootSettled = observation?.stop?.fullyIdle !== false;
669
+ // SUCCESS can coexist with background tasks. Only a current native idle
670
+ // proof (or verified prior clean retirement before new activity) closes it.
671
+ const retained = this.state.retained_tree_settlement;
672
+ const rootSettled = this.nativeProof?.turn_generation === this.turnGeneration && this.nativeProof.connection_id === this.connectionId && this.nativeProof.revision === (this.nativeRevision ?? 0)
673
+ || observation?.stop?.source_bound === true && observation.stop?.fullyIdle === true && observation.stop.generation === this.turnGeneration
674
+ || retained?.evidence === "verified_clean_daemon_shutdown" && retained.session_id === root && retained.turn_generation === this.turnGeneration && !this.child;
478
675
  const terminalPending = this.active?.terminalResult === result;
479
- const settled = Boolean(!this.state.terminal_observation_pending && !this.active?.observationError && !this.state.unclaimed_activity && (!this.active || terminalPending) && result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status) && treeSettled && rootSettled);
480
- return { harness: "antigravity-cli", runtime_family: "antigravity", provider_session_id: root ?? null, adapter_session_id: root ?? null, cwd: this.config.cwd, profile: this.profile, result, ...(terminalMetadata ? { terminal_metadata: terminalMetadata, ...(terminalMetadata.turn_id ? { terminal_turn_id: terminalMetadata.turn_id } : {}) } : {}), ...(assistantText === null ? {} : { assistant_text: assistantText }), usage: usageSnapshot(result ?? {}, this.toolSnapshot()), usage_ingest: this.usageIngest, descendants: [...this.descendants.values()], ...settledNativeTree(root, [...this.descendants.values()], settled, !this.state.unclaimed_activity && (!this.active || terminalPending) && rootSettled && ["SUCCESS", "ERROR", "CANCELLED"].includes(result?.status)), transcript_path: observation?.transcript_path ?? null, settled };
676
+ // A positively inspected owned scope before its first Turn has no model
677
+ // result to manufacture. Native admission alone proves this empty execution.
678
+ const emptyExecution = !result && this.turnGeneration === 0 && !this.active && !this.descendants.size && this.nativeProof?.source === 'native_idle_admission';
679
+ const executionTerminal = emptyExecution || result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status);
680
+ 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);
681
+ const clock = this.observationEpisode?.clock;
682
+ if (clock) clock.sample(this.progress);
683
+ const exhausted = clock && (clock.observationLost || clock.elapsed >= clock.timeoutMs);
684
+ 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() } : {}) };
685
+ 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 ?? [])] } });
481
686
  }
482
687
  async zeroUsage() { await forwardUsage(this.config, this.init.conversation_id, { usage: {} }, this.toolSnapshot(), []); }
483
688
  async prompt(text, assertDispatch = captureDispatchGuard(this), operationId = null, livenessTimeoutMs = 1_800_000) {
484
689
  validateObservationDuration(livenessTimeoutMs);
485
690
  if (!text) throw new DaemonError("prompt_required", "prompt is required");
486
691
  await this.start(); await this.draining;
692
+ 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);
693
+ 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 });
694
+ await this.reconcileRetainedAdmission();
487
695
  if (this.active || this.promptObservation) throw new DaemonError("operation_in_progress", "an Antigravity turn or its mandatory dispatch observation is already running");
488
696
  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 });
489
697
  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");
@@ -491,7 +699,11 @@ export class Runtime {
491
699
  const reply = new Promise((resolve, reject) => { resolveTurn = resolve; rejectTurn = reject; });
492
700
  // Cancellation can reject while admission persistence is still pending.
493
701
  void reply.catch(() => {});
494
- const current = { resolve: resolveTurn, reject: rejectTurn, generation: ++this.turnGeneration, operationId, previousError: this.lastResult?.error ?? null };
702
+ this.connectionId ??= randomUUID();
703
+ const current = { resolve: resolveTurn, reject: rejectTurn, generation: ++this.turnGeneration, operationId, connectionId: this.connectionId, dispatched: false };
704
+ this.nativeAbort?.abort(); this.nativeProof = null; this.nativeInspection = null;
705
+ current.clock = new ObservationClock({ timeoutMs: livenessTimeoutMs, onGap: gap => { void this.journal('observation_gap', { gap, operation_id: operationId }).catch(() => {}); } });
706
+ this.observationEpisode = current;
495
707
  let resolveDispatchJournal;
496
708
  const dispatchJournal = current.dispatchJournal = new Promise(resolve => { resolveDispatchJournal = resolve; });
497
709
  this.promptObservation = current;
@@ -504,6 +716,7 @@ export class Runtime {
504
716
  this.active = current;
505
717
  this.hookRequests = new Map();
506
718
  const root = this.init?.conversation_id, prior = this.sessionObservations.get(root) ?? {};
719
+ current.stepFloor = prior.last_step_index ?? -1;
507
720
  this.sessionObservations.set(root, { ...prior, step_floor: prior.last_step_index ?? null, stop: null, last_hook: null });
508
721
  this.lastProviderEvent = null; this.progressSteps = new Map(); this.progressHooks = new Set(); this.progress = undefined;
509
722
  try {
@@ -511,10 +724,11 @@ export class Runtime {
511
724
  assertDispatch();
512
725
  assertDaemonDispatch(this.paths?.dir);
513
726
  if (this.active !== current) throw new DaemonError("operation_cancelled", "prompt reservation was superseded");
727
+ current.outboundPosition = this.journalPosition = (this.journalPosition ?? 0) + 1;
514
728
  current.dispatched = true;
515
729
  this.child.stdin.write(`${JSON.stringify({ event: "user", message: { content: text } })}\n`, error => {
516
730
  if (error) { this.drainingError ??= nativeObservationError(error); current.dispatchObservationError = this.drainingError; current.reject(this.drainingError); finishDispatchJournal(); }
517
- else void Promise.resolve().then(() => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: this.init?.conversation_id, native_request_id: current.generation })).then(finishDispatchJournal, cause => {
731
+ 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 => {
518
732
  this.drainingError ??= nativeObservationError(cause); current.dispatchObservationError = this.drainingError;
519
733
  current.reject(this.drainingError); finishDispatchJournal();
520
734
  });
@@ -524,9 +738,10 @@ export class Runtime {
524
738
  else if (this.active === current) this.active = null;
525
739
  current.reject(error); finishDispatchJournal();
526
740
  }
527
- const clock = new ObservationClock({ timeoutMs: livenessTimeoutMs, onGap: gap => { void this.journal("observation_gap", { gap, operation_id: operationId }).catch(() => {}); } });
741
+ const clock = current.clock;
528
742
  let checking = false;
529
743
  const timer = setInterval(() => {
744
+ if (this.active === current && current.terminalResult && this.nativeInspection?.state === 'inspection_pending' && !this.nativeFlight) this.queueNativeInspection();
530
745
  if (this.active !== current || checking || !clock.sample(this.progress)) return;
531
746
  checking = true; const cursor = this.progress?.cursor;
532
747
  void this.completePendingTurn().then(() => {
@@ -552,24 +767,67 @@ export class Runtime {
552
767
  });
553
768
  }
554
769
  return result;
770
+ } catch (error) {
771
+ await dispatchJournal;
772
+ if (error.code === "native_outcome_observation_failed") throw error;
773
+ if (current.dispatchObservationError) {
774
+ if (!current.terminalResult) throw current.dispatchObservationError;
775
+ const cause = current.dispatchObservationError;
776
+ const failed = current.terminalResult.status === 'SUCCESS'
777
+ ? 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) })
778
+ : 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) });
779
+ failed.cause = cause.cause ?? cause;
780
+ current.observationError = failed; current.dispatchObservationError = failed;
781
+ this.drainingError = failed; this.state.terminal_observation_pending = true;
782
+ if (current.terminalResult.status !== 'SUCCESS') this.lastTerminalFailure = failed;
783
+ throw failed;
784
+ }
785
+ const metadata = current.terminalMetadata;
786
+ if (error === this.lastTerminalFailure && metadata && !metadata.request_proof && current.outboundPosition && metadata.stream_terminal_position && this.config.daemonId && current.operationId) {
787
+ metadata.request_proof = { kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
788
+ turn_generation: current.generation, session_id: this.init.conversation_id, outbound_position: current.outboundPosition, terminal_position: metadata.stream_terminal_position };
789
+ const proven = agyTerminalFailure(current.terminalResult, { ...error.details, ...metadata });
790
+ this.lastTerminalFailure = proven;
791
+ await this.persist({ last_terminal_metadata: metadata, last_terminal_failure: errorRecord(proven) });
792
+ throw proven;
793
+ }
794
+ throw error;
555
795
  } finally {
556
796
  clearInterval(timer); current.observerReturned = true;
557
797
  if (current.dispatchJournalSettled && this.promptObservation === current) this.promptObservation = null;
558
798
  }
559
799
  }
560
800
  childActivity(sessionId) {
801
+ this.invalidateSettlement(sessionId);
561
802
  if (sessionId === this.init?.conversation_id && !this.active) this.state.unclaimed_activity = true;
562
803
  const child = this.descendants.get(sessionId);
563
804
  if (!child) return;
564
805
  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 });
565
806
  }
807
+ invalidateSettlement(sessionId) {
808
+ this.nativeProof = null; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.nativeAbort?.abort();
809
+ this.state.native_execution_revision = this.nativeRevision;
810
+ if (this.init) this.state.active_tree = true;
811
+ this.state.retained_tree_settlement = null;
812
+ for (const id of new Set([this.init?.conversation_id, sessionId])) {
813
+ const observation = this.sessionObservations.get(id);
814
+ if (observation?.stop) this.sessionObservations.set(id, { ...observation, stop: null });
815
+ }
816
+ }
566
817
  async childForHook(conversationId) {
567
818
  if (conversationId === this.init?.conversation_id) return null;
819
+ await this.captureChildTools();
568
820
  const deadline = Date.now() + 1_000;
569
- while (!this.descendants.has(conversationId) && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 10));
821
+ while (!this.descendants.has(conversationId) && Date.now() < deadline) { await new Promise(resolve => setTimeout(resolve, 25)); await this.captureChildTools(); }
570
822
  const child = this.descendants.get(conversationId);
571
823
  if (!child) throw new DaemonError("agy_child_identity_unconfirmed", "Antigravity hook has no confirmed native parent", true, { conversation_id: conversationId });
572
- if (child.parent_provider_session_id !== this.init?.conversation_id) throw new DaemonError("agy_child_parent_unqualified", "Nested Antigravity worker is not a direct child of the controlled Session", false, { conversation_id: conversationId, parent_conversation_id: child.parent_provider_session_id });
824
+ const seen = new Set([conversationId]); let current = child;
825
+ while (current.parent_provider_session_id !== this.init?.conversation_id) {
826
+ const parent = this.descendants.get(current.parent_provider_session_id);
827
+ 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 });
828
+ seen.add(parent.provider_session_id); current = parent;
829
+ }
830
+ if (this.state.native_topology_error) throw new DaemonError("agy_child_parent_unqualified", "Antigravity native ancestry is contradictory");
573
831
  return child;
574
832
  }
575
833
  async rejectHook(error, event, payload, current = this.active) {
@@ -603,10 +861,45 @@ export class Runtime {
603
861
  await this.rejectHook(Object.assign(new Error(failure.message ?? "Native hook failed"), { code: failure.code, details: failure.details }), binding.event, binding.payload, binding.current);
604
862
  }
605
863
  }
606
- async observeHook(event, payload, budget = null) { const conversationId = payload.conversationId ?? this.init?.conversation_id; const child = await this.childForHook(conversationId); budget?.remaining("identity"); const prior = this.sessionObservations.get(conversationId) ?? {}; const execution = Number.isInteger(payload.executionNum) ? payload.executionNum : null; const step = Number.isInteger(payload.stepIdx) ? payload.stepIdx : null; const floor = prior.step_floor; const stale = step !== null && ((Number.isInteger(floor) && step <= floor) || (child && Number.isInteger(prior.last_step_index) && step < prior.last_step_index)); if (stale) { await this.journal("stale_hook", { hook_event: event, conversation_id: conversationId, step_index: step }); return { stale: true }; } const fingerprint = createHash("sha256").update(JSON.stringify({ event, payload })).digest("hex"); this.progressHooks ??= new Set(); const scoped = this.active && (!child || child.activity_generation === this.active.generation); const progressKey = JSON.stringify([conversationId, step, execution, event, payload.toolCall ?? null, payload.fullyIdle ?? null]); if (scoped && step !== null && (["PreToolUse", "PostToolUse"].includes(event) || event === "Stop" && payload.fullyIdle === true) && !this.progressHooks.has(progressKey) && this.progressHooks.size < 1_024) { this.progressHooks.add(progressKey); this.markActivity(); } if (conversationId && !stale) this.sessionObservations.set(conversationId, { ...prior, ...(step !== null ? { last_step_index: Math.max(prior.last_step_index ?? -1, step) } : {}), last_hook: fingerprint, ...(event === "PreToolUse" ? { last_pre_hook: fingerprint } : {}), ...(payload.transcriptPath ? { transcript_path: payload.transcriptPath } : {}), ...(event === "Stop" ? { stop: { fullyIdle: payload.fullyIdle === true, terminationReason: payload.terminationReason ?? null, executionNum: payload.executionNum ?? null } } : {}) }); if (!stale && event === "PreToolUse" && prior.last_pre_hook !== fingerprint) this.childActivity(conversationId); if (child && !stale && event === "Stop" && payload.fullyIdle === true) this.descendants.set(conversationId, { ...this.descendants.get(conversationId), tree_settled: true, settlement_evidence: "child_fully_idle_stop" }); const terminalPending = this.active?.terminalResult === this.lastResult; if (!child && !stale && event === "Stop" && payload.fullyIdle === true && (!this.active || terminalPending) && ["SUCCESS", "ERROR", "CANCELLED"].includes(this.lastResult?.status)) for (const [id, descendant] of this.descendants) if (descendant.status === "unknown") this.descendants.set(id, { ...descendant, status: "settled_by_root", settlement_evidence: "root_fully_idle_stop" }); const parent = child?.parent_provider_session_id ?? null;
864
+ async observeHook(event, payload, budget = null) {
865
+ const conversationId = payload.conversationId ?? this.init?.conversation_id;
866
+ const child = await this.childForHook(conversationId); budget?.remaining("identity");
867
+ const prior = this.sessionObservations.get(conversationId) ?? {};
868
+ const execution = Number.isInteger(payload.executionNum) ? payload.executionNum : null;
869
+ const step = Number.isInteger(payload.stepIdx) ? payload.stepIdx : null;
870
+ const stale = step !== null && (Number.isInteger(prior.step_floor) && step <= prior.step_floor
871
+ || Number.isInteger(prior.last_step_index) && step < prior.last_step_index
872
+ || step === prior.last_step_index && execution !== null && Number.isInteger(prior.last_execution_num) && execution < prior.last_execution_num);
873
+ if (stale) { await this.journal("stale_hook", { hook_event: event, conversation_id: conversationId, step_index: step }); return { stale: true }; }
874
+ const fingerprint = createHash("sha256").update(JSON.stringify({ event, payload })).digest("hex");
875
+ if (event === "Stop" && prior.last_stop_hook === fingerprint && prior.last_stop_generation === this.turnGeneration) return { stale: true };
876
+ this.progressHooks ??= new Set();
877
+ const scoped = this.active && (!child || child.activity_generation === this.active.generation);
878
+ const sourceBound = Boolean(scoped && step !== null && step > (this.active.stepFloor ?? -1)
879
+ && this.progressSteps?.get(conversationId)?.index === step);
880
+ const progressKey = JSON.stringify([conversationId, step, execution, event, payload.toolCall ?? null, payload.fullyIdle ?? null]);
881
+ 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(); }
882
+ if (conversationId) this.sessionObservations.set(conversationId, { ...prior,
883
+ ...(step !== null ? { last_step_index: step, last_execution_num: execution ?? (step === prior.last_step_index ? prior.last_execution_num : null) } : {}),
884
+ last_hook: fingerprint, ...(event === "PreToolUse" ? { last_pre_hook: fingerprint } : {}),
885
+ ...(payload.transcriptPath ? { transcript_path: payload.transcriptPath } : {}),
886
+ ...(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 } } : {}) });
887
+ if (event === "PreToolUse" && prior.last_pre_hook !== fingerprint) this.childActivity(conversationId);
888
+ 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" });
889
+ const terminalPending = this.active?.terminalResult === this.lastResult;
890
+ 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" });
891
+ const parent = child?.parent_provider_session_id ?? null;
892
+ if (event === "Stop" && !stale) {
893
+ const observation = this.sessionObservations.get(conversationId);
894
+ this.sessionObservations.set(conversationId, { ...observation, stop: { ...observation.stop, generation: this.turnGeneration } });
895
+ }
607
896
  const observed = payload.model ? observedProfile(payload) : {};
608
897
  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" });
609
- const eventId = createHash("sha256").update(JSON.stringify({ daemon: this.config.daemonId, event, conversationId, fully_idle: payload.fullyIdle ?? null, termination_reason: payload.terminationReason ?? null, execution: payload.executionNum ?? null, step: payload.stepIdx ?? null, tool: payload.toolCall?.name ?? null, args: payload.toolCall?.args ?? null })).digest("hex"); await this.persist(); await this.journal("hook", { stale, hook_event: event, event_id: eventId, conversation_id: conversationId, turn_generation: this.turnGeneration, execution_num: execution, step_index: step, termination_reason: payload.terminationReason ?? null, fully_idle: payload.fullyIdle ?? null, persistence_timing: this.persistTiming ?? null }); await this.completePendingTurn(); return { event_id: eventId, daemon_id: this.config.daemonId, launch_cwd: this.config.cwd, parent_conversation_id: parent, profile: observed, requested_profile: { provider: this.config.provider, model: this.config.model, reasoning: this.config.reasoning, mode: this.config.mode }, project_root: this.config.projectRoot, dd_flow_bin: this.config.ddFlowBin, dd_flow_home: this.config.ddFlowHome };
898
+ 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");
899
+ 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 });
900
+ if (!stale && !child && event === "PreToolUse" && Number.isInteger(step) && this.active) { this.active.nativeHookEvents ??= new Map(); this.active.nativeHookEvents.set(step, `${eventId}:before`); }
901
+ this.queueNativeInspection();
902
+ 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 };
610
903
  }
611
904
  async finalizeProviderForControl() {
612
905
  const pending = this.providerFinalization;
@@ -633,19 +926,40 @@ export class Runtime {
633
926
  }
634
927
  async prepareStop(cancelTree) {
635
928
  const current = this.active, priorResult = this.lastResult;
636
- const emptyTree = !current && !this.state.active_tree && !this.state.unclaimed_activity && !priorResult && !this.descendants.size;
637
- if (cancelTree) await this.cancel();
929
+ if (!cancelTree && !current && !priorResult) await this.reconcileRetainedAdmission();
930
+ if (cancelTree && (this.active || !this.receipt().settled)) await this.cancel();
638
931
  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");
639
932
  // A physical kill is settlement, not a native clean terminal receipt.
640
933
  // In particular, the previous Turn's SUCCESS cannot certify a cancelled
641
934
  // in-flight Turn that exited without its own result.
642
935
  const currentResult = !current || current.terminalResult === this.lastResult || this.lastResult !== priorResult;
643
- const clean = emptyTree || (currentResult && this.receipt().settled);
644
- return { clean, ...(clean ? {} : { native_settlement: { confirmed: false, reason: "native_terminal_unconfirmed" } }) };
936
+ if (currentResult && this.receipt().settled) await this.retireNativeServices();
937
+ const clean = currentResult && this.receipt().settled;
938
+ return { ...this.receipt(), clean, settled: Boolean(clean), native_settled: Boolean(clean), ...(clean ? {} : { native_settlement: { confirmed: false, reason: "native_terminal_unconfirmed" } }) };
939
+ }
940
+ async retireNativeServices() {
941
+ const services = [...(this.nativeResources?.values() ?? [])].filter(item => item.classification === 'support_service' && item.state !== 'stopped');
942
+ // Unknown classification/unsupported binaries keep the existing cleanup
943
+ // blocker. A fixture PID printed to stdout is never a production mapping.
944
+ if (!services.length || !this.nativeQualification?.qualified) return;
945
+ const revision = this.nativeRevision ?? 0;
946
+ const endpoint = this.nativeStopper ? null : await ownedAgyEndpoint(this.nativeLaunch);
947
+ const input = { endpoint, sessionId: this.init.conversation_id, cwd: this.config.cwd,
948
+ csrfToken: this.nativeCsrfToken, remainingMs: 10_000, rootRelation: this.nativeQualification.rootRelation };
949
+ const stopped = await (this.nativeStopper ?? stopAgyNativeTree)(input, [...this.descendants.keys()]);
950
+ const observed = await (this.nativeObserver ?? inspectAgyNative)(input);
951
+ if (!this.nativeStopper && !isDeepStrictEqual(await ownedAgyEndpoint(this.nativeLaunch), endpoint)
952
+ || !observed.idle || revision !== (this.nativeRevision ?? 0) || !this.receipt().settled)
953
+ throw new DaemonError('native_resources_unproven', 'AGY native service retirement lost its owner/execution fence');
954
+ // This is native handle retirement, not physical cleanup. Shared shutdown
955
+ // still re-observes the retained local PID/birth inventory after close.
956
+ for (const service of services) this.nativeResources.set(service.resource_id, { ...service, state: 'stopped',
957
+ native_retirement: { method: stopped.method, stopped_session_ids: stopped.stopped_session_ids, revision } });
958
+ await this.persist({ native_resources_entries: [...this.nativeResources.values()] });
645
959
  }
646
- async cancel() { cancelPendingDispatch(this); if (!this.child) return { ...this.receipt(), settled: Boolean(this.exited) }; if (!this.exited) { try { process.kill(-this.child.pid, "SIGINT"); } catch (error) { if (error.code !== "ESRCH") throw error; } const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) await stopProcessGroup(this.child, 5_000); } await this.finalizeProviderForControl(); await this.persist({ active_tree: false }); return { ...this.receipt(), cancelled: true, settled: Boolean(this.exited) };
960
+ 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 };
647
961
  }
648
- async close(cancelTree) { if (cancelTree) cancelPendingDispatch(this); if (!cancelTree && !this.exited && (this.active || this.state.active_tree || this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled))) throw new DaemonError("tree_not_settled", "Antigravity root or child tree is still running"); if (cancelTree) await this.cancel(); else if (this.child && !this.exited) this.child.stdin.end(); const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) await this.cancel(); await this.finalizeProviderForControl(); }
962
+ 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(); }
649
963
  async abortProvider(reason) {
650
964
  const child = this.child, providerProcess = this.providerProcess;
651
965
  await stopProcessGroup(child);
@@ -700,7 +1014,7 @@ async function dispatch(runtime, request) {
700
1014
  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() }; }
701
1015
  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(); }
702
1016
  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); }
703
- if (request.operation === "session.inspect") { runtime.requireSessionIdentity(params.sessionId); await runtime.captureChildTools(); return runtime.receipt(); }
1017
+ 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(); }
704
1018
  if (request.operation === "session.resume") { await runtime.start(params.sessionId ?? runtime.init?.conversation_id ?? null); runtime.requireSessionIdentity(params.sessionId); return runtime.receipt(); }
705
1019
  if (request.operation === "session.cancel") { runtime.requireSessionIdentity(params.sessionId); return await runtime.cancel(); }
706
1020
  if (request.operation === "hook.observe") {