@deksden-com/dd-flow-cli 0.9.0-beta.143 → 0.9.0-beta.145
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 +65 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -11
- package/dist/services/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +27 -8
- package/dist/services/hybrid-contracts.js +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-layer.js +68 -4
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +38 -17
- package/dist/services/memory-verification-checks.js +77 -23
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +74 -9
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +8 -4
|
@@ -60,7 +60,7 @@ export function delegationContract(harness) {
|
|
|
60
60
|
return `dd-flow/delegation/${key}@${["codex", "opencode"].includes(key) ? 2 : 1}`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export const lifecycleCommandCompletionInstruction = "Run runtime-issued lifecycle CLI calls in the foreground, never with a native background or detached-shell option. A shell tool may yield before the lifecycle process exits. Retain and poll its exact process/session handle to completion, including inside a tool wrapper; empty interim output is not a CLI result. If a wrapper returns 'Script running with cell ID …', use that wrapper's wait tool to finish the same cell before expecting an inner shell session handle; do not execute the command again. Read final stdout or response_file before deciding continuation, and never reissue the command while it is pending.";
|
|
63
|
+
export const lifecycleCommandCompletionInstruction = "Run runtime-issued lifecycle CLI calls in the foreground, never with a native background or detached-shell option. A shell tool may yield before the lifecycle process exits. Retain and poll its exact process/session handle to completion, including inside a tool wrapper; empty interim output is not a CLI result. If a wrapper returns 'Script running with cell ID …', use that wrapper's wait tool to finish the same cell before expecting an inner shell session handle; do not execute the command again. Read final stdout or response_file before deciding continuation, and never reissue the command while it is pending. When the result provides prompt_delivery, read its complete plain Markdown file at prompt_delivery.path before working; it contains the current instructions, unlike the historical prompt_path. If delivery publication is pending, stop for reconciliation without repeating the accepted command.";
|
|
64
64
|
|
|
65
65
|
export const lifecycleRetryInstruction = `${lifecycleCommandCompletionInstruction} A failure is not permission to replay a lifecycle command. For proven effect=no_effect with recoverable=true and retry_command, correct the input and execute that exact issued successor. A check failure can retain receipts while leaving Work running: correct the evidenced cause first, then use the CLI's exact retry_command for an intentional fresh check attempt; never replay a check merely because Work is unsettled. If a successful CODE finish returns outcome=verification_required, inspect the retained receipts, update its verification file, and use only next.finish_command: the Stage is still running and the previous invocation ID is consumed. If continuation.kind=repair_required, or a successful result has outcome=repair_required and a repair Work ID, end this Turn for the registered repair child; use any retry_command only after that repair settles and verification is refreshed. Unknown effects require reconciliation, never replay. A committed effect without an explicit continuation is not retryable. A native shell launch refusal before the CLI starts permits correcting only that shell-form error and retrying the same exact command. Otherwise report the primary error and stop; never invent IDs, call hooks directly, or run status as a substitute.`;
|
|
66
66
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Stable model-facing reference for persisted project, RUN and Work IDs. */
|
|
2
|
+
export function shortEntityReference(id) {
|
|
3
|
+
return /^(?:PRJ|RUN|WRK)-\d{3,}(?:-|$)/.exec(id)?.[0]?.replace(/-$/, "") ?? id;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Accepted spellings of one already-resolved entity. Wrong full slugs stay distinct. */
|
|
7
|
+
export function entityReferenceVariants(id) {
|
|
8
|
+
return [...new Set([id, shortEntityReference(id)])];
|
|
9
|
+
}
|
|
@@ -409,21 +409,28 @@ export async function closeManagedBridge(config, state, bridge) {
|
|
|
409
409
|
await finishDaemonProcess(config, state.provider_process, "stopped", "daemon_stopped");
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
export function scheduleDaemonProcessHeartbeat(config, record) {
|
|
412
|
+
export function scheduleDaemonProcessHeartbeat(config, record, { signal } = {}) {
|
|
413
413
|
if (!record?.id || !record.lease_token) return;
|
|
414
414
|
const key = leaseKey(config, record);
|
|
415
|
-
if (leases.has(key))
|
|
416
|
-
|
|
415
|
+
if (leases.has(key)) {
|
|
416
|
+
if (signal && leases.get(key).signal !== signal) throw Object.assign(new Error("Heartbeat cancellation owner differs from the retained lease"), { code: "process_maintenance_signal_conflict" });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const lease = { pending: null, error: null, timer: null, lost: false, closing: false, signal, expiresAt: Date.parse(record.lease_expires_at ?? ""), renewal: createRenewalState({ leaseExpiresAt: record.lease_expires_at }), stateDir: path.resolve(config.runtime_owner?.state_dir ?? config.stateDir ?? config.cwd), config, record };
|
|
417
420
|
leases.set(key, lease);
|
|
418
421
|
// Retry no-effect contention inside the same authority episode while native
|
|
419
422
|
// work is active; scheduling ticks are not ownership evidence.
|
|
420
423
|
lease.timer = setInterval(() => reconfirmLease(lease, () => {}).catch(() => {}), 60_000);
|
|
421
424
|
lease.timer.unref();
|
|
425
|
+
if (signal) {
|
|
426
|
+
signal.addEventListener("abort", () => clearInterval(lease.timer), { once: true });
|
|
427
|
+
if (signal.aborted) clearInterval(lease.timer);
|
|
428
|
+
}
|
|
422
429
|
}
|
|
423
430
|
|
|
424
|
-
export async function heartbeatDaemonProcess(config, record) {
|
|
431
|
+
export async function heartbeatDaemonProcess(config, record, { signal } = {}) {
|
|
425
432
|
if (!record?.id || !record.lease_token) return;
|
|
426
|
-
scheduleDaemonProcessHeartbeat(config, record);
|
|
433
|
+
scheduleDaemonProcessHeartbeat(config, record, { signal });
|
|
427
434
|
const lease = leases.get(leaseKey(config, record));
|
|
428
435
|
if (lease.closing) throw Object.assign(new Error("Managed process is closing"), { code: "process_closing" });
|
|
429
436
|
if (lease.lost) throw lease.error;
|
|
@@ -435,7 +442,7 @@ export async function heartbeatDaemonProcess(config, record) {
|
|
|
435
442
|
lease.pending = (async () => {
|
|
436
443
|
try {
|
|
437
444
|
lease.renewal.uncertainSince ??= performance.now();
|
|
438
|
-
const result = await runtimeProcess(config, "heartbeat", { id: record.id, "lease-token": record.lease_token, "lease-ms": 300_000 }, { timeoutMs: renewalRemaining(lease.renewal) });
|
|
445
|
+
const result = await runtimeProcess(config, "heartbeat", { id: record.id, "lease-token": record.lease_token, "lease-ms": 300_000 }, { timeoutMs: renewalRemaining(lease.renewal), signal: lease.signal });
|
|
439
446
|
if (result?.ok === false) {
|
|
440
447
|
lease.lost = true; clearInterval(lease.timer);
|
|
441
448
|
throw Object.assign(new Error("managed process lease is no longer owned; new productive operations are blocked"), { code: "process_lease_lost", details: { process_id: record.id } });
|
|
@@ -37,7 +37,7 @@ export async function writeNativeHookFailure(stateDir, binding, error, budget) {
|
|
|
37
37
|
if (!binding?.request_id || !/^[A-Za-z0-9-]{1,128}$/.test(binding.request_id)) return;
|
|
38
38
|
await budget.run(() => writeJsonAtomic(path.join(stateDir, "hook-failures", `${binding.request_id}.json`), {
|
|
39
39
|
...errorRecord(error), request_id: binding.request_id, observed_at: new Date().toISOString(), native_hook_binding: binding,
|
|
40
|
-
}), "failure_receipt");
|
|
40
|
+
}, () => budget.remaining("failure_receipt_commit")), "failure_receipt");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** Read only requests accepted for this exact live operation, never scan a mailbox. */
|
|
@@ -74,7 +74,7 @@ export function hookBudget(timeoutMs = 25_000, deadlineAt = process.env.DD_FLOW_
|
|
|
74
74
|
return { deadlineAt: Math.floor(Date.now() + duration), remaining, async run(action, phase) {
|
|
75
75
|
const timeoutMs = remaining(phase);
|
|
76
76
|
let timer;
|
|
77
|
-
try { return await Promise.race([Promise.resolve().then(action), new Promise((_, reject) => { timer = setTimeout(() => reject(Object.assign(new Error(`Native hook deadline exceeded during ${phase}`), { code: "native_hook_timeout", details: { phase, effect: "unknown" } })), timeoutMs); })]); }
|
|
77
|
+
try { return await Promise.race([Promise.resolve().then(() => { remaining(phase); return action(); }), new Promise((_, reject) => { timer = setTimeout(() => reject(Object.assign(new Error(`Native hook deadline exceeded during ${phase}`), { code: "native_hook_timeout", details: { phase, effect: "unknown" } })), timeoutMs); })]); }
|
|
78
78
|
finally { clearTimeout(timer); }
|
|
79
79
|
} };
|
|
80
80
|
}
|
|
@@ -241,8 +241,8 @@ function hookDiagnostics(stderr) {
|
|
|
241
241
|
}).slice(-16);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
export async function invokeNativeHook({ bin, args, nodeBin, payload, home, cwd, env, timeoutMs =
|
|
245
|
-
const output = await runBoundedCommand({ bin, args, nodeBin, input: `${JSON.stringify(payload)}\n`, cwd, env: { ...env, DD_FLOW_HOME: home }, timeoutMs });
|
|
244
|
+
export async function invokeNativeHook({ bin, args, nodeBin, payload, home, cwd, env, timeoutMs = 25_000 }) {
|
|
245
|
+
const output = await runBoundedCommand({ bin, args, nodeBin, input: `${JSON.stringify(payload)}\n`, cwd, env: { ...env, DD_FLOW_HOME: home }, timeoutMs, cleanupWithinBudget: true });
|
|
246
246
|
let result;
|
|
247
247
|
try { result = JSON.parse(output); } catch { throw Object.assign(new Error("Invalid native hook JSON response"), { code: "native_hook_response_invalid" }); }
|
|
248
248
|
if (result?.ok === false && typeof result.error?.code === "string") throw Object.assign(new Error(result.error.message ?? "Native hook failed"), { code: result.error.code, details: result.error.details });
|
|
@@ -33,16 +33,16 @@ export function makeOpenCodePlugin(binding, invoke = invokeNativeHook, resolve =
|
|
|
33
33
|
try {
|
|
34
34
|
const result = await invoke({ bin: binding.ddFlowBin, nodeBin: binding.nodeBin,
|
|
35
35
|
args: ["opencode", "event", "handle", "--project-root", binding.projectRoot, "--json"],
|
|
36
|
-
payload: { ...payload, native_hook_binding: identity.nativeHookBinding }, home: binding.ddFlowHome, timeoutMs: budget.remaining("admission"
|
|
36
|
+
payload: { ...payload, native_hook_binding: identity.nativeHookBinding }, home: binding.ddFlowHome, timeoutMs: budget.remaining("admission") });
|
|
37
37
|
await budget.run(() => resolve(binding.stateDir, "hook.verify", { binding: identity.nativeHookBinding }, budget.remaining("generation")), "generation");
|
|
38
38
|
if (result?.permissionDecision === "deny" || result?.hookSpecificOutput?.permissionDecision === "deny") throw Object.assign(new Error("dd-flow denied native shell admission"), { code: "native_hook_rejected" });
|
|
39
39
|
return result;
|
|
40
40
|
} catch (error) {
|
|
41
41
|
error.details = { ...error.details, native_hook_binding: identity.nativeHookBinding, ...(payload.phase === "after" ? { effect: "unknown", tool_outcome: payload.outcome } : {}) };
|
|
42
|
-
try { await writeNativeHookFailure(binding.stateDir, identity.nativeHookBinding, error,
|
|
42
|
+
try { await writeNativeHookFailure(binding.stateDir, identity.nativeHookBinding, error, budget); await resolve(binding.stateDir, "hook.failed", {}, budget.remaining("failure_notification")); }
|
|
43
43
|
catch (storage) {
|
|
44
44
|
error.details.receipt_error = { code: storage.code ?? "hook_receipt_write_failed", message: storage.message };
|
|
45
|
-
try { await resolve(binding.stateDir, "hook.failed", { binding: identity.nativeHookBinding, failure: errorRecord(error) },
|
|
45
|
+
try { await resolve(binding.stateDir, "hook.failed", { binding: identity.nativeHookBinding, failure: errorRecord(error) }, budget.remaining("failure_notification")); }
|
|
46
46
|
catch (notify) { error.details.receipt_error.notification_error = notify.code ?? "hook_notification_failed"; }
|
|
47
47
|
}
|
|
48
48
|
throw error;
|
|
@@ -35,7 +35,7 @@ function boundedText(value, budget, limit = 16000) {
|
|
|
35
35
|
// Lifecycle controls are not optional diagnostics. Reserve them before bulk
|
|
36
36
|
// messages/details, including nested typed causes, without copying authority
|
|
37
37
|
// that was truncated into a different command or continuation receipt.
|
|
38
|
-
const operationalDetailKeys = ["phase", "effect", "recoverable", "retryable_no_effect", "handoff_required", "storage_retry", "repeated_without_progress", "cleanup_unconfirmed", "native_dispatch_started", "disposition", "cli_admission_barrier", "native_pre_cli_refusal", "issued_invocation_id", "invocation_id", "event_key", "refusal_event_key", "hook_event_id", "work_id", "hook_retry_signature", "rejection_signature", "retry_command", "repair_command", "continuation", "retry_instruction", "recovery_instruction", "authority_truncated"];
|
|
38
|
+
const operationalDetailKeys = ["phase", "effect", "recoverable", "retryable_no_effect", "handoff_required", "storage_retry", "repeated_without_progress", "cleanup_unconfirmed", "native_dispatch_started", "disposition", "cli_admission_barrier", "native_pre_cli_refusal", "issued_invocation_id", "invocation_id", "event_key", "refusal_event_key", "hook_event_id", "work_id", "hook_retry_signature", "rejection_signature", "retry_command", "repair_command", "continuation", "retry_instruction", "correction_instruction", "recovery_instruction", "authority_truncated"];
|
|
39
39
|
function operationalDetails(value, budget) {
|
|
40
40
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
41
41
|
const entries = [];
|
|
@@ -3,9 +3,11 @@ export const MAX_OVERLOAD_RETRIES: 2;
|
|
|
3
3
|
export const MAX_RETRY_AFTER_MS: 30000;
|
|
4
4
|
export function classifyProviderError(native: unknown): string;
|
|
5
5
|
export function safeProviderError(native: unknown): Record<string, unknown>;
|
|
6
|
-
export type
|
|
6
|
+
export type RequestProof = { kind: "native_turn"; session_id: string; turn_id: string } | { kind: "stream_request"; daemon_instance: string; connection_id: string; operation_id: string; turn_generation: number; session_id: string; outbound_position: number; terminal_position: number };
|
|
7
|
+
export function streamRequestKey(proof: unknown): string | null;
|
|
8
|
+
export type CapacityRefusal = { policy: string; ordinal: number; session_id: string; turn_id: string; request_key?: string; request_proof?: RequestProof; first_observed_at: string; native_terminal_at?: string; native_terminal_basis?: string };
|
|
7
9
|
export function parseCapacityOperation(id: string): { root: string; ordinal: number };
|
|
8
|
-
export function normalizeProviderFailure(error: unknown, sessionId?: string, turnId?: string): null | { code: string; session_id: string; turn_id: string; native_error: Record<string, unknown>; observed_at: unknown; terminal_at: unknown; native_turn_items: unknown; primary_error: { details?: Record<string, unknown> } };
|
|
10
|
+
export function normalizeProviderFailure(error: unknown, sessionId?: string, turnId?: string): null | { code: string; session_id: string; turn_id: string; request_key: string; request_proof: RequestProof; native_error: Record<string, unknown>; observed_at: unknown; terminal_at: unknown; native_turn_items: unknown; primary_error: { details?: Record<string, unknown> } };
|
|
9
11
|
export function terminalProviderOverload(error: unknown, sessionId?: string, turnId?: string): boolean;
|
|
10
12
|
export function nativeItemsSettled(items: unknown): boolean;
|
|
11
13
|
export function capacityBackoff(error: unknown, ordinal: number, now?: number, random?: () => number): number;
|
|
@@ -11,10 +11,10 @@ export function classifyProviderError(native) {
|
|
|
11
11
|
const code = [native.code, native.type, native.codexErrorInfo?.code ?? native.codexErrorInfo].filter(value => typeof value === "string").join(" ");
|
|
12
12
|
const message = String(native.message ?? "");
|
|
13
13
|
const text = `${code} ${message}`;
|
|
14
|
-
const status = Number(native.status ?? native.statusCode ?? native.status_code ?? native.http_status);
|
|
14
|
+
const status = Number(native.status ?? native.statusCode ?? native.status_code ?? native.http_status ?? /\(code (\d{3})\)/i.exec(message)?.[1]);
|
|
15
15
|
if (status === 402 || /quota|usageLimitExceeded|sessionBudgetExceeded|billing|payment.required|balance.exhausted|insufficient.credit|resource_exhausted.*limit/i.test(text)) return "provider_quota_exhausted";
|
|
16
16
|
if (status === 401 || status === 403 || /provider_auth_failed|invalid.api.key|authentication|unauthorized|eligib|forbidden|access.denied|rejected.this.account/i.test(text)) return "provider_auth_failed";
|
|
17
|
-
if (/provider_capacity_transient|serverOverloaded|model.?capacity|at capacity|server.overload|model.overload|server.?busy/i.test(text) || /(?:^|\s)overloaded_error(?:\s|$)/i.test(code) || status === 503 || status === 529) return "provider_capacity_transient";
|
|
17
|
+
if (/provider_capacity_transient|serverOverloaded|model.?capacity|at capacity|no capacity available for model|server.overload|model.overload|server.?busy/i.test(text) || /(?:^|\s)overloaded_error(?:\s|$)/i.test(code) || status === 503 || status === 529) return "provider_capacity_transient";
|
|
18
18
|
if (/rateLimitExceeded|rate.limit/i.test(text) && (native.retryAfter !== undefined || native.retry_after !== undefined || Object.keys(native.headers ?? {}).some(key => key.toLowerCase() === "retry-after"))) return "provider_rate_limited";
|
|
19
19
|
return status === 429 || /provider_limit_unknown|\b429\b|rateLimitExceeded|rate.limit/i.test(text) ? "provider_limit_unknown" : "provider_failed";
|
|
20
20
|
}
|
|
@@ -45,6 +45,15 @@ export function parseCapacityOperation(id) {
|
|
|
45
45
|
return { root: match[1], ordinal };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Local ordered-stream identity is not a native Turn ID. */
|
|
49
|
+
export function streamRequestKey(proof) {
|
|
50
|
+
if (proof?.kind !== "stream_request" || ![proof.daemon_instance, proof.connection_id, proof.operation_id, proof.session_id].every(value => typeof value === "string" && value)
|
|
51
|
+
|| !Number.isSafeInteger(proof.turn_generation) || proof.turn_generation < 1
|
|
52
|
+
|| !Number.isSafeInteger(proof.outbound_position) || proof.outbound_position < 1
|
|
53
|
+
|| !Number.isSafeInteger(proof.terminal_position) || proof.terminal_position <= proof.outbound_position) return null;
|
|
54
|
+
return JSON.stringify(["stream_request", proof.daemon_instance, proof.connection_id, proof.operation_id, proof.session_id, proof.turn_generation, proof.outbound_position, proof.terminal_position]);
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
export function normalizeProviderFailure(error, expectedSessionId, expectedTurnId) {
|
|
49
58
|
let current = error;
|
|
50
59
|
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
|
@@ -52,14 +61,17 @@ export function normalizeProviderFailure(error, expectedSessionId, expectedTurnI
|
|
|
52
61
|
// refusal. Never classify transport loss or an active native retry here.
|
|
53
62
|
if (current.code === "provider_capacity_transient" || current.code === "provider_rate_limited") {
|
|
54
63
|
const details = current.details ?? {};
|
|
64
|
+
const streamKey = details.native_source === "agy.result" && details.turn_id === undefined && details.native_turn_id === undefined ? streamRequestKey(details.request_proof) : null;
|
|
65
|
+
const turnKey = streamKey ?? details.turn_id;
|
|
55
66
|
if (!["opencode.v2.session.execution", "agy.result", "acp.error_response", "droid.agent_turn_outcome", "grok.subagent.event"].includes(details.native_source) || details.terminal_status !== "failed"
|
|
56
67
|
|| details.will_retry === true || details.native_retry_active === true || details.provider_error?.willRetry === true
|
|
57
68
|
|| details.provider_error && !["provider_capacity_transient", "provider_rate_limited"].includes(classifyProviderError(details.provider_error))
|
|
58
69
|
|| typeof details.provider_session_id !== "string" || !details.provider_session_id
|
|
59
|
-
|| typeof
|
|
70
|
+
|| typeof turnKey !== "string" || !turnKey || (streamKey ? details.request_proof.session_id !== details.provider_session_id || details.native_turn_id !== undefined : details.native_turn_id !== details.turn_id)
|
|
60
71
|
|| expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
|
|
61
|
-
|| expectedTurnId !== undefined &&
|
|
62
|
-
return { code: "serverOverloaded", session_id: details.provider_session_id, turn_id:
|
|
72
|
+
|| expectedTurnId !== undefined && turnKey !== expectedTurnId) return null;
|
|
73
|
+
return { code: "serverOverloaded", session_id: details.provider_session_id, turn_id: turnKey, request_key: turnKey,
|
|
74
|
+
request_proof: streamKey ? details.request_proof : { kind: "native_turn", session_id: details.provider_session_id, turn_id: details.turn_id },
|
|
63
75
|
native_error: details.provider_error ?? { type: details.native_error_type, status: details.http_status }, observed_at: details.observed_at ?? null,
|
|
64
76
|
terminal_at: null, terminal_basis: null, native_turn_items: details.native_turn_items ?? null, primary_error: current };
|
|
65
77
|
}
|
|
@@ -70,7 +82,7 @@ export function normalizeProviderFailure(error, expectedSessionId, expectedTurnI
|
|
|
70
82
|
|| expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
|
|
71
83
|
|| typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
|
|
72
84
|
|| expectedTurnId !== undefined && details.turn_id !== expectedTurnId || typeof code !== "string") return null;
|
|
73
|
-
return { code, session_id: details.provider_session_id, turn_id: details.turn_id, native_error: native,
|
|
85
|
+
return { code, session_id: details.provider_session_id, turn_id: details.turn_id, request_key: details.turn_id, request_proof: { kind: "native_turn", session_id: details.provider_session_id, turn_id: details.turn_id }, native_error: native,
|
|
74
86
|
observed_at: details.observed_at ?? null, terminal_at: details.terminal_at ?? null, terminal_basis: details.terminal_basis ?? null,
|
|
75
87
|
native_turn_items: details.native_turn_items ?? null, primary_error: current };
|
|
76
88
|
}
|
|
@@ -118,6 +130,7 @@ export function refusalObservation(error, ordinal, firstObservedAt) {
|
|
|
118
130
|
throw policyError("capacity_continuation_conflict", "Native refusal observation time is invalid or changed");
|
|
119
131
|
const native = failure.terminal_at && Date.parse(failure.terminal_at);
|
|
120
132
|
return { policy: CAPACITY_POLICY, ordinal, session_id: failure.session_id, turn_id: failure.turn_id,
|
|
133
|
+
request_key: failure.request_key, request_proof: failure.request_proof,
|
|
121
134
|
first_observed_at: firstObservedAt, ...(Number.isFinite(native) && typeof failure.terminal_basis === "string" && failure.terminal_basis ? { native_terminal_at: failure.terminal_at, native_terminal_basis: failure.terminal_basis } : {}) };
|
|
122
135
|
}
|
|
123
136
|
|
|
@@ -22,13 +22,15 @@ export function sha256(value) {
|
|
|
22
22
|
|
|
23
23
|
export function hashJson(value) { return sha256(canonicalJson(value)); }
|
|
24
24
|
|
|
25
|
-
export async function writeJsonAtomic(file, value) {
|
|
25
|
+
export async function writeJsonAtomic(file, value, beforeCommit = () => {}) {
|
|
26
|
+
beforeCommit();
|
|
26
27
|
await mkdir(path.dirname(file), { recursive: true });
|
|
27
28
|
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
28
29
|
try {
|
|
29
30
|
const payload = await open(temporary, "wx", 0o600);
|
|
30
31
|
try { await payload.writeFile(`${JSON.stringify(value, null, 2)}\n`); await payload.sync(); }
|
|
31
32
|
finally { await payload.close(); }
|
|
33
|
+
beforeCommit();
|
|
32
34
|
await rename(temporary, file);
|
|
33
35
|
const directory = await open(path.dirname(file), "r");
|
|
34
36
|
try { await directory.sync(); }
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
export const SETUP_STARTUP_MS = 120_000;
|
|
4
5
|
export const SETUP_QUIET_MS = 30_000;
|
|
5
6
|
export const setupPhases = new Set(['ingress_entered', 'imports_ready', 'probe_started', 'probe_completed', 'dependency_check_completed', 'transfer_progress', 'identity_digest_progress', 'identity_digest_completed', 'native_initialized', 'ready']);
|
|
6
7
|
export function setupReporter(operationId = process.env.DD_EVAL_OPERATION_ID) {
|
|
7
8
|
if (!operationId) return () => {};
|
|
8
|
-
let sequence = 0;
|
|
9
|
+
let sequence = 0; const producerId = randomUUID();
|
|
9
10
|
return (phase, step = null) => {
|
|
10
11
|
if (!setupPhases.has(phase)) throw new Error('Invalid setup phase');
|
|
11
|
-
process.stderr.write(JSON.stringify({ kind: 'setup_phase', operation_id: operationId, phase, step, sequence: ++sequence, observed_at: new Date().toISOString() }) + '\n');
|
|
12
|
+
process.stderr.write(JSON.stringify({ kind: 'setup_phase', operation_id: operationId, producer_id: producerId, phase, step, sequence: ++sequence, observed_at: new Date().toISOString() }) + '\n');
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
export const publishSetup = setupReporter();
|
|
15
16
|
export function isSetupPhase(value) {
|
|
16
17
|
return value?.kind === 'setup_phase' && typeof value.operation_id === 'string' && value.operation_id.length > 0 && setupPhases.has(value.phase)
|
|
18
|
+
&& (!Object.hasOwn(value, 'producer_id') || typeof value.producer_id === 'string' && value.producer_id.length > 0 && value.producer_id.length <= 128)
|
|
17
19
|
&& Number.isSafeInteger(value.sequence) && value.sequence > 0 && value.sequence <= 1_000_000
|
|
18
20
|
&& (value.step === null || typeof value.step === 'string' && value.step.length <= 4096 || Number.isSafeInteger(value.step) && value.step >= 0)
|
|
19
21
|
&& typeof value.observed_at === 'string' && Number.isFinite(Date.parse(value.observed_at))
|
|
@@ -36,13 +38,13 @@ export function startupObserver(stateDir, daemonId) {
|
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
export function setupObserver(operationId) {
|
|
39
|
-
const startedAt = Date.now();
|
|
41
|
+
const startedAt = Date.now(); const sequences = new Map(), markers = new Set();
|
|
40
42
|
return value => {
|
|
41
43
|
if (typeof operationId !== 'string' || markers.size >= 4096 || !isSetupPhase(value) || value.operation_id !== operationId
|
|
42
|
-
|| value.sequence <=
|
|
43
|
-
const marker = JSON.stringify([value.phase, value.step]);
|
|
44
|
+
|| value.sequence <= (sequences.get(value.producer_id ?? null) ?? 0) || Date.parse(value.observed_at) < startedAt || Date.parse(value.observed_at) > Date.now()) return false;
|
|
45
|
+
const marker = JSON.stringify([value.producer_id ?? null, value.phase, value.step]);
|
|
44
46
|
if (markers.has(marker)) return false;
|
|
45
|
-
markers.add(marker);
|
|
47
|
+
markers.add(marker); sequences.set(value.producer_id ?? null, value.sequence); return true;
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
export function observationDetails(clock, phase, pid) {
|