@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.
- package/CHANGELOG.md +16 -0
- package/README.md +10 -1
- package/dist/build-info.json +7 -7
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +4 -3
- package/dist/cli/help.js +1 -0
- package/dist/cli/hook-ingress.js +1 -1
- package/dist/cli/run-cli.js +126 -193
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +51 -0
- package/dist/cli/runtime-process.js +128 -0
- package/dist/cli.js +8 -5
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
- package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
- package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
- package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
- package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +60 -13
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +151 -72
- package/dist/harness-runtime/lib/dd-agy.mjs +11 -23
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +9 -14
- package/dist/harness-runtime/lib/dd-codex.mjs +252 -80
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +14 -19
- package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +56 -30
- package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +18 -27
- package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +56 -53
- package/dist/harness-runtime/lib/dd-zcode.mjs +200 -111
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
- package/dist/harness-runtime/lib/managed-daemon.d.mts +10 -1
- package/dist/harness-runtime/lib/managed-daemon.mjs +228 -40
- package/dist/harness-runtime/lib/native-children.d.mts +13 -0
- package/dist/harness-runtime/lib/native-children.mjs +78 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
- package/dist/harness-runtime/lib/process-json.mjs +136 -38
- package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
- package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/run-control-receipt.schema.json +19 -0
- package/dist/services/code-checks.js +79 -9
- package/dist/services/codex-capacity.js +117 -41
- package/dist/services/codex-hook-delivery.js +2 -2
- package/dist/services/compatibility-preflight.js +4 -1
- package/dist/services/controller-fanout.js +9 -76
- package/dist/services/engines.js +70 -20
- package/dist/services/eval-snapshots.js +179 -40
- package/dist/services/external-work-launch.js +13 -8
- package/dist/services/harness-adapter.js +175 -11
- package/dist/services/hooks.js +65 -21
- package/dist/services/lifecycle-invocations.js +404 -32
- package/dist/services/managed-lease-monitor.js +128 -0
- package/dist/services/managed-processes.js +103 -47
- package/dist/services/merge-server.js +58 -21
- package/dist/services/recovery-observation-budget.js +67 -7
- package/dist/services/recovery-snapshot-database.js +7 -4
- package/dist/services/run-control-receipt.js +1 -1
- package/dist/services/run-control-worker.js +177 -118
- package/dist/services/run-control.js +99 -8
- package/dist/services/run-controller-adapter.js +169 -46
- package/dist/services/run-controller-capture.js +14 -2
- package/dist/services/run-controller-process.js +11 -4
- package/dist/services/run-controller-state.js +68 -7
- package/dist/services/run-controller.js +166 -94
- package/dist/services/run-fork.js +8 -3
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +1 -56
- package/dist/services/run-recovery.js +17 -13
- package/dist/services/runs.js +39 -6
- package/dist/services/runtime-budget.js +32 -15
- package/dist/services/runtime-scope-control.js +8 -0
- package/dist/services/runtime-scope-resume.js +30 -8
- package/dist/services/runtime-scope-worker.js +118 -73
- package/dist/services/runtime-service.js +6 -7
- package/dist/services/scope-writer-cleanup.js +3 -2
- package/dist/services/stage-context.js +135 -0
- package/dist/services/stage-lifecycle.js +3 -15
- package/dist/services/stage-pause.js +3 -2
- package/dist/services/usage.js +14 -3
- package/dist/services/vnext-code-review.js +10 -9
- package/dist/services/vnext-code.js +10 -8
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +75 -2
- package/dist/services/vnext-fanout.js +5 -0
- package/dist/services/vnext-merge.js +10 -9
- package/dist/services/vnext-plan-review.js +21 -17
- package/dist/services/vnext-plan.js +9 -7
- package/dist/services/vnext-protocolize.js +15 -9
- package/dist/services/vnext-specify.js +6 -6
- package/dist/services/work-registry.js +34 -4
- package/dist/services/workspace-bootstrap.js +14 -3
- package/dist/shared/errors.js +11 -6
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +80 -21
- package/package.json +7 -4
package/dist/storage/database.js
CHANGED
|
@@ -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
|
-
|
|
336
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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 --
|
|
32
|
-
"test:integration": "vitest run --
|
|
33
|
-
"test:
|
|
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",
|