@deksden-com/dd-flow-cli 0.9.0-beta.8 → 0.9.0-beta.81
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 +402 -0
- package/README.md +65 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +300 -0
- package/dist/cli/help.js +81 -9
- package/dist/cli/hook-ingress.js +82 -0
- package/dist/cli/input-preparation.js +115 -0
- package/dist/cli/run-cli.js +1344 -376
- package/dist/cli.js +6 -2
- package/dist/harness-runtime/bin/dd-agy.mjs +37 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +423 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +471 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +910 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +260 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +70 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +2 -2
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +4 -4
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +57 -13
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +543 -96
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/config.js +11 -8
- package/dist/services/controller-fanout.js +116 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +121 -76
- package/dist/services/eval-snapshots.js +750 -73
- package/dist/services/execution-policy.js +170 -0
- package/dist/services/external-work-launch.js +110 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +575 -293
- package/dist/services/lanes.js +61 -53
- package/dist/services/lifecycle-command.js +87 -7
- package/dist/services/lifecycle-invocations.js +835 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +283 -27
- package/dist/services/merge-queue.js +179 -104
- package/dist/services/merge-server.js +73 -28
- package/dist/services/migrations.js +14 -9
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +19 -8
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +18 -5
- package/dist/services/prompts.js +23 -16
- package/dist/services/protocols.js +64 -20
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +363 -0
- package/dist/services/run-control.js +876 -0
- package/dist/services/run-controller-adapter.js +222 -0
- package/dist/services/run-controller-capture.js +134 -0
- package/dist/services/run-controller-process.js +189 -0
- package/dist/services/run-controller-recovery.js +222 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +779 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +138 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +343 -0
- package/dist/services/runs.js +368 -74
- package/dist/services/runtime-budget.js +434 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +450 -0
- package/dist/services/runtime-scope-resume.js +565 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +243 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +44 -18
- package/dist/services/stage-lifecycle.js +99 -102
- package/dist/services/stage-pause.js +69 -28
- package/dist/services/usage.js +37 -93
- package/dist/services/vnext-code-review.js +189 -85
- package/dist/services/vnext-code.js +257 -243
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +134 -19
- package/dist/services/vnext-merge.js +285 -102
- package/dist/services/vnext-plan-review.js +148 -94
- package/dist/services/vnext-plan.js +141 -72
- package/dist/services/vnext-protocolize.js +129 -56
- package/dist/services/vnext-specify.js +87 -64
- package/dist/services/work-registry.js +701 -164
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/worktrees.js +85 -35
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +444 -32
- package/dist/storage/paths.js +17 -4
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +11 -4
- package/tools/repair-paused-run-status.mjs +59 -0
package/dist/cli/run-cli.js
CHANGED
|
@@ -1,56 +1,80 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { quote } from "shell-quote";
|
|
5
|
+
import { observeLifecycleCommand } from "../services/lifecycle-invocations.js";
|
|
6
|
+
import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
|
|
7
|
+
import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleInvocation, settleLifecycleInvocation, settleLifecycleRejection } from "../services/lifecycle-invocations.js";
|
|
3
8
|
import { createContext, createRouterContext } from "../runtime/context.js";
|
|
9
|
+
import { migrateStoreWriter } from "../storage/writer-migration.js";
|
|
4
10
|
import { helpForArgs } from "./help.js";
|
|
5
11
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
12
|
+
import { entityReferenceVariants } from "../shared/entity-references.js";
|
|
6
13
|
import { writeJson } from "../shared/json.js";
|
|
7
|
-
import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
8
|
-
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, requireProtocol, syncProtocolFromRun, transitionProtocol } from "../services/protocols.js";
|
|
9
|
-
import {
|
|
10
|
-
import { cancelMergeQueueJob, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
14
|
+
import { archiveProject, prepareProjectArchive, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
15
|
+
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, prepareProtocolRegistration, requireProtocol, syncProtocolFromRun, prepareProtocolTransition, transitionProtocol } from "../services/protocols.js";
|
|
16
|
+
import { getPlanStatus, preparePlanItemMutation, updatePlanItem } from "../services/plans.js";
|
|
17
|
+
import { cancelMergeQueueJob, prepareMergeQueueCancellation, prepareMergeQueueWait, prepareMergeClaim, prepareMergeBundleClaim, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, prepareMergeBundleCompletion, completeMergeJob, prepareMergeJobCompletion, failMergeBundle, prepareMergeBundleFailure, failMergeJob, prepareMergeJobFailure, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, prepareMergeJobNote, waitNextMergeJob } from "../services/merge-queue.js";
|
|
11
18
|
import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
|
|
12
|
-
import { cleanupApply, cleanupScan } from "../services/cleanup.js";
|
|
13
|
-
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
|
|
14
|
-
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
15
|
-
import { acquireLaneLock, cancelLaneWaiter, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
|
|
16
|
-
import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
|
|
17
|
-
import { getCanonStatus, registerCanonRoot, resolveCanonRoot } from "../services/canon.js";
|
|
19
|
+
import { cleanupApply, cleanupScan, prepareCleanupApply } from "../services/cleanup.js";
|
|
20
|
+
import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleDroidEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeHook, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, prepareCodexHome, prepareCodexHooks, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, prepareCodexHomeRemoval, removeCodexHooks } from "../services/hooks.js";
|
|
21
|
+
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, prepareWorktreeCreate, prepareWorktreeBootstrap, prepareWorktreeClose, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
22
|
+
import { acquireLaneLock, cancelLaneWaiter, prepareLaneWaiterCancellation, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, prepareLaneWorkspace, prepareLaneLock, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
|
|
23
|
+
import { getProjectConfigStatus, prepareProjectConfigValue, setProjectConfigValue } from "../services/config.js";
|
|
24
|
+
import { getCanonStatus, registerCanonRoot, resolveCanonRoot, prepareCanonRegistration } from "../services/canon.js";
|
|
18
25
|
import { getRuntimeStatus } from "../services/status.js";
|
|
19
26
|
import { getCliVersionReport } from "../services/build-info.js";
|
|
20
27
|
import { parseCommandArgs } from "../services/lifecycle-command.js";
|
|
21
|
-
import {
|
|
28
|
+
import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile } from "./input-preparation.js";
|
|
29
|
+
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, prepareDashboardRefresh, prepareDashboardRender, prepareGlobalDashboardRender, prepareDashboardOptions, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
22
30
|
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
23
|
-
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
31
|
+
import { getFlowSessionStatus, prepareFlowSessionPayload, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
24
32
|
import { getProtocolBranchContext } from "../services/branch-context.js";
|
|
25
|
-
import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, setFlowRunVariable, startFlowRun } from "../services/runs.js";
|
|
33
|
+
import { attachFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, completeFlowRun, prepareFlowRunCompletion, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, prepareFlowRunConfig, prepareFlowRunVariable, setFlowRunVariable, startFlowRun, prepareFlowRunStart } from "../services/runs.js";
|
|
26
34
|
import { previewNextEntityId } from "../services/ids.js";
|
|
27
35
|
import { validateSchema } from "../services/schema-validation.js";
|
|
28
36
|
import { preflightMemoryPermissions } from "../services/memory-permissions.js";
|
|
29
|
-
import { ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
|
|
30
|
-
import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, verifyMigrationReport } from "../services/migrations.js";
|
|
37
|
+
import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
|
|
38
|
+
import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, prepareMigrationApply, verifyMigrationReport } from "../services/migrations.js";
|
|
31
39
|
import { publishProjectSummary } from "../services/project-summary.js";
|
|
32
40
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
33
41
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
34
|
-
import { doctorEngines, bindRunEngine, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
42
|
+
import { doctorEngines, bindRunEngine, prepareRunEngineBind, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
35
43
|
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
36
|
-
import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify } from "../services/vnext-specify.js";
|
|
37
|
-
import { finishVnextProtocolize, isVnextProtocolizeRun, startVnextProtocolize } from "../services/vnext-protocolize.js";
|
|
38
|
-
import { finishVnextPlan, isVnextPlanRun, startVnextPlan } from "../services/vnext-plan.js";
|
|
39
|
-
import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
|
|
40
|
-
import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services/vnext-code.js";
|
|
41
|
-
import {
|
|
42
|
-
import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
44
|
+
import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify, prepareVnextSpecifyStart } from "../services/vnext-specify.js";
|
|
45
|
+
import { finishVnextProtocolize, isVnextProtocolizeRun, prepareProtocolizeResultPath, prepareVnextProtocolizeStart, startVnextProtocolize } from "../services/vnext-protocolize.js";
|
|
46
|
+
import { finishVnextPlan, isVnextPlanRun, startVnextPlan, prepareVnextPlanStart, prepareVnextPlanFinish } from "../services/vnext-plan.js";
|
|
47
|
+
import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, prepareVnextPlanReviewStart, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
|
|
48
|
+
import { addVnextCodeRepair, finishVnextCode, prepareVnextCodeRepair, prepareVnextCodeStart, startVnextCode } from "../services/vnext-code.js";
|
|
49
|
+
import { finishVnextCodeReview, isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReviewRepair, prepareVnextCodeReviewStart, startVnextCodeReview } from "../services/vnext-code-review.js";
|
|
50
|
+
import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
43
51
|
import { mergeServerStatus, serveMergeRequests, stopMergeServer } from "../services/merge-server.js";
|
|
44
|
-
import { getVnextFanoutStatus } from "../services/vnext-fanout.js";
|
|
45
|
-
import { pauseStageForUser, resumeStageAfterUser } from "../services/stage-pause.js";
|
|
46
|
-
import { blockStageForRuntime, unblockStageAfterRuntimeRepair } from "../services/stage-blocker.js";
|
|
52
|
+
import { getVnextFanoutStatus, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
|
|
53
|
+
import { pauseStageForUser, resumeStageAfterUser, prepareStagePause, prepareStageResume } from "../services/stage-pause.js";
|
|
54
|
+
import { blockStageForRuntime, unblockStageAfterRuntimeRepair, prepareStageRuntimeBlock, prepareStageRuntimeUnblock } from "../services/stage-blocker.js";
|
|
47
55
|
import { classifyCliOperation, requiresExplicitUpgradeAuthorization } from "../services/cli-operation-classifier.js";
|
|
48
|
-
import { renderWorkerPrompt } from "../services/prompts.js";
|
|
49
|
-
import { finishStage, startStage } from "../services/stage-lifecycle.js";
|
|
50
|
-
import { addWorkBatch, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
56
|
+
import { prepareWorkerPrompt, renderWorkerPrompt } from "../services/prompts.js";
|
|
57
|
+
import { finishStage, prepareStageFinish, startStage, prepareLegacyStageStart } from "../services/stage-lifecycle.js";
|
|
58
|
+
import { addWorkBatch, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
59
|
+
import { launchExternalWork } from "../services/external-work-launch.js";
|
|
60
|
+
import { createEvalBootstrapSnapshot, prepareEvalBootstrapSnapshot, createEvalRunSnapshot, prepareEvalRunSnapshotCreation, prepareVnextSpecifyRun, prepareEvalRunRestore, prepareEvalBootstrapRestore, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
|
|
61
|
+
import { forkRunFromSnapshot, prepareRunFork } from "../services/run-fork.js";
|
|
62
|
+
import { acceptRunRecovery, assertRunMutationAllowed, beginRunRecovery, inspectRunRecovery, prepareRecoveryCapture, prepareRecoveryPayload, prepareRunRecoveryResume, recordRecoveryCapture, recoveryGuard, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
|
|
63
|
+
import { recoveryRuntimeAdmission } from "../services/run-recovery-runtime.js";
|
|
64
|
+
import { loadExternalStageContext, prepareExternalStageContext } from "../services/stage-context.js";
|
|
65
|
+
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
66
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
67
|
+
import { parseLifecycleCommand } from "../services/lifecycle-command.js";
|
|
68
|
+
import { prepareManagedProcessRegistration, prepareManagedProcessConfirmation, prepareManagedProcessHeartbeat, prepareManagedProcessFinish, prepareManagedProcessStopOperation, prepareManagedProcessReconcile, stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
|
|
69
|
+
import { fenceRuntimeBudget, runtimeBudgetStatus, admitRuntimeCheck, parseRuntimeBudget, prepareRuntimeScopeControl, prepareRuntimeScopeFence, reserveProviderTurn, releaseProviderTurns, prepareProviderTurnRelease } from "../services/runtime-budget.js";
|
|
70
|
+
import { stopRuntimeScope } from "../services/runtime-scope-stop.js";
|
|
71
|
+
import { launchRuntimeScopeControl, launchRuntimeScopeResume, prepareRuntimeScopeReconciliation, prepareRuntimeScopeServing, reconcileRuntimeScopeControl, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
|
|
72
|
+
import { prepareRuntimeScopeResumeRequest } from "../services/runtime-scope-resume.js";
|
|
73
|
+
import { launchRunController, prepareRunControllerLaunch, resumeRunController, serveRunController, runControllerStatus, answerRunController, prepareRunControllerAnswer, supplyRunControllerContext, prepareRunControllerContext } from "../services/run-controller.js";
|
|
74
|
+
import { controllerHitlAdmission } from "../services/run-controller-adapter.js";
|
|
75
|
+
import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
|
|
76
|
+
import { reconcileRunControl, requestRunControl, serveRunControl } from "../services/run-control-worker.js";
|
|
77
|
+
import { startRuntimeService, prepareRuntimeService } from "../services/runtime-service.js";
|
|
54
78
|
const defaultIo = {
|
|
55
79
|
stdout: process.stdout,
|
|
56
80
|
stderr: process.stderr,
|
|
@@ -58,9 +82,20 @@ const defaultIo = {
|
|
|
58
82
|
};
|
|
59
83
|
const flagValue = "__dd_flow_flag__";
|
|
60
84
|
export async function runCli(args, io = defaultIo, env = process.env) {
|
|
61
|
-
|
|
85
|
+
let output;
|
|
86
|
+
try {
|
|
87
|
+
output = parseOutputOptions(args);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
writeJson(io.stderr, { ok: false, error: { code: "usage", message } });
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
62
94
|
const progress = progressForCommand(output.args);
|
|
63
95
|
let heartbeat;
|
|
96
|
+
let invocationSettlement;
|
|
97
|
+
let settleHook;
|
|
98
|
+
let diagnosticHome;
|
|
64
99
|
try {
|
|
65
100
|
if (output.args.length === 1 && output.args[0] === "--version") {
|
|
66
101
|
const payload = getCliVersionReport();
|
|
@@ -75,6 +110,16 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
75
110
|
io.stdout.write(`${help}\n`);
|
|
76
111
|
return 0;
|
|
77
112
|
}
|
|
113
|
+
const suppliedInvocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
|
|
114
|
+
if (output.args[0] === "run" && ["drive", "control"].includes(output.args[1] ?? "")) {
|
|
115
|
+
const controlArgs = parseArgs(output.args.slice(2));
|
|
116
|
+
requiredOption(controlArgs, "run");
|
|
117
|
+
requiredOption(controlArgs, "project-root");
|
|
118
|
+
}
|
|
119
|
+
if (io.stdin?.isTTY && ["work", "stage"].includes(output.args[0] ?? "") && output.args[1] === "finish"
|
|
120
|
+
&& hasOption(parseArgs(output.args.slice(2)), "result-stdin")) {
|
|
121
|
+
throw new AppError("usage", "--result-stdin requires non-interactive input. Pipe or redirect the complete JSON into this command, or use --result-file; interactive terminals can truncate long JSON and wait indefinitely for EOF.", 2);
|
|
122
|
+
}
|
|
78
123
|
const classification = classifyCliOperation(output.args, env);
|
|
79
124
|
if (requiresExplicitUpgradeAuthorization(output.args) && classification.mode !== "mb_upgrade") {
|
|
80
125
|
throw new AppError("upgrade_authorization_required", "This mb-upgrade command requires the explicit mb-upgrade authorization marker and allowlisted target context", 1, {
|
|
@@ -83,25 +128,101 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
83
128
|
});
|
|
84
129
|
}
|
|
85
130
|
const routerContext = createRouterContext(env);
|
|
131
|
+
const publicCommand = quote(["dd-flow", ...output.args]);
|
|
132
|
+
const lifecycleCommand = parseLifecycleCommand(publicCommand);
|
|
133
|
+
const observedInvocation = observedLifecycleInvocation(routerContext, publicCommand);
|
|
134
|
+
if (!observedInvocation && !suppliedInvocationId && env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone") {
|
|
135
|
+
throw new AppError("invocation_receipt_missing", "Managed lifecycle command has no committed native receipt", 1, { effect: "no_effect", recoverable: false });
|
|
136
|
+
}
|
|
137
|
+
const invocationId = observedInvocation?.id ?? suppliedInvocationId;
|
|
138
|
+
const boundInvocationArgs = invocationId && invocationId !== suppliedInvocationId
|
|
139
|
+
? [...output.args.filter((_, index, values) => values[index - 1] !== "--invocation-id" && values[index] !== "--invocation-id"), "--invocation-id", invocationId]
|
|
140
|
+
: output.args;
|
|
141
|
+
const observedOperation = lifecycleCommand.kind === "standalone" ? lifecycleCommand.invocation.operation : null;
|
|
142
|
+
if (observedInvocation && !observedOperation)
|
|
143
|
+
throw new AppError("invocation_command_invalid", "Observed lifecycle command could not be parsed", 1);
|
|
144
|
+
const invocationScope = invocationId ? lifecycleInvocationScope({ ...routerContext, env }, invocationId) : undefined;
|
|
145
|
+
const invocationArgs = invocationScope && observedOperation
|
|
146
|
+
? expandLifecycleInvocationArgs(routerContext, boundInvocationArgs, invocationScope, observedOperation)
|
|
147
|
+
: boundInvocationArgs;
|
|
86
148
|
emitHumanProgress(io, output, progress, "start");
|
|
87
149
|
if (output.progressJsonl) {
|
|
88
150
|
writeProgressJsonl(io, "start", `Starting ${output.args.slice(0, 3).join(" ")}`);
|
|
89
151
|
heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
|
|
90
152
|
}
|
|
91
|
-
|
|
92
|
-
|
|
153
|
+
// Route by retained authority, so a mistyped project path reaches the CLI
|
|
154
|
+
// validator instead of selecting a different engine or failing preflight.
|
|
155
|
+
const scopeProjectRoot = invocationScope?.projectRoot ?? resolveProjectRootBeforeRouting(output.args, env);
|
|
156
|
+
const routedArgs = output.responseFile ? [...invocationArgs, "--response-file", output.responseFile] : invocationArgs;
|
|
93
157
|
const routed = await routeArgsThroughEngine(routerContext, output.json ? [...routedArgs, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : routedArgs, io, io.stdin, env, scopeProjectRoot);
|
|
94
158
|
if (routed?.routed && !routed.inProcess) {
|
|
95
159
|
if (heartbeat)
|
|
96
160
|
clearInterval(heartbeat);
|
|
97
|
-
emitHumanProgress(io, output, progress, "done");
|
|
161
|
+
emitHumanProgress(io, output, progress, routed.exitCode === 0 ? "done" : "failed");
|
|
162
|
+
if (output.progressJsonl && routed.exitCode !== 0)
|
|
163
|
+
writeProgressJsonl(io, "failed", "Routed engine failed", { exit_code: routed.exitCode });
|
|
98
164
|
return routed.exitCode;
|
|
99
165
|
}
|
|
166
|
+
let preparedInput;
|
|
167
|
+
let preparedRoute;
|
|
168
|
+
if (!invocationId) {
|
|
169
|
+
const preparationContext = createContext({ ...env }, "read_existing");
|
|
170
|
+
try {
|
|
171
|
+
preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
preparationContext.db.close?.();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
100
177
|
const dispatchContext = routed?.inProcess
|
|
101
|
-
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(
|
|
102
|
-
: createContext(env, databaseModeForCommand(
|
|
103
|
-
|
|
104
|
-
|
|
178
|
+
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(invocationArgs, classification))
|
|
179
|
+
: createContext({ ...env }, databaseModeForCommand(invocationArgs, classification));
|
|
180
|
+
diagnosticHome = dispatchContext.ddFlowHome;
|
|
181
|
+
dispatchContext.env.DD_FLOW_INVOCATION_READONLY = classification.mode === "read_only_diagnostics" ? "1" : undefined;
|
|
182
|
+
let dispatchArgs = invocationArgs;
|
|
183
|
+
let retainedOutcome;
|
|
184
|
+
// Issued transports may deliver their receipt asynchronously. Observe only
|
|
185
|
+
// after admission; retained outcomes must not need a fresh hook receipt.
|
|
186
|
+
if (invocationId) {
|
|
187
|
+
const command = quote(["dd-flow", ...invocationArgs]);
|
|
188
|
+
const admission = await awaitLifecycleInvocation(dispatchContext, { id: invocationId, command,
|
|
189
|
+
validateArgs: () => { preparedRoute = prepareCommandRoute(invocationArgs); },
|
|
190
|
+
prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute); },
|
|
191
|
+
assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command) });
|
|
192
|
+
if (admission.replay) {
|
|
193
|
+
retainedOutcome = admission.outcome;
|
|
194
|
+
settleHook = undefined;
|
|
195
|
+
delete dispatchContext.observeLifecycleHook;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
invocationSettlement = { context: dispatchContext, id: invocationId };
|
|
199
|
+
dispatchArgs = [...invocationArgs, "--hook-event-id", admission.eventKey];
|
|
200
|
+
preparedInput?.route?.parsed.options.set("hook-event-id", [admission.eventKey]);
|
|
201
|
+
dispatchContext.env.DD_FLOW_INVOCATION_SCOPE = JSON.stringify(lifecycleInvocationScope(dispatchContext, invocationId));
|
|
202
|
+
dispatchContext.env.DD_FLOW_CURRENT_INVOCATION = invocationId;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (retainedOutcome?.error) {
|
|
206
|
+
const error = retainedOutcome.error;
|
|
207
|
+
throw new AppError(error.code, error.message, error.exitCode, error.details);
|
|
208
|
+
}
|
|
209
|
+
if (!retainedOutcome && !preparedInput)
|
|
210
|
+
preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile);
|
|
211
|
+
if (!retainedOutcome)
|
|
212
|
+
preflightCliCompatibility(dispatchContext, invocationArgs, env, scopeProjectRoot);
|
|
213
|
+
if (!retainedOutcome)
|
|
214
|
+
settleHook = observeLifecycleCommand(dispatchContext, quote(["dd-flow", ...dispatchArgs]));
|
|
215
|
+
const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput);
|
|
216
|
+
const completedHook = settleHook;
|
|
217
|
+
settleHook = undefined;
|
|
218
|
+
completedHook?.({ result });
|
|
219
|
+
if (invocationSettlement) {
|
|
220
|
+
const completed = invocationSettlement;
|
|
221
|
+
invocationSettlement = undefined;
|
|
222
|
+
// A failure to persist an already completed result is an unknown reply,
|
|
223
|
+
// not a failed operation eligible for a new productive retry.
|
|
224
|
+
settleLifecycleInvocation(completed.context, completed.id, { result });
|
|
225
|
+
}
|
|
105
226
|
const refreshWarnings = refreshDashboardsAfterMutation(dispatchContext, output.args, result, scopeProjectRoot);
|
|
106
227
|
const resultWithWarnings = addWarnings(result, refreshWarnings);
|
|
107
228
|
if (heartbeat)
|
|
@@ -117,17 +238,59 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
117
238
|
io.stdout.write(renderHumanResult(output.args, payload));
|
|
118
239
|
return completedCommandExitCode(payload);
|
|
119
240
|
}
|
|
120
|
-
catch (
|
|
241
|
+
catch (caught) {
|
|
242
|
+
const error = isAppError(caught) ? caught : new AppError("unexpected", caught instanceof Error ? caught.message : String(caught), 1, { effect: "unknown", cause: { code: caught?.code ?? null, sqlite_extended_code: caught?.errcode ?? null } });
|
|
243
|
+
if (diagnosticHome && (!isAppError(caught) || error.code === "storage_write_failed")) {
|
|
244
|
+
try {
|
|
245
|
+
const directory = path.join(diagnosticHome, "diagnostics", "cli");
|
|
246
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
247
|
+
const artifact = path.join(directory, `${randomUUID()}.json`);
|
|
248
|
+
fs.writeFileSync(artifact, JSON.stringify({ code: error.code, message: error.message, details: error.details, stack: caught instanceof Error ? caught.stack : null }), { flag: "wx", mode: 0o600 });
|
|
249
|
+
error.details.diagnostic_artifact = artifact;
|
|
250
|
+
}
|
|
251
|
+
catch (secondary) {
|
|
252
|
+
io.stderr.write(`CLI diagnostic persistence failed: ${String(secondary)}\n`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
121
255
|
if (heartbeat)
|
|
122
256
|
clearInterval(heartbeat);
|
|
257
|
+
if (invocationSettlement && error.details.effect !== "committed") {
|
|
258
|
+
try {
|
|
259
|
+
if (isAppError(error))
|
|
260
|
+
settleLifecycleRejection(invocationSettlement.context, invocationSettlement.id, { code: error.code, message: error.message, exitCode: error.exitCode, details: error.details });
|
|
261
|
+
else
|
|
262
|
+
settleLifecycleInvocation(invocationSettlement.context, invocationSettlement.id, { error: { code: "unexpected", message: String(error), exitCode: 1, details: {} } });
|
|
263
|
+
}
|
|
264
|
+
catch (persistenceError) {
|
|
265
|
+
io.stderr.write(`Invocation settlement persistence failed: ${String(persistenceError)}\n`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (settleHook) {
|
|
269
|
+
try {
|
|
270
|
+
settleHook({ error: isAppError(error) ? error : { code: "unexpected", message: String(error), details: {} } });
|
|
271
|
+
}
|
|
272
|
+
catch (persistence) {
|
|
273
|
+
io.stderr.write(`${JSON.stringify({ primary: String(error), lifecycle_persistence_error: String(persistence) })}\n`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
123
276
|
const payload = isAppError(error)
|
|
124
277
|
? { ok: false, error: { code: error.code, message: error.message, details: error.details } }
|
|
125
278
|
: { ok: false, error: { code: "unexpected", message: String(error) } };
|
|
279
|
+
progress.splice(0, progress.length, ...progress.filter(event => event.phase !== "done"));
|
|
280
|
+
if (progress.length)
|
|
281
|
+
progress.push({ phase: "failed", message: payload.error.message, at: new Date().toISOString() });
|
|
126
282
|
emitHumanProgress(io, output, progress, "failed");
|
|
127
283
|
if (output.progressJsonl)
|
|
128
284
|
writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
|
|
129
285
|
const errorPayload = attachProgress(payload, progress, output);
|
|
130
|
-
|
|
286
|
+
let visible = errorPayload;
|
|
287
|
+
try {
|
|
288
|
+
if (error.details.parameter !== "response-file")
|
|
289
|
+
visible = writeResponseFile(errorPayload, output);
|
|
290
|
+
}
|
|
291
|
+
catch (publication) {
|
|
292
|
+
error.details.response_publication_error = { code: publication?.code ?? "response_publication_failed", message: publication instanceof Error ? publication.message : String(publication) };
|
|
293
|
+
}
|
|
131
294
|
if (output.json && output.progressJsonl)
|
|
132
295
|
writeJson(io.stdout, errorPayload);
|
|
133
296
|
else if (output.json)
|
|
@@ -151,7 +314,22 @@ function resolveProjectRootBeforeRouting(args, env) {
|
|
|
151
314
|
}
|
|
152
315
|
function databaseModeForCommand(args, classification) {
|
|
153
316
|
const [family, command] = args;
|
|
317
|
+
// Managed attempts are issued only from a prepared store. Opening that
|
|
318
|
+
// existing writer must not run migrations before their input is admitted.
|
|
319
|
+
if (parseArgs(args.slice(2)).options.has("invocation-id"))
|
|
320
|
+
return "hook";
|
|
321
|
+
// Native hook ingress must remain synchronous, but it must not run schema
|
|
322
|
+
// migration/VACUUM while the provider is waiting for its decision. Startup
|
|
323
|
+
// prepares the store; the hook mode only verifies the prepared contract and
|
|
324
|
+
// records the receipt.
|
|
325
|
+
if (family === "codex" && command === "hook" && args[2] === "handle") {
|
|
326
|
+
// Only the participating PreToolUse ingress needs the prepared hot-path
|
|
327
|
+
// contract. SessionStart/Stop/PostToolUse are observational no-ops here
|
|
328
|
+
// and retain the ordinary startup path for backwards compatibility.
|
|
329
|
+
return optionalOption(parseArgs(args.slice(3)), "event") === "PreToolUse" ? "hook" : "initialize";
|
|
330
|
+
}
|
|
154
331
|
if (family === "engine" ||
|
|
332
|
+
family === "storage" && command === "migrate-writer" ||
|
|
155
333
|
family === "version" ||
|
|
156
334
|
family === "schema" ||
|
|
157
335
|
family === "canon" && command === "resolve" ||
|
|
@@ -207,8 +385,10 @@ function parseOutputOptions(args) {
|
|
|
207
385
|
continue;
|
|
208
386
|
}
|
|
209
387
|
if (arg === "--response-file") {
|
|
388
|
+
if (responseFile !== null)
|
|
389
|
+
throw new AppError("usage", "--response-file may be supplied only once", 2);
|
|
210
390
|
const file = args[++index];
|
|
211
|
-
if (!file)
|
|
391
|
+
if (!file?.trim() || file.startsWith("--"))
|
|
212
392
|
throw new AppError("usage", "--response-file requires a path", 2);
|
|
213
393
|
responseFile = file;
|
|
214
394
|
continue;
|
|
@@ -220,6 +400,33 @@ function parseOutputOptions(args) {
|
|
|
220
400
|
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
221
401
|
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
|
|
222
402
|
}
|
|
403
|
+
/** Validate the response destination without creating it. The response is an
|
|
404
|
+
* optional publication, but a known-bad destination should never be found
|
|
405
|
+
* only after a successful lifecycle mutation. */
|
|
406
|
+
function prepareResponseDestination(value) {
|
|
407
|
+
const file = path.resolve(value);
|
|
408
|
+
const parent = path.dirname(file);
|
|
409
|
+
try {
|
|
410
|
+
let ancestor = parent;
|
|
411
|
+
for (;;) {
|
|
412
|
+
try {
|
|
413
|
+
if (!fs.statSync(ancestor).isDirectory())
|
|
414
|
+
throw new AppError("response_file_parent_invalid", "--response-file parent must be a directory", 2, { file, parent });
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
if (error.code !== "ENOENT" || path.dirname(ancestor) === ancestor)
|
|
419
|
+
throw error;
|
|
420
|
+
ancestor = path.dirname(ancestor);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (fs.existsSync(file) && !fs.statSync(file).isFile())
|
|
424
|
+
throw new AppError("response_file_invalid", "--response-file must name a regular file", 2, { file });
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
return prepareError(error, { parameter: "response-file", file, parent });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
223
430
|
/** Persist the complete machine response for harnesses that cap tool output.
|
|
224
431
|
* The visible response keeps routing fields but omits the potentially large
|
|
225
432
|
* rendered prompt; an agent can safely read the named file without rerunning
|
|
@@ -500,27 +707,636 @@ function stringValue(value) {
|
|
|
500
707
|
function shortSha(value) {
|
|
501
708
|
return value ? value.slice(0, 12) : "unknown";
|
|
502
709
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
710
|
+
/** All caller supplied file/stdin data is consumed here, before a managed
|
|
711
|
+
* invocation is allowed to claim execution or a stage claims its hook event. */
|
|
712
|
+
async function prepareCliInput(...args) {
|
|
713
|
+
try {
|
|
714
|
+
return await prepareCliInputValue(...args);
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
return prepareError(error);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args)) {
|
|
721
|
+
const { family, command, parsed } = route;
|
|
722
|
+
const prepared = { route };
|
|
723
|
+
if (route.key === "project register")
|
|
724
|
+
resolveProjectRoot(requiredOption(parsed, "root"));
|
|
725
|
+
if (route.key === "project archive")
|
|
726
|
+
prepareProjectArchive(context, { idOrAlias: parsed.positional[0], root: optionalOption(parsed, "root"), reason: requiredOption(parsed, "reason") });
|
|
727
|
+
if (route.key === "protocol register")
|
|
728
|
+
prepareProtocolRegistration(context, { handshakeId: requiredPosition(parsed, 0, "handshake-id"), projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "workspace-path") });
|
|
729
|
+
if (route.key === "canon register")
|
|
730
|
+
prepared.canonRegistration = prepareCanonRegistration(context, { root: requiredOption(parsed, "root") });
|
|
731
|
+
if (route.key === "run attach-stage")
|
|
732
|
+
prepareFlowRunStageAttachment(context, stageAttachmentInput(parsed));
|
|
733
|
+
if (route.key === "run complete-stage")
|
|
734
|
+
prepareFlowRunStageCompletion(context, stageCompletionInput(parsed));
|
|
735
|
+
if (route.key === "run override")
|
|
736
|
+
prepareFlowRunCompletion(context, runOverrideInput(parsed));
|
|
737
|
+
if (route.key === "engine bind-run")
|
|
738
|
+
prepared.engineBind = prepareRunEngineBind(context, engineBindInput(parsed));
|
|
739
|
+
if (route.key === "worktree create")
|
|
740
|
+
prepared.worktreeCreate = prepareWorktreeCreate(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree create"), branch: requiredOption(parsed, "branch"), base: requiredOption(parsed, "base"), path: requiredOption(parsed, "path") });
|
|
741
|
+
if (route.key === "worktree bootstrap")
|
|
742
|
+
prepared.worktreeBootstrap = prepareWorktreeBootstrap(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree bootstrap") });
|
|
743
|
+
if (route.key === "worktree close")
|
|
744
|
+
prepareWorktreeClose(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree close"), mode: requiredOption(parsed, "mode") });
|
|
745
|
+
if (route.key.startsWith("plan item "))
|
|
746
|
+
preparePlanItemMutation(context, planItemInput(parsed, requiredScopeProjectRoot(scopeProjectRoot, route.key)));
|
|
747
|
+
if (route.key === "lane workspace set")
|
|
748
|
+
prepareLaneWorkspace(context, laneWorkspaceInput(parsed));
|
|
749
|
+
if (route.key === "lane waiter cancel")
|
|
750
|
+
prepareLaneWaiterCancellation(context, laneWaiterCancelInput(parsed));
|
|
751
|
+
if (route.key === "merge-queue complete")
|
|
752
|
+
prepareMergeJobCompletion(context, { ...mergeJobTarget(parsed, scopeProjectRoot), summary: requiredOption(parsed, "summary") });
|
|
753
|
+
if (route.key === "merge-queue cancel")
|
|
754
|
+
prepareMergeQueueCancellation(context, mergeCancellationInput(parsed, scopeProjectRoot));
|
|
755
|
+
if (route.key === "merge-queue wait-next")
|
|
756
|
+
prepareMergeQueueWait(context, mergeWaitInput(parsed, scopeProjectRoot));
|
|
757
|
+
if (route.key === "merge-queue next" || route.key === "merge-queue claim")
|
|
758
|
+
prepareMergeClaim(context, { projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed), ...(route.key === "merge-queue claim" ? { protocolId: requiredPosition(parsed, 0, "protocol-id") } : {}) });
|
|
759
|
+
if (route.key === "merge-queue note")
|
|
760
|
+
prepareMergeJobNote(context, { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
|
|
761
|
+
if (route.key === "merge bundle complete")
|
|
762
|
+
prepareMergeBundleCompletion(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
|
|
763
|
+
if (route.key === "merge bundle claim")
|
|
764
|
+
prepareMergeBundleClaim(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed) });
|
|
765
|
+
if (route.key === "merge bundle fail")
|
|
766
|
+
prepareMergeBundleFailure(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
|
|
767
|
+
if (route.key === "merge-queue fail")
|
|
768
|
+
prepareMergeJobFailure(context, { ...mergeJobTarget(parsed, scopeProjectRoot), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
|
|
769
|
+
if (route.key.startsWith("lane lock ") && route.key !== "lane lock status") {
|
|
770
|
+
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
771
|
+
prepareLaneLock(context, {
|
|
772
|
+
projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"),
|
|
773
|
+
workspacePath: workspacePathOption(parsed), leaseToken: optionalOption(parsed, "lease-token"), reason: optionalOption(parsed, "reason"),
|
|
774
|
+
ttlSeconds: optionalNumber(parsed, "ttl"), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
|
|
775
|
+
}, action);
|
|
776
|
+
}
|
|
777
|
+
if (route.key === "run recovery begin")
|
|
778
|
+
prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "interruption-json"), "interruption");
|
|
779
|
+
if (route.key === "run recovery seal")
|
|
780
|
+
prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "settlement-json"), "settlement");
|
|
781
|
+
if (route.key === "run recovery capture")
|
|
782
|
+
prepared.recoveryCapture = prepareRecoveryCapture(context, recoveryCaptureInput(parsed));
|
|
783
|
+
if (route.key === "run recovery resume")
|
|
784
|
+
prepared.recoveryResume = prepareRunRecoveryResume(context, recoveryResumeInput(parsed));
|
|
785
|
+
if (route.key === "run drive answer")
|
|
786
|
+
prepared.controllerAnswer = prepareRunControllerAnswer(context, controllerAnswerInput(parsed));
|
|
787
|
+
if (route.key === "run drive launch")
|
|
788
|
+
prepared.controllerLaunch = prepareRunControllerLaunch(context, controllerLaunchInput(parsed));
|
|
789
|
+
if (route.key === "run drive context")
|
|
790
|
+
prepared.controllerContext = prepareRunControllerContext(context, controllerContextInput(parsed));
|
|
791
|
+
if (route.key === "protocol transition" || route.key === "transition")
|
|
792
|
+
prepared.protocolTransition = prepareProtocolTransition(context, protocolTransitionInput(parsed, scopeProjectRoot ?? resolveOperationProjectRoot(context, args)));
|
|
793
|
+
if (route.key === "codex home init")
|
|
794
|
+
prepared.codexHome = prepareCodexHome(context, codexHomeInput(parsed));
|
|
795
|
+
if (route.key === "codex home remove")
|
|
796
|
+
prepareCodexHomeRemoval(context, { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), mode: requiredOption(parsed, "mode") });
|
|
797
|
+
if (["codex hooks install", "codex hooks remove"].includes(route.key))
|
|
798
|
+
prepared.codexHooks = prepareCodexHooks(context, { projectRoot: requiredOption(parsed, "project-root"), target: optionalOption(parsed, "target"), profile: optionalOption(parsed, "profile"), yes: hasOption(parsed, "yes") });
|
|
799
|
+
if (route.key === "migration apply")
|
|
800
|
+
prepared.migrationApply = prepareMigrationApply(context, migrationApplyInput(parsed, classifyCliOperation(args, context.env)));
|
|
801
|
+
if (route.key === "migration verify")
|
|
802
|
+
prepared.migrationVerification = verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
803
|
+
if (responseFile)
|
|
804
|
+
prepareResponseDestination(responseFile);
|
|
805
|
+
if (family === "dashboard") {
|
|
806
|
+
const action = dashboardAction(command);
|
|
807
|
+
prepareDashboardOptions({ format: optionalOption(parsed, "format"), viewer: optionalOption(parsed, "viewer"), open: optionalOption(parsed, "open") });
|
|
808
|
+
const target = resolveDashboardTarget(context, { action, project: optionalOption(parsed, "project"), projectRoot: optionalOption(parsed, "project-root"), protocol: optionalOption(parsed, "protocol"), all: hasOption(parsed, "all"), global: hasOption(parsed, "global") || command?.endsWith("-global") });
|
|
809
|
+
if (action === "refresh" && target.kind !== "global" && optionalOption(parsed, "output") !== undefined) {
|
|
810
|
+
throw new AppError("validation", "--output is supported only for global dashboard refresh; use dashboard render for a custom project/protocol output", 2, { parameter: "output" });
|
|
811
|
+
}
|
|
812
|
+
if (target.kind === "protocol")
|
|
813
|
+
requireProtocol(context, target.protocolId, target.project.id);
|
|
814
|
+
if (action !== "data") {
|
|
815
|
+
if (target.kind === "global" || target.kind === "all_projects") {
|
|
816
|
+
prepareGlobalDashboardRender(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? (command?.endsWith("-global") ? undefined : "html") });
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
const format = optionalOption(parsed, "format") ?? (action === "open" || hasOption(parsed, "project") ? "html" : undefined);
|
|
820
|
+
const input = { projectRoot: target.project.root, format, ...(target.kind === "protocol" ? { protocol: target.protocolId } : {}) };
|
|
821
|
+
if (action === "refresh")
|
|
822
|
+
prepareDashboardRefresh(context, { ...input, open: optionalOption(parsed, "open") });
|
|
823
|
+
else if (action === "render")
|
|
824
|
+
prepareDashboardRender(context, { ...input, output: optionalOption(parsed, "output") });
|
|
825
|
+
else if (target.kind === "protocol") {
|
|
826
|
+
if (format !== "html")
|
|
827
|
+
throw new AppError("validation", "Protocol dashboard open supports --format html", 2, { protocol: target.protocolId });
|
|
828
|
+
prepareDashboardRender(context, input);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (route.key === "prompt render") {
|
|
834
|
+
const input = workerPromptInput(parsed);
|
|
835
|
+
prepared.workerPrompt = { input, value: prepareWorkerPrompt(context, input) };
|
|
836
|
+
}
|
|
837
|
+
if (route.key === "run snapshot restore" || route.key === "run snapshot bootstrap restore") {
|
|
838
|
+
try {
|
|
839
|
+
const input = snapshotRestoreInput(parsed);
|
|
840
|
+
if (route.key === "run snapshot restore")
|
|
841
|
+
prepared.snapshotRestore = prepareEvalRunRestore(context, input);
|
|
842
|
+
else
|
|
843
|
+
prepared.bootstrapRestore = prepareEvalBootstrapRestore(input);
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
return prepareError(error, { parameter: "snapshot/project-root" });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (route.key === "run snapshot bootstrap create") {
|
|
850
|
+
try {
|
|
851
|
+
prepareEvalBootstrapSnapshot(context, { projectRoot: requiredOption(parsed, "project-root"), output: requiredOption(parsed, "output") });
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
return prepareError(error, { parameter: "project-root/output" });
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (route.key === "run snapshot create") {
|
|
858
|
+
prepareEvalRunSnapshotCreation(context, snapshotCreateInput(parsed));
|
|
859
|
+
}
|
|
860
|
+
if (route.key === "run start") {
|
|
861
|
+
try {
|
|
862
|
+
const input = runStartInput(parsed);
|
|
863
|
+
prepared.runStart = { input, value: prepareFlowRunStart(context, input) };
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
return prepareError(error, { parameter: "run start" });
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (route.key === "run fork") {
|
|
870
|
+
try {
|
|
871
|
+
prepared.runFork = prepareRunFork(runForkInput(parsed));
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
return prepareError(error, { parameter: "from/output" });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (["runtime scope fence", "runtime scope stop"].includes(route.key))
|
|
878
|
+
prepareRuntimeScopeFence(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"));
|
|
879
|
+
if (route.key === "runtime scope resume")
|
|
880
|
+
prepareRuntimeScopeResumeRequest(context, runtimeScopeResumeInput(parsed));
|
|
881
|
+
if (route.key === "runtime scope reconcile")
|
|
882
|
+
prepareRuntimeScopeReconciliation(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id"));
|
|
883
|
+
if (route.key === "runtime scope serve")
|
|
884
|
+
prepareRuntimeScopeServing(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")));
|
|
885
|
+
if (route.key === "runtime scope control") {
|
|
886
|
+
try {
|
|
887
|
+
const raw = optionalOption(parsed, "manifest-json");
|
|
888
|
+
prepared.runtimeManifest = prepareRuntimeScopeControl(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), requiredOption(parsed, "mode"), raw === undefined ? undefined : prepareJsonText(raw, { file: "<manifest-json>", label: "scope manifest" }).value, context);
|
|
889
|
+
}
|
|
890
|
+
catch (error) {
|
|
891
|
+
return prepareError(error, { parameter: "scope-id/request-id/manifest-json" });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (route.key === "runtime process register") {
|
|
895
|
+
prepareManagedProcessRegistration(context, runtimeProcessRegistrationInput(context, parsed));
|
|
896
|
+
}
|
|
897
|
+
if (route.key === "runtime process heartbeat")
|
|
898
|
+
prepareManagedProcessHeartbeat(context, runtimeProcessHeartbeatInput(parsed));
|
|
899
|
+
if (route.key === "runtime process finish")
|
|
900
|
+
prepareManagedProcessFinish(context, runtimeProcessFinishInput(parsed));
|
|
901
|
+
if (route.key === "runtime process confirm")
|
|
902
|
+
prepareManagedProcessConfirmation(context, runtimeProcessConfirmationInput(parsed));
|
|
903
|
+
if (route.key === "runtime process stop")
|
|
904
|
+
prepareManagedProcessStopOperation(context, runtimeProcessStopInput(parsed));
|
|
905
|
+
if (route.key === "runtime process reconcile")
|
|
906
|
+
prepareManagedProcessReconcile(requiredOption(parsed, "owner"), optionalNumber(parsed, "grace-ms"));
|
|
907
|
+
if (route.key === "runtime process release-turn") {
|
|
908
|
+
prepared.providerTurnRelease = prepareProviderTurnRelease({ daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") });
|
|
909
|
+
}
|
|
910
|
+
if (route.key === "runtime process start") {
|
|
911
|
+
try {
|
|
912
|
+
prepareRuntimeService(context, runtimeServiceInput(parsed));
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
return prepareError(error, { parameter: "runtime process start" });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (family === "work" && command === "retry") {
|
|
919
|
+
try {
|
|
920
|
+
prepareWorkRetry(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
return prepareError(error, { parameter: "work-id/reason" });
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (family === "work" && (command === "fail" || command === "cancel")) {
|
|
927
|
+
try {
|
|
928
|
+
prepareWorkSettlement(context, requiredPosition(parsed, 0, "work-id"), command === "fail" ? "failed" : "cancelled", requiredOption(parsed, "reason"));
|
|
929
|
+
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
return prepareError(error, { parameter: "work-id/reason" });
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (family === "work" && (command === "deps" || command === "delete")) {
|
|
935
|
+
try {
|
|
936
|
+
if (command === "delete")
|
|
937
|
+
prepareWorkDelete(context, requiredPosition(parsed, 0, "work-id"));
|
|
938
|
+
else
|
|
939
|
+
prepareWorkDeps(context, { workId: requiredPosition(parsed, 1, "work-id"), action: requiredPosition(parsed, 0, "action"), on: parsed.options.get("on") ?? [] });
|
|
940
|
+
}
|
|
941
|
+
catch (error) {
|
|
942
|
+
return prepareError(error, { parameter: "work-id/on" });
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (route.key === "cleanup apply") {
|
|
946
|
+
const planFile = requiredOption(parsed, "plan-file");
|
|
947
|
+
try {
|
|
948
|
+
prepared.cleanup = prepareCleanupApply(context, { projectRoot: requiredOption(parsed, "project-root"), planFile, reason: requiredOption(parsed, "reason"), force: hasOption(parsed, "force") });
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
return prepareError(error, { parameter: "plan-file", file: planFile });
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (route.key === "stage block" || route.key === "stage unblock") {
|
|
955
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
|
|
956
|
+
try {
|
|
957
|
+
if (route.key === "stage block") {
|
|
958
|
+
if (!hasOption(parsed, "summary-stdin"))
|
|
959
|
+
throw new AppError("usage", "stage block requires --summary-stdin", 2);
|
|
960
|
+
prepared.interactionText = await readStdin(io.stdin);
|
|
961
|
+
prepareStageRuntimeBlock(context, { ...input, kind: requiredOption(parsed, "kind"), code: requiredOption(parsed, "code"), summary: prepared.interactionText, retryable: hasOption(parsed, "retryable") });
|
|
962
|
+
}
|
|
963
|
+
else
|
|
964
|
+
prepareStageRuntimeUnblock(context, input);
|
|
965
|
+
}
|
|
966
|
+
catch (error) {
|
|
967
|
+
return prepareError(error, { parameter: "stage/work/summary" });
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (route.key === "zcode invocation prepare") {
|
|
971
|
+
const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label: "invocation issuance" }).value;
|
|
972
|
+
const payload = value && typeof value === "object" ? value : {};
|
|
973
|
+
try {
|
|
974
|
+
prepared.lifecycleIssuance = prepareLifecycleIssuance(payload.command, payload.scope);
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
if (error instanceof AppError && ["invocation_command_invalid", "invocation_scope_invalid"].includes(error.code))
|
|
978
|
+
return prepareError(new AppError("usage", error.message, 2, { cause_code: error.code }), { parameter: "stdin" });
|
|
979
|
+
throw error;
|
|
980
|
+
}
|
|
981
|
+
assertLifecycleInvocationCurrent(context, prepared.lifecycleIssuance.scope, prepared.lifecycleIssuance.command);
|
|
982
|
+
}
|
|
983
|
+
if (route.key === "work add-batch") {
|
|
984
|
+
const file = requiredOption(parsed, "file");
|
|
985
|
+
try {
|
|
986
|
+
prepared.workBatch = prepareWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file });
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
return prepareError(error, { parameter: "file/parent", file });
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (route.key === "work repair add") {
|
|
993
|
+
if (!hasOption(parsed, "task-stdin"))
|
|
994
|
+
throw new AppError("usage", "work repair add requires --task-stdin", 2);
|
|
995
|
+
if (io.stdin?.isTTY)
|
|
996
|
+
throw new AppError("usage", "--task-stdin requires piped or redirected input", 2);
|
|
997
|
+
prepared.workRepair = prepareCliWorkRepair(context, parsed, await readStdin(io.stdin), scopeProjectRoot);
|
|
998
|
+
}
|
|
999
|
+
if (route.key === "run config set" || route.key === "run vars set") {
|
|
1000
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), key: requiredOption(parsed, "key"), value: requiredOption(parsed, "value") };
|
|
1001
|
+
try {
|
|
1002
|
+
if (route.key === "run config set")
|
|
1003
|
+
prepareFlowRunConfig(context, { ...input, reason: requiredOption(parsed, "reason") });
|
|
1004
|
+
else
|
|
1005
|
+
prepareFlowRunVariable(context, input);
|
|
1006
|
+
}
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
return prepareError(error, { parameter: "run config/vars" });
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (route.key === "schema validate") {
|
|
1012
|
+
try {
|
|
1013
|
+
prepared.schemaValidation = dispatchSchema(context, command, parsed);
|
|
1014
|
+
}
|
|
1015
|
+
catch (error) {
|
|
1016
|
+
return prepareError(error, { parameter: "file", file: requiredOption(parsed, "file") });
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (route.key === "project config set") {
|
|
1020
|
+
try {
|
|
1021
|
+
prepareProjectConfigValue(requiredOption(parsed, "key"), requiredOption(parsed, "value"));
|
|
1022
|
+
}
|
|
1023
|
+
catch (error) {
|
|
1024
|
+
return prepareError(error, { parameter: "key/value" });
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (family === "session" && command === "register") {
|
|
1028
|
+
try {
|
|
1029
|
+
prepared.sessionPayload = prepareFlowSessionPayload({ payloadBase64: optionalOption(parsed, "payload-base64"), payloadFile: optionalOption(parsed, "payload-file"), payloadJson: optionalOption(parsed, "payload-json") });
|
|
1030
|
+
}
|
|
1031
|
+
catch (error) {
|
|
1032
|
+
return prepareError(error, { parameter: "session payload" });
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const run = (projectRoot, runId) => {
|
|
1036
|
+
try {
|
|
1037
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
|
|
1038
|
+
const row = context.db.get("SELECT run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
|
|
1039
|
+
if (!row)
|
|
1040
|
+
throw new AppError("not_found", "RUN is not registered", 2, { run_id: runId });
|
|
1041
|
+
if (!row.run_root)
|
|
1042
|
+
throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: runId });
|
|
1043
|
+
return { runRoot: row.run_root, workspaceRoot: row.workspace_root, indexJson: row.index_json };
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
return prepareError(error, { parameter: "run-id", run_id: runId });
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
const json = (file, stdin, label, schema) => {
|
|
1050
|
+
if (stdin) {
|
|
1051
|
+
// The caller awaits this helper; the synchronous facade below is only
|
|
1052
|
+
// used for file inputs. Keep stdin handling in the stage branch.
|
|
1053
|
+
throw new AppError("usage", `${label} stdin preparation must be awaited`, 2);
|
|
1054
|
+
}
|
|
1055
|
+
if (!file)
|
|
1056
|
+
throw new AppError("usage", `${label} input is required`, 2);
|
|
1057
|
+
return prepareJsonSchema(file, label, schema);
|
|
1058
|
+
};
|
|
1059
|
+
if (family === "stage" && command === "start") {
|
|
1060
|
+
const stage = requiredOption(parsed, "stage");
|
|
1061
|
+
if (stage !== "specify" && ["bootstrap", "intake-file", "intake-stdin"].some(option => hasOption(parsed, option)))
|
|
1062
|
+
throw new AppError("usage", "--bootstrap and intake inputs are only supported for SPECIFY", 2);
|
|
1063
|
+
requiredOption(parsed, "project-root");
|
|
1064
|
+
if (!hasOption(parsed, "bootstrap"))
|
|
1065
|
+
requiredPosition(parsed, 0, "run-id");
|
|
1066
|
+
if (hasOption(parsed, "session-id"))
|
|
1067
|
+
throw new AppError("usage", "--session-id is managed by the harness lifecycle adapter and is not accepted", 2);
|
|
1068
|
+
const contextFile = optionalOption(parsed, "context-file"), contextSha256 = optionalOption(parsed, "context-sha256");
|
|
1069
|
+
if (Boolean(contextFile) !== Boolean(contextSha256))
|
|
1070
|
+
throw new AppError("usage", "stage start requires --context-file together with --context-sha256", 2);
|
|
1071
|
+
if ([hasOption(parsed, "intake-file"), hasOption(parsed, "intake-stdin"), Boolean(contextFile)].filter(Boolean).length > 1)
|
|
1072
|
+
throw new AppError("usage", "Use exactly one of --intake-file, --intake-stdin or --context-file", 2);
|
|
1073
|
+
if (contextFile && contextSha256) {
|
|
1074
|
+
try {
|
|
1075
|
+
prepared.stageStart = { externalContext: loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) };
|
|
1076
|
+
}
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
return prepareError(error, { file: contextFile, parameter: "context-file" });
|
|
1079
|
+
}
|
|
1080
|
+
if (!hasOption(parsed, "bootstrap")) {
|
|
1081
|
+
const { runRoot } = run(requiredOption(parsed, "project-root"), requiredPosition(parsed, 0, "run-id"));
|
|
1082
|
+
prepareExternalStageContext({ stageRoot: path.join(runRoot, vnextStageDirectory(stage)), loaded: prepared.stageStart.externalContext });
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (stage === "specify") {
|
|
1086
|
+
const intakeFile = optionalOption(parsed, "intake-file");
|
|
1087
|
+
if (intakeFile)
|
|
1088
|
+
prepared.stageStart = { intakeMarkdown: prepareTextFile(intakeFile, "SPECIFY intake").text };
|
|
1089
|
+
else if (hasOption(parsed, "intake-stdin"))
|
|
1090
|
+
prepared.stageStart = { intakeMarkdown: await readStdin(io.stdin) };
|
|
1091
|
+
else if (prepared.stageStart?.externalContext)
|
|
1092
|
+
prepared.stageStart.intakeMarkdown = runnerIntake(prepared.stageStart.externalContext);
|
|
1093
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1094
|
+
const bootstrap = hasOption(parsed, "bootstrap");
|
|
1095
|
+
if (isVnextSpecifyFlow(projectRoot) && (bootstrap || isVnextSpecifyRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") }))) {
|
|
1096
|
+
prepared.specifyStart = prepareVnextSpecifyStart(context, { projectRoot, ...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : {}), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}), slug: optionalOption(parsed, "subject"), ...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {}) });
|
|
1097
|
+
}
|
|
1098
|
+
else if (bootstrap) {
|
|
1099
|
+
if (hasOption(parsed, "intake-stdin") || contextFile)
|
|
1100
|
+
throw new AppError("usage", "Legacy SPECIFY bootstrap supports --intake-file, not stdin or runner context", 2);
|
|
1101
|
+
prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot, stage, bootstrap: { subject: requiredOption(parsed, "subject"), ...(intakeFile ? { intakeFile } : {}) }, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) }, prepared.stageStart?.intakeMarkdown);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (stage === "merge" && isVnextMergeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1105
|
+
prepared.mergeStart = prepareVnextMergeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1106
|
+
}
|
|
1107
|
+
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1108
|
+
prepared.protocolizeStart = prepareVnextProtocolizeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1109
|
+
}
|
|
1110
|
+
if (stage === "plan" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1111
|
+
prepared.planStart = prepareVnextPlanStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1112
|
+
}
|
|
1113
|
+
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1114
|
+
prepared.planReviewStart = prepareVnextPlanReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1115
|
+
}
|
|
1116
|
+
if (stage === "code" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1117
|
+
prepared.codeStart = prepareVnextCodeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1118
|
+
}
|
|
1119
|
+
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1120
|
+
prepared.codeReviewStart = prepareVnextCodeReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1121
|
+
}
|
|
1122
|
+
if (![prepared.specifyStart, prepared.protocolizeStart, prepared.planStart, prepared.planReviewStart, prepared.codeStart, prepared.codeReviewStart, prepared.mergeStart, prepared.legacyStageStart].some(Boolean)) {
|
|
1123
|
+
if (prepared.stageStart?.externalContext)
|
|
1124
|
+
throw new AppError("usage", "--context-file is currently supported only by vNext flow stages", 2);
|
|
1125
|
+
prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) });
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (family === "stage" && (command === "pause" || command === "resume")) {
|
|
1129
|
+
requiredPosition(parsed, 0, "run-id");
|
|
1130
|
+
requiredOption(parsed, "project-root");
|
|
1131
|
+
requiredOption(parsed, "stage");
|
|
1132
|
+
requiredOption(parsed, "work");
|
|
1133
|
+
const parameter = command === "pause" ? "question-stdin" : "answer-stdin";
|
|
1134
|
+
if (!hasOption(parsed, parameter))
|
|
1135
|
+
throw new AppError("usage", `stage ${command} requires --${parameter}`, 2);
|
|
1136
|
+
if (io.stdin?.isTTY)
|
|
1137
|
+
throw new AppError("usage", `--${parameter} requires piped or redirected input`, 2);
|
|
1138
|
+
prepared.interactionText = await readStdin(io.stdin);
|
|
1139
|
+
if (!prepared.interactionText.trim())
|
|
1140
|
+
return prepareError(new AppError("validation", `stage ${command} requires non-empty --${parameter}`, 2), { parameter });
|
|
1141
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
|
|
1142
|
+
try {
|
|
1143
|
+
if (command === "pause")
|
|
1144
|
+
prepareStagePause(context, { ...input, question: prepared.interactionText });
|
|
1145
|
+
else
|
|
1146
|
+
prepared.resumeEvidence = prepareStageResume(context, { ...input, answer: prepared.interactionText }).evidence;
|
|
1147
|
+
}
|
|
1148
|
+
catch (error) {
|
|
1149
|
+
return prepareError(error, { parameter: "stage/work" });
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (family === "work" && command === "finish") {
|
|
1153
|
+
const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = optionalOption(parsed, "reason");
|
|
1154
|
+
if (Boolean(retryReceiptId) !== Boolean(retryReason))
|
|
1155
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1156
|
+
const resultFile = optionalOption(parsed, "result-file");
|
|
1157
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1158
|
+
if (Boolean(resultFile) === resultStdin)
|
|
1159
|
+
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
1160
|
+
prepared.workFinish = { result: resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin) };
|
|
1161
|
+
prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result);
|
|
1162
|
+
}
|
|
1163
|
+
if (family === "stage" && command === "fanout" && requiredPosition(parsed, 0, "fanout action") === "reconcile") {
|
|
1164
|
+
prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
|
|
1165
|
+
}
|
|
1166
|
+
if (family === "stage" && command === "native" && requiredPosition(parsed, 0, "native action") === "observe") {
|
|
1167
|
+
prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
|
|
1168
|
+
}
|
|
1169
|
+
if (family !== "stage" || command !== "finish")
|
|
1170
|
+
return prepared;
|
|
1171
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1172
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
1173
|
+
const stage = requiredOption(parsed, "stage");
|
|
1174
|
+
if (hasOption(parsed, "status"))
|
|
1175
|
+
throw new AppError("usage", "Stage status belongs in the semantic input file", 2);
|
|
1176
|
+
if (["result-file", "semantic-file", "data", "result-stdin"].filter(key => hasOption(parsed, key)).length > 1)
|
|
1177
|
+
throw new AppError("usage", "Use exactly one result input: --result-file, --semantic-file, --data or --result-stdin", 2);
|
|
1178
|
+
if (Boolean(optionalOption(parsed, "retry-check")) !== Boolean(optionalOption(parsed, "reason")))
|
|
1179
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1180
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1181
|
+
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
1182
|
+
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
1183
|
+
const runtime = run(projectRoot, runId);
|
|
1184
|
+
const terminalReplay = (name) => {
|
|
1185
|
+
let index;
|
|
1186
|
+
try {
|
|
1187
|
+
index = JSON.parse(runtime.indexJson);
|
|
1188
|
+
}
|
|
1189
|
+
catch (error) {
|
|
1190
|
+
throw new AppError("runtime_artifact_invalid", "RUN index is not valid retained JSON", 1, { run_id: runId, cause: error instanceof Error ? error.message : String(error) });
|
|
1191
|
+
}
|
|
1192
|
+
return index.stage_runs?.some(entry => entry.stage === name && entry.status === "done") === true
|
|
1193
|
+
&& fs.existsSync(path.join(runtime.runRoot, vnextStageDirectory(name), "stage-report.json"));
|
|
1194
|
+
};
|
|
1195
|
+
const acceptStageInputs = (allowed) => {
|
|
1196
|
+
const options = allowed.split(" ");
|
|
1197
|
+
for (const name of ["dir", "semantic-file", "data", "result-file", "result-stdin", "outcome", "decision-file", "verification-file", "retry-check", "reason", "request", "work"]) {
|
|
1198
|
+
if (hasOption(parsed, name) && !options.includes(name))
|
|
1199
|
+
throw new AppError("usage", `stage finish ${stage} does not accept --${name} for this flow`, 2, { parameter: name });
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
const fromStdin = async (label, schema) => {
|
|
1203
|
+
const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label });
|
|
1204
|
+
try {
|
|
1205
|
+
validateSchema({ ...schema, file: value.file, data: value.value });
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
return prepareError(error, { file: value.file, label });
|
|
1209
|
+
}
|
|
1210
|
+
return value;
|
|
1211
|
+
};
|
|
1212
|
+
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
1213
|
+
acceptStageInputs("result-file semantic-file data result-stdin outcome");
|
|
1214
|
+
const outcome = optionalOption(parsed, "outcome");
|
|
1215
|
+
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome))
|
|
1216
|
+
throw new AppError("usage", "vNext SPECIFY requires --outcome specified|failed|cancelled; use stage pause for a user question", 2);
|
|
1217
|
+
if (Boolean(resultFile) === resultStdin)
|
|
1218
|
+
throw new AppError("usage", "vNext SPECIFY finish requires exactly one result input", 2);
|
|
1219
|
+
const schema = { schemaName: "vnext-specify", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot };
|
|
1220
|
+
const input = resultStdin ? await fromStdin("SPECIFY result", schema) : json(resultFile, false, "SPECIFY result", schema);
|
|
1221
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1222
|
+
}
|
|
1223
|
+
else if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
1224
|
+
acceptStageInputs("result-file semantic-file data");
|
|
1225
|
+
if (!resultFile || resultStdin || hasOption(parsed, "outcome"))
|
|
1226
|
+
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
1227
|
+
let absolute;
|
|
1228
|
+
try {
|
|
1229
|
+
absolute = prepareProtocolizeResultPath(runtime.runRoot, resultFile);
|
|
1230
|
+
}
|
|
1231
|
+
catch (error) {
|
|
1232
|
+
return prepareError(error, { file: resultFile });
|
|
1233
|
+
}
|
|
1234
|
+
const input = json(absolute, false, "PROTOCOLIZE result", { schemaName: "vnext-protocolize-result", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1235
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1236
|
+
}
|
|
1237
|
+
else if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
1238
|
+
acceptStageInputs("");
|
|
1239
|
+
if (hasOption(parsed, "code-work-batch-file"))
|
|
1240
|
+
throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
|
|
1241
|
+
try {
|
|
1242
|
+
prepared.planFinish = prepareVnextPlanFinish(context, { projectRoot, runId });
|
|
1243
|
+
}
|
|
1244
|
+
catch (error) {
|
|
1245
|
+
return prepareError(error, { parameter: "PLAN artifacts" });
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
else if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
1249
|
+
acceptStageInputs("request work retry-check reason");
|
|
1250
|
+
if (resultStdin || resultFile || hasOption(parsed, "outcome"))
|
|
1251
|
+
throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
|
|
1252
|
+
requiredOption(parsed, "request");
|
|
1253
|
+
requiredOption(parsed, "work");
|
|
1254
|
+
try {
|
|
1255
|
+
prepared.mergeFinish = prepareVnextMergeFinish(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(optionalOption(parsed, "retry-check") ? { retryCheckId: optionalOption(parsed, "retry-check"), retryReason: optionalOption(parsed, "reason") } : {}) });
|
|
1256
|
+
}
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
return prepareError(error, { parameter: "MERGE finish" });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
else if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
1262
|
+
acceptStageInputs("decision-file");
|
|
1263
|
+
if (resultStdin || hasOption(parsed, "outcome"))
|
|
1264
|
+
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
1265
|
+
const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "04-plan-review", "decision.json");
|
|
1266
|
+
const input = json(decision, false, "PLAN-REVIEW decision", { schemaName: "plan-review-decision", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1267
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1268
|
+
}
|
|
1269
|
+
else if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
1270
|
+
acceptStageInputs("verification-file retry-check reason");
|
|
1271
|
+
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome"))
|
|
1272
|
+
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
1273
|
+
const verification = optionalOption(parsed, "verification-file");
|
|
1274
|
+
if (!verification)
|
|
1275
|
+
throw new AppError("usage", "vNext CODE finish requires --verification-file", 2);
|
|
1276
|
+
if (!terminalReplay(stage)) {
|
|
1277
|
+
const input = json(verification, false, "CODE verification", { schemaName: "code-verification", projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1278
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
else if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
1282
|
+
acceptStageInputs("decision-file retry-check reason");
|
|
1283
|
+
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
1284
|
+
throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
|
|
1285
|
+
const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "06-code-review", "decision.json");
|
|
1286
|
+
if (!terminalReplay(stage)) {
|
|
1287
|
+
const input = prepareCodeReviewDecision(runtime.runRoot, optionalOption(parsed, "decision-file"));
|
|
1288
|
+
try {
|
|
1289
|
+
validateSchema({ schemaName: "code-review-decision", file: decision, data: input.value, projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1290
|
+
}
|
|
1291
|
+
catch (error) {
|
|
1292
|
+
return prepareError(error, { file: decision, label: "CODE-REVIEW decision" });
|
|
1293
|
+
}
|
|
1294
|
+
prepared.stageFinish = input;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
acceptStageInputs("dir semantic-file data");
|
|
1299
|
+
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin)
|
|
1300
|
+
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
1301
|
+
prepared.legacyStageFinish = prepareStageFinish(context, { projectRoot, runId, stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}), ...(semanticFile ? { semanticFile } : {}) });
|
|
1302
|
+
}
|
|
1303
|
+
return prepared;
|
|
1304
|
+
}
|
|
1305
|
+
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args), prepared = {}) {
|
|
1306
|
+
if (!prepared.route)
|
|
1307
|
+
throw new AppError("command_not_prepared", "Command dispatch requires prepared arguments", 1);
|
|
1308
|
+
const { family, command, parsed } = prepared.route;
|
|
1309
|
+
if (family === "zcode" && command === "invocation" && parsed.positional[0] === "prepare") {
|
|
1310
|
+
const payload = prepared.lifecycleIssuance;
|
|
1311
|
+
if (!payload)
|
|
1312
|
+
throw new AppError("command_not_prepared", "Invocation issuance requires prepared input", 1);
|
|
1313
|
+
assertLifecycleInvocationCurrent(context, payload.scope, payload.command);
|
|
1314
|
+
return { ok: true, ...issueLifecycleInvocation(context, payload.command, payload.scope) };
|
|
1315
|
+
}
|
|
1316
|
+
if (family === "storage" && command === "migrate-writer") {
|
|
1317
|
+
if (parsed.positional.length || [...parsed.options.keys()].some(key => !["database", "backup", "offline"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1318
|
+
throw new AppError("usage", "Usage: dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline", 2);
|
|
1319
|
+
if (parsed.options.get("offline")?.[0] !== flagValue)
|
|
1320
|
+
throw new AppError("usage", "Writer migration requires --offline after draining all local and external runtime owners", 2);
|
|
1321
|
+
return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
|
|
1322
|
+
}
|
|
506
1323
|
if (family === "engine") {
|
|
507
|
-
return dispatchEngine(context, command, parsed, classification);
|
|
1324
|
+
return dispatchEngine(context, command, parsed, classification, prepared);
|
|
508
1325
|
}
|
|
509
1326
|
if (family === "status") {
|
|
510
|
-
const rootParsed = parseArgs([command ?? "", ...rest]);
|
|
511
1327
|
return getRuntimeStatus(context, {
|
|
512
|
-
projectRoot: optionalOption(
|
|
513
|
-
checkRegistry: hasOption(
|
|
1328
|
+
projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root"),
|
|
1329
|
+
checkRegistry: hasOption(parsed, "check-registry")
|
|
514
1330
|
});
|
|
515
1331
|
}
|
|
516
1332
|
if (family === "version") {
|
|
517
1333
|
return getCliVersionReport();
|
|
518
1334
|
}
|
|
519
1335
|
if (family === "runtime") {
|
|
520
|
-
return await dispatchRuntime(context, command, parsed);
|
|
1336
|
+
return await dispatchRuntime(context, command, parsed, commandProgress(io, output), prepared);
|
|
521
1337
|
}
|
|
522
1338
|
if (family === "canon") {
|
|
523
|
-
return dispatchCanon(context, command, parsed);
|
|
1339
|
+
return dispatchCanon(context, command, parsed, prepared);
|
|
524
1340
|
}
|
|
525
1341
|
if (family === "project" && command === "register") {
|
|
526
1342
|
return registerProject(context, { root: requiredOption(parsed, "root") });
|
|
@@ -597,16 +1413,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
597
1413
|
return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
|
|
598
1414
|
}
|
|
599
1415
|
if (family === "protocol" && command === "transition") {
|
|
600
|
-
|
|
601
|
-
const reason = optionalOption(parsed, "reason");
|
|
602
|
-
return transitionProtocol(context, {
|
|
603
|
-
protocolId,
|
|
604
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"),
|
|
605
|
-
to: requiredOption(parsed, "to"),
|
|
606
|
-
jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]),
|
|
607
|
-
force: hasOption(parsed, "force"),
|
|
608
|
-
...(reason ? { reason } : {})
|
|
609
|
-
});
|
|
1416
|
+
return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
|
|
610
1417
|
}
|
|
611
1418
|
if (family === "protocol" && command === "sync-from-run") {
|
|
612
1419
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
@@ -631,17 +1438,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
631
1438
|
});
|
|
632
1439
|
}
|
|
633
1440
|
if (family === "transition") {
|
|
634
|
-
|
|
635
|
-
const protocolId = requiredPosition(transitionParsed, 0, "protocol-id");
|
|
636
|
-
const reason = optionalOption(transitionParsed, "reason");
|
|
637
|
-
return transitionProtocol(context, {
|
|
638
|
-
protocolId,
|
|
639
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "transition"),
|
|
640
|
-
to: requiredOption(transitionParsed, "to"),
|
|
641
|
-
jsonFile: requiredOptionAlias(transitionParsed, ["payload-file", "json-file"]),
|
|
642
|
-
force: hasOption(transitionParsed, "force"),
|
|
643
|
-
...(reason ? { reason } : {})
|
|
644
|
-
});
|
|
1441
|
+
return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
|
|
645
1442
|
}
|
|
646
1443
|
if (family === "plan" && command === "status") {
|
|
647
1444
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
@@ -660,12 +1457,19 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
660
1457
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "stage fanout");
|
|
661
1458
|
if (action === "status")
|
|
662
1459
|
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
1460
|
+
if (action === "reconcile")
|
|
1461
|
+
return reconcileVnextFanout(context, { projectRoot, runId, stage, observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
|
|
663
1462
|
if (action === "dispatch") {
|
|
664
1463
|
if (stage === "plan-review")
|
|
665
1464
|
return dispatchVnextPlanReview(context, { projectRoot, runId });
|
|
666
1465
|
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
667
1466
|
}
|
|
668
|
-
throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch> <RUN> --stage <stage> --project-root <path>", 2);
|
|
1467
|
+
throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch|reconcile> <RUN> --stage <stage> --project-root <path>; reconcile requires --observations-file <native-observations.json>", 2);
|
|
1468
|
+
}
|
|
1469
|
+
if (family === "stage" && command === "native") {
|
|
1470
|
+
if (requiredPosition(parsed, 0, "native action") !== "observe")
|
|
1471
|
+
throw new AppError("usage", "Use: dd-flow stage native observe <RUN> --stage <stage> --observations-file <native-observations.json> --project-root <path>", 2);
|
|
1472
|
+
return observeVnextNativeChildren(context, { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "stage native observe"), runId: requiredPosition(parsed, 1, "run-id"), stage: requiredOption(parsed, "stage"), observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
|
|
669
1473
|
}
|
|
670
1474
|
if (family === "run" && command === "capacity") {
|
|
671
1475
|
if (requiredPosition(parsed, 0, "capacity action") !== "record")
|
|
@@ -677,16 +1481,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
677
1481
|
return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
|
|
678
1482
|
}
|
|
679
1483
|
if (family === "prompt" && command === "render") {
|
|
680
|
-
|
|
681
|
-
const taskFile = optionalOption(parsed, "task-file");
|
|
682
|
-
return renderWorkerPrompt(context, {
|
|
683
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
684
|
-
runId: requiredOption(parsed, "run"),
|
|
685
|
-
stage: requiredOption(parsed, "stage"),
|
|
686
|
-
profile: requiredOption(parsed, "profile"),
|
|
687
|
-
...(taskFile ? { taskFile } : { planItemId: requiredOption(parsed, "plan-item") }),
|
|
688
|
-
...(workspaceRoot ? { workspaceRoot } : {})
|
|
689
|
-
});
|
|
1484
|
+
return renderWorkerPrompt(context, prepared.workerPrompt?.input ?? workerPromptInput(parsed), prepared.workerPrompt?.value);
|
|
690
1485
|
}
|
|
691
1486
|
if (family === "work" && command === "status") {
|
|
692
1487
|
return getVnextWorkStatus(context, {
|
|
@@ -695,7 +1490,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
695
1490
|
});
|
|
696
1491
|
}
|
|
697
1492
|
if (family === "work" && command === "add-batch") {
|
|
698
|
-
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file") });
|
|
1493
|
+
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file"), prepared: prepared.workBatch });
|
|
699
1494
|
}
|
|
700
1495
|
if (family === "work" && command === "ls") {
|
|
701
1496
|
return listWorks(context, { ...(optionalOption(parsed, "run") ? { runId: optionalOption(parsed, "run") } : {}), ...(optionalOption(parsed, "parent") ? { parentWorkId: optionalOption(parsed, "parent") } : {}), ...(optionalOption(parsed, "status") ? { status: optionalOption(parsed, "status") } : {}), ...(hasOption(parsed, "ready") ? { ready: true } : {}), ...(hasOption(parsed, "include-results") ? { includeResults: true } : {}), ...(optionalOption(parsed, "limit") ? { limit: Number(optionalOption(parsed, "limit")) } : {}) });
|
|
@@ -703,48 +1498,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
703
1498
|
if (family === "work" && command === "show")
|
|
704
1499
|
return showWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
705
1500
|
if (family === "work" && command === "repair" && requiredPosition(parsed, 0, "action") === "add") {
|
|
706
|
-
if (!
|
|
707
|
-
throw new AppError("
|
|
708
|
-
|
|
709
|
-
const findings = parsed.options.get("from-finding") ?? [];
|
|
710
|
-
const unresolved = parsed.options.get("from-unresolved") ?? [];
|
|
711
|
-
if (!origins.length && !findings.length)
|
|
712
|
-
throw new AppError("usage", "work repair add requires --from-check with --origin-work, --from-finding, or --from-unresolved with --origin-work", 2);
|
|
713
|
-
if (findings.length)
|
|
714
|
-
return addVnextCodeReviewRepair(context, {
|
|
715
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
716
|
-
runId: requiredOption(parsed, "run"),
|
|
717
|
-
findingIds: findings,
|
|
718
|
-
checkRefsByFinding: Object.fromEntries(findings.map((finding) => [finding, parsed.options.get("check-ref") ?? []])),
|
|
719
|
-
objective: await readStdin(io.stdin)
|
|
720
|
-
});
|
|
721
|
-
if (unresolved.length) {
|
|
722
|
-
if (!origins.length)
|
|
723
|
-
throw new AppError("usage", "semantic repair requires at least one --origin-work", 2);
|
|
724
|
-
const verificationPath = requiredOption(parsed, "verification-file");
|
|
725
|
-
return addVnextCodeRepair(context, {
|
|
726
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
727
|
-
runId: requiredOption(parsed, "run"),
|
|
728
|
-
originWorkIds: origins,
|
|
729
|
-
semanticUnresolved: unresolved,
|
|
730
|
-
verificationPath,
|
|
731
|
-
objective: await readStdin(io.stdin)
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
if (!hasOption(parsed, "from-check"))
|
|
735
|
-
throw new AppError("usage", "check repair requires --from-check", 2);
|
|
736
|
-
if (!origins.length)
|
|
737
|
-
throw new AppError("usage", "check repair requires at least one --origin-work", 2);
|
|
738
|
-
const receipts = parsed.options.get("from-check") ?? [];
|
|
739
|
-
if (receipts.length !== 1)
|
|
740
|
-
throw new AppError("usage", "check repair accepts exactly one --from-check; create one causal repair Work per failed receipt", 2);
|
|
741
|
-
return addVnextCodeRepair(context, {
|
|
742
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
|
|
743
|
-
runId: requiredOption(parsed, "run"),
|
|
744
|
-
checkReceiptId: receipts[0],
|
|
745
|
-
originWorkIds: origins,
|
|
746
|
-
objective: await readStdin(io.stdin)
|
|
747
|
-
});
|
|
1501
|
+
if (!prepared.workRepair)
|
|
1502
|
+
throw new AppError("cli_input_unprepared", "Repair input was not prepared", 1);
|
|
1503
|
+
return addVnextCodeRepair(context, prepared.workRepair.input, prepared.workRepair.value);
|
|
748
1504
|
}
|
|
749
1505
|
if (family === "work" && command === "deps") {
|
|
750
1506
|
const on = parsed.options.get("on") ?? [];
|
|
@@ -752,21 +1508,34 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
752
1508
|
}
|
|
753
1509
|
if (family === "work" && command === "delete")
|
|
754
1510
|
return deleteWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
1511
|
+
if (family === "work" && command === "launch")
|
|
1512
|
+
return await launchExternalWork(context, { workId: requiredPosition(parsed, 0, "work-id"), ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}), progress: commandProgress(io, output) });
|
|
755
1513
|
if (family === "work" && command === "start") {
|
|
756
1514
|
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
1515
|
+
const recoveryId = optionalOption(parsed, "recovery-id");
|
|
757
1516
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
|
|
758
|
-
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), projectRoot });
|
|
1517
|
+
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot });
|
|
759
1518
|
}
|
|
760
1519
|
if (family === "work" && command === "finish") {
|
|
761
1520
|
const resultFile = optionalOption(parsed, "result-file");
|
|
762
1521
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
763
1522
|
if (Boolean(resultFile) === resultStdin)
|
|
764
1523
|
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
765
|
-
const
|
|
766
|
-
|
|
1524
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
1525
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work finish");
|
|
1526
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_finish", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1527
|
+
const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = optionalOption(parsed, "reason");
|
|
1528
|
+
if (Boolean(retryReceiptId) !== Boolean(retryReason))
|
|
1529
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1530
|
+
const result = prepared.workFinish?.result ?? (resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin));
|
|
1531
|
+
return await finishWork(context, workId, result, commandProgress(io, output), retryReceiptId ? { receiptId: retryReceiptId, reason: retryReason } : undefined);
|
|
1532
|
+
}
|
|
1533
|
+
if (family === "work" && command === "fail") {
|
|
1534
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
1535
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work fail");
|
|
1536
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_fail", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1537
|
+
return failWork(context, workId, requiredOption(parsed, "reason"));
|
|
767
1538
|
}
|
|
768
|
-
if (family === "work" && command === "fail")
|
|
769
|
-
return failWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
770
1539
|
if (family === "work" && command === "cancel")
|
|
771
1540
|
return cancelWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
772
1541
|
if (family === "work" && command === "retry")
|
|
@@ -788,12 +1557,12 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
788
1557
|
const requireSessionBinding = hasOption(parsed, "require-session-binding");
|
|
789
1558
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
790
1559
|
const stage = requiredOption(parsed, "stage");
|
|
791
|
-
const externalContext =
|
|
1560
|
+
const externalContext = prepared.stageStart?.externalContext;
|
|
792
1561
|
const claimVnextStageStart = (runId) => {
|
|
793
1562
|
registerProject(context, { root: projectRoot });
|
|
794
1563
|
return findRecentMatchingHookEvent(context, {
|
|
795
1564
|
projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
|
|
796
|
-
matchKey: stageStartMatchKey(
|
|
1565
|
+
matchKey: entityReferenceVariants(runId).map((reference) => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
|
|
797
1566
|
errorCode: "trusted_session_binding_required",
|
|
798
1567
|
operation: `vNext ${stage.toUpperCase()} start`
|
|
799
1568
|
}).eventKey;
|
|
@@ -819,37 +1588,37 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
819
1588
|
}
|
|
820
1589
|
const result = launchVnextSpecify(context, {
|
|
821
1590
|
projectRoot,
|
|
822
|
-
...(externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
1591
|
+
...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
823
1592
|
hookEventId,
|
|
824
1593
|
...(subject ? { slug: subject } : {}),
|
|
825
1594
|
...(externalContext ? { externalContext } : {}),
|
|
826
1595
|
...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {})
|
|
827
|
-
});
|
|
1596
|
+
}, prepared.specifyStart);
|
|
828
1597
|
return attachExternalStageContext(result, externalContext);
|
|
829
1598
|
}
|
|
830
1599
|
if (!bootstrap && stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
831
1600
|
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
832
|
-
return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
1601
|
+
return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.protocolizeStart), externalContext);
|
|
833
1602
|
}
|
|
834
1603
|
if (!bootstrap && stage === "plan" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
835
1604
|
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
836
|
-
return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
1605
|
+
return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planStart), externalContext);
|
|
837
1606
|
}
|
|
838
1607
|
if (!bootstrap && stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
839
1608
|
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
840
|
-
return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
1609
|
+
return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planReviewStart), externalContext);
|
|
841
1610
|
}
|
|
842
1611
|
if (!bootstrap && stage === "code" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
843
1612
|
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
844
|
-
return attachExternalStageContext(await startVnextCode(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
1613
|
+
return attachExternalStageContext(await startVnextCode(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeStart), externalContext);
|
|
845
1614
|
}
|
|
846
1615
|
if (!bootstrap && stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
847
1616
|
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
848
|
-
return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
1617
|
+
return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeReviewStart), externalContext);
|
|
849
1618
|
}
|
|
850
1619
|
if (!bootstrap && stage === "merge" && isVnextMergeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
851
1620
|
hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
|
|
852
|
-
return attachExternalStageContext(await startVnextMerge(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
|
|
1621
|
+
return attachExternalStageContext(await startVnextMerge(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.mergeStart), externalContext);
|
|
853
1622
|
}
|
|
854
1623
|
if (intakeStdin) {
|
|
855
1624
|
throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
|
|
@@ -864,7 +1633,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
864
1633
|
...(hookEventId ? { hookEventId } : {}),
|
|
865
1634
|
...(requireSessionBinding ? { requireSessionBinding: true } : {}),
|
|
866
1635
|
...(dir ? { dir } : {})
|
|
867
|
-
});
|
|
1636
|
+
}, prepared.legacyStageStart);
|
|
868
1637
|
}
|
|
869
1638
|
if (family === "stage" && command === "finish") {
|
|
870
1639
|
const dir = optionalOption(parsed, "dir");
|
|
@@ -874,9 +1643,23 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
874
1643
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
875
1644
|
const runId = requiredPosition(parsed, 0, "run-id");
|
|
876
1645
|
const stage = requiredOption(parsed, "stage");
|
|
1646
|
+
const controlledReply = (result) => controlLifecycleReply(context, { projectRoot, runId }, result);
|
|
877
1647
|
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
878
1648
|
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
879
1649
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1650
|
+
const retryCheckId = optionalOption(parsed, "retry-check");
|
|
1651
|
+
const retryReason = optionalOption(parsed, "reason");
|
|
1652
|
+
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
1653
|
+
if (retryCheckId && !retryReason?.trim())
|
|
1654
|
+
throw new AppError("usage", "--retry-check requires --reason explaining the recovered environment", 2);
|
|
1655
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
1656
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
1657
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1658
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
1659
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1660
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
1661
|
+
if (protectedLifecycle)
|
|
1662
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, operation: "stage_finish", ...(hookEventId ? { hookEventId } : {}) });
|
|
880
1663
|
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
881
1664
|
const outcome = optionalOption(parsed, "outcome");
|
|
882
1665
|
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome)) {
|
|
@@ -888,66 +1671,79 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
888
1671
|
if (!resultFile && !resultStdin) {
|
|
889
1672
|
throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
|
|
890
1673
|
}
|
|
891
|
-
return finishVnextSpecifyStage(context, {
|
|
1674
|
+
return controlledReply(await finishVnextSpecifyStage(context, {
|
|
892
1675
|
projectRoot,
|
|
893
1676
|
runId,
|
|
894
|
-
...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
|
|
1677
|
+
...(resultFile ? { resultFile } : prepared.stageFinish?.value ? {} : { resultJson: await readStdin(io.stdin) }),
|
|
1678
|
+
...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}),
|
|
895
1679
|
outcome: outcome
|
|
896
|
-
});
|
|
1680
|
+
}));
|
|
897
1681
|
}
|
|
898
1682
|
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
899
1683
|
if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
900
1684
|
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
901
1685
|
}
|
|
902
|
-
return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
|
|
1686
|
+
return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile, ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}) }));
|
|
903
1687
|
}
|
|
904
1688
|
if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
905
1689
|
if (optionalOption(parsed, "code-work-batch-file"))
|
|
906
1690
|
throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
|
|
907
|
-
return finishVnextPlan(context, { projectRoot, runId });
|
|
1691
|
+
return controlledReply(await finishVnextPlan(context, { projectRoot, runId, ...(prepared.planFinish ? { prepared: prepared.planFinish } : {}) }));
|
|
908
1692
|
}
|
|
909
1693
|
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
910
1694
|
if (resultStdin || hasOption(parsed, "outcome"))
|
|
911
1695
|
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
912
1696
|
const decisionFile = optionalOption(parsed, "decision-file");
|
|
913
|
-
return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
|
|
1697
|
+
return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}) }));
|
|
914
1698
|
}
|
|
915
1699
|
if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
916
1700
|
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
917
1701
|
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
918
1702
|
}
|
|
919
|
-
return await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), progress: commandProgress(io, output) });
|
|
1703
|
+
return controlledReply(await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), ...(prepared.stageFinish?.value ? { verification: prepared.stageFinish.value } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
|
|
920
1704
|
}
|
|
921
1705
|
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
922
1706
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
923
1707
|
throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
|
|
924
|
-
return await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), progress: commandProgress(io, output) });
|
|
1708
|
+
return controlledReply(await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
|
|
925
1709
|
}
|
|
926
1710
|
if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
927
1711
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
928
1712
|
throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
|
|
929
|
-
return await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), progress: commandProgress(io, output) });
|
|
1713
|
+
return controlledReply(await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }, prepared.mergeFinish));
|
|
930
1714
|
}
|
|
931
1715
|
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
|
|
932
1716
|
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
933
1717
|
}
|
|
934
|
-
return finishStage(context, {
|
|
1718
|
+
return controlledReply(await finishStage(context, {
|
|
935
1719
|
projectRoot,
|
|
936
1720
|
runId,
|
|
937
1721
|
stage,
|
|
938
1722
|
...(dir ? { dir } : {}),
|
|
939
1723
|
...(semanticFile ? { semanticFile } : {})
|
|
940
|
-
});
|
|
1724
|
+
}, prepared.legacyStageFinish));
|
|
941
1725
|
}
|
|
942
1726
|
if (family === "stage" && command === "pause") {
|
|
943
1727
|
if (!hasOption(parsed, "question-stdin"))
|
|
944
1728
|
throw new AppError("usage", "stage pause requires --question-stdin", 2);
|
|
1729
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1730
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
1731
|
+
const stage = requiredOption(parsed, "stage");
|
|
1732
|
+
const workId = requiredOption(parsed, "work");
|
|
1733
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
1734
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
1735
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1736
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
1737
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1738
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
1739
|
+
if (protectedLifecycle)
|
|
1740
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, workId, operation: "stage_pause", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
945
1741
|
return pauseStageForUser(context, {
|
|
946
|
-
projectRoot
|
|
947
|
-
runId
|
|
948
|
-
stage
|
|
949
|
-
workId
|
|
950
|
-
question: await readStdin(io.stdin)
|
|
1742
|
+
projectRoot,
|
|
1743
|
+
runId,
|
|
1744
|
+
stage,
|
|
1745
|
+
workId,
|
|
1746
|
+
question: prepared.interactionText ?? await readStdin(io.stdin)
|
|
951
1747
|
});
|
|
952
1748
|
}
|
|
953
1749
|
if (family === "stage" && command === "resume") {
|
|
@@ -959,7 +1755,8 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
959
1755
|
runId: requiredPosition(parsed, 0, "run-id"),
|
|
960
1756
|
stage: requiredOption(parsed, "stage"),
|
|
961
1757
|
workId: requiredOption(parsed, "work"),
|
|
962
|
-
answer: await readStdin(io.stdin),
|
|
1758
|
+
answer: prepared.interactionText ?? await readStdin(io.stdin),
|
|
1759
|
+
evidence: prepared.resumeEvidence,
|
|
963
1760
|
...(hookEventId ? { hookEventId } : {})
|
|
964
1761
|
});
|
|
965
1762
|
}
|
|
@@ -976,7 +1773,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
976
1773
|
workId: requiredOption(parsed, "work"),
|
|
977
1774
|
kind,
|
|
978
1775
|
code: requiredOption(parsed, "code"),
|
|
979
|
-
summary: await readStdin(io.stdin),
|
|
1776
|
+
summary: prepared.interactionText ?? await readStdin(io.stdin),
|
|
980
1777
|
retryable: hasOption(parsed, "retryable")
|
|
981
1778
|
});
|
|
982
1779
|
}
|
|
@@ -1001,13 +1798,55 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1001
1798
|
return dispatchMergeWorker(context, command, parsed);
|
|
1002
1799
|
}
|
|
1003
1800
|
if (family === "cleanup") {
|
|
1004
|
-
return dispatchCleanup(context, command, parsed);
|
|
1801
|
+
return dispatchCleanup(context, command, parsed, prepared.cleanup);
|
|
1005
1802
|
}
|
|
1006
1803
|
if (family === "session") {
|
|
1007
|
-
return dispatchSession(context, command, parsed);
|
|
1804
|
+
return dispatchSession(context, command, parsed, prepared.sessionPayload);
|
|
1008
1805
|
}
|
|
1009
1806
|
if (family === "run") {
|
|
1010
|
-
|
|
1807
|
+
if (command === "control") {
|
|
1808
|
+
const action = requiredPosition(parsed, 0, "control action");
|
|
1809
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1810
|
+
if (action === "status")
|
|
1811
|
+
return runControlStatus(context, scope);
|
|
1812
|
+
if (action === "reconcile") {
|
|
1813
|
+
if ([...parsed.options.keys()].some(key => !["run", "project-root", "control-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1814
|
+
throw new AppError("usage", "Use run control reconcile --run ID --project-root PATH --control-id ID --request-id ID", 2);
|
|
1815
|
+
return await reconcileRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id"), requestId: requiredOption(parsed, "request-id") });
|
|
1816
|
+
}
|
|
1817
|
+
if (action === "resume") {
|
|
1818
|
+
if (["force", "grace-ms", "escalate"].some(option => hasOption(parsed, option)))
|
|
1819
|
+
throw new AppError("usage", "Resume does not accept stop or pause escalation options", 2);
|
|
1820
|
+
return await resumeRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), controlId: requiredOption(parsed, "from"), ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {}) });
|
|
1821
|
+
}
|
|
1822
|
+
if (action === "serve")
|
|
1823
|
+
return await serveRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id") });
|
|
1824
|
+
if (action === "pause" || action === "stop")
|
|
1825
|
+
return await requestRunControl(context, {
|
|
1826
|
+
...scope, action, requestId: requiredOption(parsed, "request-id"),
|
|
1827
|
+
...(hasOption(parsed, "force") ? { force: true } : {}),
|
|
1828
|
+
...(hasOption(parsed, "grace-ms") ? { graceMs: Number(requiredOption(parsed, "grace-ms")) } : {}),
|
|
1829
|
+
...(hasOption(parsed, "escalate") ? { escalate: requiredOption(parsed, "escalate") } : {}),
|
|
1830
|
+
...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {})
|
|
1831
|
+
});
|
|
1832
|
+
throw new AppError("usage", "Use: dd-flow run control pause|stop|resume|reconcile|status", 2);
|
|
1833
|
+
}
|
|
1834
|
+
if (command === "drive") {
|
|
1835
|
+
const action = requiredPosition(parsed, 0, "drive action");
|
|
1836
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1837
|
+
if (action === "status")
|
|
1838
|
+
return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
|
|
1839
|
+
if (action === "launch")
|
|
1840
|
+
return await launchRunController(context, controllerLaunchInput(parsed), prepared.controllerLaunch);
|
|
1841
|
+
if (action === "serve")
|
|
1842
|
+
return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
|
|
1843
|
+
if (action === "answer")
|
|
1844
|
+
return answerRunController(context, controllerAnswerInput(parsed), prepared.controllerAnswer);
|
|
1845
|
+
if (action === "context")
|
|
1846
|
+
return supplyRunControllerContext(context, controllerContextInput(parsed), prepared.controllerContext);
|
|
1847
|
+
throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
|
|
1848
|
+
}
|
|
1849
|
+
return dispatchRun(context, command, parsed, prepared);
|
|
1011
1850
|
}
|
|
1012
1851
|
if (family === "stat") {
|
|
1013
1852
|
return dispatchStat(context, command, parsed);
|
|
@@ -1022,23 +1861,26 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1022
1861
|
return dispatchDashboard(context, command, parsed);
|
|
1023
1862
|
}
|
|
1024
1863
|
if (family === "schema") {
|
|
1025
|
-
return
|
|
1864
|
+
return prepared.schemaValidation;
|
|
1026
1865
|
}
|
|
1027
1866
|
if (family === "memory") {
|
|
1028
1867
|
return dispatchMemory(command, parsed);
|
|
1029
1868
|
}
|
|
1030
1869
|
if (family === "migration") {
|
|
1031
|
-
return dispatchMigration(context, command, parsed, classification);
|
|
1870
|
+
return dispatchMigration(context, command, parsed, classification, prepared);
|
|
1032
1871
|
}
|
|
1033
1872
|
if (family === "codex" && command === "hooks") {
|
|
1034
|
-
return dispatchCodexHooks(context, parsed);
|
|
1873
|
+
return dispatchCodexHooks(context, parsed, prepared);
|
|
1035
1874
|
}
|
|
1036
1875
|
if (family === "codex" && command === "home") {
|
|
1037
|
-
return dispatchCodexHome(context, parsed);
|
|
1876
|
+
return dispatchCodexHome(context, parsed, prepared);
|
|
1038
1877
|
}
|
|
1039
1878
|
if (family === "codex" && command === "hook") {
|
|
1040
1879
|
return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
|
|
1041
1880
|
}
|
|
1881
|
+
if (family === "zcode" && command === "hook") {
|
|
1882
|
+
return dispatchZcodeHook(context, parsed, await readStdin(io.stdin));
|
|
1883
|
+
}
|
|
1042
1884
|
if (family === "zcode" && command === "event") {
|
|
1043
1885
|
return dispatchZcodeEvent(context, parsed, await readStdin(io.stdin));
|
|
1044
1886
|
}
|
|
@@ -1063,6 +1905,16 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1063
1905
|
throw new AppError("usage", "Unknown opencode usage action", 2);
|
|
1064
1906
|
return ingestOpenCodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1065
1907
|
}
|
|
1908
|
+
if (family === "droid" && command === "event") {
|
|
1909
|
+
if (requiredPosition(parsed, 0, "droid event action") !== "handle")
|
|
1910
|
+
throw new AppError("usage", "Unknown droid event action", 2);
|
|
1911
|
+
return handleDroidEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1912
|
+
}
|
|
1913
|
+
if (family === "droid" && command === "usage") {
|
|
1914
|
+
if (requiredPosition(parsed, 0, "droid usage action") !== "ingest")
|
|
1915
|
+
throw new AppError("usage", "Unknown droid usage action", 2);
|
|
1916
|
+
return ingestDroidUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1917
|
+
}
|
|
1066
1918
|
if (family === "agy" && command === "event")
|
|
1067
1919
|
return dispatchAgyEvent(context, parsed, await readStdin(io.stdin));
|
|
1068
1920
|
if (family === "agy" && command === "usage") {
|
|
@@ -1071,11 +1923,14 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1071
1923
|
return ingestAgyUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1072
1924
|
}
|
|
1073
1925
|
if (family === "worktree") {
|
|
1074
|
-
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
|
|
1926
|
+
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`), prepared);
|
|
1075
1927
|
}
|
|
1076
1928
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
1077
1929
|
}
|
|
1078
|
-
function
|
|
1930
|
+
function engineBindInput(parsed) {
|
|
1931
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), packageName: optionalOption(parsed, "package"), engineVersion: requiredOption(parsed, "engine-version"), integrityChecksum: optionalOption(parsed, "integrity-checksum"), reason: requiredOption(parsed, "reason") };
|
|
1932
|
+
}
|
|
1933
|
+
function dispatchEngine(context, command, parsed, classification, prepared) {
|
|
1079
1934
|
if (command === "install") {
|
|
1080
1935
|
return installCurrentEngine(context, { force: hasOption(parsed, "force") });
|
|
1081
1936
|
}
|
|
@@ -1100,18 +1955,11 @@ function dispatchEngine(context, command, parsed, classification) {
|
|
|
1100
1955
|
});
|
|
1101
1956
|
}
|
|
1102
1957
|
if (command === "bind-run") {
|
|
1103
|
-
return bindRunEngine(context,
|
|
1104
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1105
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1106
|
-
packageName: optionalOption(parsed, "package"),
|
|
1107
|
-
engineVersion: requiredOption(parsed, "engine-version"),
|
|
1108
|
-
integrityChecksum: optionalOption(parsed, "integrity-checksum"),
|
|
1109
|
-
reason: requiredOption(parsed, "reason")
|
|
1110
|
-
});
|
|
1958
|
+
return bindRunEngine(context, engineBindInput(parsed), prepared.engineBind);
|
|
1111
1959
|
}
|
|
1112
1960
|
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
1113
1961
|
}
|
|
1114
|
-
function dispatchMigration(context, command, parsed, classification) {
|
|
1962
|
+
function dispatchMigration(context, command, parsed, classification, prepared) {
|
|
1115
1963
|
if (command === "impact")
|
|
1116
1964
|
return assessMigrationImpact(context, { projectRoot: requiredOption(parsed, "project-root"), ...(optionalOption(parsed, "target-version") ? { targetVersion: optionalOption(parsed, "target-version") } : {}), ...(optionalOption(parsed, "mode") ? { mode: optionalOption(parsed, "mode") } : {}) });
|
|
1117
1965
|
if (command === "plan" || command === "report") {
|
|
@@ -1127,26 +1975,42 @@ function dispatchMigration(context, command, parsed, classification) {
|
|
|
1127
1975
|
});
|
|
1128
1976
|
}
|
|
1129
1977
|
if (command === "verify") {
|
|
1130
|
-
return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
1978
|
+
return prepared.migrationVerification ?? verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
1131
1979
|
}
|
|
1132
1980
|
if (command === "apply") {
|
|
1133
|
-
return applyMigration(context,
|
|
1134
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1135
|
-
sourceVersion: optionalOption(parsed, "source-version"),
|
|
1136
|
-
targetVersion: optionalOption(parsed, "target-version"),
|
|
1137
|
-
runId: optionalOption(parsed, "run"),
|
|
1138
|
-
backupPath: optionalOption(parsed, "backup-path"),
|
|
1139
|
-
backupCreatedAt: optionalOption(parsed, "backup-created-at"),
|
|
1140
|
-
reportFile: optionalOption(parsed, "file"),
|
|
1141
|
-
allowActive: false,
|
|
1142
|
-
classification
|
|
1143
|
-
});
|
|
1981
|
+
return applyMigration(context, migrationApplyInput(parsed, classification), prepared.migrationApply);
|
|
1144
1982
|
}
|
|
1145
1983
|
throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
|
|
1146
1984
|
}
|
|
1147
|
-
function
|
|
1985
|
+
function protocolTransitionInput(parsed, scopeProjectRoot) {
|
|
1986
|
+
const reason = optionalOption(parsed, "reason");
|
|
1987
|
+
return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"), to: requiredOption(parsed, "to"), jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]), force: hasOption(parsed, "force"), ...(reason ? { reason } : {}) };
|
|
1988
|
+
}
|
|
1989
|
+
function controllerAnswerInput(parsed) {
|
|
1990
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), requestId: requiredOption(parsed, "request-id"), pauseId: requiredOption(parsed, "pause-id"), answerFile: requiredOption(parsed, "answer-file") };
|
|
1991
|
+
}
|
|
1992
|
+
function controllerLaunchInput(parsed) {
|
|
1993
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), 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") } : {}) };
|
|
1994
|
+
}
|
|
1995
|
+
function controllerContextInput(parsed) {
|
|
1996
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), controllerId: requiredOption(parsed, "controller-id"), requestId: requiredOption(parsed, "request-id"), stage: requiredOption(parsed, "stage"), contextFile: requiredOption(parsed, "context-file"), contextSha256: requiredOption(parsed, "context-sha256") };
|
|
1997
|
+
}
|
|
1998
|
+
function migrationApplyInput(parsed, classification) {
|
|
1999
|
+
return {
|
|
2000
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
2001
|
+
sourceVersion: optionalOption(parsed, "source-version"),
|
|
2002
|
+
targetVersion: optionalOption(parsed, "target-version"),
|
|
2003
|
+
runId: optionalOption(parsed, "run"),
|
|
2004
|
+
backupPath: optionalOption(parsed, "backup-path"),
|
|
2005
|
+
backupCreatedAt: optionalOption(parsed, "backup-created-at"),
|
|
2006
|
+
reportFile: optionalOption(parsed, "file"),
|
|
2007
|
+
allowActive: false,
|
|
2008
|
+
classification
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
function dispatchCanon(context, command, parsed, prepared) {
|
|
1148
2012
|
if (command === "register") {
|
|
1149
|
-
return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
|
|
2013
|
+
return registerCanonRoot(context, { root: requiredOption(parsed, "root") }, prepared.canonRegistration);
|
|
1150
2014
|
}
|
|
1151
2015
|
if (command === "status") {
|
|
1152
2016
|
return getCanonStatus(context, { root: optionalOption(parsed, "root") });
|
|
@@ -1156,6 +2020,15 @@ function dispatchCanon(context, command, parsed) {
|
|
|
1156
2020
|
}
|
|
1157
2021
|
throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
|
|
1158
2022
|
}
|
|
2023
|
+
function mergeJobTarget(parsed, scopeProjectRoot) {
|
|
2024
|
+
return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed) };
|
|
2025
|
+
}
|
|
2026
|
+
function mergeCancellationInput(parsed, scopeProjectRoot) {
|
|
2027
|
+
return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"), reason: requiredOption(parsed, "reason"), workerId: optionalOption(parsed, "worker-id"), workspacePath: optionalOption(parsed, "path"), force: hasOption(parsed, "force") };
|
|
2028
|
+
}
|
|
2029
|
+
function mergeWaitInput(parsed, scopeProjectRoot) {
|
|
2030
|
+
return { projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"), workerId: requiredWorkerId(parsed), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval"), workspacePath: workspacePathOption(parsed), acquireLock: parseOptionalBoolean(optionalOption(parsed, "acquire-lock"), "acquire-lock") ?? false };
|
|
2031
|
+
}
|
|
1159
2032
|
function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
1160
2033
|
if (hasOption(parsed, "session-id")) {
|
|
1161
2034
|
throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
|
|
@@ -1179,32 +2052,17 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
|
1179
2052
|
});
|
|
1180
2053
|
}
|
|
1181
2054
|
if (command === "wait-next") {
|
|
1182
|
-
return waitNextMergeJob(context,
|
|
1183
|
-
projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
|
|
1184
|
-
workerId: requiredWorkerId(parsed),
|
|
1185
|
-
timeoutSeconds: optionalNumber(parsed, "timeout"),
|
|
1186
|
-
pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
|
|
1187
|
-
workspacePath: workspacePathOption(parsed),
|
|
1188
|
-
acquireLock: parseOptionalBoolean(optionalOption(parsed, "acquire-lock"), "acquire-lock") ?? false
|
|
1189
|
-
});
|
|
2055
|
+
return waitNextMergeJob(context, mergeWaitInput(parsed, scopeProjectRoot));
|
|
1190
2056
|
}
|
|
1191
2057
|
if (command === "complete") {
|
|
1192
|
-
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
1193
2058
|
return completeMergeJob(context, {
|
|
1194
|
-
|
|
1195
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
|
|
1196
|
-
workerId: requiredWorkerId(parsed),
|
|
1197
|
-
workspacePath: workspacePathOption(parsed),
|
|
2059
|
+
...mergeJobTarget(parsed, scopeProjectRoot),
|
|
1198
2060
|
summary: requiredOption(parsed, "summary")
|
|
1199
2061
|
});
|
|
1200
2062
|
}
|
|
1201
2063
|
if (command === "fail") {
|
|
1202
|
-
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
1203
2064
|
return failMergeJob(context, {
|
|
1204
|
-
|
|
1205
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
|
|
1206
|
-
workerId: requiredWorkerId(parsed),
|
|
1207
|
-
workspacePath: workspacePathOption(parsed),
|
|
2065
|
+
...mergeJobTarget(parsed, scopeProjectRoot),
|
|
1208
2066
|
reason: requiredOption(parsed, "reason"),
|
|
1209
2067
|
requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
|
|
1210
2068
|
});
|
|
@@ -1219,15 +2077,7 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
|
1219
2077
|
});
|
|
1220
2078
|
}
|
|
1221
2079
|
if (command === "cancel") {
|
|
1222
|
-
|
|
1223
|
-
return cancelMergeQueueJob(context, {
|
|
1224
|
-
protocolId,
|
|
1225
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"),
|
|
1226
|
-
reason: requiredOption(parsed, "reason"),
|
|
1227
|
-
workerId: optionalOption(parsed, "worker-id"),
|
|
1228
|
-
workspacePath: optionalOption(parsed, "path"),
|
|
1229
|
-
force: hasOption(parsed, "force")
|
|
1230
|
-
});
|
|
2080
|
+
return cancelMergeQueueJob(context, mergeCancellationInput(parsed, scopeProjectRoot));
|
|
1231
2081
|
}
|
|
1232
2082
|
throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
|
|
1233
2083
|
}
|
|
@@ -1247,6 +2097,8 @@ async function dispatchMerge(context, command, parsed, progress) {
|
|
|
1247
2097
|
}
|
|
1248
2098
|
if (command === "apply")
|
|
1249
2099
|
return applyVnextMerge(context, { projectRoot: requiredOption(parsed, "project-root"), requestId: requiredPosition(parsed, 0, "merge-request-id"), workId: requiredOption(parsed, "work"), ...(progress ? { progress } : {}) });
|
|
2100
|
+
if (command === "repair")
|
|
2101
|
+
return await repairVnextMerge(context, { projectRoot: requiredOption(parsed, "project-root"), requestId: requiredPosition(parsed, 0, "merge-request-id") });
|
|
1250
2102
|
if (command === "request") {
|
|
1251
2103
|
const action = requiredPosition(parsed, 0, "request action");
|
|
1252
2104
|
const requestId = requiredPosition(parsed, 1, "merge-request-id");
|
|
@@ -1343,7 +2195,7 @@ function completedCommandExitCode(payload) {
|
|
|
1343
2195
|
const exitCode = payload.exit_code;
|
|
1344
2196
|
return typeof exitCode === "number" && Number.isInteger(exitCode) ? exitCode : 0;
|
|
1345
2197
|
}
|
|
1346
|
-
function dispatchCleanup(context, command, parsed) {
|
|
2198
|
+
function dispatchCleanup(context, command, parsed, prepared) {
|
|
1347
2199
|
if (command === "scan") {
|
|
1348
2200
|
return cleanupScan(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
1349
2201
|
}
|
|
@@ -1351,6 +2203,7 @@ function dispatchCleanup(context, command, parsed) {
|
|
|
1351
2203
|
return cleanupApply(context, {
|
|
1352
2204
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
1353
2205
|
planFile: requiredOption(parsed, "plan-file"),
|
|
2206
|
+
prepared,
|
|
1354
2207
|
reason: requiredOption(parsed, "reason"),
|
|
1355
2208
|
force: hasOption(parsed, "force")
|
|
1356
2209
|
});
|
|
@@ -1371,12 +2224,14 @@ function dispatchProjectConfig(context, parsed) {
|
|
|
1371
2224
|
}
|
|
1372
2225
|
throw new AppError("usage", `Unknown project config action: ${action}`, 2);
|
|
1373
2226
|
}
|
|
1374
|
-
function dispatchSession(context, command, parsed) {
|
|
2227
|
+
function dispatchSession(context, command, parsed, preparedPayload) {
|
|
1375
2228
|
if (command === "register") {
|
|
1376
2229
|
return registerFlowSession(context, {
|
|
2230
|
+
...(preparedPayload ? { preparedPayload } : {}),
|
|
1377
2231
|
payloadBase64: optionalOption(parsed, "payload-base64"),
|
|
1378
2232
|
payloadJson: optionalOption(parsed, "payload-json"),
|
|
1379
2233
|
payloadFile: optionalOption(parsed, "payload-file"),
|
|
2234
|
+
hookEventId: optionalOption(parsed, "hook-event-id"),
|
|
1380
2235
|
sessionId: optionalOption(parsed, "session-id")
|
|
1381
2236
|
});
|
|
1382
2237
|
}
|
|
@@ -1409,7 +2264,45 @@ function dispatchSession(context, command, parsed) {
|
|
|
1409
2264
|
}
|
|
1410
2265
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
1411
2266
|
}
|
|
1412
|
-
function
|
|
2267
|
+
function snapshotCreateInput(parsed) {
|
|
2268
|
+
const recoveryId = optionalOption(parsed, "recovery-id"), stageEntry = optionalOption(parsed, "stage-entry");
|
|
2269
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), output: requiredOption(parsed, "output"), candidate: parsed.options.has("candidate"), incomplete: parsed.options.has("incomplete"), ...(recoveryId !== undefined ? { recoveryId } : {}), ...(stageEntry !== undefined ? { stageEntry } : {}) };
|
|
2270
|
+
}
|
|
2271
|
+
function snapshotRestoreInput(parsed) {
|
|
2272
|
+
return { snapshot: requiredOption(parsed, "snapshot"), projectRoot: requiredOption(parsed, "project-root"), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), ...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}) };
|
|
2273
|
+
}
|
|
2274
|
+
function runStartInput(parsed) {
|
|
2275
|
+
const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
|
|
2276
|
+
return { projectRoot: requiredOption(parsed, "project-root"), workspaceRoot: optionalOption(parsed, "workspace-root"), flowKind: requiredOption(parsed, "flow-kind"), subjectType: requiredOption(parsed, "subject-type"), subjectId: requiredOption(parsed, "subject-id"), slug: requiredOption(parsed, "slug"), nextAction: optionalOption(parsed, "next-action"), preset: optionalOption(parsed, "preset"), taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), ...(protocolOverrides !== undefined ? { protocolOverrides } : {}), runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag")) };
|
|
2277
|
+
}
|
|
2278
|
+
function runForkInput(parsed) {
|
|
2279
|
+
return { snapshot: requiredOption(parsed, "from"), output: requiredOption(parsed, "output"), engineVersion: requiredOption(parsed, "engine-version"), requestId: requiredOption(parsed, "request-id"), ...(optionalOption(parsed, "integrity-checksum") ? { integrityChecksum: optionalOption(parsed, "integrity-checksum") } : {}) };
|
|
2280
|
+
}
|
|
2281
|
+
function recoveryCaptureInput(parsed) {
|
|
2282
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") };
|
|
2283
|
+
}
|
|
2284
|
+
function recoveryResumeInput(parsed) {
|
|
2285
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), daemonId: requiredOption(parsed, "daemon-id"), harness: requiredOption(parsed, "harness"), nativeSessionId: requiredOption(parsed, "native-session-id") };
|
|
2286
|
+
}
|
|
2287
|
+
function dispatchRun(context, command, parsed, prepared = {}) {
|
|
2288
|
+
if (command === "recovery") {
|
|
2289
|
+
const action = requiredPosition(parsed, 0, "recovery action");
|
|
2290
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
2291
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
2292
|
+
if (action === "inspect")
|
|
2293
|
+
return inspectRunRecovery(context, { projectRoot, runId });
|
|
2294
|
+
if (action === "begin")
|
|
2295
|
+
return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") }, prepared.recoveryPayload);
|
|
2296
|
+
if (action === "seal")
|
|
2297
|
+
return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") }, prepared.recoveryPayload);
|
|
2298
|
+
if (action === "capture")
|
|
2299
|
+
return recordRecoveryCapture(context, recoveryCaptureInput(parsed), prepared.recoveryCapture);
|
|
2300
|
+
if (action === "resume")
|
|
2301
|
+
return resumeRunRecovery(context, recoveryResumeInput(parsed), prepared.recoveryResume);
|
|
2302
|
+
if (action === "accept")
|
|
2303
|
+
return acceptRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
2304
|
+
throw new AppError("usage", "Use: dd-flow run recovery inspect|begin|seal|capture|resume|accept", 2);
|
|
2305
|
+
}
|
|
1413
2306
|
if (command === "snapshot") {
|
|
1414
2307
|
const action = requiredPosition(parsed, 0, "snapshot action");
|
|
1415
2308
|
if (action === "bootstrap") {
|
|
@@ -1420,56 +2313,27 @@ function dispatchRun(context, command, parsed) {
|
|
|
1420
2313
|
output: requiredOption(parsed, "output")
|
|
1421
2314
|
});
|
|
1422
2315
|
if (bootstrapAction === "restore")
|
|
1423
|
-
return restoreEvalBootstrapSnapshot(context,
|
|
1424
|
-
snapshot: requiredOption(parsed, "snapshot"),
|
|
1425
|
-
projectRoot: requiredOption(parsed, "project-root")
|
|
1426
|
-
});
|
|
2316
|
+
return restoreEvalBootstrapSnapshot(context, snapshotRestoreInput(parsed), prepared.bootstrapRestore);
|
|
1427
2317
|
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore", 2);
|
|
1428
2318
|
}
|
|
1429
2319
|
if (action === "create") {
|
|
1430
|
-
|
|
1431
|
-
const stageEntry = optionalOption(parsed, "stage-entry");
|
|
1432
|
-
if (candidate && stageEntry !== undefined)
|
|
1433
|
-
throw new AppError("usage", "Use either --candidate or --stage-entry, not both", 2);
|
|
1434
|
-
if (!candidate && stageEntry === undefined)
|
|
1435
|
-
throw new AppError("usage", "Snapshot create requires --candidate or --stage-entry", 2);
|
|
1436
|
-
const input = {
|
|
1437
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1438
|
-
runId: requiredPosition(parsed, 1, "run-id"),
|
|
1439
|
-
output: requiredOption(parsed, "output")
|
|
1440
|
-
};
|
|
1441
|
-
return candidate
|
|
1442
|
-
? createEvalRunSnapshot(context, { ...input, candidate: true })
|
|
1443
|
-
: createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
|
|
2320
|
+
return createEvalRunSnapshot(context, snapshotCreateInput(parsed));
|
|
1444
2321
|
}
|
|
1445
2322
|
if (action === "restore")
|
|
1446
|
-
return restoreEvalRunSnapshot(context,
|
|
1447
|
-
snapshot: requiredOption(parsed, "snapshot"),
|
|
1448
|
-
projectRoot: requiredOption(parsed, "project-root")
|
|
1449
|
-
});
|
|
2323
|
+
return restoreEvalRunSnapshot(context, snapshotRestoreInput(parsed), prepared.snapshotRestore);
|
|
1450
2324
|
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);
|
|
1451
2325
|
}
|
|
2326
|
+
if (command === "fork") {
|
|
2327
|
+
return forkRunFromSnapshot(context, runForkInput(parsed), prepared.runFork);
|
|
2328
|
+
}
|
|
1452
2329
|
if (command === "prepare-vnext-specify") {
|
|
1453
|
-
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
|
|
2330
|
+
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
|
|
1454
2331
|
}
|
|
1455
2332
|
if (command === "start") {
|
|
1456
2333
|
if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
|
|
1457
2334
|
assertUpgradeRunStorageReady(context, requiredOption(parsed, "project-root"));
|
|
1458
2335
|
}
|
|
1459
|
-
|
|
1460
|
-
return startFlowRun(context, {
|
|
1461
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1462
|
-
workspaceRoot: optionalOption(parsed, "workspace-root"),
|
|
1463
|
-
flowKind: requiredOption(parsed, "flow-kind"),
|
|
1464
|
-
subjectType: requiredOption(parsed, "subject-type"),
|
|
1465
|
-
subjectId: requiredOption(parsed, "subject-id"),
|
|
1466
|
-
slug: requiredOption(parsed, "slug"),
|
|
1467
|
-
nextAction: optionalOption(parsed, "next-action"),
|
|
1468
|
-
preset: optionalOption(parsed, "preset"),
|
|
1469
|
-
taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
|
|
1470
|
-
...(protocolOverrides ? { protocolOverrides } : {}),
|
|
1471
|
-
runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
|
|
1472
|
-
});
|
|
2336
|
+
return startFlowRun(context, prepared.runStart?.input ?? runStartInput(parsed), prepared.runStart?.value);
|
|
1473
2337
|
}
|
|
1474
2338
|
if (command === "status") {
|
|
1475
2339
|
return getFlowRunStatus(context, {
|
|
@@ -1539,83 +2403,163 @@ function dispatchRun(context, command, parsed) {
|
|
|
1539
2403
|
throw new AppError("usage", `Unknown run flags command: ${action}`, 2);
|
|
1540
2404
|
}
|
|
1541
2405
|
if (command === "attach-stage") {
|
|
1542
|
-
return attachFlowRunStage(context,
|
|
1543
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1544
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1545
|
-
stage: requiredOption(parsed, "stage"),
|
|
1546
|
-
dir: requiredOption(parsed, "dir"),
|
|
1547
|
-
status: requiredOption(parsed, "status"),
|
|
1548
|
-
dataSchemaId: optionalOption(parsed, "data-schema-id")
|
|
1549
|
-
});
|
|
2406
|
+
return attachFlowRunStage(context, stageAttachmentInput(parsed));
|
|
1550
2407
|
}
|
|
1551
2408
|
if (command === "complete-stage") {
|
|
1552
|
-
|
|
1553
|
-
return completeFlowRunStage(context, {
|
|
1554
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1555
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1556
|
-
stage: requiredOption(parsed, "stage"),
|
|
1557
|
-
status: requiredOption(parsed, "status"),
|
|
1558
|
-
stageReport,
|
|
1559
|
-
data: optionalOption(parsed, "data"),
|
|
1560
|
-
dataSchemaId: optionalOption(parsed, "data-schema-id"),
|
|
1561
|
-
report: optionalOption(parsed, "report"),
|
|
1562
|
-
aliases: allOptions(parsed, "alias")
|
|
1563
|
-
});
|
|
2409
|
+
return completeFlowRunStage(context, stageCompletionInput(parsed));
|
|
1564
2410
|
}
|
|
1565
2411
|
if (command === "complete") {
|
|
1566
2412
|
throw new AppError("usage", "run complete is retired; only an operator may use run override", 2);
|
|
1567
2413
|
}
|
|
1568
2414
|
if (command === "override") {
|
|
1569
|
-
|
|
1570
|
-
if (!['cancelled', 'failed'].includes(status)) {
|
|
1571
|
-
throw new AppError("usage", "run override status must be cancelled or failed", 2, { status });
|
|
1572
|
-
}
|
|
1573
|
-
return completeFlowRun(context, {
|
|
1574
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1575
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1576
|
-
status,
|
|
1577
|
-
verdict: optionalOption(parsed, "verdict"),
|
|
1578
|
-
nextAction: optionalOption(parsed, "next-action"),
|
|
1579
|
-
manualOverrideReason: requiredOption(parsed, "reason")
|
|
1580
|
-
});
|
|
2415
|
+
return completeFlowRun(context, runOverrideInput(parsed));
|
|
1581
2416
|
}
|
|
1582
2417
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1583
2418
|
}
|
|
1584
|
-
|
|
2419
|
+
function stageAttachmentInput(parsed) {
|
|
2420
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), dir: requiredOption(parsed, "dir"), status: requiredOption(parsed, "status"), dataSchemaId: optionalOption(parsed, "data-schema-id") };
|
|
2421
|
+
}
|
|
2422
|
+
function runOverrideInput(parsed) {
|
|
2423
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), status: requiredOption(parsed, "status"), verdict: optionalOption(parsed, "verdict"), nextAction: optionalOption(parsed, "next-action"), manualOverrideReason: requiredOption(parsed, "reason") };
|
|
2424
|
+
}
|
|
2425
|
+
function stageCompletionInput(parsed) {
|
|
2426
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), status: requiredOption(parsed, "status"), stageReport: optionalOption(parsed, "stage-report") ?? optionalOption(parsed, "dashboard"), data: optionalOption(parsed, "data"), dataSchemaId: optionalOption(parsed, "data-schema-id"), report: optionalOption(parsed, "report"), aliases: allOptions(parsed, "alias") };
|
|
2427
|
+
}
|
|
2428
|
+
function runtimeServiceInput(parsed) {
|
|
2429
|
+
return { 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 };
|
|
2430
|
+
}
|
|
2431
|
+
function runtimeProcessStopInput(parsed) {
|
|
2432
|
+
const graceMs = optionalNumber(parsed, "grace-ms");
|
|
2433
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(graceMs !== undefined ? { graceMs } : {}) };
|
|
2434
|
+
}
|
|
2435
|
+
function runtimeScopeResumeInput(parsed) {
|
|
2436
|
+
return { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") };
|
|
2437
|
+
}
|
|
2438
|
+
function runtimeProcessRegistrationInput(context, parsed) {
|
|
2439
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
2440
|
+
const budget = parseRuntimeBudget(optionalOption(parsed, "budget-json"));
|
|
2441
|
+
const kind = requiredOption(parsed, "kind");
|
|
2442
|
+
const projectId = optionalOption(parsed, "project-id") ?? null, runId = optionalOption(parsed, "run") ?? null;
|
|
2443
|
+
const guard = projectId && runId ? recoveryGuard(context, projectId, runId) : null;
|
|
2444
|
+
const run = projectId && runId ? context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]) : null;
|
|
2445
|
+
return {
|
|
2446
|
+
kind, ownerId: requiredOption(parsed, "owner"), uniqueActiveOperation: kind === "eval-observer",
|
|
2447
|
+
...(kind === "eval-observer" ? { pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(optionalPositiveNumber(parsed, "process-group-id", true) ? { processGroupId: optionalPositiveNumber(parsed, "process-group-id", true) } : {}) } : {}),
|
|
2448
|
+
ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), projectId, runId,
|
|
2449
|
+
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
2450
|
+
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
2451
|
+
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs !== undefined ? { leaseMs } : {}),
|
|
2452
|
+
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") } : {}) }
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
function runtimeProcessConfirmationInput(parsed) {
|
|
2456
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true), processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
|
|
2457
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(processGroupId !== undefined ? { processGroupId } : {}), ...(leaseMs !== undefined ? { leaseMs } : {}) };
|
|
2458
|
+
}
|
|
2459
|
+
function runtimeProcessHeartbeatInput(parsed) {
|
|
2460
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
2461
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs !== undefined ? { leaseMs } : {}) };
|
|
2462
|
+
}
|
|
2463
|
+
function runtimeProcessFinishInput(parsed) {
|
|
2464
|
+
const state = requiredOption(parsed, "state");
|
|
2465
|
+
if (state !== "stopped" && state !== "failed")
|
|
2466
|
+
throw new AppError("validation", "--state must be stopped or failed", 2);
|
|
2467
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state, reason: optionalOption(parsed, "reason") ?? null };
|
|
2468
|
+
}
|
|
2469
|
+
async function dispatchRuntime(context, command, parsed, progress, prepared = {}) {
|
|
2470
|
+
if (command === "scope") {
|
|
2471
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "reconcile") {
|
|
2472
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2473
|
+
throw new AppError("usage", "Use runtime scope reconcile --scope-id ID --generation N --request-id ID", 2);
|
|
2474
|
+
return { ok: true, ...await reconcileRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id")) };
|
|
2475
|
+
}
|
|
2476
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
|
|
2477
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2478
|
+
throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
|
|
2479
|
+
return { ok: true, ...await launchRuntimeScopeResume(context, runtimeScopeResumeInput(parsed)) };
|
|
2480
|
+
}
|
|
2481
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
|
|
2482
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2483
|
+
throw new AppError("usage", "Use runtime scope control --scope-id ID --request-id ID --mode pause|stop [--manifest-json JSON]", 2);
|
|
2484
|
+
const mode = requiredOption(parsed, "mode");
|
|
2485
|
+
if (mode !== "pause" && mode !== "stop")
|
|
2486
|
+
throw new AppError("usage", "Scope control mode must be pause or stop", 2);
|
|
2487
|
+
return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, undefined, undefined, undefined, prepared.runtimeManifest) };
|
|
2488
|
+
}
|
|
2489
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
|
|
2490
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2491
|
+
throw new AppError("usage", "Invalid internal scope worker arguments", 2);
|
|
2492
|
+
return { ok: true, ...await serveRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation"))) };
|
|
2493
|
+
}
|
|
2494
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "stop") {
|
|
2495
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2496
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID", 2);
|
|
2497
|
+
return { ok: true, ...await stopRuntimeScope(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
2498
|
+
}
|
|
2499
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "status") {
|
|
2500
|
+
if ([...parsed.options.keys()].some(key => key !== "scope-id") || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2501
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope status --scope-id EVAL_ID", 2);
|
|
2502
|
+
return { ok: true, ...runtimeBudgetStatus(context, requiredOption(parsed, "scope-id")) };
|
|
2503
|
+
}
|
|
2504
|
+
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))
|
|
2505
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID", 2);
|
|
2506
|
+
return { ok: true, ...fenceRuntimeBudget(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
2507
|
+
}
|
|
1585
2508
|
if (command !== "process")
|
|
1586
|
-
throw new AppError("usage", "Usage: dd-flow runtime process
|
|
2509
|
+
throw new AppError("usage", "Usage: dd-flow runtime <scope|process>; see dd-flow runtime scope --help", 2);
|
|
1587
2510
|
const action = requiredPosition(parsed, 0, "runtime process action");
|
|
2511
|
+
if (action === "check-admission")
|
|
2512
|
+
return { ok: true, ...admitRuntimeCheck(context, requiredOption(parsed, "id"), requiredOption(parsed, "lease-token")) };
|
|
2513
|
+
if (action === "start")
|
|
2514
|
+
return await startRuntimeService(context, { ...runtimeServiceInput(parsed), progress });
|
|
2515
|
+
if (action === "stop")
|
|
2516
|
+
return { ok: true, process: await stopManagedProcess(context, runtimeProcessStopInput(parsed)) };
|
|
1588
2517
|
if (action === "register") {
|
|
1589
|
-
const
|
|
1590
|
-
const process = registerManagedProcess(context, {
|
|
1591
|
-
kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
|
|
1592
|
-
projectId: optionalOption(parsed, "project-id") ?? null, runId: optionalOption(parsed, "run") ?? null,
|
|
1593
|
-
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
1594
|
-
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
1595
|
-
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
|
|
1596
|
-
});
|
|
2518
|
+
const process = registerManagedProcess(context, runtimeProcessRegistrationInput(context, parsed));
|
|
1597
2519
|
return { ok: true, process };
|
|
1598
2520
|
}
|
|
1599
|
-
if (action === "confirm")
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
return { ok:
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2521
|
+
if (action === "confirm")
|
|
2522
|
+
return { ok: true, process: confirmManagedProcess(context, runtimeProcessConfirmationInput(parsed)) };
|
|
2523
|
+
if (action === "heartbeat")
|
|
2524
|
+
return { ok: heartbeatManagedProcess(context, runtimeProcessHeartbeatInput(parsed)) };
|
|
2525
|
+
if (action === "admission") {
|
|
2526
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
2527
|
+
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))
|
|
2528
|
+
throw new AppError("process_ownership_unknown", "Productive dispatch requires its current confirmed runtime owner", 1);
|
|
2529
|
+
const metadata = JSON.parse(record.metadata_json);
|
|
2530
|
+
if (metadata.state_dir !== requiredOption(parsed, "state-dir"))
|
|
2531
|
+
throw new AppError("process_ownership_unknown", "Runtime admission belongs to another state directory", 1);
|
|
2532
|
+
if (record.project_id && record.run_id) {
|
|
2533
|
+
if (managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
2534
|
+
throw new AppError("process_runtime_scope_mismatch", "Runtime admission belongs to another or unproven flow home", 1);
|
|
2535
|
+
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") });
|
|
2536
|
+
if (!recovering) {
|
|
2537
|
+
if (metadata.run_admitted !== true || !Number.isSafeInteger(metadata.run_generation))
|
|
2538
|
+
throw new AppError("run_recovery_guarded", "Runtime was not admitted before the RUN fence; reconcile its launch", 1);
|
|
2539
|
+
assertRunMutationAllowed(context, record.project_id, record.run_id, metadata.run_generation);
|
|
2540
|
+
}
|
|
2541
|
+
const run = context.db.get("SELECT status FROM runs WHERE project_id = ? AND id = ?", [record.project_id, record.run_id]);
|
|
2542
|
+
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")]);
|
|
2543
|
+
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") });
|
|
2544
|
+
if (!run || run.status !== "running" && !retainedHitlPrompt && !acceptedHitlPrompt && !["judge", "probe"].includes(metadata.role ?? ""))
|
|
2545
|
+
throw new AppError("invalid_run_state", "Subject dispatch requires a running RUN or its retained recovery/HITL prompt", 1);
|
|
2546
|
+
}
|
|
2547
|
+
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") });
|
|
2548
|
+
return { ok: true, ...budget, process_id: record.id, generation: metadata.run_generation ?? null };
|
|
1607
2549
|
}
|
|
1608
|
-
if (action === "
|
|
1609
|
-
const
|
|
1610
|
-
if (
|
|
1611
|
-
throw new AppError("
|
|
1612
|
-
return
|
|
2550
|
+
if (action === "release-turn") {
|
|
2551
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
2552
|
+
if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
2553
|
+
throw new AppError("process_ownership_unknown", "Budget release requires its retained physical owner", 1);
|
|
2554
|
+
return releaseProviderTurns(context, record, { daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") }, prepared.providerTurnRelease);
|
|
1613
2555
|
}
|
|
2556
|
+
if (action === "finish")
|
|
2557
|
+
return { ok: finishManagedProcess(context, runtimeProcessFinishInput(parsed)) };
|
|
1614
2558
|
if (action === "status")
|
|
1615
2559
|
return { ok: true, processes: managedProcessStatus(context) };
|
|
1616
2560
|
if (action === "reconcile")
|
|
1617
|
-
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"),
|
|
1618
|
-
throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
|
|
2561
|
+
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalNumber(parsed, "grace-ms")) };
|
|
2562
|
+
throw new AppError("usage", "Usage: dd-flow runtime process <start|stop|register|confirm|heartbeat|admission|finish|status|reconcile>", 2);
|
|
1619
2563
|
}
|
|
1620
2564
|
function dispatchStat(context, command, parsed) {
|
|
1621
2565
|
if (command === "usage") {
|
|
@@ -1802,7 +2746,9 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1802
2746
|
// searching project directories: run_id is a structural field in the
|
|
1803
2747
|
// submitted artifact and the database owns its materialized run_root.
|
|
1804
2748
|
const explicitRunId = optionalOption(parsed, "run");
|
|
1805
|
-
const
|
|
2749
|
+
const data = prepareJsonFile(file, "schema input").value;
|
|
2750
|
+
const declared = data && typeof data === "object" && "run_id" in data ? data.run_id : undefined;
|
|
2751
|
+
const declaredRunId = explicitRunId ?? (typeof declared === "string" && declared.trim() ? declared : undefined);
|
|
1806
2752
|
const fileRunRoot = path.basename(file) === "run.json" ? path.dirname(path.resolve(file)) : undefined;
|
|
1807
2753
|
const bound = projectRoot
|
|
1808
2754
|
? context.db.get("SELECT r.id, r.run_root FROM runs r JOIN projects p ON p.id = r.project_id WHERE p.root = ? AND (r.id = ? OR r.run_root = ?) LIMIT 1", [fs.realpathSync(projectRoot), declaredRunId ?? "", fileRunRoot ?? ""])
|
|
@@ -1810,6 +2756,7 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1810
2756
|
return validateSchema({
|
|
1811
2757
|
schemaName: requiredOption(parsed, "schema"),
|
|
1812
2758
|
file,
|
|
2759
|
+
data,
|
|
1813
2760
|
...(schemaDir ? { schemaDir } : {}),
|
|
1814
2761
|
...(projectRoot ? { projectRoot } : {}),
|
|
1815
2762
|
...(declaredRunId || bound?.id ? { runId: declaredRunId ?? bound.id } : {}),
|
|
@@ -1819,14 +2766,11 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1819
2766
|
}
|
|
1820
2767
|
throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
|
|
1821
2768
|
}
|
|
1822
|
-
function
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1827
|
-
catch {
|
|
1828
|
-
return undefined;
|
|
1829
|
-
}
|
|
2769
|
+
function laneWorkspaceInput(parsed) {
|
|
2770
|
+
return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workspacePath: requiredOption(parsed, "path"), branch: optionalOption(parsed, "branch") };
|
|
2771
|
+
}
|
|
2772
|
+
function laneWaiterCancelInput(parsed) {
|
|
2773
|
+
return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"), reason: requiredOption(parsed, "reason") };
|
|
1830
2774
|
}
|
|
1831
2775
|
async function dispatchLane(context, command, parsed) {
|
|
1832
2776
|
if (command === "status") {
|
|
@@ -1838,24 +2782,14 @@ async function dispatchLane(context, command, parsed) {
|
|
|
1838
2782
|
if (command === "waiter") {
|
|
1839
2783
|
const action = requiredPosition(parsed, 0, "lane waiter action");
|
|
1840
2784
|
if (action === "cancel") {
|
|
1841
|
-
return cancelLaneWaiter(context,
|
|
1842
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1843
|
-
lane: requiredOption(parsed, "lane"),
|
|
1844
|
-
workerId: requiredOption(parsed, "worker-id"),
|
|
1845
|
-
reason: requiredOption(parsed, "reason")
|
|
1846
|
-
});
|
|
2785
|
+
return cancelLaneWaiter(context, laneWaiterCancelInput(parsed));
|
|
1847
2786
|
}
|
|
1848
2787
|
throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
|
|
1849
2788
|
}
|
|
1850
2789
|
if (command === "workspace") {
|
|
1851
2790
|
const action = requiredPosition(parsed, 0, "lane workspace action");
|
|
1852
2791
|
if (action === "set") {
|
|
1853
|
-
return setLaneWorkspace(context,
|
|
1854
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1855
|
-
lane: requiredOption(parsed, "lane"),
|
|
1856
|
-
workspacePath: requiredOption(parsed, "path"),
|
|
1857
|
-
branch: optionalOption(parsed, "branch")
|
|
1858
|
-
});
|
|
2792
|
+
return setLaneWorkspace(context, laneWorkspaceInput(parsed));
|
|
1859
2793
|
}
|
|
1860
2794
|
if (action === "check") {
|
|
1861
2795
|
return checkLaneWorkspace(context, {
|
|
@@ -1927,7 +2861,7 @@ async function dispatchLane(context, command, parsed) {
|
|
|
1927
2861
|
}
|
|
1928
2862
|
throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
|
|
1929
2863
|
}
|
|
1930
|
-
function dispatchCodexHooks(context, parsed) {
|
|
2864
|
+
function dispatchCodexHooks(context, parsed, prepared) {
|
|
1931
2865
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
1932
2866
|
if (action === "print") {
|
|
1933
2867
|
return printCodexHooks(context, {
|
|
@@ -1949,7 +2883,7 @@ function dispatchCodexHooks(context, parsed) {
|
|
|
1949
2883
|
target: optionalOption(parsed, "target"),
|
|
1950
2884
|
profile: optionalOption(parsed, "profile"),
|
|
1951
2885
|
yes: hasOption(parsed, "yes")
|
|
1952
|
-
});
|
|
2886
|
+
}, prepared.codexHooks);
|
|
1953
2887
|
}
|
|
1954
2888
|
if (action === "remove") {
|
|
1955
2889
|
return removeCodexHooks(context, {
|
|
@@ -1957,11 +2891,14 @@ function dispatchCodexHooks(context, parsed) {
|
|
|
1957
2891
|
target: optionalOption(parsed, "target"),
|
|
1958
2892
|
profile: optionalOption(parsed, "profile"),
|
|
1959
2893
|
yes: hasOption(parsed, "yes")
|
|
1960
|
-
});
|
|
2894
|
+
}, prepared.codexHooks);
|
|
1961
2895
|
}
|
|
1962
2896
|
throw new AppError("usage", `Unknown codex hooks action: ${action}`, 2);
|
|
1963
2897
|
}
|
|
1964
|
-
function
|
|
2898
|
+
function codexHomeInput(parsed) {
|
|
2899
|
+
return { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), sourceHome: optionalOption(parsed, "source-home"), targetHome: optionalOption(parsed, "target-home") };
|
|
2900
|
+
}
|
|
2901
|
+
function dispatchCodexHome(context, parsed, prepared) {
|
|
1965
2902
|
const action = requiredPosition(parsed, 0, "codex home action");
|
|
1966
2903
|
if (action === "plan") {
|
|
1967
2904
|
return planCodexHome(context, {
|
|
@@ -1972,12 +2909,7 @@ function dispatchCodexHome(context, parsed) {
|
|
|
1972
2909
|
});
|
|
1973
2910
|
}
|
|
1974
2911
|
if (action === "init") {
|
|
1975
|
-
return initCodexHome(context,
|
|
1976
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1977
|
-
profile: optionalOption(parsed, "profile"),
|
|
1978
|
-
sourceHome: optionalOption(parsed, "source-home"),
|
|
1979
|
-
targetHome: optionalOption(parsed, "target-home")
|
|
1980
|
-
});
|
|
2912
|
+
return initCodexHome(context, codexHomeInput(parsed), prepared.codexHome);
|
|
1981
2913
|
}
|
|
1982
2914
|
if (action === "status") {
|
|
1983
2915
|
return getCodexHomeStatus(context, {
|
|
@@ -2000,11 +2932,14 @@ function dispatchCodexHome(context, parsed) {
|
|
|
2000
2932
|
}
|
|
2001
2933
|
throw new AppError("usage", `Unknown codex home action: ${action}`, 2);
|
|
2002
2934
|
}
|
|
2003
|
-
function dispatchCodexHook(context, parsed, stdin) {
|
|
2935
|
+
async function dispatchCodexHook(context, parsed, stdin) {
|
|
2004
2936
|
const action = requiredPosition(parsed, 0, "codex hook action");
|
|
2005
2937
|
if (action === "handle") {
|
|
2006
2938
|
const isolatedHome = isolatedDdFlowHomeFromHookStdin(stdin);
|
|
2007
|
-
const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }) : context;
|
|
2939
|
+
const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }, "hook") : context;
|
|
2940
|
+
const proof = await verifyCodexHookDelivery(hookContext.env, JSON.parse(stdin || "{}"));
|
|
2941
|
+
if (proof)
|
|
2942
|
+
hookContext.codexHookProof = proof;
|
|
2008
2943
|
const result = handleCodexHook(hookContext, {
|
|
2009
2944
|
event: requiredOption(parsed, "event"),
|
|
2010
2945
|
projectRoot: optionalOption(parsed, "project-root"),
|
|
@@ -2027,6 +2962,15 @@ function dispatchZcodeEvent(context, parsed, stdin) {
|
|
|
2027
2962
|
}
|
|
2028
2963
|
throw new AppError("usage", `Unknown zcode event action: ${action}`, 2);
|
|
2029
2964
|
}
|
|
2965
|
+
function dispatchZcodeHook(context, parsed, stdin) {
|
|
2966
|
+
if (requiredPosition(parsed, 0, "zcode hook action") !== "handle")
|
|
2967
|
+
throw new AppError("usage", "Unknown zcode hook action", 2);
|
|
2968
|
+
const result = handleZcodeHook(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
2969
|
+
if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
|
|
2970
|
+
return { hookSpecificOutput: result.hookSpecificOutput };
|
|
2971
|
+
}
|
|
2972
|
+
return {};
|
|
2973
|
+
}
|
|
2030
2974
|
function dispatchGrokEvent(context, parsed, stdin) {
|
|
2031
2975
|
const action = requiredPosition(parsed, 0, "grok event action");
|
|
2032
2976
|
if (action === "handle") {
|
|
@@ -2050,7 +2994,7 @@ function dispatchAgyEvent(context, parsed, stdin) {
|
|
|
2050
2994
|
return handleAgyEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
2051
2995
|
throw new AppError("usage", `Unknown agy event action: ${action}`, 2);
|
|
2052
2996
|
}
|
|
2053
|
-
function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
2997
|
+
function dispatchWorktree(context, command, parsed, scopeProjectRoot, prepared) {
|
|
2054
2998
|
if (command === "plan") {
|
|
2055
2999
|
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
2056
3000
|
}
|
|
@@ -2061,13 +3005,13 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
|
2061
3005
|
branch: requiredOption(parsed, "branch"),
|
|
2062
3006
|
base: requiredOption(parsed, "base"),
|
|
2063
3007
|
path: requiredOption(parsed, "path")
|
|
2064
|
-
});
|
|
3008
|
+
}, prepared.worktreeCreate);
|
|
2065
3009
|
}
|
|
2066
3010
|
if (command === "status") {
|
|
2067
3011
|
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
2068
3012
|
}
|
|
2069
3013
|
if (command === "bootstrap") {
|
|
2070
|
-
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
3014
|
+
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot }, prepared.worktreeBootstrap);
|
|
2071
3015
|
}
|
|
2072
3016
|
if (command === "close") {
|
|
2073
3017
|
return closeWorktree(context, {
|
|
@@ -2079,52 +3023,50 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
|
2079
3023
|
throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
|
|
2080
3024
|
}
|
|
2081
3025
|
function dispatchPlanItem(context, parsed, projectRoot) {
|
|
3026
|
+
return updatePlanItem(context, planItemInput(parsed, projectRoot));
|
|
3027
|
+
}
|
|
3028
|
+
function planItemInput(parsed, projectRoot) {
|
|
2082
3029
|
const action = requiredPosition(parsed, 0, "plan item action");
|
|
2083
3030
|
const protocolId = requiredPosition(parsed, 1, "protocol-id");
|
|
2084
3031
|
const itemId = requiredPosition(parsed, 2, "item-id");
|
|
2085
3032
|
if (action === "start") {
|
|
2086
|
-
return
|
|
3033
|
+
return { protocolId, projectRoot, itemId, action };
|
|
2087
3034
|
}
|
|
2088
3035
|
if (action === "done") {
|
|
2089
|
-
return
|
|
3036
|
+
return {
|
|
3037
|
+
action,
|
|
2090
3038
|
protocolId,
|
|
2091
3039
|
projectRoot,
|
|
2092
3040
|
itemId,
|
|
2093
3041
|
summary: requiredOption(parsed, "summary"),
|
|
2094
3042
|
evidence: parsed.options.get("evidence") ?? []
|
|
2095
|
-
}
|
|
3043
|
+
};
|
|
2096
3044
|
}
|
|
2097
3045
|
if (action === "block") {
|
|
2098
|
-
return
|
|
3046
|
+
return {
|
|
3047
|
+
action,
|
|
2099
3048
|
protocolId,
|
|
2100
3049
|
projectRoot,
|
|
2101
3050
|
itemId,
|
|
2102
3051
|
reason: requiredOption(parsed, "reason"),
|
|
2103
3052
|
userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
|
|
2104
|
-
}
|
|
3053
|
+
};
|
|
2105
3054
|
}
|
|
2106
3055
|
if (action === "skip") {
|
|
2107
|
-
return
|
|
3056
|
+
return {
|
|
3057
|
+
action,
|
|
2108
3058
|
protocolId,
|
|
2109
3059
|
projectRoot,
|
|
2110
3060
|
itemId,
|
|
2111
3061
|
reason: requiredOption(parsed, "reason")
|
|
2112
|
-
}
|
|
3062
|
+
};
|
|
2113
3063
|
}
|
|
2114
3064
|
throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
|
|
2115
3065
|
}
|
|
2116
3066
|
function readOptionalJsonFile(file) {
|
|
2117
3067
|
if (!file)
|
|
2118
3068
|
return undefined;
|
|
2119
|
-
|
|
2120
|
-
throw new AppError("not_found", `JSON file does not exist: ${file}`, 1);
|
|
2121
|
-
}
|
|
2122
|
-
try {
|
|
2123
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
2124
|
-
}
|
|
2125
|
-
catch {
|
|
2126
|
-
throw new AppError("validation", `JSON file is malformed: ${file}`, 2);
|
|
2127
|
-
}
|
|
3069
|
+
return prepareJsonFile(file, "JSON input").value;
|
|
2128
3070
|
}
|
|
2129
3071
|
function parseFlowFlagOverrides(values) {
|
|
2130
3072
|
const result = {};
|
|
@@ -2159,7 +3101,11 @@ function requiredPosition(parsed, index, label) {
|
|
|
2159
3101
|
function optionalOption(parsed, key) {
|
|
2160
3102
|
const values = parsed.options.get(key);
|
|
2161
3103
|
const value = values?.[values.length - 1];
|
|
2162
|
-
|
|
3104
|
+
if (value === flagValue)
|
|
3105
|
+
throw new AppError("usage", `--${key} requires a value`, 2);
|
|
3106
|
+
if (value !== undefined && !value.trim())
|
|
3107
|
+
throw new AppError("usage", `--${key} requires a non-empty value`, 2);
|
|
3108
|
+
return value;
|
|
2163
3109
|
}
|
|
2164
3110
|
function allOptions(parsed, key) {
|
|
2165
3111
|
return (parsed.options.get(key) ?? []).filter((value) => value !== flagValue);
|
|
@@ -2324,7 +3270,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2324
3270
|
if (family === "merge-queue") {
|
|
2325
3271
|
return projectRootForMergeQueueMutation(context, command, parsed);
|
|
2326
3272
|
}
|
|
2327
|
-
if (family === "merge" && ["one-shot", "apply", "request"].includes(command ?? "")) {
|
|
3273
|
+
if (family === "merge" && ["one-shot", "apply", "repair", "request"].includes(command ?? "")) {
|
|
2328
3274
|
return requiredOption(parsed, "project-root");
|
|
2329
3275
|
}
|
|
2330
3276
|
if (family === "merge-worker" && ["start", "stop"].includes(command ?? "")) {
|
|
@@ -2357,7 +3303,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2357
3303
|
if (family === "opencode" && ((command === "event" && requiredPosition(parsed, 0, "opencode event action") === "handle") || command === "usage")) {
|
|
2358
3304
|
return requiredOption(parsed, "project-root");
|
|
2359
3305
|
}
|
|
2360
|
-
if (family
|
|
3306
|
+
if (["agy", "droid"].includes(family ?? "") && ((command === "event" && requiredPosition(parsed, 0, `${family} event action`) === "handle") || command === "usage"))
|
|
2361
3307
|
return requiredOption(parsed, "project-root");
|
|
2362
3308
|
if (family === "codex" && command === "hooks") {
|
|
2363
3309
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
@@ -2394,14 +3340,36 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
|
2394
3340
|
function protocolProjectRoot(context, protocolId) {
|
|
2395
3341
|
return requireProtocol(context, protocolId).project_root;
|
|
2396
3342
|
}
|
|
3343
|
+
function prepareCliWorkRepair(context, parsed, objective, scopeProjectRoot) {
|
|
3344
|
+
const origins = parsed.options.get("origin-work") ?? [], findings = parsed.options.get("from-finding") ?? [], unresolved = parsed.options.get("from-unresolved") ?? [], receipts = parsed.options.get("from-check") ?? [];
|
|
3345
|
+
if ([findings.length > 0, unresolved.length > 0, receipts.length > 0].filter(Boolean).length !== 1)
|
|
3346
|
+
throw new AppError("usage", "Repair requires exactly one source: --from-check, --from-finding or --from-unresolved", 2);
|
|
3347
|
+
const scope = { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"), runId: requiredOption(parsed, "run"), objective };
|
|
3348
|
+
if (findings.length) {
|
|
3349
|
+
if (origins.length || hasOption(parsed, "verification-file"))
|
|
3350
|
+
throw new AppError("usage", "Finding repair derives its origins and cannot accept --origin-work or --verification-file", 2);
|
|
3351
|
+
return prepareVnextCodeReviewRepair(context, { ...scope, findingIds: findings, checkRefsByFinding: Object.fromEntries(findings.map(finding => [finding, parsed.options.get("check-ref") ?? []])) });
|
|
3352
|
+
}
|
|
3353
|
+
if (!origins.length)
|
|
3354
|
+
throw new AppError("usage", "Repair requires at least one --origin-work", 2);
|
|
3355
|
+
if (hasOption(parsed, "check-ref"))
|
|
3356
|
+
throw new AppError("usage", "--check-ref is only supported with --from-finding", 2);
|
|
3357
|
+
if (receipts.length > 1)
|
|
3358
|
+
throw new AppError("usage", "Create one repair Work per --from-check receipt", 2);
|
|
3359
|
+
if (!unresolved.length && hasOption(parsed, "verification-file"))
|
|
3360
|
+
throw new AppError("usage", "--verification-file is only supported with --from-unresolved", 2);
|
|
3361
|
+
const input = { ...scope, originWorkIds: origins, ...(unresolved.length ? { semanticUnresolved: unresolved, verificationPath: requiredOption(parsed, "verification-file") } : { checkReceiptId: receipts[0] }) };
|
|
3362
|
+
return { input, value: prepareVnextCodeRepair(context, input) };
|
|
3363
|
+
}
|
|
3364
|
+
function workerPromptInput(parsed) {
|
|
3365
|
+
const workspaceRoot = optionalOption(parsed, "workspace-root"), taskFile = optionalOption(parsed, "task-file");
|
|
3366
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), stage: requiredOption(parsed, "stage"), profile: requiredOption(parsed, "profile"), ...(taskFile ? { taskFile } : { planItemId: requiredOption(parsed, "plan-item") }), ...(workspaceRoot ? { workspaceRoot } : {}) };
|
|
3367
|
+
}
|
|
2397
3368
|
function runnerIntake(context) {
|
|
2398
3369
|
const input = context.value.task_input?.[0];
|
|
2399
3370
|
if (!input?.path)
|
|
2400
3371
|
throw new AppError("validation", "Runner SPECIFY context requires one ordered task_input file", 2);
|
|
2401
|
-
|
|
2402
|
-
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
2403
|
-
throw new AppError("not_found", "Runner SPECIFY task input is missing", 2, { task_input: file });
|
|
2404
|
-
return fs.readFileSync(file, "utf8");
|
|
3372
|
+
return prepareTextFile(input.path, "Runner SPECIFY task input").text;
|
|
2405
3373
|
}
|
|
2406
3374
|
/** Stage services install context before their lifecycle transition. Keeping
|
|
2407
3375
|
* this check here makes an incomplete service migration fail closed instead of
|