@deksden-com/dd-flow-cli 0.9.0-beta.37 → 0.9.0-beta.39
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/dist/build-info.json +5 -5
- package/dist/cli/help.js +42 -1
- package/dist/cli/run-cli.js +145 -17
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
- package/dist/harness-runtime/bin/dd-codex.mjs +4 -3
- package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
- package/dist/harness-runtime/bin/dd-grok.mjs +4 -3
- package/dist/harness-runtime/bin/dd-opencode.mjs +4 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +4 -3
- package/dist/harness-runtime/lib/daemon-operations.mjs +48 -9
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +73 -20
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +57 -16
- package/dist/harness-runtime/lib/dd-codex.mjs +10 -2
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +3 -2
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +12 -6
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +4 -3
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +8 -5
- package/dist/harness-runtime/lib/managed-daemon.mjs +125 -6
- package/dist/harness-runtime/lib/process-json.mjs +1 -1
- package/dist/harness-runtime/lib/session-settlement.mjs +4 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/services/cleanup.js +7 -4
- package/dist/services/cli-operation-classifier.js +3 -1
- package/dist/services/code-checks.js +33 -7
- package/dist/services/controller-fanout.js +105 -0
- package/dist/services/engines.js +2 -0
- package/dist/services/eval-snapshots.js +347 -36
- package/dist/services/execution-policy.js +122 -6
- package/dist/services/external-work-launch.js +33 -12
- package/dist/services/harness-adapter.js +49 -2
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +54 -7
- package/dist/services/merge-server.js +53 -30
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +31 -0
- package/dist/services/protocols.js +9 -0
- package/dist/services/recovery-snapshot-database.js +103 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +297 -0
- package/dist/services/run-control.js +752 -0
- package/dist/services/run-controller-adapter.js +44 -0
- package/dist/services/run-controller-capture.js +117 -0
- package/dist/services/run-controller-process.js +178 -0
- package/dist/services/run-controller-recovery.js +219 -0
- package/dist/services/run-controller-state.js +31 -0
- package/dist/services/run-controller.js +538 -0
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +33 -9
- package/dist/services/runs.js +65 -13
- package/dist/services/runtime-budget.js +362 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +431 -0
- package/dist/services/runtime-scope-resume.js +543 -0
- package/dist/services/runtime-scope-stop.js +95 -0
- package/dist/services/runtime-scope-worker.js +165 -0
- package/dist/services/runtime-service.js +4 -0
- package/dist/services/vnext-code-review.js +1 -1
- package/dist/services/vnext-code.js +1 -1
- package/dist/services/vnext-execution-profile.js +2 -35
- package/dist/services/vnext-fanout.js +23 -7
- package/dist/services/vnext-plan-review.js +12 -9
- package/dist/services/vnext-plan.js +1 -1
- package/dist/services/vnext-protocolize.js +10 -4
- package/dist/services/vnext-specify.js +12 -5
- package/dist/services/work-registry.js +26 -8
- package/dist/services/workspace-bootstrap.js +10 -1
- package/dist/storage/database.js +160 -13
- package/dist/storage/writer-contract.js +76 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.39
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 803d2d0: Own managed Stage/Work execution, immutable boundary captures, mixed-profile handoffs and operator pause/stop/resume in the shared CLI runtime. Fence productive admission durably, reconcile exact native/process generations, preserve interrupted and unknown outcomes, and resume retained sessions without replaying completed work.
|
|
8
|
+
|
|
9
|
+
Control experiment-wide Subject, Judge and probe ownership through one retained resource registry, with detached control/recovery workers and explicit physical drain and release evidence. Require guarded shared-store migration and current runtime bindings; remove dependence on eval-owned productive adapters.
|
|
10
|
+
|
|
11
|
+
Discover native Codex children from spawn receipts and release terminal failed provider turns only after a fresh settled-tree observation.
|
|
12
|
+
|
|
13
|
+
## 0.9.0-beta.38
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 44e3c80: Allow control operations to recheck failed provider cleanup after a leaderless process group exits naturally. Coalesce concurrent finalization retries, preserve the original exit and process lease, and keep live or unproven groups blocked without sending them signals.
|
|
18
|
+
|
|
3
19
|
## 0.9.0-beta.37
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/build-info.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
3
|
+
"cli_version": "0.9.0-beta.39",
|
|
4
|
+
"cli_commit": "adabaee64d3de604908351aa1ee048208c1dd249",
|
|
5
|
+
"built_at": "2026-09-09T16:08:31.531Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
|
-
"version": "4.0
|
|
8
|
-
"commit": "
|
|
7
|
+
"version": "4.1.0",
|
|
8
|
+
"commit": "ef349bf47cba1c987468e51d73a0dbadbd48dc1f",
|
|
9
9
|
"flow_contract": "dd-flow-canonical-2026-08",
|
|
10
10
|
"repo_root": "/Users/deksden/Documents/_Projects/dd-memorybank",
|
|
11
11
|
"memorybank_root": "/Users/deksden/Documents/_Projects/dd-memorybank/.memory-bank",
|
package/dist/cli/help.js
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
const helpPages = new Map([
|
|
2
|
+
["runtime scope", `Usage:
|
|
3
|
+
dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID --json
|
|
4
|
+
dd-flow runtime scope status --scope-id EVAL_ID --json
|
|
5
|
+
dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID --json
|
|
6
|
+
dd-flow runtime scope control --scope-id EVAL_ID --request-id ID --mode pause|stop [--manifest-json JSON] --json
|
|
7
|
+
dd-flow runtime scope resume --scope-id EVAL_ID --request-id ID --generation N --capture-key SHA256 --json
|
|
8
|
+
|
|
9
|
+
Use the EVAL manifest's retained DD_FLOW_RESOURCE_HOME. Commands select exactly
|
|
10
|
+
one scope across its registered runtime homes; neighboring scopes are untouched.
|
|
11
|
+
fence durably blocks new provider turns and owned baseline checks. It does not
|
|
12
|
+
stop existing work. status reports retained ownership, not proof of settlement.
|
|
13
|
+
stop fences first and requests owned daemon-tree and baseline-process cleanup.
|
|
14
|
+
Inspect settled and each node's evidence: ok alone does not prove cleanup.
|
|
15
|
+
Unproven ownership or cleanup remains pending; repeat stop to observe settlement.
|
|
16
|
+
The fence/stop route is terminal cancellation and has no resume command.
|
|
17
|
+
control records recoverable scope intent and starts a detached drain owner.
|
|
18
|
+
The EVAL resolver supplies a runtime-scope-manifest@1 declaration with the source
|
|
19
|
+
manifest path, SHA-256 and execution IDs, including queued executions. Declaration
|
|
20
|
+
changes are rejected. resume accepts the exact captured generation and starts
|
|
21
|
+
detached recovery preparation. It does not yet release scope admission: inspect
|
|
22
|
+
dispatch_blocked and pending reasons; accepted preparation is not resumed work.
|
|
23
|
+
RUN pause/stop/resume uses dd-flow run control.`],
|
|
24
|
+
["storage", `Usage:
|
|
25
|
+
dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline --json
|
|
26
|
+
|
|
27
|
+
Drain all local and external runtime owners before using --offline. This command
|
|
28
|
+
migrates one existing database, not a whole runtime or retained provider Session.
|
|
29
|
+
It refuses unresolved ownership rows, preserves a verified WAL-safe backup, and
|
|
30
|
+
fences unaware old writers. It does not initialize the default DD_FLOW_HOME.
|
|
31
|
+
The backup path must be new; existing backups are never overwritten. An exact
|
|
32
|
+
retry of a committed migration returns its receipt after verifying the backup.
|
|
33
|
+
If no committed receipt exists, retain the failed backup and choose a new path.`],
|
|
2
34
|
[
|
|
3
35
|
"",
|
|
4
36
|
`dd-flow - mechanical control layer for dd-flow workflows
|
|
@@ -33,6 +65,8 @@ Core commands:
|
|
|
33
65
|
session register/status/stop Register flow sessions and stop workers.
|
|
34
66
|
dashboard data/render/open/refresh Render, refresh, open, and inspect project/global dashboards.
|
|
35
67
|
schema validate Validate canonical dd-flow JSON data contracts.
|
|
68
|
+
storage migrate-writer Explicitly back up and fence a drained SQLite store.
|
|
69
|
+
runtime scope fence/status/stop Fence, inspect, or cancel one retained EVAL scope.
|
|
36
70
|
memory permissions preflight Check Memory Bank write/read permissions.
|
|
37
71
|
integration cmux status Inspect optional cmux integration.
|
|
38
72
|
codex home/hooks/hook Manage Codex homes, hooks, and hook events.
|
|
@@ -153,8 +187,11 @@ Usage:
|
|
|
153
187
|
dd-flow run start --project-root <root> [--workspace-root <checkout>] --flow-kind <kind> --subject-type <type> --subject-id <id> --slug <slug> [--preset compact|normal|full] [--task-profile-file <file>] [--protocol-override-file <file>] [--flag key=value] [--next-action <text>] --json
|
|
154
188
|
dd-flow id next --type protocol|run --project-root <root> --slug <slug> --json
|
|
155
189
|
dd-flow run status <RUN-ID|RUN-short-id> --project-root <root> --json
|
|
190
|
+
dd-flow run control status --run <RUN-ID> --project-root <root> --json
|
|
191
|
+
dd-flow run drive launch --run <RUN-ID> --project-root <root> --request-id <id> --context-file <file> --context-sha256 <sha256> [--stop-after <stage>] [--capture-root <directory>] --json
|
|
192
|
+
dd-flow run drive status --run <RUN-ID> --project-root <root> [--after <sequence>] --json
|
|
156
193
|
dd-flow run list --project-root <root> --json
|
|
157
|
-
dd-flow run prepare-vnext-specify --project-root <root> --slug <slug> --json
|
|
194
|
+
dd-flow run prepare-vnext-specify --project-root <root> --slug <slug> [--execution-routing-file <file>] --json
|
|
158
195
|
dd-flow run recovery inspect|begin|seal|capture|resume <RUN-ID|RUN-short-id> --project-root <root> --json
|
|
159
196
|
dd-flow run snapshot create <RUN-ID|RUN-short-id> (--stage-entry <stage>|--candidate|--incomplete|--recovery-id <RCV-ID>) --project-root <root> --output <directory> --json
|
|
160
197
|
dd-flow run snapshot restore --snapshot <directory> --project-root <prepared-root> [--recovery-id <RCV-ID>] --json
|
|
@@ -171,8 +208,12 @@ Usage:
|
|
|
171
208
|
|
|
172
209
|
RUN-* is the execution envelope for one concrete flow launch. Semantic truth remains in protocol, experiment, DEF, scenario, evidence, and Memory Bank documents. New run artifacts are stored under DD_FLOW_HOME/projects/<PRJ-ID-slug>/runs/<RUN-ID-slug>/ with one run.json projection and an append-only timeline.jsonl. Timeline is the compact full run report: summary is always present, while --hide may suppress bulky sections. Use \`dd-flow stat usage --run …\` to reread registered local Codex transcript counters and tool-call facts and refresh the current usage projection; unavailable data is explicit rather than zero.
|
|
173
210
|
|
|
211
|
+
run start and prepare-vnext-specify accept --execution-routing-file for vNext RUNs: dd-flow/execution-routing@1 with an execution object selects the complete routing policy from existing agent-profiles. RUN creation freezes the input and selected profiles without changing project settings.
|
|
212
|
+
run snapshot restore also accepts --execution-routing-file for a new stage-entry experiment. Its fresh DD_FLOW_HOME may contain only agent-profiles and the empty database; selected profiles are read before import, previous RUN policy is archived, and existing Work overrides must resolve against the selected frozen profiles. Recovery imports reject this option.
|
|
174
213
|
run prepare-vnext-specify allocates an otherwise unstarted vNext RUN for the canonical SPECIFY entry checkpoint. run recovery makes an interrupted RUN read-only, requires an explicit settled-tree receipt, captures immutable recovery evidence, then explicitly returns delegated Work to the ready graph. run snapshot is the eval-only checkpoint boundary: it snapshots one quiescent RUN, its dedicated DD_FLOW_HOME, and the exact project tree including its Git repository, then restores them into a fresh dedicated runtime. It is not a general runtime backup command and fails closed for a shared runtime.
|
|
175
214
|
|
|
215
|
+
Managed drive is not yet native-provider qualified. With --capture-root, its detached controller fences RUN writers and captures each completed Stage before dispatching its successor. The capture root must be outside the runtime, project and workspace. Status events expose boundary_captured receipts; capturing means writers are still fenced, and capture_failed retains the fence for operator reconciliation. External snapshot creation is rejected while a managed controller owns a live RUN.
|
|
216
|
+
|
|
176
217
|
Examples:
|
|
177
218
|
dd-flow run start --project-root "$PWD" --flow-kind mb_sdlc --subject-type protocol --subject-id PRT-001-demo --slug demo --json
|
|
178
219
|
dd-flow id next --type run --project-root "$PWD" --slug demo --json
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createContext, createRouterContext } from "../runtime/context.js";
|
|
4
|
+
import { migrateStoreWriter } from "../storage/writer-migration.js";
|
|
4
5
|
import { helpForArgs } from "./help.js";
|
|
5
6
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
6
7
|
import { writeJson } from "../shared/json.js";
|
|
@@ -50,9 +51,16 @@ import { finishStage, startStage } from "../services/stage-lifecycle.js";
|
|
|
50
51
|
import { addWorkBatch, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
51
52
|
import { launchExternalWork } from "../services/external-work-launch.js";
|
|
52
53
|
import { createEvalBootstrapSnapshot, createEvalRunSnapshot, prepareVnextSpecifyRun, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
|
|
53
|
-
import { acceptRunRecovery, beginRunRecovery, inspectRunRecovery, recordRecoveryCapture, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
|
|
54
|
+
import { acceptRunRecovery, assertRunMutationAllowed, beginRunRecovery, inspectRunRecovery, recordRecoveryCapture, recoveryGuard, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
|
|
55
|
+
import { recoveryRuntimeAdmission } from "../services/run-recovery-runtime.js";
|
|
54
56
|
import { loadExternalStageContext } from "../services/stage-context.js";
|
|
55
|
-
import { stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessStatus, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
|
|
57
|
+
import { stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
|
|
58
|
+
import { fenceRuntimeBudget, runtimeBudgetStatus, admitRuntimeCheck, parseRuntimeBudget, reserveProviderTurn, releaseProviderTurns } from "../services/runtime-budget.js";
|
|
59
|
+
import { stopRuntimeScope } from "../services/runtime-scope-stop.js";
|
|
60
|
+
import { launchRuntimeScopeControl, launchRuntimeScopeResume, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
|
|
61
|
+
import { launchRunController, resumeRunController, serveRunController, runControllerStatus, answerRunController, supplyRunControllerContext } from "../services/run-controller.js";
|
|
62
|
+
import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
|
|
63
|
+
import { requestRunControl, serveRunControl } from "../services/run-control-worker.js";
|
|
56
64
|
import { startRuntimeService } from "../services/runtime-service.js";
|
|
57
65
|
const defaultIo = {
|
|
58
66
|
stdout: process.stdout,
|
|
@@ -159,6 +167,7 @@ function resolveProjectRootBeforeRouting(args, env) {
|
|
|
159
167
|
function databaseModeForCommand(args, classification) {
|
|
160
168
|
const [family, command] = args;
|
|
161
169
|
if (family === "engine" ||
|
|
170
|
+
family === "storage" && command === "migrate-writer" ||
|
|
162
171
|
family === "version" ||
|
|
163
172
|
family === "schema" ||
|
|
164
173
|
family === "canon" && command === "resolve" ||
|
|
@@ -510,6 +519,13 @@ function shortSha(value) {
|
|
|
510
519
|
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args)) {
|
|
511
520
|
const [family, command, ...rest] = args;
|
|
512
521
|
const parsed = parseArgs(rest);
|
|
522
|
+
if (family === "storage" && command === "migrate-writer") {
|
|
523
|
+
if (parsed.positional.length || [...parsed.options.keys()].some(key => !["database", "backup", "offline"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
524
|
+
throw new AppError("usage", "Usage: dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline", 2);
|
|
525
|
+
if (parsed.options.get("offline")?.[0] !== flagValue)
|
|
526
|
+
throw new AppError("usage", "Writer migration requires --offline after draining all local and external runtime owners", 2);
|
|
527
|
+
return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
|
|
528
|
+
}
|
|
513
529
|
if (family === "engine") {
|
|
514
530
|
return dispatchEngine(context, command, parsed, classification);
|
|
515
531
|
}
|
|
@@ -898,6 +914,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
898
914
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
899
915
|
const runId = requiredPosition(parsed, 0, "run-id");
|
|
900
916
|
const stage = requiredOption(parsed, "stage");
|
|
917
|
+
const controlledReply = (result) => controlLifecycleReply(context, { projectRoot, runId }, result);
|
|
901
918
|
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
902
919
|
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
903
920
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
@@ -925,56 +942,56 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
925
942
|
if (!resultFile && !resultStdin) {
|
|
926
943
|
throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
|
|
927
944
|
}
|
|
928
|
-
return finishVnextSpecifyStage(context, {
|
|
945
|
+
return controlledReply(await finishVnextSpecifyStage(context, {
|
|
929
946
|
projectRoot,
|
|
930
947
|
runId,
|
|
931
948
|
...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
|
|
932
949
|
outcome: outcome
|
|
933
|
-
});
|
|
950
|
+
}));
|
|
934
951
|
}
|
|
935
952
|
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
936
953
|
if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
937
954
|
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
938
955
|
}
|
|
939
|
-
return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
|
|
956
|
+
return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile }));
|
|
940
957
|
}
|
|
941
958
|
if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
942
959
|
if (optionalOption(parsed, "code-work-batch-file"))
|
|
943
960
|
throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
|
|
944
|
-
return finishVnextPlan(context, { projectRoot, runId });
|
|
961
|
+
return controlledReply(await finishVnextPlan(context, { projectRoot, runId }));
|
|
945
962
|
}
|
|
946
963
|
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
947
964
|
if (resultStdin || hasOption(parsed, "outcome"))
|
|
948
965
|
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
949
966
|
const decisionFile = optionalOption(parsed, "decision-file");
|
|
950
|
-
return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
|
|
967
|
+
return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) }));
|
|
951
968
|
}
|
|
952
969
|
if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
953
970
|
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
954
971
|
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
955
972
|
}
|
|
956
|
-
return await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) });
|
|
973
|
+
return controlledReply(await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
|
|
957
974
|
}
|
|
958
975
|
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
959
976
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
960
977
|
throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
|
|
961
|
-
return await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) });
|
|
978
|
+
return controlledReply(await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
|
|
962
979
|
}
|
|
963
980
|
if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
964
981
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
965
982
|
throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
|
|
966
|
-
return await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) });
|
|
983
|
+
return controlledReply(await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
|
|
967
984
|
}
|
|
968
985
|
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
|
|
969
986
|
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
970
987
|
}
|
|
971
|
-
return finishStage(context, {
|
|
988
|
+
return controlledReply(await finishStage(context, {
|
|
972
989
|
projectRoot,
|
|
973
990
|
runId,
|
|
974
991
|
stage,
|
|
975
992
|
...(dir ? { dir } : {}),
|
|
976
993
|
...(semanticFile ? { semanticFile } : {})
|
|
977
|
-
});
|
|
994
|
+
}));
|
|
978
995
|
}
|
|
979
996
|
if (family === "stage" && command === "pause") {
|
|
980
997
|
if (!hasOption(parsed, "question-stdin"))
|
|
@@ -1056,6 +1073,43 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1056
1073
|
return dispatchSession(context, command, parsed);
|
|
1057
1074
|
}
|
|
1058
1075
|
if (family === "run") {
|
|
1076
|
+
if (command === "control") {
|
|
1077
|
+
const action = requiredPosition(parsed, 0, "control action");
|
|
1078
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1079
|
+
if (action === "status")
|
|
1080
|
+
return runControlStatus(context, scope);
|
|
1081
|
+
if (action === "resume") {
|
|
1082
|
+
if (["force", "grace-ms", "escalate"].some(option => hasOption(parsed, option)))
|
|
1083
|
+
throw new AppError("usage", "Resume does not accept stop or pause escalation options", 2);
|
|
1084
|
+
return await resumeRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), controlId: requiredOption(parsed, "from"), ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {}) });
|
|
1085
|
+
}
|
|
1086
|
+
if (action === "serve")
|
|
1087
|
+
return await serveRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id") });
|
|
1088
|
+
if (action === "pause" || action === "stop")
|
|
1089
|
+
return await requestRunControl(context, {
|
|
1090
|
+
...scope, action, requestId: requiredOption(parsed, "request-id"),
|
|
1091
|
+
...(hasOption(parsed, "force") ? { force: true } : {}),
|
|
1092
|
+
...(hasOption(parsed, "grace-ms") ? { graceMs: Number(requiredOption(parsed, "grace-ms")) } : {}),
|
|
1093
|
+
...(hasOption(parsed, "escalate") ? { escalate: requiredOption(parsed, "escalate") } : {}),
|
|
1094
|
+
...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {})
|
|
1095
|
+
});
|
|
1096
|
+
throw new AppError("usage", "Use: dd-flow run control pause|stop|resume|status", 2);
|
|
1097
|
+
}
|
|
1098
|
+
if (command === "drive") {
|
|
1099
|
+
const action = requiredPosition(parsed, 0, "drive action");
|
|
1100
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1101
|
+
if (action === "status")
|
|
1102
|
+
return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
|
|
1103
|
+
if (action === "launch")
|
|
1104
|
+
return await launchRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), contextFile: requiredOption(parsed, "context-file"), contextSha256: requiredOption(parsed, "context-sha256"), ...(optionalOption(parsed, "stop-after") ? { stopAfter: optionalOption(parsed, "stop-after") } : {}), ...(optionalOption(parsed, "capture-root") ? { captureRoot: optionalOption(parsed, "capture-root") } : {}) });
|
|
1105
|
+
if (action === "serve")
|
|
1106
|
+
return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
|
|
1107
|
+
if (action === "answer")
|
|
1108
|
+
return answerRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), pauseId: requiredOption(parsed, "pause-id"), answerFile: requiredOption(parsed, "answer-file") });
|
|
1109
|
+
if (action === "context")
|
|
1110
|
+
return supplyRunControllerContext(context, { ...scope, controllerId: requiredOption(parsed, "controller-id"), requestId: requiredOption(parsed, "request-id"), stage: requiredOption(parsed, "stage"), contextFile: requiredOption(parsed, "context-file"), contextSha256: requiredOption(parsed, "context-sha256") });
|
|
1111
|
+
throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
|
|
1112
|
+
}
|
|
1059
1113
|
return dispatchRun(context, command, parsed);
|
|
1060
1114
|
}
|
|
1061
1115
|
if (family === "stat") {
|
|
@@ -1528,13 +1582,14 @@ function dispatchRun(context, command, parsed) {
|
|
|
1528
1582
|
if (action === "restore")
|
|
1529
1583
|
return restoreEvalRunSnapshot(context, {
|
|
1530
1584
|
snapshot: requiredOption(parsed, "snapshot"),
|
|
1585
|
+
executionRoutingFile: optionalOption(parsed, "execution-routing-file"),
|
|
1531
1586
|
...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}),
|
|
1532
1587
|
projectRoot: requiredOption(parsed, "project-root")
|
|
1533
1588
|
});
|
|
1534
1589
|
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore | dd-flow run snapshot create <RUN> (--stage-entry <stage>|--candidate) --project-root <path> --output <directory> | dd-flow run snapshot restore --snapshot <directory> --project-root <path>", 2);
|
|
1535
1590
|
}
|
|
1536
1591
|
if (command === "prepare-vnext-specify") {
|
|
1537
|
-
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
|
|
1592
|
+
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
|
|
1538
1593
|
}
|
|
1539
1594
|
if (command === "start") {
|
|
1540
1595
|
if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
|
|
@@ -1551,6 +1606,7 @@ function dispatchRun(context, command, parsed) {
|
|
|
1551
1606
|
nextAction: optionalOption(parsed, "next-action"),
|
|
1552
1607
|
preset: optionalOption(parsed, "preset"),
|
|
1553
1608
|
taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
|
|
1609
|
+
executionRoutingFile: optionalOption(parsed, "execution-routing-file"),
|
|
1554
1610
|
...(protocolOverrides ? { protocolOverrides } : {}),
|
|
1555
1611
|
runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
|
|
1556
1612
|
});
|
|
@@ -1666,22 +1722,64 @@ function dispatchRun(context, command, parsed) {
|
|
|
1666
1722
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1667
1723
|
}
|
|
1668
1724
|
async function dispatchRuntime(context, command, parsed, progress) {
|
|
1725
|
+
if (command === "scope") {
|
|
1726
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
|
|
1727
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1728
|
+
throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
|
|
1729
|
+
return { ok: true, ...await launchRuntimeScopeResume(context, { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") }) };
|
|
1730
|
+
}
|
|
1731
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
|
|
1732
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1733
|
+
throw new AppError("usage", "Use runtime scope control --scope-id ID --request-id ID --mode pause|stop [--manifest-json JSON]", 2);
|
|
1734
|
+
const mode = requiredOption(parsed, "mode");
|
|
1735
|
+
if (mode !== "pause" && mode !== "stop")
|
|
1736
|
+
throw new AppError("usage", "Scope control mode must be pause or stop", 2);
|
|
1737
|
+
return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, hasOption(parsed, "manifest-json") ? JSON.parse(requiredOption(parsed, "manifest-json")) : undefined) };
|
|
1738
|
+
}
|
|
1739
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
|
|
1740
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1741
|
+
throw new AppError("usage", "Invalid internal scope worker arguments", 2);
|
|
1742
|
+
return { ok: true, ...await serveRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation"))) };
|
|
1743
|
+
}
|
|
1744
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "stop") {
|
|
1745
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1746
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID", 2);
|
|
1747
|
+
return { ok: true, ...await stopRuntimeScope(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
1748
|
+
}
|
|
1749
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "status") {
|
|
1750
|
+
if ([...parsed.options.keys()].some(key => key !== "scope-id") || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1751
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope status --scope-id EVAL_ID", 2);
|
|
1752
|
+
return { ok: true, ...runtimeBudgetStatus(context, requiredOption(parsed, "scope-id")) };
|
|
1753
|
+
}
|
|
1754
|
+
if (parsed.positional.length !== 1 || parsed.positional[0] !== "fence" || [...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1755
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID", 2);
|
|
1756
|
+
return { ok: true, ...fenceRuntimeBudget(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
1757
|
+
}
|
|
1669
1758
|
if (command !== "process")
|
|
1670
|
-
throw new AppError("usage", "Usage: dd-flow runtime process
|
|
1759
|
+
throw new AppError("usage", "Usage: dd-flow runtime <scope|process>; see dd-flow runtime scope --help", 2);
|
|
1671
1760
|
const action = requiredPosition(parsed, 0, "runtime process action");
|
|
1761
|
+
if (action === "check-admission")
|
|
1762
|
+
return { ok: true, ...admitRuntimeCheck(context, requiredOption(parsed, "id"), requiredOption(parsed, "lease-token")) };
|
|
1672
1763
|
if (action === "start")
|
|
1673
1764
|
return await startRuntimeService(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), command: requiredOption(parsed, "command"), ports: requiredOption(parsed, "ports").split(","), readyPort: requiredOption(parsed, "ready-port"), readyPath: optionalOption(parsed, "ready-path") ?? "/", timeoutMs: optionalPositiveNumber(parsed, "ready-timeout-ms", true) ?? 30_000, progress });
|
|
1674
1765
|
if (action === "stop")
|
|
1675
1766
|
return { ok: true, process: await stopManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(optionalPositiveNumber(parsed, "grace-ms", true) ? { graceMs: optionalPositiveNumber(parsed, "grace-ms", true) } : {}) }) };
|
|
1676
1767
|
if (action === "register") {
|
|
1677
1768
|
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1769
|
+
const budget = parseRuntimeBudget(optionalOption(parsed, "budget-json"));
|
|
1770
|
+
const projectId = optionalOption(parsed, "project-id") ?? null, runId = optionalOption(parsed, "run") ?? null;
|
|
1771
|
+
const guard = projectId && runId ? recoveryGuard(context, projectId, runId) : null;
|
|
1772
|
+
const run = projectId && runId ? context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]) : null;
|
|
1678
1773
|
const process = registerManagedProcess(context, {
|
|
1679
1774
|
kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
|
|
1775
|
+
uniqueActiveOperation: requiredOption(parsed, "kind") === "eval-observer",
|
|
1776
|
+
...(requiredOption(parsed, "kind") === "eval-observer" ? { pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(optionalPositiveNumber(parsed, "process-group-id", true) ? { processGroupId: optionalPositiveNumber(parsed, "process-group-id", true) } : {}) } : {}),
|
|
1680
1777
|
ownerPid: optionalPositiveNumber(parsed, "owner-pid", true),
|
|
1681
|
-
projectId
|
|
1778
|
+
projectId, runId,
|
|
1682
1779
|
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
1683
1780
|
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
1684
|
-
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
|
|
1781
|
+
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {}),
|
|
1782
|
+
metadata: { ...(budget ? { budget, harness: optionalOption(parsed, "harness") ?? null } : {}), ...(projectId && runId ? { run_generation: guard?.generation ?? 0, run_admitted: Boolean(run) && !["draining", "sealed", "resuming"].includes(guard?.status ?? "") } : {}), ...(optionalOption(parsed, "dispatch-barrier") ? { dispatch_barrier: optionalOption(parsed, "dispatch-barrier") } : {}), ...(optionalOption(parsed, "role") ? { role: optionalOption(parsed, "role") } : {}), ...(optionalOption(parsed, "state-dir") ? { state_dir: optionalOption(parsed, "state-dir") } : {}), ...(optionalOption(parsed, "adapter-executable") ? { adapter_executable: optionalOption(parsed, "adapter-executable") } : {}) }
|
|
1685
1783
|
});
|
|
1686
1784
|
return { ok: true, process };
|
|
1687
1785
|
}
|
|
@@ -1694,6 +1792,36 @@ async function dispatchRuntime(context, command, parsed, progress) {
|
|
|
1694
1792
|
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1695
1793
|
return { ok: heartbeatManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs ? { leaseMs } : {}) }) };
|
|
1696
1794
|
}
|
|
1795
|
+
if (action === "admission") {
|
|
1796
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
1797
|
+
if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || record.state !== "running" || !record.pid || !record.pid_started_at || !processIsAlive(record))
|
|
1798
|
+
throw new AppError("process_ownership_unknown", "Productive dispatch requires its current confirmed runtime owner", 1);
|
|
1799
|
+
const metadata = JSON.parse(record.metadata_json);
|
|
1800
|
+
if (metadata.state_dir !== requiredOption(parsed, "state-dir"))
|
|
1801
|
+
throw new AppError("process_ownership_unknown", "Runtime admission belongs to another state directory", 1);
|
|
1802
|
+
if (record.project_id && record.run_id) {
|
|
1803
|
+
if (managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
1804
|
+
throw new AppError("process_runtime_scope_mismatch", "Runtime admission belongs to another or unproven flow home", 1);
|
|
1805
|
+
const recovering = recoveryRuntimeAdmission(context, record, { stateDir: requiredOption(parsed, "state-dir"), daemonId: optionalOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), operation: optionalOption(parsed, "operation"), nativeSessionId: optionalOption(parsed, "native-session-id"), promptSha256: optionalOption(parsed, "prompt-sha256") });
|
|
1806
|
+
if (!recovering) {
|
|
1807
|
+
if (metadata.run_admitted !== true || !Number.isSafeInteger(metadata.run_generation))
|
|
1808
|
+
throw new AppError("run_recovery_guarded", "Runtime was not admitted before the RUN fence; reconcile its launch", 1);
|
|
1809
|
+
assertRunMutationAllowed(context, record.project_id, record.run_id, metadata.run_generation);
|
|
1810
|
+
}
|
|
1811
|
+
const run = context.db.get("SELECT status FROM runs WHERE project_id = ? AND id = ?", [record.project_id, record.run_id]);
|
|
1812
|
+
const retainedHitlPrompt = recovering && ["paused", "waiting_for_user"].includes(run?.status ?? "") && ["session.prompt", "session.resume"].includes(optionalOption(parsed, "operation") ?? "") && context.db.get("SELECT 1 FROM run_recovery_bindings b JOIN run_recovery_guards g ON g.recovery_id = b.recovery_id WHERE g.project_id = ? AND g.run_id = ? AND g.generation = ? AND b.daemon_id = ? AND b.provider_session_id = ?", [record.project_id, record.run_id, metadata.run_generation, optionalOption(parsed, "daemon-id"), optionalOption(parsed, "native-session-id")]);
|
|
1813
|
+
if (!run || run.status !== "running" && !retainedHitlPrompt && !["judge", "probe"].includes(metadata.role ?? ""))
|
|
1814
|
+
throw new AppError("invalid_run_state", "Subject dispatch requires a running RUN or its retained recovery/HITL prompt", 1);
|
|
1815
|
+
}
|
|
1816
|
+
const budget = reserveProviderTurn(context, record, { operationId: optionalOption(parsed, "operation-id") ?? "", operation: optionalOption(parsed, "operation") ?? "", sessionId: optionalOption(parsed, "native-session-id"), daemonId: optionalOption(parsed, "daemon-id") });
|
|
1817
|
+
return { ok: true, ...budget, process_id: record.id, generation: metadata.run_generation ?? null };
|
|
1818
|
+
}
|
|
1819
|
+
if (action === "release-turn") {
|
|
1820
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
1821
|
+
if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
1822
|
+
throw new AppError("process_ownership_unknown", "Budget release requires its retained physical owner", 1);
|
|
1823
|
+
return releaseProviderTurns(context, record, { daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") });
|
|
1824
|
+
}
|
|
1697
1825
|
if (action === "finish") {
|
|
1698
1826
|
const state = requiredOption(parsed, "state");
|
|
1699
1827
|
if (state !== "stopped" && state !== "failed")
|
|
@@ -1704,7 +1832,7 @@ async function dispatchRuntime(context, command, parsed, progress) {
|
|
|
1704
1832
|
return { ok: true, processes: managedProcessStatus(context) };
|
|
1705
1833
|
if (action === "reconcile")
|
|
1706
1834
|
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalPositiveNumber(parsed, "grace-ms", true)) };
|
|
1707
|
-
throw new AppError("usage", "Usage: dd-flow runtime process <start|stop|register|confirm|heartbeat|finish|status|reconcile>", 2);
|
|
1835
|
+
throw new AppError("usage", "Usage: dd-flow runtime process <start|stop|register|confirm|heartbeat|admission|finish|status|reconcile>", 2);
|
|
1708
1836
|
}
|
|
1709
1837
|
function dispatchStat(context, command, parsed) {
|
|
1710
1838
|
if (command === "usage") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { durableDaemonStart, inspectDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { spawn } from "node:child_process";
|
|
4
5
|
import { doctor } from "../lib/dd-agy.mjs";
|
|
@@ -22,10 +23,10 @@ async function hook(options) {
|
|
|
22
23
|
try {
|
|
23
24
|
const { positional, options } = parse(process.argv.slice(2)), [family, command] = positional; const common = { journal: options.journal, stateDir: options["state-dir"], cwd: options.cwd, bin: options["agy-bin"], provider: options.provider, model: options.model, reasoning: options.reasoning, mode: options.mode, projectRoot: options["project-root"], ddFlowBin: options["dd-flow-bin"], ddFlowHome: options["dd-flow-home"], noFlow: options["no-flow"] === true, timeoutMs: options.timeout ? Number(options.timeout) * 1000 : undefined }; let result;
|
|
24
25
|
if (family === "doctor") result = await doctor(common);
|
|
25
|
-
else if (family === "daemon" && command === "start") result = await
|
|
26
|
+
else if (family === "daemon" && command === "start") result = await durableDaemonStart({ ...common, sessionId: options["session-id"], entryPath: process.argv[1] }, startDaemon);
|
|
26
27
|
else if (family === "daemon" && command === "serve") { await serveDaemon(common.stateDir); process.exit(0); }
|
|
27
28
|
else if (family === "daemon" && command === "status") result = await callDaemon(common.stateDir, "daemon.status");
|
|
28
|
-
else if (family === "daemon" && command === "operation") result = await
|
|
29
|
+
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(common.stateDir, options["operation-id"]);
|
|
29
30
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: common.stateDir, cancelTree: options["cancel-tree"], timeoutMs: common.timeoutMs });
|
|
30
31
|
else if (family === "hook" && command === "handle") { process.stdout.write(`${JSON.stringify(await hook(options))}\n`); process.exit(0); }
|
|
31
32
|
else if (family === "session") { if (command === "fork") throw Object.assign(new Error("Antigravity native headless fork is unsupported; use deterministic_replay"), { code: "agy_headless_fork_unsupported" }); const operation = command === "status" ? "inspect" : command; result = await callDaemon(common.stateDir, `session.${operation}`, { sessionId: options["session-id"], ...(command === "create" || command === "prompt" ? { prompt: await prompt(options) } : {}), tree: options.tree === true }, common.timeoutMs ?? 1_800_000); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { durableDaemonStart, inspectDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
2
3
|
import { cancelSession, createSession, doctor, inspectSession, promptFromFile, promptSession, startSession } from "../lib/dd-codex.mjs";
|
|
3
4
|
import { callDaemon, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-codex-daemon.mjs";
|
|
4
5
|
|
|
@@ -7,15 +8,15 @@ function common(options) { return { bin: options["codex-bin"], journal: options.
|
|
|
7
8
|
try {
|
|
8
9
|
const { positional, options } = parse(process.argv.slice(2)); const [family, command] = positional; const shared = common(options); let result;
|
|
9
10
|
if (family === "doctor") result = await doctor(shared);
|
|
10
|
-
else if (family === "daemon" && command === "start") result = await
|
|
11
|
+
else if (family === "daemon" && command === "start") result = await durableDaemonStart({ ...shared, entryPath: process.argv[1] }, startDaemon);
|
|
11
12
|
else if (family === "daemon" && command === "serve") { await serveDaemon(shared.stateDir); process.exit(0); }
|
|
12
13
|
else if (family === "daemon" && command === "status") result = await callDaemon(shared.stateDir, "daemon.status");
|
|
13
|
-
else if (family === "daemon" && command === "operation") result = await
|
|
14
|
+
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(shared.stateDir, options["operation-id"]);
|
|
14
15
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ ...shared, cancelTree: options["cancel-tree"] });
|
|
15
16
|
else if (family === "session" && command === "create") result = shared.stateDir ? await callDaemon(shared.stateDir, "session.create", shared) : await createSession(shared);
|
|
16
17
|
else if (family === "session" && command === "prompt") result = shared.stateDir ? await callDaemon(shared.stateDir, "session.prompt", { ...shared, prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt }, shared.timeoutMs ?? 1_800_000) : await promptSession({ ...shared, prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt });
|
|
17
18
|
else if (family === "session" && command === "start") result = shared.stateDir ? await callDaemon(shared.stateDir, "session.start", { ...shared, prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt }, shared.timeoutMs ?? 30_000) : await startSession({ ...shared, prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt });
|
|
18
|
-
else if (family === "session" && ["inspect", "status", "resume"].includes(command)) result = shared.stateDir ? await callDaemon(shared.stateDir, "session.inspect", shared) : await inspectSession(shared);
|
|
19
|
+
else if (family === "session" && ["inspect", "status", "resume"].includes(command)) result = shared.stateDir ? await callDaemon(shared.stateDir, command === "resume" ? "session.resume" : "session.inspect", shared) : await inspectSession(shared);
|
|
19
20
|
else if (family === "session" && command === "cancel") result = shared.stateDir ? await callDaemon(shared.stateDir, "session.cancel", shared) : await cancelSession(shared);
|
|
20
21
|
else throw new Error("usage: dd-codex doctor | daemon start|status|stop | session create|start|prompt|inspect|status|resume|cancel [--json]");
|
|
21
22
|
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}\n`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { durableDaemonStart, inspectDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
2
3
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import os from "node:os";
|
|
@@ -18,10 +19,10 @@ try {
|
|
|
18
19
|
const runtime = new DroidRuntime({ ...config, stateDir: dir, home: path.join(dir, "home"), cwd: config.cwd ?? process.cwd(), journal: path.join(dir, "events.jsonl"), daemonId: randomUUID(), ddFlowHome: null, ddFlowBin: null }, {}, async () => {});
|
|
19
20
|
try { await runtime.start(); result = { harness: "droid-cli", observed_runtime: { droid: await droidVersion(config.bin), factory_protocol: runtime.protocol, dd_harness_contract: DROID_CONTRACT }, models: runtime.models.map(model => model.id ?? model.modelId), capabilities: { native_children: true, tree_cancel: true, selective_child_cancel: false, native_fork: false }, protocol_expected: DROID_PROTOCOL }; }
|
|
20
21
|
finally { await runtime.closeTree(true); await rm(dir, { recursive: true, force: true }); }
|
|
21
|
-
} else if (family === "daemon" && command === "start") result = await
|
|
22
|
+
} else if (family === "daemon" && command === "start") result = await durableDaemonStart(config, startDaemon);
|
|
22
23
|
else if (family === "daemon" && command === "serve") { await serveDaemon(config.stateDir); process.exit(0); }
|
|
23
24
|
else if (family === "daemon" && command === "status") result = await callDaemon(config.stateDir, "daemon.status");
|
|
24
|
-
else if (family === "daemon" && command === "operation") result = await
|
|
25
|
+
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(config.stateDir, options["operation-id"]);
|
|
25
26
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: config.stateDir, cancelTree: options["cancel-tree"] });
|
|
26
27
|
else if (family === "hook" && command === "handle") { const payload = JSON.parse(await input()); const eventId = randomUUID(); result = await callDaemon(config.stateDir, "hook.observe", { payload, eventId }, 25_000, eventId); }
|
|
27
28
|
else if (family === "session") { if (command === "fork") throw new Error("Droid fork is not qualified for this adapter"); const prompt = options["prompt-file"] ? await readFile(options["prompt-file"], "utf8") : options.prompt; result = await callDaemon(config.stateDir, `session.${command === "status" ? "inspect" : command}`, { sessionId: options["session-id"], ...(prompt ? { prompt } : {}) }); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { durableDaemonStart, inspectDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
2
3
|
import { spawn } from "node:child_process";
|
|
3
4
|
import { cancelSession, createSession, doctor, forkSession, inspectSession, promptFromFile, promptSession } from "../lib/dd-grok.mjs";
|
|
4
5
|
import { callDaemon, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-grok-daemon.mjs";
|
|
@@ -16,13 +17,13 @@ async function hook(options) {
|
|
|
16
17
|
try {
|
|
17
18
|
const { positional, options } = parse(process.argv.slice(2)); const [family, command] = positional; const shared = common(options); let result;
|
|
18
19
|
if (family === "doctor") result = await doctor(shared);
|
|
19
|
-
else if (family === "daemon" && command === "start") result = await
|
|
20
|
+
else if (family === "daemon" && command === "start") result = await durableDaemonStart({ ...shared, entryPath: process.argv[1] }, startDaemon);
|
|
20
21
|
else if (family === "daemon" && command === "serve") { await serveDaemon(shared.stateDir); process.exit(0); }
|
|
21
22
|
else if (family === "daemon" && command === "status") result = await callDaemon(shared.stateDir, "daemon.status");
|
|
22
|
-
else if (family === "daemon" && command === "operation") result = await
|
|
23
|
+
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(shared.stateDir, options["operation-id"]);
|
|
23
24
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: shared.stateDir, cancelTree: options["cancel-tree"], timeoutMs: shared.timeoutMs });
|
|
24
25
|
else if (family === "hook" && command === "handle") { process.stdout.write(`${JSON.stringify(await hook(options))}\n`); process.exit(0); }
|
|
25
|
-
else if (family === "session") { const params = { ...shared, ...(command === "create" || command === "prompt" ? { prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt } : {}), ...(command === "fork" ? { target: JSON.parse(options["target-json"] ?? "null") } : {}), ...(command === "archive" ? { archiveDir: options["archive-dir"] } : {}) }; if (shared.stateDir) result = await callDaemon(shared.stateDir, `session.${
|
|
26
|
+
else if (family === "session") { const params = { ...shared, ...(command === "create" || command === "prompt" ? { prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt } : {}), ...(command === "fork" ? { target: JSON.parse(options["target-json"] ?? "null") } : {}), ...(command === "archive" ? { archiveDir: options["archive-dir"] } : {}) }; if (shared.stateDir) result = await callDaemon(shared.stateDir, `session.${command === "status" ? "inspect" : command}`, params, shared.timeoutMs ?? 1_800_000); else if (command === "create") result = await createSession(params); else if (command === "prompt") result = await promptSession(params); else if (["inspect", "status", "resume"].includes(command)) result = await inspectSession(params); else if (command === "cancel") result = await cancelSession(params); else if (command === "fork") result = await forkSession(params); else throw new Error(`unknown session command: ${command}`); }
|
|
26
27
|
else throw new Error("usage: dd-grok doctor | daemon start|status|stop | session create|prompt|inspect|status|resume|cancel|fork|archive [options] --json");
|
|
27
28
|
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}\n`);
|
|
28
29
|
} catch (error) { if (process.argv[2] === "hook") { process.stdout.write(`${JSON.stringify({ decision: "deny", reason: error.message })}\n`); process.exit(2); } process.stderr.write(`${JSON.stringify({ ok: false, error: error.message, ...(error.code ? { code: error.code, retryable: error.retryable === true, details: error.details } : {}) })}\n`); process.exit(1); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { durableDaemonStart, inspectDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { callDaemon, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-opencode-daemon.mjs";
|
|
4
5
|
|
|
@@ -6,13 +7,13 @@ function parse(argv) { const positional = [], options = {}; for (let i = 0; i <
|
|
|
6
7
|
const prompt = async options => options["prompt-file"] ? await readFile(options["prompt-file"], "utf8") : options.prompt;
|
|
7
8
|
try {
|
|
8
9
|
const { positional, options } = parse(process.argv.slice(2)), [family, command] = positional; const common = { journal: options.journal, stateDir: options["state-dir"], sessionId: options["session-id"], cwd: options.cwd, bin: options["opencode-bin"], provider: options.provider, model: options.model, variant: options.variant, agent: options.agent, authPath: options["auth-path"], projectRoot: options["project-root"], ddFlowBin: options["dd-flow-bin"], ddFlowHome: options["dd-flow-home"], noFlow: options["no-flow"] === true, sessionArchive: options["session-archive"], timeoutMs: options.timeout ? Number(options.timeout) * 1000 : undefined }; let result;
|
|
9
|
-
if (family === "daemon" && command === "start") result = await
|
|
10
|
+
if (family === "daemon" && command === "start") result = await durableDaemonStart({ ...common, entryPath: process.argv[1] }, startDaemon);
|
|
10
11
|
else if (family === "daemon" && command === "serve") { await serveDaemon(common.stateDir); process.exit(0); }
|
|
11
12
|
else if (family === "daemon" && command === "status") result = await callDaemon(common.stateDir, "daemon.status");
|
|
12
|
-
else if (family === "daemon" && command === "operation") result = await
|
|
13
|
+
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(common.stateDir, options["operation-id"]);
|
|
13
14
|
else if (family === "doctor") result = await callDaemon(common.stateDir, "daemon.doctor");
|
|
14
15
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: common.stateDir, cancelTree: options["cancel-tree"], timeoutMs: common.timeoutMs });
|
|
15
|
-
else if (family === "session") { const operation =
|
|
16
|
+
else if (family === "session") { const operation = command === "status" ? "inspect" : command; const params = { sessionId: options["session-id"], title: options.title, messageId: options["message-id"], archiveFile: options["archive-file"], ...(command === "prompt" ? { prompt: await prompt(options) } : {}) }; result = await callDaemon(common.stateDir, `session.${operation}`, params, common.timeoutMs ?? 1_800_000); }
|
|
16
17
|
else throw new Error("usage: dd-opencode daemon start|status|stop | session create|prompt|inspect|fork|cancel|archive [options] --json");
|
|
17
18
|
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}\n`);
|
|
18
19
|
} catch (error) { process.stderr.write(`${JSON.stringify({ ok: false, error: error.message, ...(error.code ? { code: error.code, retryable: error.retryable === true, details: error.details } : {}) })}\n`); process.exit(1); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { durableDaemonStart, inspectDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
2
3
|
import { cancelChild, cancelSession, createSession, doctor, forkSession, inspectSession, promptFromFile, promptSession } from "../lib/dd-zcode.mjs";
|
|
3
4
|
import { callDaemon, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-zcode-daemon.mjs";
|
|
4
5
|
|
|
@@ -33,17 +34,17 @@ try {
|
|
|
33
34
|
const answers = options["answers-file"] ? JSON.parse(await promptFromFile(options["answers-file"])) : undefined;
|
|
34
35
|
const shared = common(options);
|
|
35
36
|
if (family === "doctor") result = await doctor(shared);
|
|
36
|
-
else if (family === "daemon" && command === "start") result = await
|
|
37
|
+
else if (family === "daemon" && command === "start") result = await durableDaemonStart({ ...shared, entryPath: process.argv[1] }, startDaemon);
|
|
37
38
|
else if (family === "daemon" && command === "serve") { await serveDaemon(shared.stateDir); process.exit(0); }
|
|
38
39
|
else if (family === "daemon" && command === "status") result = await callDaemon(shared.stateDir, "daemon.status");
|
|
39
|
-
else if (family === "daemon" && command === "operation") result = await
|
|
40
|
+
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(shared.stateDir, options["operation-id"]);
|
|
40
41
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: shared.stateDir, cancelTree: options["cancel-tree"], timeoutMs: shared.timeoutMs });
|
|
41
42
|
else if (family === "session") {
|
|
42
43
|
const params = { ...shared, answers, ...(command === "create" || command === "prompt" ? { prompt: options["prompt-file"] ? await promptFromFile(options["prompt-file"]) : options.prompt } : {}), ...(command === "fork" ? { target: JSON.parse(options["target-json"] ?? "null") } : {}) };
|
|
43
44
|
// promptSessionWithBridge owns the meaningful deadline: it fails when its
|
|
44
45
|
// ACP Session has been idle for the configured liveness window. A fixed
|
|
45
46
|
// RPC deadline would wrongly terminate a long but active Turn.
|
|
46
|
-
const operation = `session.${
|
|
47
|
+
const operation = `session.${command === "status" ? "inspect" : command}`;
|
|
47
48
|
const rpcTimeout = command === "prompt" && shared.timeoutMs === undefined
|
|
48
49
|
? null
|
|
49
50
|
: (shared.timeoutMs ?? 1_800_000);
|