@deksden-com/dd-flow-cli 0.9.0-beta.151 → 0.9.0-beta.155

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 (227) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +273 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +170 -25
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +40 -19
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +77 -10
  80. package/dist/services/check-runner-proof.js +69 -1
  81. package/dist/services/cli-operation-classifier.js +6 -1
  82. package/dist/services/code-checks.js +56 -23
  83. package/dist/services/codex-hook-delivery.js +11 -2
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +9 -4
  86. package/dist/services/controller-memory-worker.js +6 -4
  87. package/dist/services/controller-result-authority.js +122 -0
  88. package/dist/services/dashboard.js +4 -2
  89. package/dist/services/delivery-access.js +123 -0
  90. package/dist/services/delivery-approval.js +68 -0
  91. package/dist/services/delivery-artifact.js +86 -0
  92. package/dist/services/delivery-checks.js +178 -0
  93. package/dist/services/delivery-execution.js +181 -0
  94. package/dist/services/delivery-intents.js +212 -0
  95. package/dist/services/delivery-operation.js +81 -0
  96. package/dist/services/delivery-policy.js +91 -0
  97. package/dist/services/delivery-procedures.js +43 -0
  98. package/dist/services/delivery-request.js +99 -0
  99. package/dist/services/delivery-result.js +144 -0
  100. package/dist/services/delivery-stage-commands.js +26 -0
  101. package/dist/services/delivery-stage-entry.js +56 -0
  102. package/dist/services/delivery-stage-handlers.js +135 -0
  103. package/dist/services/delivery-stage-results.js +94 -0
  104. package/dist/services/descriptor-coordinator-session.js +3 -1
  105. package/dist/services/descriptor-run-start.js +24 -4
  106. package/dist/services/descriptor-stage-coordinator.js +78 -10
  107. package/dist/services/descriptor-stage-settlement.js +59 -14
  108. package/dist/services/diagnostic-repair-intake.js +123 -0
  109. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  110. package/dist/services/diagnostic-report.js +30 -0
  111. package/dist/services/diagnostic-run-bundle.js +48 -9
  112. package/dist/services/diagnostic-snapshot.js +2 -2
  113. package/dist/services/diagnostic-stage-handlers.js +3 -2
  114. package/dist/services/diagnostic-stage-results.js +26 -0
  115. package/dist/services/diagnostic-start.js +56 -6
  116. package/dist/services/diagnostic-work-collection.js +3 -3
  117. package/dist/services/distill-document-publication.js +280 -0
  118. package/dist/services/distill-promotion.js +197 -0
  119. package/dist/services/distill-publication-custody.js +162 -0
  120. package/dist/services/distill-stage-handlers.js +249 -0
  121. package/dist/services/distill-stage-results.js +105 -0
  122. package/dist/services/distill-user-inputs.js +4 -0
  123. package/dist/services/distill-work-batch.js +42 -0
  124. package/dist/services/distill-work-collection.js +74 -0
  125. package/dist/services/distill-work-registration.js +93 -0
  126. package/dist/services/distill-work-results.js +98 -0
  127. package/dist/services/eval-snapshots.js +17 -7
  128. package/dist/services/external-work-launch.js +8 -2
  129. package/dist/services/flow-catalog.js +24 -0
  130. package/dist/services/frozen-engine.js +19 -2
  131. package/dist/services/generic-execution.js +4 -3
  132. package/dist/services/harness-adapter.js +2 -14
  133. package/dist/services/hook-memory-routing.js +1 -1
  134. package/dist/services/hooks.js +31 -6
  135. package/dist/services/hybrid-contracts.js +6 -1
  136. package/dist/services/installed-flow-source.js +37 -2
  137. package/dist/services/interactive-attached-session.js +215 -0
  138. package/dist/services/interactive-baseline-custody.js +54 -0
  139. package/dist/services/interactive-before-versions.js +148 -0
  140. package/dist/services/interactive-bundle-data.js +85 -0
  141. package/dist/services/interactive-checks.js +179 -0
  142. package/dist/services/interactive-execution.js +33 -0
  143. package/dist/services/interactive-finish-integration.js +236 -0
  144. package/dist/services/interactive-git-baseline.js +230 -0
  145. package/dist/services/interactive-git-dispatch.js +278 -0
  146. package/dist/services/interactive-input-delivery.js +82 -0
  147. package/dist/services/interactive-mutation-scope.js +231 -0
  148. package/dist/services/interactive-request.js +287 -0
  149. package/dist/services/interactive-run-bundle.js +109 -0
  150. package/dist/services/interactive-stage-entry.js +41 -0
  151. package/dist/services/interactive-stage-handlers.js +289 -0
  152. package/dist/services/interactive-stage-observation.js +136 -0
  153. package/dist/services/interactive-stage-results.js +111 -0
  154. package/dist/services/interactive-user-inputs.js +142 -0
  155. package/dist/services/interactive-work-batch.js +43 -0
  156. package/dist/services/interactive-work-collection.js +119 -0
  157. package/dist/services/interactive-work-completion.js +180 -0
  158. package/dist/services/interactive-work-context.js +249 -0
  159. package/dist/services/interactive-work-history.js +112 -0
  160. package/dist/services/interactive-work-registration.js +124 -0
  161. package/dist/services/interactive-work-results.js +115 -0
  162. package/dist/services/interactive-work-start.js +155 -0
  163. package/dist/services/lifecycle-invocations.js +214 -24
  164. package/dist/services/managed-daemon-binding.js +52 -1
  165. package/dist/services/memory-compose-handler.js +2 -22
  166. package/dist/services/memory-finish-integration.js +58 -17
  167. package/dist/services/memory-fix-finish-integration.js +129 -0
  168. package/dist/services/memory-fix-git-dispatch.js +139 -0
  169. package/dist/services/memory-fix-intake-custody.js +11 -0
  170. package/dist/services/memory-fix-intake-settlement.js +127 -0
  171. package/dist/services/memory-fix-publication.js +196 -0
  172. package/dist/services/memory-fix-repair-admission.js +116 -0
  173. package/dist/services/memory-fix-request.js +141 -0
  174. package/dist/services/memory-fix-run-bundle.js +83 -0
  175. package/dist/services/memory-fix-scope.js +78 -0
  176. package/dist/services/memory-fix-sources.js +85 -0
  177. package/dist/services/memory-fix-stage-entry.js +24 -0
  178. package/dist/services/memory-fix-stage-handlers.js +282 -0
  179. package/dist/services/memory-fix-stage-results.js +106 -0
  180. package/dist/services/memory-fix-workspace.js +127 -0
  181. package/dist/services/memory-flow-user-inputs.js +88 -0
  182. package/dist/services/memory-init-contexts.js +55 -5
  183. package/dist/services/memory-init-controller-start.js +20 -4
  184. package/dist/services/memory-layer.js +7 -3
  185. package/dist/services/memory-native-review-copy.js +27 -3
  186. package/dist/services/memory-repair-document-delta.js +1 -0
  187. package/dist/services/project-flow-source.js +195 -0
  188. package/dist/services/project-git-integration.js +92 -0
  189. package/dist/services/repair-intents.js +2 -4
  190. package/dist/services/retained-native-hook.js +245 -0
  191. package/dist/services/review-fix-handoff.js +82 -0
  192. package/dist/services/review-fix-intake-custody.js +17 -0
  193. package/dist/services/review-fix-stage-entry.js +43 -0
  194. package/dist/services/review-fix-stage-handlers.js +176 -0
  195. package/dist/services/review-fix-stage-results.js +86 -0
  196. package/dist/services/run-control-worker.js +42 -1
  197. package/dist/services/run-control.js +40 -5
  198. package/dist/services/run-controller-adapter.js +37 -3
  199. package/dist/services/run-controller-process.js +12 -4
  200. package/dist/services/run-controller-recovery.js +46 -6
  201. package/dist/services/run-controller-state.js +3 -2
  202. package/dist/services/run-controller.js +209 -43
  203. package/dist/services/run-recovery.js +31 -2
  204. package/dist/services/run-timeline.js +59 -0
  205. package/dist/services/runs.js +18 -31
  206. package/dist/services/sessions.js +2 -1
  207. package/dist/services/stage-handlers.js +55 -3
  208. package/dist/services/stage-lifecycle.js +4 -3
  209. package/dist/services/stage-pause.js +26 -9
  210. package/dist/services/stage-report-renderer.js +2 -6
  211. package/dist/services/technical-notes.js +31 -0
  212. package/dist/services/verification-matrix.js +1 -1
  213. package/dist/services/version-status.js +1 -1
  214. package/dist/services/vnext-code-review.js +74 -5
  215. package/dist/services/vnext-code.js +9 -32
  216. package/dist/services/vnext-fanout.js +53 -8
  217. package/dist/services/vnext-merge.js +10 -5
  218. package/dist/services/vnext-plan-review.js +1 -1
  219. package/dist/services/vnext-plan.js +1 -1
  220. package/dist/services/vnext-protocolize.js +1 -1
  221. package/dist/services/vnext-specify.js +10 -5
  222. package/dist/services/work-registry.js +252 -66
  223. package/dist/services/worktrees.js +7 -4
  224. package/dist/storage/database.js +2 -1
  225. package/dist/verification-components.json +82 -0
  226. package/package.json +4 -3
  227. package/tools/verification-jobs.mjs +342 -0
@@ -5,10 +5,10 @@ import { canonicalNativeOperation } from './adapter-timeouts.mjs';
5
5
  import { admitNativeRequestBuffer } from "./native-streams.mjs";
6
6
  import { prepareDaemonRequest } from "./native-request-proof.mjs";
7
7
  import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
8
- import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
8
+ import { errorRecord, isNativeHookObservationLoss, isProvenShellCorrection, reportedError } from "./operation-errors.mjs";
9
9
  import { ToolObservations, acpToolObservation } from "./tool-observations.mjs";
10
- import { acceptNativeHookRequest, hookBudget, readNativeHookFailures } from "./native-hook-command.mjs";
11
- import { parseLifecycleCommand, continuationCommand } from "./lifecycle-command.mjs";
10
+ import { acceptNativeHookRequest, readNativeHookFailures, writeNativeHookFailure } from "./native-hook-command.mjs";
11
+ import { parseLifecycleCommand, continuationCommand, commandOption } from "./lifecycle-command.mjs";
12
12
  import { ObservationClock } from "./observation-clock.mjs";
13
13
  import { createHash, randomUUID } from "node:crypto";
14
14
  import { chmod, copyFile, cp, lstat, mkdir, open, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
@@ -43,7 +43,8 @@ const errorPayload = errorRecord;
43
43
  function shellQuote(value) { return `'${String(value).replace(/'/g, "'\\''")}'`; }
44
44
 
45
45
  function executable(command, args = []) { const resolved = command ?? process.env.DD_GROK_BIN ?? "grok"; return /\.[cm]?js$/.test(resolved) ? { command: process.execPath, args: [resolved, ...args] } : { command: resolved, args }; }
46
- export function isolatedEnv(config) { return { ...process.env, HOME: config.grokHome, GROK_HOME: config.grokHome, ...(config.authPath ? { GROK_AUTH_PATH: config.authPath } : {}), ...managedFlowEnv(config) }; }
46
+ export function isolatedEnv(config) { return { ...process.env, HOME: config.grokHome, GROK_HOME: config.grokHome, ...(config.authPath ? { GROK_AUTH_PATH: config.authPath } : {}), ...managedFlowEnv(config),
47
+ DD_GROK_STATE_DIR: !config.noFlow && config.daemonId ? config.runtime_owner?.state_dir : undefined }; }
47
48
  function within(candidate, root) { const resolved = path.resolve(candidate); const parent = path.resolve(root); return resolved === parent || resolved.startsWith(`${parent}${path.sep}`); }
48
49
 
49
50
  async function writeIsolatedConfig(paths) {
@@ -185,7 +186,7 @@ export async function startDaemon(options) {
185
186
  export async function stopDaemon(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) }); }
186
187
 
187
188
  export class Runtime {
188
- constructor(paths, state, bridge, initialized) { this.paths = paths; this.state = state; this.bridge = bridge; this.initialized = initialized; this.sessions = new Map((state.sessions ?? []).map((item) => [item.provider_session_id, item])); this.descendants = new Map((state.descendants ?? []).map(child => [child.provider_session_id, child])); for (const session of this.sessions.values()) if (session.parent_provider_session_id && !this.descendants.has(session.provider_session_id)) this.descendants.set(session.provider_session_id, { provider_session_id: session.provider_session_id, parent_provider_session_id: session.parent_provider_session_id, status: "unknown", source: "legacy.daemon_state", outcome_error: { code: "native_child_outcome_unavailable", message: "Retained Grok child has no durable outcome receipt" } }); this.toolUsage = new Map(); this.loadedSessionId = null; this.active = null; this.server = null; this.ready = false; this.persisting = Promise.resolve(); }
189
+ constructor(paths, state, bridge, initialized) { this.paths = paths; this.state = state; this.bridge = bridge; this.initialized = initialized; this.sessions = new Map((state.sessions ?? []).map((item) => [item.provider_session_id, item])); this.descendants = new Map((state.descendants ?? []).map(child => [child.provider_session_id, child])); for (const session of this.sessions.values()) if (session.parent_provider_session_id && !this.descendants.has(session.provider_session_id)) this.descendants.set(session.provider_session_id, { provider_session_id: session.provider_session_id, parent_provider_session_id: session.parent_provider_session_id, status: "unknown", source: "legacy.daemon_state", outcome_error: { code: "native_child_outcome_unavailable", message: "Retained Grok child has no durable outcome receipt" } }); this.toolUsage = new Map(); this.promptCreators = new Map(); this.childCreators = new Map(); this.childSpawnWaiters = new Map(); this.loadedSessionId = null; this.active = null; this.server = null; this.ready = false; this.persisting = Promise.resolve(); }
189
190
  persist(patch = {}) { Object.assign(this.state, patch, { pid: process.pid, updated_at: new Date().toISOString(), sessions: [...this.sessions.values()], descendants: [...this.descendants.values()] }); const snapshot = structuredClone(this.state); this.persisting = this.persisting.catch(() => {}).then(() => writeState(this.paths.state, snapshot)); return this.persisting; }
190
191
  async reduceTerminal(outcome) {
191
192
  const session = this.sessions.get(outcome.session_id), turn = session?.native_prompt_operation;
@@ -234,7 +235,7 @@ export class Runtime {
234
235
  }
235
236
  options(params) { const sessionId = params.sessionId; const session = sessionId ? this.sessions.get(sessionId) : null; const root = session?.root_provider_session_id ?? sessionId; return { ...this.state.config, ...params, cwd: params.cwd ?? session?.cwd ?? this.state.config.cwd, initialized: this.initialized, daemonId: this.state.daemon_id, rootProviderSessionId: root, toolUsage: this.toolUsage, allowBackground: true, rootSettled: id => this.settledRoot(id), liveSession: params.liveSession ?? Boolean(sessionId && sessionId === this.loadedSessionId) }; }
236
237
  track(result, before = null) { if (!result?.provider_session_id) return; const session = this.sessions.get(result.provider_session_id); const root = result.parent_provider_session_id ? (this.sessions.get(result.parent_provider_session_id)?.root_provider_session_id ?? result.parent_provider_session_id) : (session?.root_provider_session_id ?? result.provider_session_id); this.sessions.set(result.provider_session_id, { ...session, native_root_receipt: session?.native_root_receipt ?? null, provider_session_id: result.provider_session_id, adapter_session_id: result.adapter_session_id ?? result.provider_session_id, parent_provider_session_id: result.parent_provider_session_id ?? session?.parent_provider_session_id ?? null, root_provider_session_id: root, cwd: result.cwd ?? result.target?.newCwd ?? result.info?.cwd ?? session?.cwd ?? null }); for (const child of this.running(result)) this.recordDescendant({ ...child, stale_activity: Boolean(before && before.get(child.sessionId ?? child.subagentId ?? child.provider_session_id) !== this.descendants.get(child.sessionId ?? child.subagentId ?? child.provider_session_id)) }, root, "x.ai/subagent/list_running", "running"); for (const child of result.descendants ?? []) this.recordDescendant(child, root, child.source ?? "x.ai/subagent/event", child.status ?? "unknown"); }
237
- recordDescendant(value, root, source, fallback) {
238
+ recordDescendant(value, root, source, fallback, nativeSpawn = false) {
238
239
  const id = value?.provider_session_id ?? value?.session_id ?? value?.sessionId ?? value?.childSessionId ?? value?.subagentId ?? value?.child_session_id ?? value?.subagent_id;
239
240
  const parent = value?.parent_provider_session_id ?? value?.parentSessionId ?? value?.parent_session_id ?? root;
240
241
  if (typeof id !== "string" || !id || typeof parent !== "string" || !parent) return;
@@ -273,6 +274,14 @@ export class Runtime {
273
274
  return;
274
275
  }
275
276
  if (!newAttempt && terminal(descendant?.status) && terminal(status) && descendant.status !== status) throw new DaemonError("native_child_outcome_conflict", "Grok child has contradictory terminal outcomes", false, { session_id: id, previous: descendant.status, observed: status });
277
+ if (nativeSpawn && spawned && typeof attempt === "string" && attempt && source === "x.ai/subagent/event") {
278
+ const promptId = value.parent_prompt_id;
279
+ const creator = typeof promptId === "string" && promptId ? this.promptCreators.get(JSON.stringify([parent, promptId])) : null;
280
+ const old = this.childCreators.get(id);
281
+ if (old && old.attempt === attempt && (old.parent !== parent || typeof promptId === "string" && promptId && old.promptId !== promptId)) throw new DaemonError("native_child_execution_ambiguous", "Grok child attempt has conflicting native creator", false, { session_id: id });
282
+ if (creator) this.childCreators.set(id, { parent, promptId, attempt, creator });
283
+ else if (newAttempt) this.childCreators.delete(id);
284
+ }
276
285
  this.sessions.set(id, {
277
286
  ...prior,
278
287
  provider_session_id: id,
@@ -296,6 +305,7 @@ export class Runtime {
296
305
  ...(newAttempt && descendant?.attempt_id ? { retired_attempt_ids: [...retired, descendant.attempt_id] } : retired.length ? { retired_attempt_ids: retired } : {}),
297
306
  ...(!newAttempt && !terminal(status) && descendant?.outcome_error ? { outcome_error: descendant.outcome_error } : {})
298
307
  });
308
+ if (this.childCreators.has(id)) for (const wake of this.childSpawnWaiters.get(id) ?? []) wake();
299
309
  if (spawned && typeof value.model === "string" && value.model.trim()) {
300
310
  return { sessionId: id, parentSessionId: parent, attemptId: attempt ?? null, parentPromptId: value.parent_prompt_id ?? null, model: value.model.trim() };
301
311
  }
@@ -307,7 +317,7 @@ export class Runtime {
307
317
  const modelFacts = [];
308
318
  if (/subagent/i.test(kind)) {
309
319
  for (const value of [update?.subagent, update?.subagentInfo, update?.child, update?.result, update]) {
310
- const fact = this.recordDescendant(value, root, "x.ai/subagent/event", /fail|error/i.test(kind) ? "failed" : /cancel/i.test(kind) ? "cancelled" : /complete|finish|done/i.test(kind) ? "completed" : "running");
320
+ const fact = this.recordDescendant(value, root, "x.ai/subagent/event", /fail|error/i.test(kind) ? "failed" : /cancel/i.test(kind) ? "cancelled" : /complete|finish|done/i.test(kind) ? "completed" : "running", value === update);
311
321
  if (fact) modelFacts.push(fact);
312
322
  }
313
323
  }
@@ -396,7 +406,7 @@ export class Runtime {
396
406
  assertDaemonDispatch(this.paths?.dir);
397
407
  if (this.active) throw new DaemonError("operation_busy", `${this.active} is already running`, true);
398
408
  this.active = name;
399
- const current = this.nativeHookOperation = { operationId, sessionId, generation: (this.hookGeneration = (this.hookGeneration ?? 0) + 1), requests: new Map() };
409
+ const current = this.nativeHookOperation = { operationId, sessionId, generation: (this.hookGeneration = (this.hookGeneration ?? 0) + 1), requests: new Map(), guard: captureDispatchGuard(this) };
400
410
  let rejectHook;
401
411
  const hookFailed = new Promise((_, reject) => { rejectHook = reject; });
402
412
  void hookFailed.catch(() => {});
@@ -406,15 +416,27 @@ export class Runtime {
406
416
  // and local archive export must not erase an unrelated Session's receipt.
407
417
  if (name === "session.prompt" && this.sessions.has(sessionId)) {
408
418
  const session = this.sessions.get(sessionId);
419
+ if (typeof operationId === "string" && operationId) {
420
+ current.nativePromptId = randomUUID();
421
+ // Retain only for this daemon lifetime: a late native spawn or hook
422
+ // cannot be safely distinguished from an expired one by a timer.
423
+ // After restart the in-memory correlation is deliberately unavailable.
424
+ this.promptCreators.set(JSON.stringify([sessionId, current.nativePromptId]), current);
425
+ }
409
426
  session.native_root_receipt = null;
410
427
  this.bridge.dispatchGeneration = current.generation;
411
- session.native_prompt_operation = { daemon_id: this.state.daemon_id, connection_id: this.bridge.connectionId, operation_id: operationId, generation: current.generation };
428
+ session.native_prompt_operation = { daemon_id: this.state.daemon_id, connection_id: this.bridge.connectionId, operation_id: operationId, generation: current.generation,
429
+ ...(current.nativePromptId ? { native_prompt_id: current.nativePromptId } : {}) };
412
430
  }
413
431
  try {
414
432
  await this.persist({ active_tree: true, active_operation: name });
415
433
  assertDaemonDispatch(this.paths?.dir);
416
434
  const before = new Map(this.descendants);
417
435
  const result = await Promise.race([task(), hookFailed]);
436
+ await this.observeHookFailures(current);
437
+ if (current.error) throw current.error;
438
+ const hookDiagnostics = (this.bridge.lifecycleDiagnostics ?? []).filter(item => item.operation_id === current.operationId && item.generation === current.generation);
439
+ if (hookDiagnostics.length) result.native_hook_diagnostics = [...new Map([...(result.native_hook_diagnostics ?? []), ...hookDiagnostics].map(item => [JSON.stringify(item), item])).values()];
418
440
  await this.drainTerminal();
419
441
  this.track(result, before);
420
442
  const session = this.sessions.get(result.provider_session_id);
@@ -429,41 +451,131 @@ export class Runtime {
429
451
  // root-turn proof, not proof that any descendants have stopped. A lost
430
452
  // response, bridge error, or failure before dispatch earns no receipt.
431
453
  try { await this.drainTerminal(); } catch (storage) { error.cleanup_error ??= errorRecord(storage); }
432
- try { await hookBudget(1_000, null).run(() => this.persist({ active_tree: true, active_operation: null }), "failure_persistence"); }
454
+ try { await this.persist({ active_tree: true, active_operation: null }); }
433
455
  catch (storage) { error.cleanup_error = errorRecord(storage); }
434
456
  throw error;
435
457
  }
436
- finally { clearInterval(timer); current.requests.clear(); if (this.nativeHookOperation === current) this.nativeHookOperation = null; this.active = null; }
458
+ finally { clearInterval(timer); if (!current.nativePromptId) current.requests.clear(); if (this.nativeHookOperation === current) this.nativeHookOperation = null; this.active = null; }
437
459
  }
438
460
  async observeHookFailures(current = this.nativeHookOperation) {
439
- if (!current || this.nativeHookOperation !== current || current.reading) return;
440
- current.reading = true;
441
- try { await readNativeHookFailures(this.paths.dir, current.requests, current, error => {
442
- if (!isProvenShellCorrection(error)) current.reject(error);
443
- }); } finally { current.reading = false; }
461
+ if (!current || this.nativeHookOperation !== current) return;
462
+ while (current.reading) {
463
+ await current.reading;
464
+ if (this.nativeHookOperation !== current) return;
465
+ }
466
+ const reading = readNativeHookFailures(this.paths.dir, current.requests, current, error => this.acceptHookFailure(current, error));
467
+ current.reading = reading;
468
+ try { await reading; } finally { if (current.reading === reading) current.reading = null; }
469
+ }
470
+ acceptHookFailure(current, error) {
471
+ if (this.nativeHookOperation !== current) return;
472
+ const unconfirmed = error.details?.native_hook_binding?.native_creator === "unconfirmed";
473
+ if (!unconfirmed && isProvenShellCorrection(error)) return;
474
+ if (unconfirmed || isNativeHookObservationLoss(error)) {
475
+ const diagnostic = { ...errorRecord(error), session_id: current.sessionId, operation_id: current.operationId,
476
+ generation: current.generation, delivery_stage: "hook_observation", completeness: "incomplete" };
477
+ this.bridge.lifecycleDiagnostics ??= [];
478
+ this.bridge.lifecycleDiagnostics.push(diagnostic);
479
+ try { void Promise.resolve(this.bridge.journal?.write("native_hook_observation_failure", diagnostic)).catch(() => {}); } catch { /* diagnostic is optional */ }
480
+ return;
481
+ }
482
+ current.reject(error);
444
483
  }
445
484
  async acceptHook(identity, sessionId, payload) {
446
485
  const input = payload?.tool_input ?? payload?.toolInput;
447
486
  const command = input?.command ?? input?.cmd;
448
487
  if ((payload?.hook_event_name ?? payload?.hookEventName) !== "PreToolUse" || typeof command !== "string" || (!["lifecycle", "auxiliary"].includes(parseLifecycleCommand(command).participation) && !continuationCommand(command))) return identity;
449
- const current = this.nativeHookOperation, tool = payload.tool_use_id ?? payload.toolUseId ?? payload.tool_call_id;
450
- const root = current?.sessionId ?? this.loadedSessionId;
488
+ const active = this.nativeHookOperation, tool = payload.tool_use_id ?? payload.toolUseId ?? payload.tool_call_id;
489
+ const root = identity.rootProviderSessionId;
490
+ const source = sessionId !== root ? this.childCreators.get(sessionId) : null;
491
+ // An older Grok may not surface creator metadata. Preserve its ordinary
492
+ // stateful handler, but never turn the current prompt into guessed proof.
493
+ if (sessionId !== root && !source) return identity;
494
+ const current = sessionId !== root ? source?.creator : active;
451
495
  const pending = [...(this.bridge.pending?.values() ?? [])].some(request => request.method === "session/prompt" && request.sessionId === root);
452
- if (!current || !pending || identity.rootProviderSessionId !== root || !tool) throw new DaemonError("native_hook_unproven", "Native hook has no exact active Session operation");
453
- const binding = await hookBudget(1_000, null).run(() => acceptNativeHookRequest(this.paths.dir, { daemon_id: this.state.daemon_id, operation_id: current.operationId, turn_generation: current.generation, provider_session_id: sessionId, root_provider_session_id: root, tool_call_id: tool, event: "PreToolUse", command_sha256: createHash("sha256").update(command).digest("hex"), native_cwd: payload.cwd ?? null }, current), "hook_binding");
454
- if (this.nativeHookOperation !== current) throw new DaemonError("native_hook_unproven", "Native hook operation changed during acceptance");
496
+ const nativePrompt = payload.prompt_id ?? payload.promptId;
497
+ if (!current || current.sessionId !== root || !tool || sessionId === root && (!pending || active !== current)
498
+ || sessionId === root && typeof nativePrompt === "string" && nativePrompt && nativePrompt !== current.nativePromptId
499
+ || sessionId !== root && (source.parent !== identity.parentProviderSessionId || this.promptCreators.get(JSON.stringify([source.parent, source.promptId])) !== current))
500
+ throw new DaemonError("native_hook_unproven", "Native hook has no exact source-bound Session operation");
501
+ current.guard(); assertDaemonDispatch(this.paths.dir);
502
+ const nativePayload = { ...payload, _meta: { ...(payload._meta ?? {}), ddGrok: { ...identity } } };
503
+ const binding = await acceptNativeHookRequest(this.paths.dir, { daemon_id: this.state.daemon_id, operation_id: current.operationId, turn_generation: current.generation,
504
+ provider_session_id: sessionId, root_provider_session_id: root, parent_provider_session_id: identity.parentProviderSessionId ?? null,
505
+ tool_call_id: tool, event: "PreToolUse", command_sha256: createHash("sha256").update(command).digest("hex"), native_cwd: payload.cwd ?? null,
506
+ native_creator: source || typeof nativePrompt === "string" && nativePrompt && nativePrompt === current.nativePromptId ? "confirmed" : "unconfirmed",
507
+ ...(source ? { source_harness: "grok-acp", native_command: command, native_payload: nativePayload } : {}) }, current);
508
+ current.guard();
509
+ if (sessionId === root && this.nativeHookOperation !== current || sessionId !== root && this.childCreators.get(sessionId) !== source)
510
+ throw new DaemonError("native_hook_unproven", "Native hook operation changed during acceptance");
455
511
  current.requests.set(binding.request_id, { current, binding });
456
- return { ...identity, nativeHookBinding: binding };
512
+ const parsed = parseLifecycleCommand(command);
513
+ const fastAck = Boolean(source && parsed.kind === "standalone" && parsed.invocation.operation === "work_start" && commandOption(parsed.invocation, "invocation-id"));
514
+ return { ...identity, nativeHookBinding: binding, ...(fastAck ? { fastAck: true } : {}) };
515
+ }
516
+ async waitForChildCreator(sessionId, deadlineAt) {
517
+ const remaining = deadlineAt - Date.now();
518
+ if (this.childCreators.has(sessionId) || !Number.isSafeInteger(deadlineAt) || remaining <= 0 || remaining > 30_000) return;
519
+ // Leave most of the existing host budget to the legacy handler if this
520
+ // provider omits the optional spawn correlation; do not poll the provider.
521
+ const waitMs = Math.floor(remaining / 3);
522
+ if (!waitMs) return;
523
+ await new Promise(resolve => {
524
+ const waiters = this.childSpawnWaiters.get(sessionId) ?? new Set();
525
+ const wake = () => { clearTimeout(timer); waiters.delete(wake); if (!waiters.size) this.childSpawnWaiters.delete(sessionId); resolve(); };
526
+ const timer = setTimeout(wake, waitMs);
527
+ waiters.add(wake); this.childSpawnWaiters.set(sessionId, waiters);
528
+ });
529
+ }
530
+ hookOwner(binding) {
531
+ if (!binding?.request_id) return null;
532
+ const owners = new Set([this.nativeHookOperation, ...this.promptCreators.values()]);
533
+ for (const owner of owners) {
534
+ if (!owner || owner.operationId !== binding.operation_id || owner.generation !== binding.turn_generation) continue;
535
+ const accepted = owner.requests.get(binding.request_id);
536
+ if (accepted?.current === owner && JSON.stringify(accepted.binding) === JSON.stringify(binding)) return owner;
537
+ }
538
+ return null;
457
539
  }
458
540
  async dispatch(operation, params, operationId = null) {
459
541
  const assertDispatch = captureDispatchGuard(this);
460
542
  if (["session.create", "session.prompt", "session.fork", "session.archive"].includes(operation)) assertDaemonDispatch(this.paths.dir);
461
543
  if (operation === "daemon.ready") return { ready: this.ready, daemon_id: this.state.daemon_id, pid: process.pid, socket: this.paths.socket, versions: this.state.versions, config_isolation: this.state.config_isolation, shutdown_state: this.state.shutdown_state, recovery_status: this.state.recovery_status, auth_status: this.state.auth_status, active_tree: this.state.active_tree, active_operation: this.active, sessions: [...this.sessions.values()], config: this.state.config };
462
544
  if (operation === "daemon.status") { let observationError; if (!this.active && !this.state.shutdown?.phases?.tree) try { await this.refreshTree(); } catch (error) { observationError = errorPayload(error); } return { ...await this.dispatch("daemon.ready", {}), observation_error: observationError ?? this.state.observation_error ?? null }; }
463
- if (operation === "hook.resolve") { const sessionId = String(params.sessionId ?? ""); if (!sessionId) throw new DaemonError("hook_identity_missing", "hook has no sessionId"); let session = this.sessions.get(sessionId); if (!session) { const roots = [...this.sessions.values()].filter((item) => !item.parent_provider_session_id); if (roots.length !== 1) throw new DaemonError("hook_identity_unknown", "unknown hook Session has no unique root"); const observed = await inspectSessionWithBridge(this.bridge, this.options({ sessionId, liveSession: true })); this.track(observed); session = this.sessions.get(sessionId); if (!session || session.root_provider_session_id !== roots[0].provider_session_id) throw new DaemonError("hook_identity_unknown", "hook Session is not a member of the retained native tree"); await this.persist(); } return await this.acceptHook({ daemonId: this.state.daemon_id, rootProviderSessionId: session.root_provider_session_id, parentProviderSessionId: session.parent_provider_session_id ?? null, observedProfile: this.bridge.modelProfiles?.get(sessionId) ?? {} }, sessionId, params.payload); }
464
- if (operation === "hook.failed") { await this.observeHookFailures(); return { observed: true }; }
545
+ if (operation === "hook.resolve") { const sessionId = String(params.sessionId ?? ""); if (!sessionId) throw new DaemonError("hook_identity_missing", "hook has no sessionId"); let session = this.sessions.get(sessionId); if ((!session || session.parent_provider_session_id) && !this.childCreators.has(sessionId) && this.promptCreators.size) { await this.waitForChildCreator(sessionId, params.hookDeadlineAt); session = this.sessions.get(sessionId); } if (!session) { const roots = [...this.sessions.values()].filter((item) => !item.parent_provider_session_id); if (roots.length !== 1) throw new DaemonError("hook_identity_unknown", "unknown hook Session has no unique root"); const observed = await inspectSessionWithBridge(this.bridge, this.options({ sessionId, liveSession: true })); this.track(observed); session = this.sessions.get(sessionId); if (!session || session.root_provider_session_id !== roots[0].provider_session_id) throw new DaemonError("hook_identity_unknown", "hook Session is not a member of the retained native tree"); await this.persist(); } return await this.acceptHook({ daemonId: this.state.daemon_id, rootProviderSessionId: session.root_provider_session_id, parentProviderSessionId: session.parent_provider_session_id ?? null, observedProfile: this.bridge.modelProfiles?.get(sessionId) ?? {} }, sessionId, params.payload); }
546
+ if (operation === "hook.verify") {
547
+ if (params.daemonId !== this.state.daemon_id) throw new DaemonError("native_hook_unproven", "Native hook daemon identity differs");
548
+ const owner = this.hookOwner(params.binding);
549
+ if (!owner) throw new DaemonError("native_hook_unproven", "Native hook has no matching retained operation");
550
+ owner.guard(); assertDaemonDispatch(this.paths.dir);
551
+ return { verified: true, nativeHookBinding: params.binding };
552
+ }
553
+ if (operation === "hook.failed") {
554
+ if (params.binding !== undefined || params.failure !== undefined) {
555
+ const binding = params.binding, failure = params.failure;
556
+ const current = this.hookOwner(binding);
557
+ while (current?.reading) await current.reading;
558
+ const accepted = current?.requests.get(binding?.request_id);
559
+ if (!current || this.hookOwner(binding) !== current || accepted?.current !== current || JSON.stringify(accepted.binding) !== JSON.stringify(binding)
560
+ || typeof failure?.code !== "string" || typeof failure?.message !== "string")
561
+ throw new DaemonError("native_hook_unproven", "Hook failure has no exact current owned delivery");
562
+ current.guard(); assertDaemonDispatch(this.paths.dir);
563
+ if (this.nativeHookOperation !== current) {
564
+ // The creator has returned. Retain the bound failure under its
565
+ // original operation, without rejecting the newer productive turn.
566
+ await writeNativeHookFailure(this.paths.dir, binding, reportedError(failure, "Native hook failed"));
567
+ return { observed: true, retained: true };
568
+ }
569
+ current.requests.delete(binding.request_id);
570
+ const error = reportedError(failure, "Native hook failed");
571
+ error.details = { ...error.details, native_hook_binding: binding };
572
+ this.acceptHookFailure(current, error);
573
+ return { observed: true };
574
+ }
575
+ await this.observeHookFailures(); return { observed: true };
576
+ }
465
577
  if (operation === "session.create") return await this.productive(operation, async () => { const result = await createSessionWithBridge(this.bridge, { ...this.options(params), onSessionCreated: async (session) => { this.track(session); this.loadedSessionId = session.provider_session_id; await this.persist(); } }, this.initialized); this.loadedSessionId = result.provider_session_id; return result; }, null, operationId);
466
- if (operation === "session.prompt") { await this.requireSettled(); return await this.productive(operation, async () => { const result = await promptSessionWithBridge(this.bridge, { ...this.options(params), assertDispatch, operationId }); this.loadedSessionId = params.sessionId; return result; }, params.sessionId, operationId); }
578
+ if (operation === "session.prompt") { await this.requireSettled(); return await this.productive(operation, async () => { const result = await promptSessionWithBridge(this.bridge, { ...this.options(params), assertDispatch, operationId, nativePromptId: this.nativeHookOperation?.nativePromptId }); this.loadedSessionId = params.sessionId; return result; }, params.sessionId, operationId); }
467
579
  if (operation === "session.fork") { await this.requireSettled(); return await this.productive(operation, async () => { const result = await forkSessionWithBridge(this.bridge, this.options(params)); this.loadedSessionId = params.sessionId; return result; }); }
468
580
  if (operation === "session.inspect" || operation === "session.resume") { const before = new Map(this.descendants); const result = await inspectSessionWithBridge(this.bridge, this.options({ ...params, liveSession: true })); this.track(result, before); await this.persist(); const settled = !this.active && !this.treeRunning(result); const descendants = this.topology(result.provider_session_id); const terminal = this.sessions.get(result.provider_session_id)?.native_root_receipt; return nativeExecutionReceipt({ ...result, settled, descendants, ...settledNativeTree(result.provider_session_id, descendants, settled, !this.active && !this.rootRunning(result)), ...(terminal?.kind === "failed" && terminal.request_id !== undefined ? { terminal_turn_id: String(terminal.request_id) } : {}) }, { daemonId: this.state.daemon_id, generation: this.controlGeneration ?? 0 }); }
469
581
  if (operation === "session.cancel") { if (!this.sessions.has(params.sessionId)) throw new DaemonError("session_identity_mismatch", "cancellation Session is not owned by this daemon"); cancelPendingDispatch(this); const result = await cancelSessionWithBridge(this.bridge, this.options({ ...params, liveSession: true })); await this.persist({ active_tree: Boolean(this.active) || !result.settled }); return result; }
@@ -153,12 +153,14 @@ export async function promptSessionWithBridge(bridge, options) {
153
153
  const local = { ...options, rootProviderSessionId: options.rootProviderSessionId ?? sessionId }; const initial_usage_error = await forwardUsageBestEffort(local, sessionId, await sessionUsage(bridge, sessionId), sessionToolCalls(local, bridge, sessionId));
154
154
  options.assertDispatch?.();
155
155
  bridge.dispatchOperationId = options.operationId;
156
- const cursor = bridge.toolCursor(); const textCursor = bridge.assistantTextCursor(sessionId); const turn = await bridge.request("session/prompt", { sessionId, prompt: [{ type: "text", text: prompt }] }, null, { idleSessionId: sessionId, idleTimeoutMs: options.livenessTimeoutMs ?? options.timeoutMs ?? 600_000 });
156
+ const diagnosticsCursor = bridge.lifecycleDiagnostics?.length ?? 0;
157
+ const cursor = bridge.toolCursor(); const textCursor = bridge.assistantTextCursor(sessionId); const turn = await bridge.request("session/prompt", { sessionId, prompt: [{ type: "text", text: prompt }], ...(options.nativePromptId ? { _meta: { promptId: options.nativePromptId } } : {}) }, null, { idleSessionId: sessionId, idleTimeoutMs: options.livenessTimeoutMs ?? options.timeoutMs ?? 600_000 });
157
158
  try {
158
159
  await bridge.flush();
159
160
  const evidence = await inspect(bridge, sessionId, local); evidence.tool_calls = recordToolCalls(local, bridge, sessionId, cursor); const usage_ingest_error = await forwardUsageBestEffort(local, sessionId, evidence.usage, evidence.tool_calls); if (initial_usage_error) evidence.initial_usage_error = initial_usage_error; if (usage_ingest_error) evidence.usage_ingest_error = usage_ingest_error;
160
161
  if (!options.allowBackground && (evidence.subagents?.subagents ?? []).length) { await cancelTree(bridge, sessionId); fail("background Grok Build subagents cannot outlive the one-shot dd-grok connection"); }
161
- return receipt(options, { harness: "grok-acp", provider_session_id: sessionId, adapter_session_id: sessionId, assistant_text: bridge.assistantTextSince(sessionId, textCursor), turn, profile: profileReceipt, evidence });
162
+ const diagnostics = (bridge.lifecycleDiagnostics ?? []).slice(diagnosticsCursor).filter(item => item.session_id === sessionId);
163
+ return receipt(options, { harness: "grok-acp", provider_session_id: sessionId, adapter_session_id: sessionId, assistant_text: bridge.assistantTextSince(sessionId, textCursor), turn, profile: profileReceipt, evidence, ...(diagnostics.length ? { native_hook_diagnostics: diagnostics } : {}) });
162
164
  } catch (error) { throw Object.assign(new Error("Grok native Turn completed but mandatory observation failed", { cause: error }), { code: "native_outcome_observation_failed", retryable: false,
163
165
  details: { provider_session_id: sessionId, native_outcome: { status: "completed", turn }, observation_error: errorRecord(error), settled: false, journal_locator: options.journal } }); }
164
166
  }