@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.
- package/CHANGELOG.md +15 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/runtime-process.js +1 -1
- package/dist/cli.js +2 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +49 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +1 -1
- package/dist/harness-runtime/lib/dd-codex.mjs +2 -2
- package/dist/harness-runtime/lib/dd-droid.mjs +30 -8
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +7 -1
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +95 -12
- package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +13 -1
- package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +11 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
- package/dist/harness-runtime/lib/native-children.d.mts +2 -0
- package/dist/harness-runtime/lib/native-children.mjs +20 -1
- package/dist/harness-runtime/lib/opencode-plugin.mjs +30 -9
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/schemas/flow-run.schema.json +4 -0
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +18 -2
- package/dist/services/controller-execution.js +6 -6
- package/dist/services/external-work-launch.js +2 -4
- package/dist/services/frozen-engine.js +16 -8
- package/dist/services/managed-processes.js +31 -10
- package/dist/services/memory-init-native-capacity.js +65 -10
- package/dist/services/memory-init-preflight.js +21 -3
- package/dist/services/memory-init-project-bootstrap.js +3 -3
- package/dist/services/memory-init-start.js +6 -0
- package/dist/services/memory-init-worktrunk-capability.js +9 -3
- package/dist/services/merge-server.js +2 -4
- package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
- package/dist/services/recovery-snapshot-database.js +1 -1
- package/dist/services/run-controller-adapter.js +1 -1
- package/dist/services/run-controller-recovery.js +2 -4
- package/dist/services/run-controller.js +59 -26
- package/dist/services/run-recovery-guard.js +3 -3
- package/dist/services/run-recovery-runtime.js +3 -3
- package/dist/services/runs.js +102 -13
- package/dist/services/vnext-code-review.js +1 -1
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-fanout.js +3 -2
- package/dist/services/vnext-plan-review.js +1 -1
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/vnext-protocolize.js +20 -6
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/package.json +1 -1
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
- 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.
|
package/dist/build-info.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-10-
|
|
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-
|
|
11
|
-
"memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-
|
|
12
|
-
"flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-
|
|
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/
|
|
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);
|
|
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 (
|
|
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
|
-
|
|
411
|
-
|
|
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
|
-
|
|
419
|
-
|
|
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 (
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 "./
|
|
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:
|
|
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 "./
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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") {
|