@deksden-com/dd-flow-cli 0.9.0-beta.104 → 0.9.0-beta.106

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 (69) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/build-info.json +3 -3
  3. package/dist/cli/run-cli.js +23 -8
  4. package/dist/harness-runtime/bin/dd-agy.mjs +1 -1
  5. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +2 -2
  6. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -3
  7. package/dist/harness-runtime/lib/delegation-instructions.d.mts +3 -0
  8. package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -1
  9. package/dist/protocol/local-files.js +18 -9
  10. package/dist/runtime/context.js +2 -2
  11. package/dist/services/cleanup.js +81 -32
  12. package/dist/services/code-checks.js +18 -19
  13. package/dist/services/continuation-outcome.js +1 -1
  14. package/dist/services/controller-fanout.js +12 -4
  15. package/dist/services/eval-snapshots.js +69 -41
  16. package/dist/services/execution-policy.js +12 -1
  17. package/dist/services/external-work-launch.js +2 -1
  18. package/dist/services/hooks.js +153 -43
  19. package/dist/services/lanes.js +23 -18
  20. package/dist/services/lifecycle-command.js +11 -2
  21. package/dist/services/lifecycle-invocations.js +291 -54
  22. package/dist/services/managed-processes.js +35 -23
  23. package/dist/services/merge-queue.js +71 -67
  24. package/dist/services/merge-server.js +5 -12
  25. package/dist/services/migrations.js +28 -12
  26. package/dist/services/plan-runtime.js +8 -4
  27. package/dist/services/projects.js +3 -11
  28. package/dist/services/protocols.js +98 -68
  29. package/dist/services/recovery-snapshot-database.js +43 -8
  30. package/dist/services/run-control-worker.js +15 -32
  31. package/dist/services/run-control.js +3 -17
  32. package/dist/services/run-controller-capture.js +58 -27
  33. package/dist/services/run-controller-process.js +21 -30
  34. package/dist/services/run-controller-recovery.js +36 -37
  35. package/dist/services/run-controller.js +83 -88
  36. package/dist/services/run-fork.js +10 -6
  37. package/dist/services/run-projection.js +22 -64
  38. package/dist/services/run-recovery-runtime.js +25 -14
  39. package/dist/services/run-recovery.js +56 -54
  40. package/dist/services/runs.js +218 -160
  41. package/dist/services/runtime-budget.js +19 -45
  42. package/dist/services/runtime-scope-capture.js +8 -6
  43. package/dist/services/runtime-scope-control.js +49 -39
  44. package/dist/services/runtime-scope-resume.js +134 -122
  45. package/dist/services/runtime-scope-worker.js +48 -49
  46. package/dist/services/scope-writer-cleanup.js +26 -0
  47. package/dist/services/stage-context.js +6 -1
  48. package/dist/services/stage-lifecycle.js +1 -1
  49. package/dist/services/stage-pause.js +13 -29
  50. package/dist/services/stage-report-renderer.js +116 -6
  51. package/dist/services/usage.js +2 -9
  52. package/dist/services/vnext-code-review.js +10 -7
  53. package/dist/services/vnext-code.js +9 -17
  54. package/dist/services/vnext-merge.js +64 -21
  55. package/dist/services/vnext-plan-review.js +43 -30
  56. package/dist/services/vnext-plan.js +28 -24
  57. package/dist/services/vnext-protocolize.js +183 -20
  58. package/dist/services/vnext-specify.js +27 -19
  59. package/dist/services/work-registry.js +229 -150
  60. package/dist/storage/database.js +286 -56
  61. package/dist/storage/foreign-key-transaction.js +39 -0
  62. package/dist/storage/immutable-input.js +45 -0
  63. package/dist/storage/import-guard.js +102 -0
  64. package/dist/storage/native-cleanup.js +52 -0
  65. package/dist/storage/projection-lock.js +73 -0
  66. package/dist/storage/writer-contract.js +3 -3
  67. package/dist/storage/writer-migration.js +24 -41
  68. package/package.json +1 -1
  69. package/tools/repair-paused-run-status.mjs +48 -23
@@ -23,14 +23,13 @@ export function prepareLaneLock(context, input, action) {
23
23
  const { timeoutSeconds, pollIntervalSeconds } = prepareLaneWaitTiming(input);
24
24
  const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
25
25
  const lane = normalizeLane(input.lane);
26
- if (input.workspacePath !== undefined)
27
- requireMatchingLaneWorkspace(context, project.id, lane, input.workspacePath);
28
- else if (["acquire", "wait", "wait-acquire"].includes(action))
26
+ const workspace = input.workspacePath !== undefined ? requireMatchingLaneWorkspace(context, project.id, lane, input.workspacePath) : undefined;
27
+ if (input.workspacePath === undefined && ["acquire", "wait", "wait-acquire"].includes(action))
29
28
  throw new AppError("validation", "Lane lock requires a workspace path", 2);
30
29
  if (action === "acquire" || action === "wait-acquire")
31
30
  assertWorkerMayAcquireLaneLock(context, project.id, lane, input.workerId);
32
31
  const lock = action === "heartbeat" || action === "release" ? requireOwnedActiveLock(context, project.id, lane, input.workerId, input.leaseToken) : undefined;
33
- return { project, lane, ttlSeconds, timeoutSeconds, pollIntervalSeconds, lock };
32
+ return { project, lane, ttlSeconds, timeoutSeconds, pollIntervalSeconds, lock, workspace };
34
33
  }
35
34
  export function getLaneStatus(context, input) {
36
35
  const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
@@ -99,7 +98,12 @@ export function checkLaneWorkspace(context, input) {
99
98
  };
100
99
  }
101
100
  export function acquireLaneLock(context, input) {
102
- const { project, lane, ttlSeconds } = prepareLaneLock(context, input, "acquire");
101
+ const prepared = prepareLaneLock(context, input, "acquire");
102
+ return context.db.writeTransaction(() => acquirePreparedLaneLock(context, input, prepared));
103
+ }
104
+ function acquirePreparedLaneLock(context, input, prepared) {
105
+ const { project, lane, ttlSeconds } = prepared;
106
+ assertPreparedLaneLock(context, prepared, input.workerId);
103
107
  expireStaleLocks(context, project.id);
104
108
  const active = activeLaneLock(context, project.id, lane);
105
109
  if (active) {
@@ -141,6 +145,12 @@ export function acquireLaneLock(context, input) {
141
145
  ...(expired ? { expired_lock_id: expired.id } : {})
142
146
  };
143
147
  }
148
+ function assertPreparedLaneLock(context, prepared, workerId) {
149
+ if (JSON.stringify(laneRecord(context, prepared.project.id, prepared.lane)) !== JSON.stringify(prepared.workspace)
150
+ || context.db.get("SELECT root FROM projects WHERE id = ?", [prepared.project.id])?.root !== prepared.project.root)
151
+ throw new AppError("lane_workspace_changed", "Lane registration changed after workspace preparation", 1);
152
+ assertWorkerMayAcquireLaneLock(context, prepared.project.id, prepared.lane, workerId);
153
+ }
144
154
  export function heartbeatLaneLock(context, input) {
145
155
  const { project, lock: preparedLock, ttlSeconds } = prepareLaneLock(context, input, "heartbeat");
146
156
  const lock = preparedLock;
@@ -524,13 +534,15 @@ function ensureQueuedLaneWaiter(context, input) {
524
534
  return waiter;
525
535
  }
526
536
  function tryAcquireQueuedLaneLock(context, input) {
527
- context.db.beginWriteTransaction();
528
- try {
537
+ const prepared = prepareLaneLock(context, input, "wait-acquire");
538
+ if (prepared.project.id !== input.projectId || prepared.lane !== input.lane)
539
+ throw new AppError("lane_workspace_changed", "Lane identity changed while waiting", 1);
540
+ return context.db.writeTransaction(() => {
541
+ assertPreparedLaneLock(context, prepared, input.workerId);
529
542
  expireStaleLocks(context, input.projectId);
530
543
  expireStaleWaiters(context, input.projectId);
531
544
  const existingLock = activeLaneLock(context, input.projectId, input.lane);
532
545
  if (existingLock?.worker_id === input.workerId) {
533
- context.db.exec("COMMIT");
534
546
  return {
535
547
  acquired: true,
536
548
  payload: {
@@ -548,7 +560,6 @@ function tryAcquireQueuedLaneLock(context, input) {
548
560
  }
549
561
  const latest = latestLaneWaiterForWorker(context, input.projectId, input.lane, input.workerId);
550
562
  if (latest && ["cancelled", "expired"].includes(latest.status) && latest.updated_at >= input.invocationStartedAt) {
551
- context.db.exec("COMMIT");
552
563
  return {
553
564
  acquired: false,
554
565
  payload: {
@@ -572,14 +583,14 @@ function tryAcquireQueuedLaneLock(context, input) {
572
583
  });
573
584
  const head = firstQueuedLaneWaiter(context, input.projectId, input.lane);
574
585
  if (!existingLock && head?.id === waiter.id) {
575
- const lockResult = acquireLaneLock(context, {
586
+ const lockResult = acquirePreparedLaneLock(context, {
576
587
  projectRoot: input.projectRoot,
577
588
  lane: input.lane,
578
589
  workerId: input.workerId,
579
590
  workspacePath: input.workspacePath,
580
591
  ttlSeconds: input.ttlSeconds,
581
592
  reason: input.reason
582
- });
593
+ }, prepared);
583
594
  const now = context.now();
584
595
  context.db.run(`UPDATE lane_waiters
585
596
  SET status = 'acquired', acquired_at = ?, updated_at = ?
@@ -591,7 +602,6 @@ function tryAcquireQueuedLaneLock(context, input) {
591
602
  reason: input.reason,
592
603
  payload: { project_id: input.projectId, lane: input.lane, worker_id: input.workerId, waiter_id: waiter.id }
593
604
  });
594
- context.db.exec("COMMIT");
595
605
  return {
596
606
  acquired: true,
597
607
  payload: {
@@ -608,7 +618,6 @@ function tryAcquireQueuedLaneLock(context, input) {
608
618
  };
609
619
  }
610
620
  const position = queuedWaiterPosition(context, input.projectId, input.lane, waiter.id);
611
- context.db.exec("COMMIT");
612
621
  return {
613
622
  acquired: false,
614
623
  payload: {
@@ -622,11 +631,7 @@ function tryAcquireQueuedLaneLock(context, input) {
622
631
  position
623
632
  }
624
633
  };
625
- }
626
- catch (error) {
627
- context.db.exec("ROLLBACK");
628
- throw error;
629
- }
634
+ });
630
635
  }
631
636
  function resolveExistingPath(value) {
632
637
  return resolveProjectRoot(value);
@@ -160,8 +160,16 @@ function invocationFromSegment(segment, heredoc, wrapped, inheritedEnv) {
160
160
  index += 1;
161
161
  }
162
162
  }
163
- const executable = segment.words[index];
164
- if (!executable || path.basename(executable) !== "dd-flow" && executable !== "$DD_FLOW_BIN")
163
+ let executable = segment.words[index];
164
+ let nodeLauncher = false;
165
+ let launcher;
166
+ if (executable && path.basename(executable) === "node"
167
+ && (path.basename(segment.words[index + 1] ?? "") === "cli.js" || segment.words[index + 1] === "$DD_FLOW_BIN")) {
168
+ launcher = executable;
169
+ executable = segment.words[++index];
170
+ nodeLauncher = true;
171
+ }
172
+ if (!executable || path.basename(executable) !== "dd-flow" && executable !== "$DD_FLOW_BIN" && !(nodeLauncher && path.basename(executable) === "cli.js"))
165
173
  return null;
166
174
  const argv = segment.words.slice(index + 1);
167
175
  const operation = lifecycleOperation(argv);
@@ -170,6 +178,7 @@ function invocationFromSegment(segment, heredoc, wrapped, inheritedEnv) {
170
178
  return {
171
179
  operation,
172
180
  executable,
181
+ ...(launcher ? { launcher } : {}),
173
182
  argv,
174
183
  env,
175
184
  args: parseCommandArgs(argv.slice(operation === "recovery_accept" ? 3 : 2)),