@deksden-com/dd-flow-cli 0.9.0-beta.123 → 0.9.0-beta.127

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 (122) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +33 -1
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +4 -3
  6. package/dist/cli/help.js +1 -0
  7. package/dist/cli/hook-ingress.js +1 -1
  8. package/dist/cli/run-cli.js +126 -193
  9. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  10. package/dist/cli/runtime-maintenance-ingress.js +51 -0
  11. package/dist/cli/runtime-process.js +128 -0
  12. package/dist/cli.js +8 -5
  13. package/dist/domain/stage-catalog.js +1 -1
  14. package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
  15. package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
  16. package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
  17. package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
  18. package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
  19. package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
  20. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  21. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  22. package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +10 -0
  23. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -0
  24. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  25. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  26. package/dist/harness-runtime/lib/daemon-operations.mjs +159 -15
  27. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +215 -86
  28. package/dist/harness-runtime/lib/dd-agy.mjs +11 -13
  29. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +47 -23
  30. package/dist/harness-runtime/lib/dd-codex.mjs +257 -99
  31. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +32 -30
  32. package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
  33. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +96 -38
  34. package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
  35. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +54 -20
  36. package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
  37. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +81 -76
  38. package/dist/harness-runtime/lib/dd-zcode.mjs +204 -116
  39. package/dist/harness-runtime/lib/driver-recovery.mjs +12 -2
  40. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  41. package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
  42. package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
  43. package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
  44. package/dist/harness-runtime/lib/managed-daemon.d.mts +11 -0
  45. package/dist/harness-runtime/lib/managed-daemon.mjs +321 -65
  46. package/dist/harness-runtime/lib/native-children.d.mts +13 -0
  47. package/dist/harness-runtime/lib/native-children.mjs +78 -0
  48. package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
  49. package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
  50. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  51. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  52. package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
  53. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  54. package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
  55. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  56. package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
  57. package/dist/harness-runtime/lib/process-json.mjs +136 -38
  58. package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
  59. package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
  60. package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
  61. package/dist/runtime/context.js +8 -4
  62. package/dist/schemas/flow-run.schema.json +1 -0
  63. package/dist/schemas/plan-review-decision.schema.json +3 -3
  64. package/dist/schemas/run-control-receipt.schema.json +19 -0
  65. package/dist/services/code-checks.js +93 -27
  66. package/dist/services/codex-capacity.js +251 -74
  67. package/dist/services/codex-hook-delivery.js +2 -2
  68. package/dist/services/compatibility-preflight.js +4 -1
  69. package/dist/services/controller-fanout.js +9 -75
  70. package/dist/services/engines.js +70 -20
  71. package/dist/services/eval-snapshots.js +179 -40
  72. package/dist/services/external-work-launch.js +21 -9
  73. package/dist/services/harness-adapter.js +195 -25
  74. package/dist/services/hooks.js +65 -21
  75. package/dist/services/lifecycle-invocations.js +404 -32
  76. package/dist/services/managed-lease-monitor.js +128 -0
  77. package/dist/services/managed-processes.js +117 -53
  78. package/dist/services/merge-server.js +66 -23
  79. package/dist/services/recovery-observation-budget.js +67 -7
  80. package/dist/services/recovery-snapshot-database.js +7 -4
  81. package/dist/services/review-deferral.js +23 -0
  82. package/dist/services/run-control-receipt.js +1 -1
  83. package/dist/services/run-control-worker.js +177 -118
  84. package/dist/services/run-control.js +99 -8
  85. package/dist/services/run-controller-adapter.js +177 -51
  86. package/dist/services/run-controller-capture.js +14 -2
  87. package/dist/services/run-controller-process.js +11 -4
  88. package/dist/services/run-controller-recovery.js +17 -0
  89. package/dist/services/run-controller-state.js +68 -7
  90. package/dist/services/run-controller.js +207 -107
  91. package/dist/services/run-fork.js +8 -3
  92. package/dist/services/run-recovery-guard.js +75 -0
  93. package/dist/services/run-recovery-runtime.js +21 -30
  94. package/dist/services/run-recovery.js +17 -13
  95. package/dist/services/runs.js +40 -7
  96. package/dist/services/runtime-budget.js +32 -15
  97. package/dist/services/runtime-scope-control.js +8 -0
  98. package/dist/services/runtime-scope-resume.js +30 -8
  99. package/dist/services/runtime-scope-worker.js +118 -73
  100. package/dist/services/runtime-service.js +6 -7
  101. package/dist/services/scope-writer-cleanup.js +3 -2
  102. package/dist/services/stage-context.js +135 -0
  103. package/dist/services/stage-lifecycle.js +3 -15
  104. package/dist/services/stage-pause.js +3 -2
  105. package/dist/services/usage.js +14 -3
  106. package/dist/services/verification-matrix.js +30 -1
  107. package/dist/services/vnext-code-review.js +26 -13
  108. package/dist/services/vnext-code.js +10 -8
  109. package/dist/services/vnext-contracts.js +10 -1
  110. package/dist/services/vnext-execution-profile.js +75 -2
  111. package/dist/services/vnext-fanout.js +5 -0
  112. package/dist/services/vnext-merge.js +30 -11
  113. package/dist/services/vnext-plan-review.js +102 -35
  114. package/dist/services/vnext-plan.js +9 -7
  115. package/dist/services/vnext-protocolize.js +15 -9
  116. package/dist/services/vnext-specify.js +6 -6
  117. package/dist/services/work-registry.js +43 -6
  118. package/dist/services/workspace-bootstrap.js +14 -3
  119. package/dist/shared/errors.js +11 -6
  120. package/dist/storage/agent-output.js +26 -0
  121. package/dist/storage/database.js +80 -21
  122. package/package.json +7 -4
@@ -7,7 +7,7 @@ import { AppError } from "../shared/errors.js";
7
7
  import { getResourceDatabase } from "../storage/database.js";
8
8
  import { finishManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, processStartedAt, processTreeIsAlive, registerManagedProcess } from "./managed-processes.js";
9
9
  import { setFlowRunRecoveryPaused } from "./runs.js";
10
- import { assertRunMutationAllowed, recoveryGuard } from "./run-recovery.js";
10
+ import { assertRunMutationAllowed, assertRunRecoveryAllowed, recoveryGuard } from "./run-recovery.js";
11
11
  import { requireRun, requireController, controllerReceipt, appendEvent, digest, errorRecord } from "./run-controller-state.js";
12
12
  const launchId = (row) => `${row.controller_id}:owner-launch:${digest(row.owner_token)}`;
13
13
  const processOperation = (context, row) => `controller:${digest({ database: context.db.path, project: row.project_id, run: row.run_id })}`;
@@ -27,17 +27,24 @@ function launchRecord(context, row) {
27
27
  const value = JSON.parse(record.receipt_json);
28
28
  return value?.registration === "controller_self@1" ? { ...record, value } : null;
29
29
  }
30
- function assertLaunchAllowed(context, row) {
30
+ export function assertLaunchAllowed(context, row) {
31
31
  const run = requireRun(context, row.project_id, row.run_id);
32
32
  if (!["running", "paused", "waiting_for_user"].includes(run.status))
33
33
  throw new AppError("invalid_run_state", "A terminal RUN cannot start a physical controller", 1);
34
+ if (JSON.parse(row.state_json).recovery)
35
+ assertControllerRecoveryAllowed(context, row);
36
+ else
37
+ assertRunMutationAllowed(context, row.project_id, row.run_id, row.generation);
38
+ }
39
+ /** Existing owners must retain the recovery fence while finishing terminal capture/settlement. */
40
+ export function assertControllerRecoveryAllowed(context, row) {
34
41
  const state = JSON.parse(row.state_json);
35
42
  if (!state.recovery) {
36
- assertRunMutationAllowed(context, row.project_id, row.run_id, row.generation);
43
+ assertRunRecoveryAllowed(context, row.project_id, row.run_id, row.generation);
37
44
  return;
38
45
  }
39
46
  const guard = recoveryGuard(context, row.project_id, row.run_id);
40
- if (guard?.generation !== row.generation || guard.recovery_id !== state.recovery.recovery_id || !["sealed", "resuming"].includes(guard.status)
47
+ if (guard?.generation !== row.generation || guard.recovery_id !== state.recovery.recovery_id || !["sealed", "resuming", "recovered"].includes(guard.status)
41
48
  || !context.db.get("SELECT 1 FROM run_controls WHERE control_id = ? AND recovery_id = ? AND status = 'resuming'", [state.recovery.control_id, state.recovery.recovery_id]))
42
49
  throw new AppError("run_control_superseded", "Controller launch no longer owns the accepted recovery", 1);
43
50
  }
@@ -221,6 +221,23 @@ export async function resumeControllerSession(context, row, manifest, state, ass
221
221
  const binding = context.db.get("SELECT runtime_dispatch_json FROM run_recovery_bindings WHERE recovery_id = ?", [recovery.recovery_id]);
222
222
  return binding?.runtime_dispatch_json ? JSON.parse(binding.runtime_dispatch_json).capacity_intents ?? [] : [];
223
223
  },
224
+ retainedFailures: () => {
225
+ const binding = context.db.get("SELECT runtime_dispatch_json FROM run_recovery_bindings WHERE recovery_id = ?", [recovery.recovery_id]);
226
+ return binding?.runtime_dispatch_json ? JSON.parse(binding.runtime_dispatch_json).capacity_failures ?? [] : [];
227
+ },
228
+ retainFailure: (_id, refusal) => {
229
+ assertRecoveryOwner();
230
+ context.db.writeTransaction(() => {
231
+ const binding = context.db.get("SELECT runtime_dispatch_json FROM run_recovery_bindings WHERE recovery_id = ? AND daemon_id = ? AND status = 'prepared'", [recovery.recovery_id, daemonId]);
232
+ if (!binding)
233
+ throw new AppError("recovery_capacity_owner_changed", "Recovery binding changed before refusal retention", 1);
234
+ const retained = JSON.parse(binding.runtime_dispatch_json);
235
+ retained.capacity_failures = [...(retained.capacity_failures ?? []), refusal];
236
+ const changed = context.db.run("UPDATE run_recovery_bindings SET runtime_dispatch_json = ? WHERE recovery_id = ? AND daemon_id = ? AND status = 'prepared' AND runtime_dispatch_json = ?", [JSON.stringify(retained), recovery.recovery_id, daemonId, binding.runtime_dispatch_json]);
237
+ if (changed.changes !== 1)
238
+ throw new AppError("recovery_capacity_owner_changed", "Recovery binding changed during refusal retention", 1);
239
+ });
240
+ },
224
241
  inspectOperation: id => inspect(["daemon", "operation", "--state-dir", session.state_dir, "--operation-id", id, "--json"], `${id}:capacity-observe`),
225
242
  inspectSession: turnId => inspect(["session", "inspect", "--session-id", session.id, ...(turnId ? ["--turn-id", turnId] : []), ...sessionArguments(context, manifest, session)], `${dispatch.operation_id}:capacity-inspect`),
226
243
  assertCurrent: () => {
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
- import { AppError } from "../shared/errors.js";
2
+ import { AppError, errorRecord as structuredErrorRecord } from "../shared/errors.js";
3
3
  import { runtimeHarness } from "./execution-policy.js";
4
+ import { managedProcessRuntimeHome, managedProcessStatus, processTreeIsAlive } from "./managed-processes.js";
4
5
  export function requireRun(context, projectId, runId) {
5
6
  const run = context.db.get("SELECT id, project_id, project_root, workspace_root, run_root, status, index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
6
7
  if (!run?.run_root)
@@ -18,8 +19,66 @@ export function controllerReceipt(row) {
18
19
  const manifest = JSON.parse(row.manifest_json);
19
20
  return { ok: true, schema_id: "dd-flow/run-controller-receipt@1", controller: { controller_id: row.controller_id, request_id: row.request_id, project_id: row.project_id, run_id: row.run_id, generation: row.generation, runtime_budget: manifest.budget ?? null, status: row.status, process_id: row.process_id, owner_pid: row.owner_pid, state_dir: row.state_dir, stage: state.stage, sessions: state.sessions.map(session => ({ session_id: session.id, harness: runtimeHarness(session.profile), profile_id: session.profile.id, state_dir: session.state_dir, journal: session.journal, stopped: session.stopped === true })), error: row.last_error_json ? JSON.parse(row.last_error_json) : null } };
20
21
  }
22
+ /** Read-only causal diagnostics, not a settlement or capture authorization. */
23
+ export function terminalControllerFinalization(context, project, runId, runStatus) {
24
+ if (!["done", "failed", "cancelled"].includes(runStatus) || !context.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'run_controllers'"))
25
+ return null;
26
+ const row = context.db.get("SELECT * FROM run_controllers WHERE project_id = ? AND run_id = ? ORDER BY created_at DESC, controller_id DESC LIMIT 1", [project.id, runId]);
27
+ if (!row?.last_error_json || !["recovery_required", "capture_failed"].includes(row.status))
28
+ return null;
29
+ const error = JSON.parse(row.last_error_json);
30
+ if (error.details?.phase !== "terminal_finalization" || error.details.project_root !== project.root || error.details.run_id !== runId || error.details.controller_id !== row.controller_id)
31
+ return null;
32
+ let owner = "unknown";
33
+ try {
34
+ const manifest = JSON.parse(row.manifest_json);
35
+ const records = managedProcessStatus({ ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: manifest.resource_home } });
36
+ const record = records.find(item => item.id === row.process_id);
37
+ if (record && record.kind === "run-controller" && record.owner_id === row.controller_id && record.project_id === project.id && record.run_id === runId
38
+ && record.lease_token === row.process_lease_token && record.pid === row.owner_pid && record.pid && record.pid_started_at
39
+ && managedProcessRuntimeHome(record, context) === context.ddFlowHome) {
40
+ if (processTreeIsAlive(record))
41
+ owner = "live";
42
+ else if (["stopped", "failed"].includes(record.state) && record.finished_at)
43
+ owner = "absent";
44
+ }
45
+ // Any other still-owned controller record makes absence unproven.
46
+ if (owner === "absent" && records.some(item => item.id !== row.process_id && item.kind === "run-controller" && item.project_id === project.id && item.run_id === runId
47
+ && managedProcessRuntimeHome(item, context) === context.ddFlowHome && (!item.pid_started_at || !["stopped", "failed"].includes(item.state) || processTreeIsAlive(item))))
48
+ owner = "unknown";
49
+ }
50
+ catch { /* Missing registry or unreadable identity is uncertainty, never death. */ }
51
+ const control = context.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'run_controls'")
52
+ && context.db.get("SELECT 1 FROM run_controls WHERE project_id = ? AND run_id = ? AND status IN ('requested','settled','resuming')", [project.id, runId]);
53
+ const capture = context.db.get("SELECT 1 FROM run_controller_operations WHERE controller_id = ? AND generation = ? AND status = 'completed' AND json_extract(input_json, '$.action') = 'boundary_capture' AND json_type(input_json, '$.stage_entry') = 'null'", [row.controller_id, row.generation]);
54
+ return { schema_id: "dd-flow/terminal-finalization@1", issuer: "dd-flow", phase: "terminal_finalization", project_root: project.root, run_id: runId,
55
+ controller_id: row.controller_id, generation: row.generation, controller_status: row.status, run_status: runStatus,
56
+ status: !control && owner === "absent" ? "blocked" : "pending", owner, capture: capture ? "recorded" : "missing", error };
57
+ }
21
58
  export function appendEvent(context, id, type, data) { context.db.run("INSERT INTO run_controller_events (controller_id, event_type, data_json, created_at) VALUES (?, ?, ?, ?)", [id, type, JSON.stringify(data), context.now()]); }
22
- export function saveState(context, row, state) { context.db.run("UPDATE run_controllers SET state_json = ?, updated_at = ? WHERE controller_id = ? AND owner_token = ?", [JSON.stringify(state), context.now(), row.controller_id, row.owner_token]); }
59
+ export function saveState(context, row, state) {
60
+ const changed = context.db.run("UPDATE run_controllers SET state_json = ?, updated_at = ? WHERE controller_id = ? AND owner_token = ? AND generation = ? AND status NOT IN ('completed','cancelled','superseded')", [JSON.stringify(state), context.now(), row.controller_id, row.owner_token, row.generation]);
61
+ if (changed.changes !== 1)
62
+ throw new AppError("controller_owner_mismatch", "Controller state owner or generation changed", 1);
63
+ }
64
+ export function retainControllerCapacityIntent(context, row, state, intent, sessionId, kind, assertCurrent) {
65
+ const root = intent.root_operation_id;
66
+ if (!root || intent.session_id !== sessionId || !intent.refusal)
67
+ throw new AppError("capacity_continuation_conflict", "Capacity intent has no exact controller root/Session/refusal", 1);
68
+ const pending = { root_operation_id: root, predecessor: intent.predecessor, successor: intent.operation_id, session_id: sessionId, generation: row.generation, kind };
69
+ context.db.writeTransaction(() => {
70
+ assertCurrent();
71
+ const owner = context.db.get("SELECT status FROM run_controllers WHERE controller_id = ? AND owner_token = ? AND generation = ?", [row.controller_id, row.owner_token, row.generation]);
72
+ if (!owner || !["running", "waiting_for_user", "waiting_for_children", "waiting_for_capacity", "waiting_for_merge"].includes(owner.status))
73
+ throw new AppError("controller_owner_mismatch", "Controller no longer owns continuation preparation", 1);
74
+ const changed = context.db.run("UPDATE run_controller_operations SET status = 'failed', receipt_json = ?, updated_at = ? WHERE operation_id = ? AND controller_id = ? AND generation = ? AND status = 'outcome_unknown'", [JSON.stringify({ capacity_root: root, capacity_failure: intent.refusal, capacity_intent: intent }), context.now(), intent.predecessor, row.controller_id, row.generation]);
75
+ if (changed.changes !== 1)
76
+ throw new AppError("capacity_continuation_blocked", "Failed native operation changed before continuation", 1);
77
+ saveState(context, row, { ...state, pending_capacity: pending });
78
+ appendEvent(context, row.controller_id, "capacity_continuation", intent);
79
+ });
80
+ state.pending_capacity = pending;
81
+ }
23
82
  export function retainControllerContinuation(state, fingerprint, error) {
24
83
  if (state.last_continuation === fingerprint)
25
84
  throw new AppError(error.code, error.message, 1, error.details);
@@ -28,9 +87,11 @@ export function retainControllerContinuation(state, fingerprint, error) {
28
87
  export function digest(value) { return digestBytes(JSON.stringify(value)); }
29
88
  export function digestBytes(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
30
89
  export function errorRecord(error) {
31
- return {
32
- code: error instanceof AppError ? error.code : "controller_failed",
33
- message: error instanceof Error ? error.message : String(error),
34
- ...(error instanceof AppError && Object.keys(error.details).length ? { details: error.details } : {})
35
- };
90
+ const record = structuredErrorRecord(error);
91
+ let typed = false;
92
+ try {
93
+ typed = typeof error?.code === "string";
94
+ }
95
+ catch { /* Preserve a serializable controller failure for unreadable diagnostics. */ }
96
+ return { ...record, code: typed ? record.code : "controller_failed" };
36
97
  }