@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.
- package/CHANGELOG.md +26 -0
- package/README.md +33 -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/domain/stage-catalog.js +1 -1
- 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/codex-capacity-policy.d.mts +10 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -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 +159 -15
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +215 -86
- package/dist/harness-runtime/lib/dd-agy.mjs +11 -13
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +47 -23
- package/dist/harness-runtime/lib/dd-codex.mjs +257 -99
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +32 -30
- package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +96 -38
- package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +54 -20
- package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +81 -76
- package/dist/harness-runtime/lib/dd-zcode.mjs +204 -116
- package/dist/harness-runtime/lib/driver-recovery.mjs +12 -2
- 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 +11 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +321 -65
- 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/flow-run.schema.json +1 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/run-control-receipt.schema.json +19 -0
- package/dist/services/code-checks.js +93 -27
- package/dist/services/codex-capacity.js +251 -74
- package/dist/services/codex-hook-delivery.js +2 -2
- package/dist/services/compatibility-preflight.js +4 -1
- package/dist/services/controller-fanout.js +9 -75
- package/dist/services/engines.js +70 -20
- package/dist/services/eval-snapshots.js +179 -40
- package/dist/services/external-work-launch.js +21 -9
- package/dist/services/harness-adapter.js +195 -25
- 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 +117 -53
- package/dist/services/merge-server.js +66 -23
- package/dist/services/recovery-observation-budget.js +67 -7
- package/dist/services/recovery-snapshot-database.js +7 -4
- package/dist/services/review-deferral.js +23 -0
- 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 +177 -51
- package/dist/services/run-controller-capture.js +14 -2
- package/dist/services/run-controller-process.js +11 -4
- package/dist/services/run-controller-recovery.js +17 -0
- package/dist/services/run-controller-state.js +68 -7
- package/dist/services/run-controller.js +207 -107
- package/dist/services/run-fork.js +8 -3
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +21 -30
- package/dist/services/run-recovery.js +17 -13
- package/dist/services/runs.js +40 -7
- 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/verification-matrix.js +30 -1
- package/dist/services/vnext-code-review.js +26 -13
- 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 +30 -11
- package/dist/services/vnext-plan-review.js +102 -35
- 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 +43 -6
- 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
|
@@ -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
|
-
|
|
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) {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
}
|