@deksden-com/dd-flow-cli 0.9.0-beta.124 → 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 (113) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +10 -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/harness-runtime/bin/dd-agy.mjs +3 -2
  14. package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
  15. package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
  16. package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
  17. package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
  18. package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
  19. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  20. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  21. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  22. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  23. package/dist/harness-runtime/lib/daemon-operations.mjs +60 -13
  24. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +151 -72
  25. package/dist/harness-runtime/lib/dd-agy.mjs +11 -23
  26. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +9 -14
  27. package/dist/harness-runtime/lib/dd-codex.mjs +252 -80
  28. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +14 -19
  29. package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
  30. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +56 -30
  31. package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
  32. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +18 -27
  33. package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
  34. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +56 -53
  35. package/dist/harness-runtime/lib/dd-zcode.mjs +200 -111
  36. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  37. package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
  38. package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
  39. package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
  40. package/dist/harness-runtime/lib/managed-daemon.d.mts +10 -1
  41. package/dist/harness-runtime/lib/managed-daemon.mjs +228 -40
  42. package/dist/harness-runtime/lib/native-children.d.mts +13 -0
  43. package/dist/harness-runtime/lib/native-children.mjs +78 -0
  44. package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
  45. package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
  46. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  47. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  48. package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
  49. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  50. package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
  51. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  52. package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
  53. package/dist/harness-runtime/lib/process-json.mjs +136 -38
  54. package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
  55. package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
  56. package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
  57. package/dist/runtime/context.js +8 -4
  58. package/dist/schemas/run-control-receipt.schema.json +19 -0
  59. package/dist/services/code-checks.js +79 -9
  60. package/dist/services/codex-capacity.js +117 -41
  61. package/dist/services/codex-hook-delivery.js +2 -2
  62. package/dist/services/compatibility-preflight.js +4 -1
  63. package/dist/services/controller-fanout.js +9 -76
  64. package/dist/services/engines.js +70 -20
  65. package/dist/services/eval-snapshots.js +179 -40
  66. package/dist/services/external-work-launch.js +13 -8
  67. package/dist/services/harness-adapter.js +175 -11
  68. package/dist/services/hooks.js +65 -21
  69. package/dist/services/lifecycle-invocations.js +404 -32
  70. package/dist/services/managed-lease-monitor.js +128 -0
  71. package/dist/services/managed-processes.js +103 -47
  72. package/dist/services/merge-server.js +58 -21
  73. package/dist/services/recovery-observation-budget.js +67 -7
  74. package/dist/services/recovery-snapshot-database.js +7 -4
  75. package/dist/services/run-control-receipt.js +1 -1
  76. package/dist/services/run-control-worker.js +177 -118
  77. package/dist/services/run-control.js +99 -8
  78. package/dist/services/run-controller-adapter.js +169 -46
  79. package/dist/services/run-controller-capture.js +14 -2
  80. package/dist/services/run-controller-process.js +11 -4
  81. package/dist/services/run-controller-state.js +68 -7
  82. package/dist/services/run-controller.js +166 -94
  83. package/dist/services/run-fork.js +8 -3
  84. package/dist/services/run-recovery-guard.js +75 -0
  85. package/dist/services/run-recovery-runtime.js +1 -56
  86. package/dist/services/run-recovery.js +17 -13
  87. package/dist/services/runs.js +39 -6
  88. package/dist/services/runtime-budget.js +32 -15
  89. package/dist/services/runtime-scope-control.js +8 -0
  90. package/dist/services/runtime-scope-resume.js +30 -8
  91. package/dist/services/runtime-scope-worker.js +118 -73
  92. package/dist/services/runtime-service.js +6 -7
  93. package/dist/services/scope-writer-cleanup.js +3 -2
  94. package/dist/services/stage-context.js +135 -0
  95. package/dist/services/stage-lifecycle.js +3 -15
  96. package/dist/services/stage-pause.js +3 -2
  97. package/dist/services/usage.js +14 -3
  98. package/dist/services/vnext-code-review.js +10 -9
  99. package/dist/services/vnext-code.js +10 -8
  100. package/dist/services/vnext-contracts.js +10 -1
  101. package/dist/services/vnext-execution-profile.js +75 -2
  102. package/dist/services/vnext-fanout.js +5 -0
  103. package/dist/services/vnext-merge.js +10 -9
  104. package/dist/services/vnext-plan-review.js +21 -17
  105. package/dist/services/vnext-plan.js +9 -7
  106. package/dist/services/vnext-protocolize.js +15 -9
  107. package/dist/services/vnext-specify.js +6 -6
  108. package/dist/services/work-registry.js +34 -4
  109. package/dist/services/workspace-bootstrap.js +14 -3
  110. package/dist/shared/errors.js +11 -6
  111. package/dist/storage/agent-output.js +26 -0
  112. package/dist/storage/database.js +80 -21
  113. package/package.json +7 -4
@@ -5,7 +5,7 @@ import { channel } from "node:diagnostics_channel";
5
5
  import { createRequire } from "node:module";
6
6
  import { ensureDir } from "./paths.js";
7
7
  import { AppError } from "../shared/errors.js";
8
- import { executeStoreSchema, readStoreWriterContract, registerStoreWriter } from "./writer-contract.js";
8
+ import { executeStoreSchema, readStoreWriterContract, registerStoreWriter, STORE_WRITER_VERSION } from "./writer-contract.js";
9
9
  import { assertImportAdmission } from "./import-guard.js";
10
10
  import { withStoreCleanup, rollbackStoreTransaction } from "./native-cleanup.js";
11
11
  const workContinuationStorageSql = `
@@ -332,8 +332,9 @@ export function ensureWorkLaunchStorage(db) {
332
332
  `);
333
333
  }
334
334
  export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hookDeadlineAt) {
335
- if (hookDeadlineAt !== undefined && (!Number.isSafeInteger(hookDeadlineAt) || mode !== "hook"))
336
- throw new AppError("hook_deadline_invalid", "Hook deadline must be a finite absolute millisecond deadline", 1, { effect: "unknown" });
335
+ channel("dd-flow.sqlite.ingress").publish({ phase: "store_open", store_kind: "run" });
336
+ if (hookDeadlineAt !== undefined && !Number.isSafeInteger(hookDeadlineAt))
337
+ throw new AppError("hook_deadline_invalid", "Deadline must be a finite absolute millisecond deadline", 1, { effect: "unknown" });
337
338
  const deadline = hookDeadlineAt === undefined ? undefined : (phase) => {
338
339
  const remaining = hookDeadlineAt - Date.now();
339
340
  if (remaining <= 0)
@@ -461,18 +462,23 @@ export function withResourceReadSnapshot(resourceHome, read) {
461
462
  return withStoreCleanup(operation, () => { if (native.isOpen)
462
463
  native.close(); });
463
464
  }
464
- export function getResourceDatabase(resourceHome, mode = "initialize") {
465
+ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineAt) {
466
+ channel("dd-flow.sqlite.ingress").publish({ phase: "store_open", store_kind: "resource" });
467
+ const deadline = requestDeadline(deadlineAt);
468
+ deadline?.("resource_open");
465
469
  const dbPath = path.join(resourceHome, "runtime.sqlite");
466
470
  const cached = resourceDatabases.get(dbPath);
467
471
  if (mode === "read_existing" || mode === "read_isolated") {
468
472
  // Reuse a current transaction's view without exposing its write capability.
469
- if (cached && mode === "read_existing")
470
- return { ...emptyReadOnlyDatabase(dbPath), get: cached.get, all: cached.all };
473
+ if (cached && mode === "read_existing") {
474
+ const bounded = deadlineAt === undefined ? cached : cached.withDeadline(deadlineAt);
475
+ return { ...emptyReadOnlyDatabase(dbPath), get: bounded.get, all: bounded.all };
476
+ }
471
477
  if (!fs.existsSync(dbPath))
472
478
  return emptyReadOnlyDatabase(dbPath);
473
479
  const db = new DatabaseSync(dbPath, { readOnly: true });
474
480
  try {
475
- db.exec("PRAGMA busy_timeout = 4000; PRAGMA query_only = ON");
481
+ db.exec(`PRAGMA busy_timeout = ${deadline?.("resource_open") ?? 4000}; PRAGMA query_only = ON`);
476
482
  if (!db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get()) {
477
483
  db.close();
478
484
  return emptyReadOnlyDatabase(dbPath);
@@ -481,21 +487,32 @@ export function getResourceDatabase(resourceHome, mode = "initialize") {
481
487
  catch (error) {
482
488
  return closeAfterOpenFailure(db, error);
483
489
  }
484
- return databaseFacade(dbPath, false, db);
490
+ return databaseFacade(dbPath, false, db, undefined, deadline);
485
491
  }
486
- ensureDir(resourceHome);
487
- if (cached)
488
- return cached;
492
+ if (mode === "prepared_existing" && !fs.existsSync(dbPath))
493
+ throw new AppError("resource_storage_unprepared", "Prepare the resource registry before maintenance", 1);
494
+ if (cached) {
495
+ const bounded = deadlineAt === undefined ? cached : cached.withDeadline(deadlineAt);
496
+ if (mode === "prepared_existing")
497
+ assertResourceStoreReady({ prepare: sql => ({ get: (...params) => bounded.get(sql, params), all: (...params) => bounded.all(sql, params), run: (...params) => bounded.run(sql, params) }) });
498
+ return bounded;
499
+ }
500
+ if (mode !== "prepared_existing")
501
+ ensureDir(resourceHome);
489
502
  const db = new DatabaseSync(dbPath);
490
- db.exec("PRAGMA busy_timeout = 4000");
503
+ db.exec(`PRAGMA busy_timeout = ${deadline?.("resource_open") ?? 4000}`);
491
504
  try {
492
505
  registerStoreWriter(db);
493
506
  }
494
507
  catch (error) {
495
508
  return closeAfterOpenFailure(db, error);
496
509
  }
510
+ channel("dd-flow.sqlite.ingress").publish({ phase: "store_validation", store_kind: "resource" });
497
511
  try {
498
- configureDatabase(db, "initialize");
512
+ if (mode === "prepared_existing")
513
+ assertResourceStoreReady(db);
514
+ else
515
+ configureDatabase(db, "initialize", deadline);
499
516
  }
500
517
  catch (error) {
501
518
  return closeAfterOpenFailure(db, error);
@@ -503,7 +520,8 @@ export function getResourceDatabase(resourceHome, mode = "initialize") {
503
520
  db.exec("PRAGMA foreign_keys = ON");
504
521
  const fresh = !db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get();
505
522
  try {
506
- executeStoreSchema(db, `
523
+ if (mode !== "prepared_existing")
524
+ executeStoreSchema(db, `
507
525
  CREATE TABLE IF NOT EXISTS managed_processes (
508
526
  id TEXT PRIMARY KEY,
509
527
  kind TEXT NOT NULL,
@@ -552,7 +570,33 @@ export function getResourceDatabase(resourceHome, mode = "initialize") {
552
570
  db.close?.();
553
571
  });
554
572
  resourceDatabases.set(dbPath, database);
555
- return database;
573
+ return deadlineAt === undefined ? database : database.withDeadline(deadlineAt);
574
+ }
575
+ function requestDeadline(at) {
576
+ if (at === undefined)
577
+ return undefined;
578
+ if (!Number.isSafeInteger(at))
579
+ throw new AppError("maintenance_deadline_invalid", "Maintenance deadline must be an absolute millisecond timestamp", 1);
580
+ return phase => { const remaining = at - Date.now(); if (remaining <= 0)
581
+ throw new AppError("process_heartbeat_timeout", "Maintenance storage deadline expired", 1, { phase, deadline_at: at, effect: "unknown" }); return Math.max(1, Math.min(4000, remaining)); };
582
+ }
583
+ function assertResourceStoreReady(db) {
584
+ const version = readStoreWriterContract(db);
585
+ if (version === null)
586
+ throw new AppError("resource_storage_unprepared", "Prepare the resource registry writer contract before maintenance", 1);
587
+ if (version !== STORE_WRITER_VERSION)
588
+ throw new AppError("store_writer_incompatible", "This CLI cannot maintain the resource registry's required contract", 1, { required: version, supported: STORE_WRITER_VERSION });
589
+ for (const [table, columns] of Object.entries({ managed_processes: "id,kind,pid,pid_started_at,owner_id,lease_token,lease_expires_at,project_id,run_id,work_id,check_id,operation_id,stdout_path,stderr_path,state,started_at,updated_at,finished_at,termination_reason,metadata_json", managed_resources: "resource_kind,resource_key,owner_id,lease_token,lease_expires_at,process_id,metadata_json,created_at,updated_at" })) {
590
+ try {
591
+ db.prepare(`SELECT ${columns} FROM ${table} LIMIT 0`);
592
+ }
593
+ catch {
594
+ throw new AppError("resource_storage_unprepared", "Prepare the resource registry schema before maintenance", 1, { table });
595
+ }
596
+ }
597
+ for (const name of ["idx_managed_processes_lease", "idx_managed_resources_lease"])
598
+ if (!db.prepare("SELECT 1 FROM sqlite_schema WHERE type='index' AND name=?").get(name))
599
+ throw new AppError("resource_storage_unprepared", "Prepare resource registry indexes before maintenance", 1, { index: name });
556
600
  }
557
601
  function emptyReadOnlyDatabase(dbPath) {
558
602
  return {
@@ -570,6 +614,7 @@ function emptyReadOnlyDatabase(dbPath) {
570
614
  };
571
615
  }
572
616
  function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadline) {
617
+ let scopedDeadline = deadline;
573
618
  let reserved = false;
574
619
  let poisoned = false;
575
620
  let closed = false;
@@ -583,12 +628,13 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
583
628
  let readDepth = 0;
584
629
  const writerTiming = channel("dd-flow.sqlite.writer");
585
630
  const ingressPhase = channel("dd-flow.sqlite.ingress");
631
+ const storeKind = path.basename(dbPath) === "runtime.sqlite" ? "resource" : "run";
586
632
  const bound = (phase) => {
587
- const remaining = deadline?.(phase);
633
+ const remaining = scopedDeadline?.(phase);
588
634
  if (remaining !== undefined)
589
635
  db.exec(`PRAGMA busy_timeout = ${remaining}`);
590
636
  if (ingressPhase.hasSubscribers)
591
- ingressPhase.publish({ phase, ...(remaining !== undefined ? { remaining_ms: remaining } : {}) });
637
+ ingressPhase.publish({ phase, store_kind: storeKind, ...(remaining !== undefined ? { remaining_ms: remaining } : {}) });
592
638
  };
593
639
  const publishCommitted = () => {
594
640
  const pending = afterCommit;
@@ -611,7 +657,7 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
611
657
  db.exec("BEGIN IMMEDIATE");
612
658
  reserved = true;
613
659
  if (ingressPhase.hasSubscribers)
614
- ingressPhase.publish({ phase: "writer_reserved" });
660
+ ingressPhase.publish({ phase: "writer_reserved", store_kind: storeKind });
615
661
  };
616
662
  const rollback = (sql, original) => {
617
663
  try {
@@ -670,8 +716,8 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
670
716
  else
671
717
  db.exec("COMMIT");
672
718
  phase = "committed";
673
- if (ingressPhase.hasSubscribers)
674
- ingressPhase.publish({ phase: "committed" });
719
+ if (!nested && ingressPhase.hasSubscribers)
720
+ ingressPhase.publish({ phase: "committed", store_kind: storeKind });
675
721
  if (!nested && writerTiming.hasSubscribers)
676
722
  writerTiming.publish({ operation: operationName, wall_time_ms: performance.now() - startedAt, path: dbPath });
677
723
  if (!nested)
@@ -769,7 +815,7 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
769
815
  }
770
816
  }
771
817
  };
772
- return {
818
+ const facade = {
773
819
  path: dbPath,
774
820
  writable,
775
821
  close: closeOnce,
@@ -821,6 +867,19 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
821
867
  get: (sql, params = []) => { assertUsable(); bound("lookup"); return db.prepare(sql).get(...params); },
822
868
  all: (sql, params = []) => { assertUsable(); bound("lookup"); return db.prepare(sql).all(...params); }
823
869
  };
870
+ facade.withDeadline = (at) => {
871
+ const bounded = requestDeadline(at);
872
+ const scoped = (operation) => { const previous = scopedDeadline; scopedDeadline = phase => Math.min(bounded(phase), previous?.(phase) ?? 4000); try {
873
+ return operation();
874
+ }
875
+ finally {
876
+ scopedDeadline = previous;
877
+ if (db.isOpen)
878
+ db.exec("PRAGMA busy_timeout = 4000");
879
+ } };
880
+ return { ...facade, exec: sql => scoped(() => facade.exec(sql)), execSchema: sql => scoped(() => facade.execSchema(sql)), beginWriteTransaction: () => scoped(() => facade.beginWriteTransaction()), writeTransaction: (operation, name) => scoped(() => facade.writeTransaction(operation, name)), readSnapshot: operation => scoped(() => facade.readSnapshot(operation)), get: (sql, params) => scoped(() => facade.get(sql, params)), all: (sql, params) => scoped(() => facade.all(sql, params)), run: (sql, params) => scoped(() => facade.run(sql, params)) };
881
+ };
882
+ return facade;
824
883
  }
825
884
  function errorDetails(error) {
826
885
  const value = error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.124",
3
+ "version": "0.9.0-beta.127",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.23.0",
@@ -28,9 +28,12 @@
28
28
  "typecheck": "tsc --noEmit",
29
29
  "lint": "eslint . --max-warnings=0",
30
30
  "test": "pnpm test:release && pnpm test:integration && pnpm test:runtime-sensitive",
31
- "test:release": "node --test scripts/verify-release-build.test.mjs && vitest run --pool=forks --no-file-parallelism test/publish-release.test.ts",
32
- "test:integration": "vitest run --pool=forks --no-file-parallelism --exclude test/publish-release.test.ts --exclude test/run-control-worker.test.ts --exclude test/native-adapter-contracts.test.ts",
33
- "test:runtime-sensitive": "vitest run --pool=forks --no-file-parallelism test/run-control-worker.test.ts test/native-adapter-contracts.test.ts",
31
+ "test:release": "node --test scripts/verify-release-build.test.mjs && vitest run --project=release",
32
+ "test:integration": "vitest run --project='integration-*'",
33
+ "test:fast": "vitest run --project=integration-fast",
34
+ "test:core": "vitest run --project='integration-core-*'",
35
+ "test:flow": "vitest run --project=integration-flow",
36
+ "test:runtime-sensitive": "vitest run --project=runtime-sensitive",
34
37
  "changeset": "changeset",
35
38
  "version-packages": "changeset version",
36
39
  "release:candidate:pack": "node scripts/release-candidate.mjs pack",