@deksden-com/dd-flow-cli 0.9.0-beta.38 → 0.9.0-beta.40
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 +22 -1
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +42 -1
- package/dist/cli/run-cli.js +147 -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 +49 -12
- 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 +379 -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/hooks.js +5 -5
- 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 +79 -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 +539 -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,27 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.40
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2b4b714: Admit ordinary managed HITL answers through their exact generation-bound native
|
|
8
|
+
prompt, preserving pause, Session, raw-answer and prompt integrity. Keep accepted
|
|
9
|
+
answer intent separate from physical delivery so stop drains native operations
|
|
10
|
+
and recovery can deliver an unanswered pause without replaying completed work.
|
|
11
|
+
- c5eeb6f: Capture owned Codex home configuration and evidence without copying shared host
|
|
12
|
+
history or ephemeral helper links. Reuse the provisioned shared-entry contract
|
|
13
|
+
and retain strict rejection of unrelated external recovery artifact links.
|
|
14
|
+
|
|
15
|
+
## 0.9.0-beta.39
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 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.
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
|
|
23
|
+
Discover native Codex children from spawn receipts and release terminal failed provider turns only after a fresh settled-tree observation.
|
|
24
|
+
|
|
3
25
|
## 0.9.0-beta.38
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -1037,7 +1059,6 @@ record` command with the bounded 15-probe contract.
|
|
|
1037
1059
|
- 16f68a3: Add router-native engine snapshot commands and routed project-command dispatch.
|
|
1038
1060
|
|
|
1039
1061
|
Also include the runtime/dashboard compatibility wave needed by the current Memory Bank canon:
|
|
1040
|
-
|
|
1041
1062
|
- target-based dashboard `open`/`refresh` commands and related-command help;
|
|
1042
1063
|
- persisted FIFO lane waiters and merge wait-acquire specialization;
|
|
1043
1064
|
- `migration plan/report/verify` evidence helpers for controlled `mb-upgrade` runtime migrations;
|
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.40",
|
|
4
|
+
"cli_commit": "652524c6f370aa17c75474de5ebb7b3b9bf0b3a2",
|
|
5
|
+
"built_at": "2026-09-09T17:31:05.981Z",
|
|
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,17 @@ 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 { controllerHitlAdmission } from "../services/run-controller-adapter.js";
|
|
63
|
+
import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
|
|
64
|
+
import { requestRunControl, serveRunControl } from "../services/run-control-worker.js";
|
|
56
65
|
import { startRuntimeService } from "../services/runtime-service.js";
|
|
57
66
|
const defaultIo = {
|
|
58
67
|
stdout: process.stdout,
|
|
@@ -159,6 +168,7 @@ function resolveProjectRootBeforeRouting(args, env) {
|
|
|
159
168
|
function databaseModeForCommand(args, classification) {
|
|
160
169
|
const [family, command] = args;
|
|
161
170
|
if (family === "engine" ||
|
|
171
|
+
family === "storage" && command === "migrate-writer" ||
|
|
162
172
|
family === "version" ||
|
|
163
173
|
family === "schema" ||
|
|
164
174
|
family === "canon" && command === "resolve" ||
|
|
@@ -510,6 +520,13 @@ function shortSha(value) {
|
|
|
510
520
|
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args)) {
|
|
511
521
|
const [family, command, ...rest] = args;
|
|
512
522
|
const parsed = parseArgs(rest);
|
|
523
|
+
if (family === "storage" && command === "migrate-writer") {
|
|
524
|
+
if (parsed.positional.length || [...parsed.options.keys()].some(key => !["database", "backup", "offline"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
525
|
+
throw new AppError("usage", "Usage: dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline", 2);
|
|
526
|
+
if (parsed.options.get("offline")?.[0] !== flagValue)
|
|
527
|
+
throw new AppError("usage", "Writer migration requires --offline after draining all local and external runtime owners", 2);
|
|
528
|
+
return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
|
|
529
|
+
}
|
|
513
530
|
if (family === "engine") {
|
|
514
531
|
return dispatchEngine(context, command, parsed, classification);
|
|
515
532
|
}
|
|
@@ -898,6 +915,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
898
915
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
899
916
|
const runId = requiredPosition(parsed, 0, "run-id");
|
|
900
917
|
const stage = requiredOption(parsed, "stage");
|
|
918
|
+
const controlledReply = (result) => controlLifecycleReply(context, { projectRoot, runId }, result);
|
|
901
919
|
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
902
920
|
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
903
921
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
@@ -925,56 +943,56 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
925
943
|
if (!resultFile && !resultStdin) {
|
|
926
944
|
throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
|
|
927
945
|
}
|
|
928
|
-
return finishVnextSpecifyStage(context, {
|
|
946
|
+
return controlledReply(await finishVnextSpecifyStage(context, {
|
|
929
947
|
projectRoot,
|
|
930
948
|
runId,
|
|
931
949
|
...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
|
|
932
950
|
outcome: outcome
|
|
933
|
-
});
|
|
951
|
+
}));
|
|
934
952
|
}
|
|
935
953
|
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
936
954
|
if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
937
955
|
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
938
956
|
}
|
|
939
|
-
return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
|
|
957
|
+
return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile }));
|
|
940
958
|
}
|
|
941
959
|
if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
942
960
|
if (optionalOption(parsed, "code-work-batch-file"))
|
|
943
961
|
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 });
|
|
962
|
+
return controlledReply(await finishVnextPlan(context, { projectRoot, runId }));
|
|
945
963
|
}
|
|
946
964
|
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
947
965
|
if (resultStdin || hasOption(parsed, "outcome"))
|
|
948
966
|
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
949
967
|
const decisionFile = optionalOption(parsed, "decision-file");
|
|
950
|
-
return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
|
|
968
|
+
return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) }));
|
|
951
969
|
}
|
|
952
970
|
if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
953
971
|
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
954
972
|
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
955
973
|
}
|
|
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) });
|
|
974
|
+
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
975
|
}
|
|
958
976
|
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
959
977
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
960
978
|
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) });
|
|
979
|
+
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
980
|
}
|
|
963
981
|
if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
964
982
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
965
983
|
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) });
|
|
984
|
+
return controlledReply(await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
|
|
967
985
|
}
|
|
968
986
|
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
|
|
969
987
|
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
970
988
|
}
|
|
971
|
-
return finishStage(context, {
|
|
989
|
+
return controlledReply(await finishStage(context, {
|
|
972
990
|
projectRoot,
|
|
973
991
|
runId,
|
|
974
992
|
stage,
|
|
975
993
|
...(dir ? { dir } : {}),
|
|
976
994
|
...(semanticFile ? { semanticFile } : {})
|
|
977
|
-
});
|
|
995
|
+
}));
|
|
978
996
|
}
|
|
979
997
|
if (family === "stage" && command === "pause") {
|
|
980
998
|
if (!hasOption(parsed, "question-stdin"))
|
|
@@ -1056,6 +1074,43 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1056
1074
|
return dispatchSession(context, command, parsed);
|
|
1057
1075
|
}
|
|
1058
1076
|
if (family === "run") {
|
|
1077
|
+
if (command === "control") {
|
|
1078
|
+
const action = requiredPosition(parsed, 0, "control action");
|
|
1079
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1080
|
+
if (action === "status")
|
|
1081
|
+
return runControlStatus(context, scope);
|
|
1082
|
+
if (action === "resume") {
|
|
1083
|
+
if (["force", "grace-ms", "escalate"].some(option => hasOption(parsed, option)))
|
|
1084
|
+
throw new AppError("usage", "Resume does not accept stop or pause escalation options", 2);
|
|
1085
|
+
return await resumeRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), controlId: requiredOption(parsed, "from"), ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {}) });
|
|
1086
|
+
}
|
|
1087
|
+
if (action === "serve")
|
|
1088
|
+
return await serveRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id") });
|
|
1089
|
+
if (action === "pause" || action === "stop")
|
|
1090
|
+
return await requestRunControl(context, {
|
|
1091
|
+
...scope, action, requestId: requiredOption(parsed, "request-id"),
|
|
1092
|
+
...(hasOption(parsed, "force") ? { force: true } : {}),
|
|
1093
|
+
...(hasOption(parsed, "grace-ms") ? { graceMs: Number(requiredOption(parsed, "grace-ms")) } : {}),
|
|
1094
|
+
...(hasOption(parsed, "escalate") ? { escalate: requiredOption(parsed, "escalate") } : {}),
|
|
1095
|
+
...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {})
|
|
1096
|
+
});
|
|
1097
|
+
throw new AppError("usage", "Use: dd-flow run control pause|stop|resume|status", 2);
|
|
1098
|
+
}
|
|
1099
|
+
if (command === "drive") {
|
|
1100
|
+
const action = requiredPosition(parsed, 0, "drive action");
|
|
1101
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1102
|
+
if (action === "status")
|
|
1103
|
+
return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
|
|
1104
|
+
if (action === "launch")
|
|
1105
|
+
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") } : {}) });
|
|
1106
|
+
if (action === "serve")
|
|
1107
|
+
return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
|
|
1108
|
+
if (action === "answer")
|
|
1109
|
+
return answerRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), pauseId: requiredOption(parsed, "pause-id"), answerFile: requiredOption(parsed, "answer-file") });
|
|
1110
|
+
if (action === "context")
|
|
1111
|
+
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") });
|
|
1112
|
+
throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
|
|
1113
|
+
}
|
|
1059
1114
|
return dispatchRun(context, command, parsed);
|
|
1060
1115
|
}
|
|
1061
1116
|
if (family === "stat") {
|
|
@@ -1528,13 +1583,14 @@ function dispatchRun(context, command, parsed) {
|
|
|
1528
1583
|
if (action === "restore")
|
|
1529
1584
|
return restoreEvalRunSnapshot(context, {
|
|
1530
1585
|
snapshot: requiredOption(parsed, "snapshot"),
|
|
1586
|
+
executionRoutingFile: optionalOption(parsed, "execution-routing-file"),
|
|
1531
1587
|
...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}),
|
|
1532
1588
|
projectRoot: requiredOption(parsed, "project-root")
|
|
1533
1589
|
});
|
|
1534
1590
|
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
1591
|
}
|
|
1536
1592
|
if (command === "prepare-vnext-specify") {
|
|
1537
|
-
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
|
|
1593
|
+
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
|
|
1538
1594
|
}
|
|
1539
1595
|
if (command === "start") {
|
|
1540
1596
|
if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
|
|
@@ -1551,6 +1607,7 @@ function dispatchRun(context, command, parsed) {
|
|
|
1551
1607
|
nextAction: optionalOption(parsed, "next-action"),
|
|
1552
1608
|
preset: optionalOption(parsed, "preset"),
|
|
1553
1609
|
taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
|
|
1610
|
+
executionRoutingFile: optionalOption(parsed, "execution-routing-file"),
|
|
1554
1611
|
...(protocolOverrides ? { protocolOverrides } : {}),
|
|
1555
1612
|
runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
|
|
1556
1613
|
});
|
|
@@ -1666,22 +1723,64 @@ function dispatchRun(context, command, parsed) {
|
|
|
1666
1723
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1667
1724
|
}
|
|
1668
1725
|
async function dispatchRuntime(context, command, parsed, progress) {
|
|
1726
|
+
if (command === "scope") {
|
|
1727
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
|
|
1728
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1729
|
+
throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
|
|
1730
|
+
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") }) };
|
|
1731
|
+
}
|
|
1732
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
|
|
1733
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1734
|
+
throw new AppError("usage", "Use runtime scope control --scope-id ID --request-id ID --mode pause|stop [--manifest-json JSON]", 2);
|
|
1735
|
+
const mode = requiredOption(parsed, "mode");
|
|
1736
|
+
if (mode !== "pause" && mode !== "stop")
|
|
1737
|
+
throw new AppError("usage", "Scope control mode must be pause or stop", 2);
|
|
1738
|
+
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) };
|
|
1739
|
+
}
|
|
1740
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
|
|
1741
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1742
|
+
throw new AppError("usage", "Invalid internal scope worker arguments", 2);
|
|
1743
|
+
return { ok: true, ...await serveRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation"))) };
|
|
1744
|
+
}
|
|
1745
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "stop") {
|
|
1746
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1747
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID", 2);
|
|
1748
|
+
return { ok: true, ...await stopRuntimeScope(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
1749
|
+
}
|
|
1750
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "status") {
|
|
1751
|
+
if ([...parsed.options.keys()].some(key => key !== "scope-id") || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1752
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope status --scope-id EVAL_ID", 2);
|
|
1753
|
+
return { ok: true, ...runtimeBudgetStatus(context, requiredOption(parsed, "scope-id")) };
|
|
1754
|
+
}
|
|
1755
|
+
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))
|
|
1756
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID", 2);
|
|
1757
|
+
return { ok: true, ...fenceRuntimeBudget(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
1758
|
+
}
|
|
1669
1759
|
if (command !== "process")
|
|
1670
|
-
throw new AppError("usage", "Usage: dd-flow runtime process
|
|
1760
|
+
throw new AppError("usage", "Usage: dd-flow runtime <scope|process>; see dd-flow runtime scope --help", 2);
|
|
1671
1761
|
const action = requiredPosition(parsed, 0, "runtime process action");
|
|
1762
|
+
if (action === "check-admission")
|
|
1763
|
+
return { ok: true, ...admitRuntimeCheck(context, requiredOption(parsed, "id"), requiredOption(parsed, "lease-token")) };
|
|
1672
1764
|
if (action === "start")
|
|
1673
1765
|
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
1766
|
if (action === "stop")
|
|
1675
1767
|
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
1768
|
if (action === "register") {
|
|
1677
1769
|
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1770
|
+
const budget = parseRuntimeBudget(optionalOption(parsed, "budget-json"));
|
|
1771
|
+
const projectId = optionalOption(parsed, "project-id") ?? null, runId = optionalOption(parsed, "run") ?? null;
|
|
1772
|
+
const guard = projectId && runId ? recoveryGuard(context, projectId, runId) : null;
|
|
1773
|
+
const run = projectId && runId ? context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]) : null;
|
|
1678
1774
|
const process = registerManagedProcess(context, {
|
|
1679
1775
|
kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
|
|
1776
|
+
uniqueActiveOperation: requiredOption(parsed, "kind") === "eval-observer",
|
|
1777
|
+
...(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
1778
|
ownerPid: optionalPositiveNumber(parsed, "owner-pid", true),
|
|
1681
|
-
projectId
|
|
1779
|
+
projectId, runId,
|
|
1682
1780
|
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
1683
1781
|
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
1684
|
-
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
|
|
1782
|
+
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {}),
|
|
1783
|
+
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
1784
|
});
|
|
1686
1785
|
return { ok: true, process };
|
|
1687
1786
|
}
|
|
@@ -1694,6 +1793,37 @@ async function dispatchRuntime(context, command, parsed, progress) {
|
|
|
1694
1793
|
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1695
1794
|
return { ok: heartbeatManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs ? { leaseMs } : {}) }) };
|
|
1696
1795
|
}
|
|
1796
|
+
if (action === "admission") {
|
|
1797
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
1798
|
+
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))
|
|
1799
|
+
throw new AppError("process_ownership_unknown", "Productive dispatch requires its current confirmed runtime owner", 1);
|
|
1800
|
+
const metadata = JSON.parse(record.metadata_json);
|
|
1801
|
+
if (metadata.state_dir !== requiredOption(parsed, "state-dir"))
|
|
1802
|
+
throw new AppError("process_ownership_unknown", "Runtime admission belongs to another state directory", 1);
|
|
1803
|
+
if (record.project_id && record.run_id) {
|
|
1804
|
+
if (managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
1805
|
+
throw new AppError("process_runtime_scope_mismatch", "Runtime admission belongs to another or unproven flow home", 1);
|
|
1806
|
+
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") });
|
|
1807
|
+
if (!recovering) {
|
|
1808
|
+
if (metadata.run_admitted !== true || !Number.isSafeInteger(metadata.run_generation))
|
|
1809
|
+
throw new AppError("run_recovery_guarded", "Runtime was not admitted before the RUN fence; reconcile its launch", 1);
|
|
1810
|
+
assertRunMutationAllowed(context, record.project_id, record.run_id, metadata.run_generation);
|
|
1811
|
+
}
|
|
1812
|
+
const run = context.db.get("SELECT status FROM runs WHERE project_id = ? AND id = ?", [record.project_id, record.run_id]);
|
|
1813
|
+
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")]);
|
|
1814
|
+
const acceptedHitlPrompt = ["paused", "waiting_for_user"].includes(run?.status ?? "") && controllerHitlAdmission(context, record, { stateDir: requiredOption(parsed, "state-dir"), operationId: optionalOption(parsed, "operation-id"), operation: optionalOption(parsed, "operation"), nativeSessionId: optionalOption(parsed, "native-session-id"), promptSha256: optionalOption(parsed, "prompt-sha256") });
|
|
1815
|
+
if (!run || run.status !== "running" && !retainedHitlPrompt && !acceptedHitlPrompt && !["judge", "probe"].includes(metadata.role ?? ""))
|
|
1816
|
+
throw new AppError("invalid_run_state", "Subject dispatch requires a running RUN or its retained recovery/HITL prompt", 1);
|
|
1817
|
+
}
|
|
1818
|
+
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") });
|
|
1819
|
+
return { ok: true, ...budget, process_id: record.id, generation: metadata.run_generation ?? null };
|
|
1820
|
+
}
|
|
1821
|
+
if (action === "release-turn") {
|
|
1822
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
1823
|
+
if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
1824
|
+
throw new AppError("process_ownership_unknown", "Budget release requires its retained physical owner", 1);
|
|
1825
|
+
return releaseProviderTurns(context, record, { daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") });
|
|
1826
|
+
}
|
|
1697
1827
|
if (action === "finish") {
|
|
1698
1828
|
const state = requiredOption(parsed, "state");
|
|
1699
1829
|
if (state !== "stopped" && state !== "failed")
|
|
@@ -1704,7 +1834,7 @@ async function dispatchRuntime(context, command, parsed, progress) {
|
|
|
1704
1834
|
return { ok: true, processes: managedProcessStatus(context) };
|
|
1705
1835
|
if (action === "reconcile")
|
|
1706
1836
|
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);
|
|
1837
|
+
throw new AppError("usage", "Usage: dd-flow runtime process <start|stop|register|confirm|heartbeat|admission|finish|status|reconcile>", 2);
|
|
1708
1838
|
}
|
|
1709
1839
|
function dispatchStat(context, command, parsed) {
|
|
1710
1840
|
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); }
|