@deksden-com/dd-flow-cli 0.9.0-beta.138 → 0.9.0-beta.139

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 (53) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/build-info.json +6 -6
  3. package/dist/cli/runtime-process.js +1 -1
  4. package/dist/cli.js +2 -0
  5. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +49 -20
  6. package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
  7. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +1 -1
  8. package/dist/harness-runtime/lib/dd-codex.mjs +2 -2
  9. package/dist/harness-runtime/lib/dd-droid.mjs +30 -8
  10. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +7 -1
  11. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +95 -12
  12. package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
  13. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +13 -1
  14. package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
  15. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  16. package/dist/harness-runtime/lib/delegation-instructions.mjs +11 -0
  17. package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
  18. package/dist/harness-runtime/lib/native-children.d.mts +2 -0
  19. package/dist/harness-runtime/lib/native-children.mjs +20 -1
  20. package/dist/harness-runtime/lib/opencode-plugin.mjs +30 -9
  21. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  22. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  23. package/dist/schemas/flow-run.schema.json +4 -0
  24. package/dist/services/controller-child-capacity.js +13 -0
  25. package/dist/services/controller-child-observer.js +18 -2
  26. package/dist/services/controller-execution.js +6 -6
  27. package/dist/services/external-work-launch.js +2 -4
  28. package/dist/services/frozen-engine.js +16 -8
  29. package/dist/services/managed-processes.js +31 -10
  30. package/dist/services/memory-init-native-capacity.js +65 -10
  31. package/dist/services/memory-init-preflight.js +21 -3
  32. package/dist/services/memory-init-project-bootstrap.js +3 -3
  33. package/dist/services/memory-init-start.js +6 -0
  34. package/dist/services/memory-init-worktrunk-capability.js +9 -3
  35. package/dist/services/merge-server.js +2 -4
  36. package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
  37. package/dist/services/recovery-snapshot-database.js +1 -1
  38. package/dist/services/run-controller-adapter.js +1 -1
  39. package/dist/services/run-controller-recovery.js +2 -4
  40. package/dist/services/run-controller.js +59 -26
  41. package/dist/services/run-recovery-guard.js +3 -3
  42. package/dist/services/run-recovery-runtime.js +3 -3
  43. package/dist/services/runs.js +102 -13
  44. package/dist/services/vnext-code-review.js +1 -1
  45. package/dist/services/vnext-code.js +3 -3
  46. package/dist/services/vnext-fanout.js +3 -2
  47. package/dist/services/vnext-plan-review.js +1 -1
  48. package/dist/services/vnext-plan.js +3 -3
  49. package/dist/services/vnext-protocolize.js +20 -6
  50. package/dist/services/vnext-workspace-policy.js +7 -2
  51. package/package.json +1 -1
  52. package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
  53. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.139
4
+
5
+ ### Patch Changes
6
+
7
+ - Route each Stage from current RUN workspace authority, preserve known native
8
+ terminal outcomes independently of diagnostic reads, and retain exact owned
9
+ OpenCode hook identity failures. Fence automatic cleanup from active owners,
10
+ persist retirement observation budgets and causal errors, validate resource
11
+ homes before writes, and preserve maintenance effect/cleanup boundaries.
12
+ Use progress-observed fixture transport without losing native CLI exit codes.
13
+ - dafdb20: Use a shared bounded provider-overload episode: two same-Session continuations,
14
+ 5/15-second backoff with jitter, and explicit provider-unavailable exhaustion.
15
+ Normalize native capacity failures across adapters, retain restart-safe retry
16
+ budgets, and close child episodes only after confirmed successful native Turns.
17
+
3
18
  ## 0.9.0-beta.138
4
19
 
5
20
  - Retain causal native check failures and permit fenced source repair in the same Session. Reject malformed repair proofs and preserve physical execution blockers during reconciliation.
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.138",
4
- "cli_commit": "c1a9786d04e0944daac204e1c767414904d9ced2",
5
- "built_at": "2026-10-15T05:27:46.626Z",
3
+ "cli_version": "0.9.0-beta.139",
4
+ "cli_commit": "bf550ff8ffdc8ce96dac783d0cfb0000a3b83e74",
5
+ "built_at": "2026-10-17T07:23:43.465Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.2",
8
8
  "commit": "1ddedf9d3f086e046f8e7b194c739ec386e7c8b9",
9
9
  "flow_contract": "dd-flow-canonical-2026-08",
10
- "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-084-release",
11
- "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-084-release/.memory-bank",
12
- "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-084-release/.memory-bank/dd-flow",
10
+ "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-cp231-release",
11
+ "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-cp231-release/.memory-bank",
12
+ "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-cp231-release/.memory-bank/dd-flow",
13
13
  "layout": "dot_memory_bank"
14
14
  }
15
15
  }
@@ -91,7 +91,7 @@ export async function dispatchRuntimeProcess(context, action, parsed, prepared =
91
91
  return { ...receipt, ok: receipt.owned };
92
92
  }
93
93
  if (action === "admission") {
94
- const [{ controllerHitlAdmission }, { assertCapacityOwnerAdmission }] = await Promise.all([import("../services/run-controller-adapter.js"), import("../services/codex-capacity.js")]);
94
+ const [{ controllerHitlAdmission }, { assertCapacityOwnerAdmission }] = await Promise.all([import("../services/run-controller-adapter.js"), import("../services/provider-capacity.js")]);
95
95
  const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
96
96
  if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || record.state !== "running" || !record.pid || !record.pid_started_at || !processIsAlive(record))
97
97
  throw new AppError("process_ownership_unknown", "Productive dispatch requires its current confirmed runtime owner", 1);
package/dist/cli.js CHANGED
@@ -44,7 +44,9 @@ if (args[0] === "flow" && ["list", "show"].includes(args[1] ?? "")) {
44
44
  process.exit(runFlowCatalogCli(args));
45
45
  }
46
46
  if (args[0] === "init" || args[0] === "flow" && args[1] === "mb-init") {
47
+ publishSetup('ingress_entered');
47
48
  const { runMemoryInitCli } = await import("./cli/memory-init.js");
49
+ publishSetup('imports_ready');
48
50
  process.exit(await runMemoryInitCli(args, process.env));
49
51
  }
50
52
  if (args.slice(0, 3).join(" ") === "runtime storage admit") {
@@ -4,7 +4,7 @@ import { callDaemonObserved } from './daemon-observation.mjs';
4
4
  import { operationProgressReporter } from './operation-progress.mjs';
5
5
  import { prepareDaemonRequest } from "./native-request-proof.mjs";
6
6
  import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
7
- import { errorRecord } from "./operation-errors.mjs";
7
+ import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
8
8
  import { ToolObservations, agyToolObservation, agyTranscriptObservations } from "./tool-observations.mjs";
9
9
  import { constants } from "node:fs";
10
10
  import { fileURLToPath } from "node:url";
@@ -20,6 +20,7 @@ import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, as
20
20
  import path from "node:path";
21
21
  import { observeModel } from "./model-observations.mjs";
22
22
  import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
23
+ import { settledNativeTree } from "./native-children.mjs";
23
24
  import { prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
24
25
  import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
25
26
 
@@ -341,7 +342,9 @@ export class Runtime {
341
342
  if (!this.init) this.startupError ??= agyTerminalFailure(event.result, event.terminal_metadata);
342
343
  this.receivedTerminalMetadata = event.terminal_metadata;
343
344
  }
344
- this.observeProviderActivity(event); await this.journal("provider_event", { event }); if (event.event === "init") { this.init = { conversation_id: event.conversation_id, ...(event.init ?? {}) }; if (!this.init.conversation_id) throw new DaemonError("agy_init_missing", "Antigravity init has no conversation ID"); const requested = { provider: this.config.provider, model: this.config.model, reasoning: this.config.reasoning, mode: this.config.mode, permission_mode: "always-proceed" }; this.profile = assertProfile(requested, observedProfile(this.init, requested)); await observeModel({ journal: this.config.journal, harness: "antigravity-cli", sessionId: this.init.conversation_id, requested, observed: this.profile.observed, source: "native.init" }); await this.persist({ shutdown_state: "running", active_tree: false }); readyResolve(this.init); } else if (event.event === "step_update") this.observeStep(event.step_update ?? {}); else if (event.event === "result") { if (this.startupError) throw this.startupError; await this.finishTurn(event.result ?? {}); } } }
345
+ this.observeProviderActivity(event);
346
+ if (event.event === "result" && this.init && !this.startupError) { await this.finishTurn(event.result ?? {}, event.terminal_metadata, event); continue; }
347
+ await this.journal("provider_event", { event }); if (event.event === "init") { this.init = { conversation_id: event.conversation_id, ...(event.init ?? {}) }; if (!this.init.conversation_id) throw new DaemonError("agy_init_missing", "Antigravity init has no conversation ID"); const requested = { provider: this.config.provider, model: this.config.model, reasoning: this.config.reasoning, mode: this.config.mode, permission_mode: "always-proceed" }; this.profile = assertProfile(requested, observedProfile(this.init, requested)); await observeModel({ journal: this.config.journal, harness: "antigravity-cli", sessionId: this.init.conversation_id, requested, observed: this.profile.observed, source: "native.init" }); await this.persist({ shutdown_state: "running", active_tree: false }); readyResolve(this.init); } else if (event.event === "step_update") this.observeStep(event.step_update ?? {}); else if (event.event === "result") { if (this.startupError) throw this.startupError; await this.finishTurn(event.result ?? {}); } } }
345
348
  observeStep(step) {
346
349
  const sessionId = step.conversation_id ?? this.init?.conversation_id, observation = this.sessionObservations.get(sessionId) ?? {};
347
350
  const fresh = Number.isInteger(step.step_index) && step.step_index > (observation.last_step_index ?? -1) && step.step_index > (observation.step_floor ?? -1);
@@ -378,7 +381,7 @@ export class Runtime {
378
381
  }
379
382
  }
380
383
  }
381
- async finishTurn(result, retainedMetadata = null) {
384
+ async finishTurn(result, retainedMetadata = null, providerEvent = null) {
382
385
  if (result.status === "RUNNING") { await this.persist({ active_tree: true }); return; }
383
386
  if (!["SUCCESS", "ERROR", "CANCELLED"].includes(result.status)) throw new DaemonError("agy_terminal_result_invalid", "Antigravity result has no recognized terminal status", false, { provider_result: result });
384
387
  if (result.conversation_id && result.conversation_id !== this.init?.conversation_id) {
@@ -391,40 +394,51 @@ export class Runtime {
391
394
  return;
392
395
  }
393
396
  const current = this.active;
394
- const terminalMetadata = retainedMetadata ?? this.receivedTerminalMetadata ?? (this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result.error
397
+ const terminalMetadata = { received_at: new Date().toISOString(), ...(retainedMetadata ?? this.receivedTerminalMetadata ?? (this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result.error
395
398
  && this.lastTerminalFailure.details.provider_session_id === (result.conversation_id || this.init?.conversation_id || null)
396
399
  && (current?.previousError === result.error || this.lastResult?.status === "ERROR" && this.lastResult.error === result.error) ? this.lastTerminalFailure.details
397
400
  : { observed_at: new Date().toISOString(), provider_session_id: result.conversation_id || this.init?.conversation_id || null, operation_id: current?.operationId ?? null,
398
- turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" });
401
+ turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" })) };
399
402
  this.receivedTerminalMetadata = null;
400
403
  // The ordered stream returns one result per input. Model usage counters
401
404
  // are not request IDs: native command rejections can report zero turns.
402
405
  // prompt() drains earlier events before accepting another input.
403
406
  if (!current && this.lastResult) { await this.journal("stale_terminal_observed", { result }); return; }
404
- await this.captureChildTools();
405
407
  this.lastResult = result; this.markTerminalActivity(result);
406
408
  const rootStop = this.sessionObservations.get(this.init?.conversation_id)?.stop;
407
409
  const pendingTerminal = current && result.status === "SUCCESS";
408
- if (pendingTerminal) current.terminalResult = result;
410
+ if (current) { current.terminalResult = result; current.terminalMetadata = terminalMetadata; current.terminalPersisted = false; }
409
411
  if (pendingTerminal && rootStop?.fullyIdle === true) for (const [id, descendant] of this.descendants) if (descendant.status === "unknown") this.descendants.set(id, { ...descendant, status: "settled_by_root", settlement_evidence: "root_fully_idle_stop" });
410
- const terminalReceipt = pendingTerminal ? this.receipt(result) : null;
411
- await this.persist({ active_tree: pendingTerminal ? !terminalReceipt.settled : result.status === "RUNNING" || rootStop?.fullyIdle === false, last_result: result });
412
+ this.state.terminal_observation_pending = true;
413
+ try {
414
+ await this.persist({ active_tree: true, last_result: result, last_terminal_metadata: terminalMetadata });
415
+ if (current) current.terminalPersisted = true;
416
+ if (providerEvent) await this.journal("provider_event", { event: providerEvent });
417
+ await this.captureChildTools();
418
+ } catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
412
419
  try { this.usageIngest = await forwardUsage(this.config, result.conversation_id ?? this.init.conversation_id, result, this.toolSnapshot(), [...this.descendants.values()]); }
413
- catch (error) { this.usageIngest = { status: "failed", error: error.message ?? String(error) }; await this.journal("usage_ingest_failed", this.usageIngest); }
420
+ catch (error) { this.usageIngest = { status: "failed", error: error.message ?? String(error) }; try { await this.journal("usage_ingest_failed", this.usageIngest); } catch (storage) { return this.failTerminalObservation(current, result, terminalMetadata, storage); } }
421
+ try { await this.persist({ terminal_observation_pending: false }); }
422
+ catch (error) { this.state.terminal_observation_pending = true; return this.failTerminalObservation(current, result, terminalMetadata, error); }
423
+ this.state.terminal_observation_pending = false;
414
424
  if (!current) return;
415
425
  if (this.active !== current) return;
416
426
  if (result.status === "ERROR" || result.status === "CANCELLED") {
417
427
  this.active = null;
418
- this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, provider_error_scope: current.previousError && current.previousError === result.error ? "repeated_conversation_error" : "terminal_result", usage_ingest: this.usageIngest });
419
- await this.persist({ last_terminal_failure: errorRecord(this.lastTerminalFailure) });
428
+ const settled = this.receipt().settled;
429
+ this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, native_turn_items: { observed: settled, possible_effects: true, pending: !settled }, provider_error_scope: current.previousError && current.previousError === result.error ? "repeated_conversation_error" : "terminal_result", usage_ingest: this.usageIngest });
430
+ try { await this.persist({ active_tree: !settled, last_terminal_failure: errorRecord(this.lastTerminalFailure) }); }
431
+ catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
420
432
  return current.reject(this.lastTerminalFailure);
421
433
  }
422
- await this.completePendingTurn();
434
+ try { await this.completePendingTurn(); }
435
+ catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
423
436
  }
424
437
  async completePendingTurn() {
425
438
  await this.observeHookFailures();
426
439
  const current = this.active;
427
- if (!current?.terminalResult || current.terminalResult !== this.lastResult) return;
440
+ if (current?.observationError) throw current.observationError;
441
+ if (current?.terminalResult?.status !== "SUCCESS" || current.terminalResult !== this.lastResult) return;
428
442
  await this.persist({ active_tree: !this.receipt().settled });
429
443
  // Hooks can invalidate the tree while persistence is pending. Never
430
444
  // publish a proof captured before that asynchronous boundary.
@@ -434,9 +448,20 @@ export class Runtime {
434
448
  this.active = null;
435
449
  current.resolve(receipt);
436
450
  }
451
+ failTerminalObservation(current, result, metadata, cause) {
452
+ this.state.terminal_observation_pending = true;
453
+ const details = { ...metadata, native_outcome: result, settled: false, terminal_persisted: current?.terminalPersisted === true, observation_error: errorRecord(cause), native_turn_items: { observed: false, possible_effects: true, pending: true } };
454
+ const error = ["ERROR", "CANCELLED"].includes(result.status)
455
+ ? agyTerminalFailure(result, details)
456
+ : new DaemonError("native_outcome_observation_failed", "Antigravity terminal was received but mandatory observation failed", false, details);
457
+ error.cause = cause;
458
+ if (result.status !== "SUCCESS") this.lastTerminalFailure = error;
459
+ if (current) { current.observationError = error; current.reject(error); }
460
+ throw error;
461
+ }
437
462
  receipt(result = this.lastResult) {
438
463
  if (this.state.last_terminal_failure && !this.lastTerminalFailure) this.lastTerminalFailure = this.state.last_terminal_failure;
439
- const terminalMetadata = this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result?.error
464
+ const terminalMetadata = this.active?.terminalResult === result ? this.active.terminalMetadata : this.state.last_result === result ? this.state.last_terminal_metadata : this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result?.error
440
465
  && this.lastTerminalFailure.details.provider_session_id === (result?.conversation_id || this.init?.conversation_id || null) ? this.lastTerminalFailure.details : null;
441
466
  const assistantText = typeof result?.response === "string" ? result.response : typeof result?.text === "string" ? result.text : null;
442
467
  const root = this.init?.conversation_id; const observation = this.sessionObservations.get(root);
@@ -446,7 +471,8 @@ export class Runtime {
446
471
  // non-idle Stop still wins, and every discovered child must be terminal.
447
472
  const rootSettled = observation?.stop?.fullyIdle !== false;
448
473
  const terminalPending = this.active?.terminalResult === result;
449
- 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 } : {}), ...(assistantText === null ? {} : { assistant_text: assistantText }), usage: usageSnapshot(result ?? {}, this.toolSnapshot()), usage_ingest: this.usageIngest, descendants: [...this.descendants.values()], transcript_path: observation?.transcript_path ?? null, settled: Boolean(!this.state.unclaimed_activity && (!this.active || terminalPending) && result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status) && treeSettled && rootSettled) };
474
+ const settled = Boolean(!this.state.terminal_observation_pending && !this.active?.observationError && !this.state.unclaimed_activity && (!this.active || terminalPending) && result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status) && treeSettled && rootSettled);
475
+ return { harness: "antigravity-cli", runtime_family: "antigravity", provider_session_id: root ?? null, adapter_session_id: root ?? null, cwd: this.config.cwd, profile: this.profile, result, ...(terminalMetadata ? { terminal_metadata: terminalMetadata, ...(terminalMetadata.turn_id ? { terminal_turn_id: terminalMetadata.turn_id } : {}) } : {}), ...(assistantText === null ? {} : { assistant_text: assistantText }), usage: usageSnapshot(result ?? {}, this.toolSnapshot()), usage_ingest: this.usageIngest, descendants: [...this.descendants.values()], ...settledNativeTree(root, [...this.descendants.values()], settled, !this.state.unclaimed_activity && (!this.active || terminalPending) && rootSettled && ["SUCCESS", "ERROR", "CANCELLED"].includes(result?.status)), transcript_path: observation?.transcript_path ?? null, settled };
450
476
  }
451
477
  async zeroUsage() { await forwardUsage(this.config, this.init.conversation_id, { usage: {} }, this.toolSnapshot(), []); }
452
478
  async prompt(text, assertDispatch = captureDispatchGuard(this), operationId = null, livenessTimeoutMs = 1_800_000) {
@@ -500,9 +526,14 @@ export class Runtime {
500
526
  checking = true; const cursor = this.progress?.cursor;
501
527
  void this.completePendingTurn().then(() => {
502
528
  if (this.active !== current || this.progress?.cursor !== cursor || !clock.sample(this.progress)) return;
529
+ if (current.terminalResult) return this.failTerminalObservation(current, current.terminalResult, current.terminalMetadata, new Error("Native tree settlement was not confirmed within the productive silence window"));
503
530
  current.reject(new DaemonError("operation_observation_lost", "Antigravity native outcome is unknown after the productive silence window", true, { operation_id: operationId, turn_generation: current.generation, native_outcome: "unknown", last_activity_at: this.lastActivityAt ?? null }));
504
531
  }).catch(error => {
505
- if (this.active === current && this.progress?.cursor === cursor) current.reject(new DaemonError("operation_observation_lost", "Antigravity quiet reconciliation could not confirm the native outcome", true, { operation_id: operationId, turn_generation: current.generation, native_outcome: "unknown", observation_error: errorRecord(error) }));
532
+ if (this.active === current && this.progress?.cursor === cursor) {
533
+ if (current.observationError) current.reject(current.observationError);
534
+ else if (current.terminalResult) { try { this.failTerminalObservation(current, current.terminalResult, current.terminalMetadata, error); } catch { /* reply owns the typed failure */ } }
535
+ else current.reject(new DaemonError("operation_observation_lost", "Antigravity quiet reconciliation could not confirm the native outcome", true, { operation_id: operationId, turn_generation: current.generation, native_outcome: "unknown", observation_error: errorRecord(error) }));
536
+ }
506
537
  }).finally(() => { checking = false; });
507
538
  }, Math.max(10, Math.min(1_000, livenessTimeoutMs / 4)));
508
539
  try {
@@ -538,9 +569,7 @@ export class Runtime {
538
569
  }
539
570
  async rejectHook(error, event, payload, current = this.active) {
540
571
  if (current && this.active !== current) return;
541
- const correctable = current && event === "PreToolUse" && error.code === "lifecycle_shell_syntax_invalid"
542
- && error.details?.effect === "no_effect" && error.details?.recoverable === true
543
- && error.details?.disposition === "pre_cli_shell_refusal" && error.details?.native_pre_cli_refusal === true;
572
+ const correctable = current && event === "PreToolUse" && isProvenShellCorrection(error);
544
573
  if (correctable && current.hookCorrectionGeneration !== current.generation) {
545
574
  // The request was correlated with this still-active Turn by
546
575
  // observeHookFailures. Persist before allowing native same-Turn recovery.
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { SETUP_STARTUP_MS } from './setup-progress.mjs';
5
5
 
6
6
  import { checkObservedProfile } from "./model-observations.mjs";
7
+ import { classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
7
8
 
8
9
  const DEFAULT_MODEL = "gemini-3.1-pro-high";
9
10
  export const AGY_VERSION_TIMEOUT_MS = 30_000;
@@ -13,11 +14,21 @@ export class AgyError extends Error {
13
14
  }
14
15
 
15
16
  export function agyTerminalFailure(result, metadata = {}) {
16
- const message = String(result.error ?? "Antigravity returned ERROR"), normalized = message.toLowerCase();
17
- const code = result.status === "CANCELLED" ? "agy_turn_cancelled" : /quota|insufficient.?credit|billing|usage limit/.test(normalized) ? "agy_provider_quota_exhausted"
18
- : /rate.?limit|too many requests/.test(normalized) ? "agy_provider_rate_limited" : /\b429\b/.test(normalized) ? "agy_provider_limit_unknown"
19
- : /auth|unauthori[sz]ed|forbidden|account|eligib/.test(normalized) ? "agy_provider_account" : "agy_provider_failed";
20
- return new AgyError(code, message, code === "agy_provider_rate_limited", { provider_result: result, ...metadata });
17
+ const native = typeof result.error === "object" && result.error ? result.error : { message: result.error ?? "Antigravity returned ERROR" };
18
+ const category = classifyProviderError(native);
19
+ const legacy = { provider_quota_exhausted: "agy_provider_quota_exhausted", provider_limit_unknown: "agy_provider_limit_unknown", provider_auth_failed: "agy_provider_account", provider_failed: "agy_provider_failed" };
20
+ let code = result.status === "CANCELLED" ? "agy_turn_cancelled" : legacy[category] ?? category;
21
+ // A local generation/usage count is not a native request identity.
22
+ const identity = result.turn_id ?? result.request_id;
23
+ const turn = typeof identity === "string" && identity ? identity : null;
24
+ const proofMissing = result.status === "ERROR" && ["provider_capacity_transient", "provider_rate_limited"].includes(category) && !turn;
25
+ if (proofMissing) code = "provider_overload_evidence_unavailable";
26
+ return new AgyError(code, String(native.message ?? "Antigravity returned ERROR"), code === "provider_capacity_transient" || code === "provider_rate_limited", {
27
+ provider_result: result, ...metadata, provider_error: safeProviderError(native), native_source: "agy.result", terminal_status: result.status === "ERROR" ? "failed" : "cancelled",
28
+ ...(turn ? { turn_id: turn, native_turn_id: turn } : {}),
29
+ ...(proofMissing ? { provider_category: category, reason: "native_turn_identity_unavailable" } : {}),
30
+ will_retry: native.willRetry === true, native_retry_active: native.willRetry === true
31
+ });
21
32
  }
22
33
 
23
34
  export function executable(bin, args = []) {
@@ -9,7 +9,7 @@ import { chmod, lstat, mkdir, open, readFile, rename, unlink, writeFile } from "
9
9
  import net from "node:net";
10
10
  import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
11
11
  import { ObservationClock } from "./observation-clock.mjs";
12
- import { parseCapacityOperation } from "./codex-capacity-policy.mjs";
12
+ import { parseCapacityOperation } from "./provider-overload-policy.mjs";
13
13
  import path from "node:path";
14
14
  import { spawn } from "node:child_process";
15
15
  import { waitForSettlement } from "./session-settlement.mjs";
@@ -281,7 +281,7 @@ export class CodexBridge {
281
281
  const correction = isProvenShellCorrection(error);
282
282
  error.details = { ...error.details, native_hook_binding: { provider_session_id: threadId, root_provider_session_id: root, turn_id: turnId,
283
283
  hook_run_id: run.id, source: "codex.hook/completed" }, provider_session_id: threadId, root_provider_session_id: root, turn_id: turnId,
284
- hook_run_id: run.id, effect: correction ? "no_effect" : "unknown", duration_ms: run.durationMs };
284
+ hook_run_id: run.id, effect: error.details?.effect ?? "unknown", native_turn_items: { observed: false, possible_effects: true, pending: true }, duration_ms: run.durationMs };
285
285
  if (correction) {
286
286
  try { void Promise.resolve(this.journal.write("native_hook_correction", errorRecord(error))).catch(storage => { error.cleanup_error ??= errorRecord(storage); }); }
287
287
  catch (storage) { error.cleanup_error ??= errorRecord(storage); }
@@ -734,7 +734,7 @@ export function turnItemEvidence(turn, bridge, sessionId) {
734
734
  possible_effects: items.some(item => classifyCodexItem(item?.type) !== "non_tool") } : { observed: false };
735
735
  }
736
736
 
737
- export { terminalCodexOverload } from "./codex-capacity-policy.mjs";
737
+ export { terminalProviderOverload as terminalCodexOverload } from "./provider-overload-policy.mjs";
738
738
 
739
739
  function isTerminalTurnStatus(status) { return ["completed", "failed", "interrupted", "cancelled"].includes(status); }
740
740
  function isAuthoritativeTerminal(bridge, turnId, status, threadStatus, failureNotBefore) {
@@ -11,7 +11,7 @@ import path from "node:path";
11
11
  import { ObservationClock, observedTimeout, validateObservationDuration } from "./observation-clock.mjs";
12
12
  import { operationProgressReporter } from "./operation-progress.mjs";
13
13
  import { writeJsonAtomic } from "./runner-events.mjs";
14
- import { errorRecord, reportedError, isObservationLoss } from "./operation-errors.mjs";
14
+ import { errorRecord, reportedError, isObservationLoss, isProvenShellCorrection } from "./operation-errors.mjs";
15
15
  import { hookBudget, invokeNativeHook } from "./native-hook-command.mjs";
16
16
  import { checkObservedProfile, observeModel } from "./model-observations.mjs";
17
17
  import { readNativeLines, streamNativeDiagnostics, nativeObservationError } from "./native-streams.mjs";
@@ -21,6 +21,8 @@ const metadataReaders = new Map();
21
21
  function metadataReader(factory) { if (!metadataReaders.has(factory)) metadataReaders.set(factory, new DroidMetadataReader(factory)); return metadataReaders.get(factory); }
22
22
  import { confirmDaemonProcess, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
23
23
  import { renderAdapterPolicy, lifecycleRetryInstruction } from "./delegation-instructions.mjs";
24
+ import { classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
25
+ import { settledNativeTree } from "./native-children.mjs";
24
26
 
25
27
  export const DROID_CONTRACT = "dd-droid-harness@1";
26
28
  export const DROID_PROTOCOL = "1.201.0";
@@ -96,6 +98,19 @@ export async function readDroidSession(factory, id) {
96
98
  const terminal = value => ["completed", "cancelled", "failed", "process_exit", "error"].includes(value);
97
99
  const status = value => value === "completed" ? "completed" : ["cancelled", "process_exit"].includes(value) ? "cancelled" : ["failed", "error"].includes(value) ? "failed" : "unknown";
98
100
 
101
+ export function droidTerminalFailure(outcome, sessionId, settled) {
102
+ if (!["failed", "error"].includes(outcome?.reason) || !outcome.error || typeof outcome.error !== "object") return null;
103
+ const code = classifyProviderError(outcome.error);
104
+ if (code === "provider_failed") return null;
105
+ const turnId = outcome.turnId;
106
+ return Object.assign(new DroidError(code, "Droid native provider request failed", {
107
+ provider_session_id: sessionId, session_id: sessionId, turn_id: turnId, native_turn_id: turnId,
108
+ terminal_status: "failed", native_source: "droid.agent_turn_outcome", observed_at: new Date().toISOString(),
109
+ provider_error: safeProviderError(outcome.error), will_retry: outcome.error.willRetry === true, native_retry_active: outcome.error.willRetry === true,
110
+ native_turn_items: { observed: settled === true, possible_effects: true, pending: settled !== true }
111
+ }), { retryable: code === "provider_capacity_transient" || code === "provider_rate_limited" });
112
+ }
113
+
99
114
 
100
115
  function descendants(rows, rootPid) { const ids = new Set([rootPid]); for (;;) { const size = ids.size; for (const row of rows) if (ids.has(row.ppid)) ids.add(row.pid); if (size === ids.size) return rows.filter(row => ids.has(row.pid)); } }
101
116
 
@@ -361,15 +376,18 @@ export class DroidRuntime {
361
376
  });
362
377
  }
363
378
  const outcome = await completed; await this.draining;
379
+ let receipt;
364
380
  try {
365
- const receipt = await this.inspect(id, { ignoreActive: true });
366
- return { ...receipt, result: { ...outcome, status: status(outcome.reason) }, assistant_text: current.text, turn_id: turnId };
381
+ receipt = await this.inspect(id, { ignoreActive: true });
367
382
  } catch (error) {
368
383
  // A terminal native outcome is durable independently of a later
369
384
  // filesystem/topology observation. Preserve it and let settlement
370
385
  // reconcile; do not convert it into a failed prompt.
371
386
  return { provider_session_id: id, adapter_session_id: id, result: { ...outcome, status: status(outcome.reason) }, assistant_text: current.text, turn_id: turnId, settled: false, settlement_observation_error: errorRecord(error) };
372
387
  }
388
+ const providerFailure = droidTerminalFailure(outcome, id, receipt.settled);
389
+ if (providerFailure) throw providerFailure;
390
+ return { ...receipt, result: { ...outcome, status: status(outcome.reason) }, assistant_text: current.text, turn_id: turnId };
373
391
  } catch (error) {
374
392
  if (current.outcome && isObservationLoss(error)) error = Object.assign(new Error("Droid completed natively but its dispatch acknowledgement was lost", { cause: error }), {
375
393
  code: "native_outcome_observation_failed", details: { native_outcome: { provider_session_id: id, turn_id: turnId, result: current.outcome }, settled: false, observation_error: errorRecord(error) }
@@ -400,6 +418,8 @@ export class DroidRuntime {
400
418
  catch (error) {
401
419
  receipt = { provider_session_id: binding.provider_session_id, adapter_session_id: binding.provider_session_id, settled: false, settlement_observation_error: errorRecord(error) };
402
420
  }
421
+ const failure = droidTerminalFailure(outcome, binding.provider_session_id, receipt.settled);
422
+ if (failure) throw failure;
403
423
  // Native outcome and tree settlement are independent. Retain the former
404
424
  // even while children are running; settled:false keeps capacity reserved.
405
425
  const current = this.active;
@@ -449,7 +469,9 @@ export class DroidRuntime {
449
469
  const ordered = tasks.toSorted((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0));
450
470
  const latest = ordered.at(-1); const running = tasks.some(task => !terminal(task.status));
451
471
  const closed = running && prior?.source === "adapter.tree_closed";
452
- this.topology.set(id, { provider_session_id: id, parent_provider_session_id: this.rootId, tool_use_id: creationTool, latest_tool_use_id: latest.parentToolUseId, task_id: latest.taskInvocationId, task_invocation_ids: ordered.map(task => task.taskInvocationId), status: closed ? prior.status : running ? "running" : status(latest.status), source: closed ? prior.source : "droid.task_invocations", transcript_path: native.transcript_path });
472
+ const outcome = native.outcomes.at(-1), nativeTurn = outcome && terminal(outcome.reason) && typeof outcome.turnId === "string" ? outcome.turnId : null;
473
+ const evidence = !running && nativeTurn ? { terminal_turn_id: nativeTurn, native_source: "droid.agent_turn_outcome", native_turn_items: { observed: native.complete && native.tool_calls.completeness === "complete", possible_effects: native.tool_calls.total > 0, pending: native.tool_calls.pending > 0 }, ...(outcome.error && typeof outcome.error === "object" ? { provider_error: safeProviderError(outcome.error) } : {}) } : {};
474
+ this.topology.set(id, { provider_session_id: id, parent_provider_session_id: this.rootId, tool_use_id: creationTool, latest_tool_use_id: latest.parentToolUseId, task_id: latest.taskInvocationId, task_invocation_ids: ordered.map(task => task.taskInvocationId), status: closed ? prior.status : running ? "running" : status(latest.status), source: closed ? prior.source : "droid.task_invocations", transcript_path: native.transcript_path, ...evidence });
453
475
  }
454
476
  return [...this.topology.values()];
455
477
  }
@@ -470,7 +492,8 @@ export class DroidRuntime {
470
492
  this.requireIdentity(id); await this.captureProcesses(); const children = await this.refreshTopology(); const source = await readDroidSession(this.paths.factory, id);
471
493
  const usage = []; for (const sessionId of [id, ...children.map(child => child.provider_session_id)]) usage.push(await this.snapshotUsage(sessionId, { ingest: ingestUsage }));
472
494
  const settled = !this.topologyIncomplete && (!this.active || ignoreActive) && (this.closed || this.workingState === "idle") && children.every(child => terminal(child.status));
473
- return { harness: "droid-cli", runtime_family: "droid", session: { harness_id: "droid-cli", session_id: id }, provider_session_id: id, adapter_session_id: id, cwd: this.config.cwd, observed_profile: source?.settings ? await this.observeSettings(id, source.settings, { path: source.transcript_path, sha256: source.source_sha256 }) : this.observedProfile, observed_runtime: { droid: this.config.version, factory_protocol: this.protocol ?? DROID_PROTOCOL, dd_harness_contract: DROID_CONTRACT }, status: settled ? "idle" : "running", settled, descendants: children, transcript_path: source?.transcript_path ?? null, usage, result: this.lastResult, provider_pid: this.closed ? null : this.child?.pid ?? null };
495
+ const rootSettled = (!this.active || ignoreActive) && (this.closed || this.workingState === "idle") && terminal((source?.outcomes?.at(-1) ?? this.lastResult)?.reason);
496
+ return { harness: "droid-cli", runtime_family: "droid", session: { harness_id: "droid-cli", session_id: id }, provider_session_id: id, adapter_session_id: id, cwd: this.config.cwd, observed_profile: source?.settings ? await this.observeSettings(id, source.settings, { path: source.transcript_path, sha256: source.source_sha256 }) : this.observedProfile, observed_runtime: { droid: this.config.version, factory_protocol: this.protocol ?? DROID_PROTOCOL, dd_harness_contract: DROID_CONTRACT }, status: settled ? "idle" : "running", settled, ...settledNativeTree(id, children, settled, rootSettled && !this.topologyIncomplete), terminal_turn_id: source?.outcomes?.at(-1)?.turnId ?? this.lastResult?.turnId, descendants: children, transcript_path: source?.transcript_path ?? null, usage, result: this.lastResult, provider_pid: this.closed ? null : this.child?.pid ?? null };
474
497
  }
475
498
  async observeHook(payload, eventId, deadlineAt = null) {
476
499
  try { return await this.observeHookChecked(payload, eventId, hookBudget(23_000, deadlineAt)); }
@@ -500,15 +523,14 @@ export class DroidRuntime {
500
523
  try {
501
524
  return await this.flow("event", "handle", { schema_id: "dd-flow/droid-tool-event@1", phase: event === "PreToolUse" ? "before" : "after", event_id: eventId, daemon_id: this.config.daemonId, session: { provider_session_id: id, parent_provider_session_id: parent, directory: this.config.cwd }, tool: payload.tool_name, input: payload.tool_input, profile, transcript_path: native.transcript_path, outcome: { status: payload.tool_response?.is_error ? "error" : "completed" } }, budget.remaining("admission", 15_000));
502
525
  } catch (error) {
503
- const details = error.details;
504
- if (event === "PreToolUse" && error.code === "lifecycle_shell_syntax_invalid" && details?.phase === "prepare" && details.effect === "no_effect" && details.recoverable === true && details.disposition === "pre_cli_shell_refusal" && (details.refusal_event_key || details.event_key)) {
526
+ if (event === "PreToolUse" && isProvenShellCorrection(error)) {
505
527
  const refusal = errorRecord(error);
506
528
  await this.journal("hook_rejected", { error: refusal, event_id: eventId, session_id: id });
507
529
  await this.save();
508
530
  return { continue: false, stopReason: error.message };
509
531
  }
510
532
  // Identity is proven above; only this owned admission can fail its turn.
511
- this.fatalError ??= Object.assign(error, { details: { ...error.details, provider_session_id: id, event_id: eventId, effect: "unknown" } });
533
+ this.fatalError ??= Object.assign(error, { details: { ...error.details, provider_session_id: id, event_id: eventId, effect: error.details?.effect ?? "unknown", native_turn_items: { observed: false, possible_effects: true, pending: true } } });
512
534
  this.rejectPending(this.fatalError);
513
535
  try { await budget.run(async () => { await this.save({ fatal_error: errorRecord(this.fatalError) }); await this.journal("hook_rejected", { error: errorRecord(this.fatalError) }); }, "failure_persistence"); }
514
536
  catch (storage) { this.fatalError.details.receipt_error = errorRecord(storage); }
@@ -20,6 +20,8 @@ import os from "node:os";
20
20
  import path from "node:path";
21
21
 
22
22
  import { AcpBridge } from "./dd-zcode.mjs";
23
+ import { safeProviderError } from "./provider-overload-policy.mjs";
24
+ import { settledNativeTree } from "./native-children.mjs";
23
25
  import { observeModel, readModelObservations } from "./model-observations.mjs";
24
26
  import { cancelSessionWithBridge, createSessionWithBridge, doctor, forkSessionWithBridge, inspectSessionWithBridge, promptSessionWithBridge } from "./dd-grok.mjs";
25
27
  import { prepareRuntimeOwner, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, startManagedBridge, stopProcessGroup } from "./managed-daemon.mjs";
@@ -241,6 +243,10 @@ export class Runtime {
241
243
  parent_provider_session_id: parent,
242
244
  status,
243
245
  source,
246
+ ...(terminal(status) && typeof attempt === "string" && attempt ? { terminal_turn_id: attempt,
247
+ ...(typeof value.observed_at === "string" ? { terminal_observed_at: value.observed_at } : {}),
248
+ ...(value.provider_error && typeof value.provider_error === "object" ? { provider_error: safeProviderError(value.provider_error), native_source: "grok.subagent.event" } : {}),
249
+ ...(value.native_turn_items && typeof value.native_turn_items === "object" ? { native_turn_items: value.native_turn_items } : {}) } : {}),
244
250
  ...(attempt || descendant?.attempt_id ? { attempt_id: attempt ?? descendant.attempt_id } : {}),
245
251
  ...(newAttempt && descendant?.attempt_id ? { retired_attempt_ids: [...retired, descendant.attempt_id] } : retired.length ? { retired_attempt_ids: retired } : {}),
246
252
  ...(!newAttempt && !terminal(status) && descendant?.outcome_error ? { outcome_error: descendant.outcome_error } : {})
@@ -410,7 +416,7 @@ export class Runtime {
410
416
  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);
411
417
  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); }
412
418
  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; }); }
413
- 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(); return { ...result, settled: !this.active && !this.treeRunning(result), descendants: this.topology(result.provider_session_id) }; }
419
+ 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 { ...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) } : {}) }; }
414
420
  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; }
415
421
  if (operation === "session.archive") { await this.requireSettled(); return await this.productive(operation, async () => { let session = this.sessions.get(params.sessionId); if (!session?.cwd) { const result = await inspectSessionWithBridge(this.bridge, this.options({ ...params, liveSession: true })); this.track(result); session = this.sessions.get(result.provider_session_id); } return await exportSessionArchive(this.paths, session.provider_session_id, absolute(session.cwd, "Grok Build session cwd"), params.archiveDir, this.state.versions); }); }
416
422
  if (operation === "daemon.stop") {