@deksden-com/dd-flow-cli 0.9.0-beta.13 → 0.9.0-beta.134
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 +687 -0
- package/README.md +101 -1
- package/dist/build-info.json +10 -10
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +306 -0
- package/dist/cli/help.js +88 -12
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/run-cli.js +1465 -450
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +52 -0
- package/dist/cli/runtime-process.js +153 -0
- package/dist/cli.js +17 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/harness-runtime/DD-ZCODE.md +99 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +10 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +370 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +693 -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 +190 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +805 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +128 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +561 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +490 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +255 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +128 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +703 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1128 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +13 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +144 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +36 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +272 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +12 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +619 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +14 -0
- package/dist/harness-runtime/lib/native-children.mjs +94 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
- package/dist/harness-runtime/lib/process-json.d.mts +5 -0
- package/dist/harness-runtime/lib/process-json.mjs +201 -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/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/flow-run.schema.json +2 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +121 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +851 -109
- package/dist/services/codex-capacity.js +367 -0
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +136 -0
- package/dist/services/dashboard.js +135 -56
- package/dist/services/engines.js +245 -119
- package/dist/services/eval-snapshots.js +1146 -105
- package/dist/services/execution-policy.js +181 -0
- package/dist/services/external-work-launch.js +230 -0
- package/dist/services/harness-adapter.js +329 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +753 -325
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +1 -288
- package/dist/services/lifecycle-contract.js +1 -0
- package/dist/services/lifecycle-invocations.js +1828 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-lease-monitor.js +133 -0
- package/dist/services/managed-processes.js +409 -51
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +163 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +20 -15
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +145 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +157 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +985 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +179 -0
- package/dist/services/run-controller-process.js +187 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1035 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +349 -0
- package/dist/services/runs.js +558 -217
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +180 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +185 -19
- package/dist/services/stage-lifecycle.js +98 -107
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +158 -6
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/tooling.js +32 -23
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +374 -0
- package/dist/services/vnext-code-review.js +222 -87
- package/dist/services/vnext-code.js +324 -251
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +79 -4
- package/dist/services/vnext-fanout.js +182 -17
- package/dist/services/vnext-merge.js +335 -113
- package/dist/services/vnext-plan-review.js +276 -126
- package/dist/services/vnext-plan.js +227 -102
- package/dist/services/vnext-protocolize.js +335 -82
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/work-registry.js +1083 -194
- package/dist/services/workspace-bootstrap.js +87 -0
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +98 -44
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +17 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +785 -37
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +30 -13
- package/tools/repair-paused-run-status.mjs +84 -0
package/dist/cli/run-cli.js
CHANGED
|
@@ -1,56 +1,83 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { requiredOption, optionalOption, optionalNumber, optionalPositiveNumber } from "./argument-values.js";
|
|
3
|
+
import { resolveCommandPaths, resolveCommandProjectRoot } from "../services/command-context.js";
|
|
4
|
+
import { assertAuxiliaryCommandScope, cliManagedInvocationScope, manualRecoveryInvocationScope, assertManualRecoveryInvocationTarget } from "../services/lifecycle-invocations.js";
|
|
2
5
|
import path from "node:path";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { quote } from "shell-quote";
|
|
8
|
+
import { observeLifecycleCommand, observedLifecycleNativeEvent } from "../services/lifecycle-invocations.js";
|
|
9
|
+
import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
|
|
10
|
+
import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, bindLifecyclePublishedPacket, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleDiagnostic, observedLifecycleInvocation, resolveObservedLifecycleArgs, retainObservedLifecycleSemantics, settleLifecycleInvocation, settleLifecyclePreparationRejection, settleLifecycleRejection, validateLifecyclePathAliases } from "../services/lifecycle-invocations.js";
|
|
3
11
|
import { createContext, createRouterContext } from "../runtime/context.js";
|
|
12
|
+
import { dispatchNativeHook, isNativeHookIngress } from "./native-hook-ingress.js";
|
|
13
|
+
import { isRuntimeMaintenance, runtimeMaintenanceDiagnostics, withMaintenanceDiagnosticScope } from "./runtime-maintenance-diagnostics.js";
|
|
14
|
+
import { migrateStoreWriter } from "../storage/writer-migration.js";
|
|
4
15
|
import { helpForArgs } from "./help.js";
|
|
5
|
-
import { AppError, isAppError } from "../shared/errors.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
16
|
+
import { AppError, errorRecord, isAppError } from "../shared/errors.js";
|
|
17
|
+
import { entityReferenceVariants } from "../shared/entity-references.js";
|
|
18
|
+
import { writeJson, parseJsonObject } from "../shared/json.js";
|
|
19
|
+
import { archiveProject, prepareProjectArchive, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
20
|
+
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, prepareProtocolRegistration, requireProtocol, syncProtocolFromRun, prepareProtocolTransition, transitionProtocol } from "../services/protocols.js";
|
|
21
|
+
import { getPlanStatus, preparePlanItemMutation, updatePlanItem } from "../services/plans.js";
|
|
22
|
+
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
23
|
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";
|
|
24
|
+
import { cleanupApply, cleanupScan, prepareCleanupApply } from "../services/cleanup.js";
|
|
25
|
+
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";
|
|
26
|
+
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, prepareWorktreeCreate, prepareWorktreeBootstrap, prepareWorktreeClose, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
27
|
+
import { acquireLaneLock, cancelLaneWaiter, prepareLaneWaiterCancellation, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, prepareLaneWorkspace, prepareLaneLock, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
|
|
28
|
+
import { getProjectConfigStatus, prepareProjectConfigValue, setProjectConfigValue } from "../services/config.js";
|
|
29
|
+
import { getCanonStatus, registerCanonRoot, resolveCanonRoot, prepareCanonRegistration } from "../services/canon.js";
|
|
18
30
|
import { getRuntimeStatus } from "../services/status.js";
|
|
19
31
|
import { getCliVersionReport } from "../services/build-info.js";
|
|
20
32
|
import { parseCommandArgs } from "../services/lifecycle-command.js";
|
|
21
|
-
import {
|
|
33
|
+
import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile } from "./input-preparation.js";
|
|
34
|
+
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, prepareDashboardRefresh, prepareDashboardRender, prepareGlobalDashboardRender, prepareDashboardOptions, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
22
35
|
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
23
|
-
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
36
|
+
import { getFlowSessionStatus, prepareFlowSessionPayload, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
24
37
|
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";
|
|
38
|
+
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
39
|
import { previewNextEntityId } from "../services/ids.js";
|
|
27
40
|
import { validateSchema } from "../services/schema-validation.js";
|
|
28
41
|
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";
|
|
42
|
+
import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
|
|
43
|
+
import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, prepareMigrationApply, verifyMigrationReport } from "../services/migrations.js";
|
|
31
44
|
import { publishProjectSummary } from "../services/project-summary.js";
|
|
32
45
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
33
|
-
import { resolveProjectRoot } from "../storage/paths.js";
|
|
34
|
-
import { doctorEngines, bindRunEngine, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
46
|
+
import { resolveProjectRoot, resolveDdFlowHome } from "../storage/paths.js";
|
|
47
|
+
import { doctorEngines, bindRunEngine, prepareRunEngineBind, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
35
48
|
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, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
49
|
+
import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify, prepareVnextSpecifyStart } from "../services/vnext-specify.js";
|
|
50
|
+
import { finishVnextProtocolize, isVnextProtocolizeRun, prepareProtocolizeResultPath, prepareVnextProtocolizeStart, startVnextProtocolize } from "../services/vnext-protocolize.js";
|
|
51
|
+
import { finishVnextPlan, isVnextPlanRun, startVnextPlan, prepareVnextPlanStart, prepareVnextPlanFinish } from "../services/vnext-plan.js";
|
|
52
|
+
import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, prepareVnextPlanReviewStart, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
|
|
53
|
+
import { addVnextCodeRepair, finishVnextCode, prepareVnextCodeRepair, prepareVnextCodeStart, startVnextCode } from "../services/vnext-code.js";
|
|
54
|
+
import { finishVnextCodeReview, isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReviewRepair, prepareVnextCodeReviewStart, startVnextCodeReview } from "../services/vnext-code-review.js";
|
|
55
|
+
import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
43
56
|
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";
|
|
57
|
+
import { getVnextFanoutStatus, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
|
|
58
|
+
import { pauseStageForUser, resumeStageAfterUser, prepareStagePause, prepareStageResume } from "../services/stage-pause.js";
|
|
59
|
+
import { blockStageForRuntime, unblockStageAfterRuntimeRepair, prepareStageRuntimeBlock, prepareStageRuntimeUnblock } from "../services/stage-blocker.js";
|
|
47
60
|
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 {
|
|
61
|
+
import { prepareWorkerPrompt, renderWorkerPrompt } from "../services/prompts.js";
|
|
62
|
+
import { finishStage, prepareStageFinish, startStage, prepareLegacyStageStart } from "../services/stage-lifecycle.js";
|
|
63
|
+
import { addWorkBatch, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
64
|
+
import { launchExternalWork } from "../services/external-work-launch.js";
|
|
65
|
+
import { createEvalBootstrapSnapshot, prepareEvalBootstrapSnapshot, createEvalRunSnapshot, prepareEvalRunSnapshotCreation, prepareVnextSpecifyRun, prepareEvalRunRestore, prepareEvalBootstrapRestore, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
|
|
66
|
+
import { forkRunFromSnapshot, prepareRunFork } from "../services/run-fork.js";
|
|
67
|
+
import { acceptRunRecovery, beginRunRecovery, inspectRunRecovery, prepareRecoveryCapture, prepareRecoveryPayload, prepareRunRecoveryResume, recordRecoveryCapture, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
|
|
68
|
+
import { loadExternalStageContext, prepareExternalStageContext } from "../services/stage-context.js";
|
|
69
|
+
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
70
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
71
|
+
import { parseLifecycleCommand } from "../services/lifecycle-command.js";
|
|
72
|
+
import { dispatchRuntimeProcess, prepareRuntimeProcess } from "./runtime-process.js";
|
|
73
|
+
import { fenceRuntimeBudget, runtimeBudgetStatus, prepareRuntimeScopeControl, prepareRuntimeScopeFence } from "../services/runtime-budget.js";
|
|
74
|
+
import { stopRuntimeScope } from "../services/runtime-scope-stop.js";
|
|
75
|
+
import { launchRuntimeScopeControl, launchRuntimeScopeResume, prepareRuntimeScopeReconciliation, prepareRuntimeScopeServing, reconcileRuntimeScopeControl, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
|
|
76
|
+
import { prepareRuntimeScopeResumeRequest } from "../services/runtime-scope-resume.js";
|
|
77
|
+
import { launchRunController, prepareRunControllerLaunch, resumeRunController, serveRunController, runControllerStatus, answerRunController, prepareRunControllerAnswer, supplyRunControllerContext, prepareRunControllerContext } from "../services/run-controller.js";
|
|
78
|
+
import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
|
|
79
|
+
import { reconcileRunControl, requestRunControl, serveRunControl } from "../services/run-control-worker.js";
|
|
80
|
+
import { startRuntimeService, prepareRuntimeService } from "../services/runtime-service.js";
|
|
54
81
|
const defaultIo = {
|
|
55
82
|
stdout: process.stdout,
|
|
56
83
|
stderr: process.stderr,
|
|
@@ -58,9 +85,43 @@ const defaultIo = {
|
|
|
58
85
|
};
|
|
59
86
|
const flagValue = "__dd_flow_flag__";
|
|
60
87
|
export async function runCli(args, io = defaultIo, env = process.env) {
|
|
61
|
-
|
|
88
|
+
return withMaintenanceDiagnosticScope(() => runCliRequest(args, io, env));
|
|
89
|
+
}
|
|
90
|
+
async function runCliRequest(args, io, env) {
|
|
91
|
+
let output;
|
|
92
|
+
try {
|
|
93
|
+
output = parseOutputOptions(args);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const rejection = isAppError(error) ? error : new AppError("usage", error instanceof Error ? error.message : String(error), 2);
|
|
97
|
+
Object.assign(rejection.details, { phase: "prepare", effect: "no_effect", recoverable: true, ...rejection.details });
|
|
98
|
+
try {
|
|
99
|
+
const command = quote(["dd-flow", ...args]);
|
|
100
|
+
const analysis = parseLifecycleCommand(command);
|
|
101
|
+
const event = analysis.kind !== "none" || analysis.auxiliary
|
|
102
|
+
? observedLifecycleNativeEvent(createRouterContext(env), command, env.DD_FLOW_DAEMON_ID) : null;
|
|
103
|
+
if (event) {
|
|
104
|
+
const context = createContext({ ...env }, "hook");
|
|
105
|
+
try {
|
|
106
|
+
observeLifecycleCommand(context, command, event)({ error: rejection });
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
context.db.close?.();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (persistence) {
|
|
114
|
+
rejection.details.lifecycle_diagnostic_error = String(persistence);
|
|
115
|
+
}
|
|
116
|
+
writeJson(io.stderr, { ok: false, error: errorRecord(rejection) });
|
|
117
|
+
return rejection.exitCode;
|
|
118
|
+
}
|
|
62
119
|
const progress = progressForCommand(output.args);
|
|
63
120
|
let heartbeat;
|
|
121
|
+
let invocationSettlement;
|
|
122
|
+
let settleHook;
|
|
123
|
+
let diagnosticHome;
|
|
124
|
+
let maintenanceDiagnostics;
|
|
64
125
|
try {
|
|
65
126
|
if (output.args.length === 1 && output.args[0] === "--version") {
|
|
66
127
|
const payload = getCliVersionReport();
|
|
@@ -75,6 +136,50 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
75
136
|
io.stdout.write(`${help}\n`);
|
|
76
137
|
return 0;
|
|
77
138
|
}
|
|
139
|
+
const suppliedInvocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
|
|
140
|
+
if (isNativeHookIngress(output.args)) {
|
|
141
|
+
writeJson(io.stdout, await dispatchNativeHook(output.args, await readStdin(io.stdin), env));
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
if (output.args.slice(0, 3).join(" ") === "codex hook handle") {
|
|
145
|
+
const parsed = prepareCommandRoute(output.args).parsed;
|
|
146
|
+
const stdin = await readStdin(io.stdin);
|
|
147
|
+
const payload = parseJsonObject(stdin || "{}", "codex hook stdin");
|
|
148
|
+
const event = requiredOption(parsed, "event");
|
|
149
|
+
const isolatedHome = event === "PreToolUse" ? isolatedDdFlowHomeFromHookStdin(stdin) : null;
|
|
150
|
+
const hookEnv = isolatedHome ? { ...env, DD_FLOW_HOME: isolatedHome } : env;
|
|
151
|
+
const proof = await verifyCodexHookDelivery(hookEnv, payload);
|
|
152
|
+
let opened;
|
|
153
|
+
const mode = databaseModeForCommand(output.args, classifyCliOperation(output.args, hookEnv));
|
|
154
|
+
const context = {
|
|
155
|
+
env: hookEnv, ddFlowHome: resolveDdFlowHome(hookEnv), now: () => new Date().toISOString(),
|
|
156
|
+
get db() { return (opened ??= createContext(hookEnv, mode)).db; }
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
// Retired events retain the explicit startup/migration path. PreToolUse
|
|
160
|
+
// only opens its selected prepared store when the handler needs it.
|
|
161
|
+
if (mode !== "hook")
|
|
162
|
+
opened = createContext(hookEnv, mode);
|
|
163
|
+
if (proof)
|
|
164
|
+
context.codexHookProof = proof;
|
|
165
|
+
const result = handleCodexHook(context, { event, projectRoot: optionalOption(parsed, "project-root"), stdin });
|
|
166
|
+
writeJson(io.stdout, result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result
|
|
167
|
+
? { hookSpecificOutput: result.hookSpecificOutput } : {});
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
opened?.db.close?.();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (output.args[0] === "run" && ["drive", "control"].includes(output.args[1] ?? "")) {
|
|
175
|
+
const controlArgs = parseArgs(output.args.slice(2));
|
|
176
|
+
requiredOption(controlArgs, "run");
|
|
177
|
+
requiredOption(controlArgs, "project-root");
|
|
178
|
+
}
|
|
179
|
+
if (io.stdin?.isTTY && ["work", "stage"].includes(output.args[0] ?? "") && output.args[1] === "finish"
|
|
180
|
+
&& hasOption(parseArgs(output.args.slice(2)), "result-stdin")) {
|
|
181
|
+
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);
|
|
182
|
+
}
|
|
78
183
|
const classification = classifyCliOperation(output.args, env);
|
|
79
184
|
if (requiresExplicitUpgradeAuthorization(output.args) && classification.mode !== "mb_upgrade") {
|
|
80
185
|
throw new AppError("upgrade_authorization_required", "This mb-upgrade command requires the explicit mb-upgrade authorization marker and allowlisted target context", 1, {
|
|
@@ -82,26 +187,189 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
82
187
|
required_marker: "DD_FLOW_COMPATIBILITY_MODE=mb-upgrade"
|
|
83
188
|
});
|
|
84
189
|
}
|
|
85
|
-
|
|
190
|
+
// Registration may bootstrap the resource registry, but never the unrelated
|
|
191
|
+
// RUN store. Its caller's absolute deadline also bounds that first open.
|
|
192
|
+
const maintenanceOptions = isRuntimeMaintenance(output.args) ? { resourceDatabaseMode: output.args[2] === "register" ? "initialize" : output.args[2] === "status" ? "read_existing" : "prepared_existing", ...(env.DD_FLOW_HOOK_DEADLINE_AT ? { databaseDeadlineAt: Number(env.DD_FLOW_HOOK_DEADLINE_AT) } : {}) } : {};
|
|
193
|
+
// Keep ordinary CLI JSON/stderr compatibility; bounded maintenance callers
|
|
194
|
+
// provide this request's deadline and consume its structured phase evidence.
|
|
195
|
+
if (maintenanceOptions.databaseDeadlineAt !== undefined)
|
|
196
|
+
maintenanceDiagnostics = runtimeMaintenanceDiagnostics(output.args, maintenanceOptions.databaseDeadlineAt, line => io.stderr.write(line));
|
|
197
|
+
const routerContext = createRouterContext(env, maintenanceOptions.databaseDeadlineAt);
|
|
198
|
+
const publicCommand = quote(["dd-flow", ...output.args]);
|
|
199
|
+
const nativeCommand = quote(["dd-flow", ...args]);
|
|
200
|
+
const lifecycleCommand = parseLifecycleCommand(publicCommand);
|
|
201
|
+
if (lifecycleCommand.kind === "standalone")
|
|
202
|
+
validateLifecyclePathAliases(output.args, lifecycleCommand.invocation.operation);
|
|
203
|
+
const managedScope = lifecycleCommand.kind === "standalone" && env.DD_FLOW_DAEMON_ID ? cliManagedInvocationScope({ ...routerContext, env }) : null;
|
|
204
|
+
const nativeEvent = env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone"
|
|
205
|
+
? observedLifecycleNativeEvent(routerContext, nativeCommand, env.DD_FLOW_DAEMON_ID) : null;
|
|
206
|
+
const nativeEventKey = nativeEvent && routerContext.db.get("SELECT event_key FROM hook_events WHERE project_id = ? AND id = ?", [nativeEvent.project_id, nativeEvent.id])?.event_key;
|
|
207
|
+
const observedInvocation = managedScope ? observedLifecycleInvocation(routerContext, publicCommand, managedScope, nativeEventKey ? { eventKey: nativeEventKey } : undefined) : null;
|
|
208
|
+
if (managedScope && suppliedInvocationId
|
|
209
|
+
&& routerContext.db.get("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'lifecycle_invocations'")
|
|
210
|
+
&& routerContext.db.get("SELECT id FROM lifecycle_invocations WHERE id = ?", [suppliedInvocationId])) {
|
|
211
|
+
const suppliedScope = lifecycleInvocationScope({ ...routerContext, env }, suppliedInvocationId);
|
|
212
|
+
if (["projectRoot", "daemonId", "rootSessionId", "runId", "generation"].some(key => suppliedScope[key] !== managedScope[key])) {
|
|
213
|
+
throw new AppError("invocation_scope_mismatch", "Explicit lifecycle assignment differs from the current physical native owner", 1, { effect: "no_effect", recoverable: false });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const diagnostic = env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone"
|
|
217
|
+
? observedLifecycleDiagnostic(routerContext, publicCommand, env.DD_FLOW_DAEMON_ID, observedInvocation?.id) : null;
|
|
218
|
+
if (diagnostic && (!nativeEvent || Number(diagnostic.hook_event_id) > nativeEvent.id)) {
|
|
219
|
+
throw new AppError("lifecycle_shell_syntax_invalid", `Native observer rejected the shell form: ${String(diagnostic.reason ?? "unsupported shell composition")}. Write structured input to a file and run the returned dd-flow command separately.`, 2, { ...diagnostic, phase: "prepare", effect: "no_effect", recoverable: true });
|
|
220
|
+
}
|
|
221
|
+
let manualRecoveryHookEventKey;
|
|
222
|
+
if (!observedInvocation && !suppliedInvocationId && env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone") {
|
|
223
|
+
const receipt = nativeEvent && routerContext.db.get("SELECT event_key,harness,provider_session_id,parent_session_id,daemon_id,status FROM hook_events WHERE id = ? AND project_id = ?", [nativeEvent.id, nativeEvent.project_id]);
|
|
224
|
+
const manual = managedScope && receipt?.status === "observed" ? manualRecoveryInvocationScope({ ...routerContext, env }, managedScope.projectRoot, { daemonId: receipt.daemon_id, rootSessionId: receipt.provider_session_id, sessionId: receipt.provider_session_id, parentSessionId: receipt.parent_session_id, toolCallId: String(nativeEvent.id) }, receipt.harness) : null;
|
|
225
|
+
if (!manual || JSON.stringify(manual) !== JSON.stringify(managedScope))
|
|
226
|
+
throw new AppError(nativeEvent ? "invocation_assignment_missing" : "invocation_receipt_missing", nativeEvent ? "Native call was observed, but its arguments do not match an issued lifecycle assignment" : "Managed lifecycle command has no committed native receipt", 1, { effect: "no_effect", recoverable: false });
|
|
227
|
+
assertManualRecoveryInvocationTarget({ ...routerContext, env }, manual, publicCommand);
|
|
228
|
+
manualRecoveryHookEventKey = receipt.event_key;
|
|
229
|
+
}
|
|
230
|
+
const invocationId = observedInvocation?.id ?? suppliedInvocationId;
|
|
231
|
+
let boundInvocationArgs;
|
|
232
|
+
try {
|
|
233
|
+
boundInvocationArgs = observedInvocation
|
|
234
|
+
? resolveObservedLifecycleArgs(routerContext, observedInvocation.id, output.args)
|
|
235
|
+
: invocationId && invocationId !== suppliedInvocationId
|
|
236
|
+
? [...output.args.filter((_, index, values) => values[index - 1] !== "--invocation-id" && values[index] !== "--invocation-id"), "--invocation-id", invocationId]
|
|
237
|
+
: output.args;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (!(observedInvocation && error instanceof AppError))
|
|
241
|
+
throw error;
|
|
242
|
+
const rejectionContext = createContext({ ...env }, "hook");
|
|
243
|
+
try {
|
|
244
|
+
Object.assign(error.details, settleLifecyclePreparationRejection(rejectionContext, observedInvocation.id, error));
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
rejectionContext.db.close?.();
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
const observedOperation = lifecycleCommand.kind === "standalone" ? lifecycleCommand.invocation.operation : null;
|
|
252
|
+
if (observedInvocation && !observedOperation)
|
|
253
|
+
throw new AppError("invocation_command_invalid", "Observed lifecycle command could not be parsed", 1);
|
|
254
|
+
const invocationScope = invocationId ? lifecycleInvocationScope({ ...routerContext, env }, invocationId) : undefined;
|
|
255
|
+
const expandedInvocationArgs = invocationScope && observedOperation
|
|
256
|
+
? expandLifecycleInvocationArgs(routerContext, boundInvocationArgs, invocationScope, observedOperation)
|
|
257
|
+
: boundInvocationArgs;
|
|
258
|
+
const retainedOutput = observedInvocation ? parseOutputOptions(expandedInvocationArgs) : null;
|
|
259
|
+
if (retainedOutput?.responseFile)
|
|
260
|
+
output.responseFile = retainedOutput.responseFile;
|
|
261
|
+
let invocationArgs = retainedOutput?.args ?? expandedInvocationArgs;
|
|
86
262
|
emitHumanProgress(io, output, progress, "start");
|
|
87
263
|
if (output.progressJsonl) {
|
|
88
264
|
writeProgressJsonl(io, "start", `Starting ${output.args.slice(0, 3).join(" ")}`);
|
|
89
265
|
heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
|
|
90
266
|
}
|
|
91
|
-
|
|
92
|
-
|
|
267
|
+
// Route by retained authority, so a mistyped project path reaches the CLI
|
|
268
|
+
// validator instead of selecting a different engine or failing preflight.
|
|
269
|
+
const scopeProjectRoot = invocationScope?.projectRoot ?? resolveCommandProjectRoot(routerContext, output.args, resolveProjectRootBeforeRouting(output.args, env, maintenanceOptions.databaseDeadlineAt));
|
|
270
|
+
assertAuxiliaryCommandScope({ ...routerContext, env }, publicCommand, scopeProjectRoot);
|
|
271
|
+
invocationArgs = resolveCommandPaths(routerContext, invocationArgs, scopeProjectRoot);
|
|
272
|
+
const routedArgs = output.responseFile ? [...invocationArgs, "--response-file", output.responseFile] : invocationArgs;
|
|
93
273
|
const routed = await routeArgsThroughEngine(routerContext, output.json ? [...routedArgs, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : routedArgs, io, io.stdin, env, scopeProjectRoot);
|
|
94
274
|
if (routed?.routed && !routed.inProcess) {
|
|
95
275
|
if (heartbeat)
|
|
96
276
|
clearInterval(heartbeat);
|
|
97
|
-
emitHumanProgress(io, output, progress, "done");
|
|
277
|
+
emitHumanProgress(io, output, progress, routed.exitCode === 0 ? "done" : "failed");
|
|
278
|
+
if (output.progressJsonl && routed.exitCode !== 0)
|
|
279
|
+
writeProgressJsonl(io, "failed", "Routed engine failed", { exit_code: routed.exitCode });
|
|
98
280
|
return routed.exitCode;
|
|
99
281
|
}
|
|
282
|
+
let preparedInput;
|
|
283
|
+
let preparedRoute;
|
|
284
|
+
if (!invocationId) {
|
|
285
|
+
const preparationContext = createContext({ ...env }, "read_existing", undefined, maintenanceOptions);
|
|
286
|
+
try {
|
|
287
|
+
preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile);
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
preparationContext.db.close?.();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
100
293
|
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
|
-
|
|
294
|
+
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(invocationArgs, classification), undefined, maintenanceOptions)
|
|
295
|
+
: createContext({ ...env }, databaseModeForCommand(invocationArgs, classification), undefined, maintenanceOptions);
|
|
296
|
+
diagnosticHome = dispatchContext.ddFlowHome;
|
|
297
|
+
dispatchContext.env.DD_FLOW_INVOCATION_READONLY = classification.mode === "read_only_diagnostics" ? "1" : undefined;
|
|
298
|
+
let dispatchArgs = invocationArgs;
|
|
299
|
+
let retainedOutcome;
|
|
300
|
+
// Issued transports may deliver their receipt asynchronously. Observe only
|
|
301
|
+
// after admission; retained outcomes must not need a fresh hook receipt.
|
|
302
|
+
if (invocationId) {
|
|
303
|
+
const command = quote(["dd-flow", ...invocationArgs]);
|
|
304
|
+
const admission = await awaitLifecycleInvocation(dispatchContext, { id: invocationId, command,
|
|
305
|
+
validateArgs: () => {
|
|
306
|
+
try {
|
|
307
|
+
preparedRoute = prepareCommandRoute(invocationArgs);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
if (error instanceof AppError && error.code === "usage" && ["invocation-id", "hook-event-id"].includes(String(error.details.parameter ?? ""))) {
|
|
311
|
+
throw new AppError("lifecycle_contract_invalid", `The runtime generated arguments rejected by its own CLI contract: ${error.message}`, 1, { ...error.details, recoverable: false, operation: observedOperation });
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute); },
|
|
317
|
+
assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command) });
|
|
318
|
+
if (admission.replay) {
|
|
319
|
+
retainedOutcome = admission.outcome;
|
|
320
|
+
settleHook = undefined;
|
|
321
|
+
delete dispatchContext.observeLifecycleHook;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
invocationSettlement = { context: dispatchContext, id: invocationId };
|
|
325
|
+
if (observedInvocation)
|
|
326
|
+
retainObservedLifecycleSemantics(dispatchContext, invocationId, invocationArgs);
|
|
327
|
+
dispatchArgs = [...invocationArgs, "--hook-event-id", admission.eventKey];
|
|
328
|
+
preparedInput?.route?.parsed.options.set("hook-event-id", [admission.eventKey]);
|
|
329
|
+
dispatchContext.env.DD_FLOW_INVOCATION_SCOPE = JSON.stringify(lifecycleInvocationScope(dispatchContext, invocationId));
|
|
330
|
+
dispatchContext.env.DD_FLOW_CURRENT_INVOCATION = invocationId;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!invocationId && manualRecoveryHookEventKey) {
|
|
334
|
+
// Keep the physically scoped receipt selected by admission; a service must
|
|
335
|
+
// not rediscover a different daemon's matching command event.
|
|
336
|
+
if (!invocationArgs.includes("--hook-event-id"))
|
|
337
|
+
dispatchArgs = [...invocationArgs, "--hook-event-id", manualRecoveryHookEventKey];
|
|
338
|
+
preparedInput?.route?.parsed.options.set("hook-event-id", [manualRecoveryHookEventKey]);
|
|
339
|
+
}
|
|
340
|
+
if (retainedOutcome?.error) {
|
|
341
|
+
const error = retainedOutcome.error;
|
|
342
|
+
throw new AppError(error.code, error.message, error.exitCode, error.details);
|
|
343
|
+
}
|
|
344
|
+
if (!retainedOutcome && !preparedInput)
|
|
345
|
+
preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile);
|
|
346
|
+
if (!retainedOutcome)
|
|
347
|
+
await preflightCliCompatibility(dispatchContext, invocationArgs, env, scopeProjectRoot);
|
|
348
|
+
if (!retainedOutcome) {
|
|
349
|
+
const command = !invocationId ? nativeCommand : quote(["dd-flow", ...dispatchArgs]);
|
|
350
|
+
const event = !invocationId ? nativeEvent : null;
|
|
351
|
+
settleHook = observeLifecycleCommand(dispatchContext, command, event ?? undefined);
|
|
352
|
+
}
|
|
353
|
+
const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput);
|
|
354
|
+
if (!retainedOutcome && invocationArgs[0] === "stage" && invocationArgs[1] === "start"
|
|
355
|
+
&& result && typeof result === "object" && "prompt_path" in result && typeof result.prompt_path === "string") {
|
|
356
|
+
try {
|
|
357
|
+
bindLifecyclePublishedPacket(dispatchContext, fs.readFileSync(result.prompt_path, "utf8"));
|
|
358
|
+
}
|
|
359
|
+
catch (cause) {
|
|
360
|
+
throw new AppError("invocation_packet_binding_failed", "Stage started but its published lifecycle assignments could not be retained", 1, { effect: "committed", cause: String(cause) });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const completedHook = settleHook;
|
|
364
|
+
settleHook = undefined;
|
|
365
|
+
completedHook?.({ result });
|
|
366
|
+
if (invocationSettlement) {
|
|
367
|
+
const completed = invocationSettlement;
|
|
368
|
+
invocationSettlement = undefined;
|
|
369
|
+
// A failure to persist an already completed result is an unknown reply,
|
|
370
|
+
// not a failed operation eligible for a new productive retry.
|
|
371
|
+
settleLifecycleInvocation(completed.context, completed.id, { result });
|
|
372
|
+
}
|
|
105
373
|
const refreshWarnings = refreshDashboardsAfterMutation(dispatchContext, output.args, result, scopeProjectRoot);
|
|
106
374
|
const resultWithWarnings = addWarnings(result, refreshWarnings);
|
|
107
375
|
if (heartbeat)
|
|
@@ -111,23 +379,99 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
111
379
|
emitHumanProgress(io, output, progress, "done");
|
|
112
380
|
const payload = attachProgress(resultWithWarnings, progress, output);
|
|
113
381
|
const visible = writeResponseFile(payload, output);
|
|
382
|
+
const maintenanceReceipt = payload;
|
|
383
|
+
const maintenanceExpiry = maintenanceReceipt?.lease_expires_at ?? maintenanceReceipt?.process?.lease_expires_at;
|
|
384
|
+
maintenanceDiagnostics?.phase("ack", { ...(typeof maintenanceExpiry === "string" && Number.isFinite(Date.parse(maintenanceExpiry)) ? { lease_expires_at: maintenanceExpiry } : {}) });
|
|
114
385
|
if (output.json)
|
|
115
386
|
writeJson(io.stdout, visible);
|
|
116
387
|
else
|
|
117
388
|
io.stdout.write(renderHumanResult(output.args, payload));
|
|
118
389
|
return completedCommandExitCode(payload);
|
|
119
390
|
}
|
|
120
|
-
catch (
|
|
391
|
+
catch (caught) {
|
|
392
|
+
const record = errorRecord(caught);
|
|
393
|
+
maintenanceDiagnostics?.failure(caught);
|
|
394
|
+
const error = isAppError(caught) ? caught : Object.assign(new AppError(record.code, record.message, record.exitCode, { effect: "unknown", ...record.details, ...(caught?.errcode !== undefined ? { sqlite_extended_code: caught.errcode } : {}) }), { ...(record.cause ? { cause: record.cause } : {}), ...(record.cleanup_error ? { cleanup_error: record.cleanup_error } : {}), ...(record.retryable !== undefined ? { retryable: record.retryable } : {}) });
|
|
395
|
+
if (!settleHook && !invocationSettlement) {
|
|
396
|
+
let earlyContext;
|
|
397
|
+
try {
|
|
398
|
+
const command = quote(["dd-flow", ...args]);
|
|
399
|
+
const analysis = parseLifecycleCommand(command);
|
|
400
|
+
// Maintenance failures must not start an unrelated, unbounded hook
|
|
401
|
+
// lookup after their own request deadline has already expired.
|
|
402
|
+
const router = analysis.kind !== "none" || analysis.auxiliary ? createRouterContext(env) : null;
|
|
403
|
+
const event = router ? observedLifecycleNativeEvent(router, command, env.DD_FLOW_DAEMON_ID) : null;
|
|
404
|
+
// A correctable prepare rejection precedes the terminal command's
|
|
405
|
+
// single-use claim. Correlate its unique hook by the same lifecycle
|
|
406
|
+
// match key used at dispatch, including compound provider tool shells;
|
|
407
|
+
// otherwise a repaired retry sees two fresh, unclaimed events.
|
|
408
|
+
if (router && (event || (error.details.phase === "prepare" && error.details.effect === "no_effect"
|
|
409
|
+
&& fs.existsSync(path.join(router.ddFlowHome, "db.sqlite"))))) {
|
|
410
|
+
earlyContext = createContext({ ...env }, "hook");
|
|
411
|
+
observeLifecycleCommand(earlyContext, command, event ?? undefined)({ error });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (persistence) {
|
|
415
|
+
error.details.lifecycle_diagnostic_error = String(persistence);
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
earlyContext?.db.close?.();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (diagnosticHome && (!isAppError(caught) || error.code === "storage_write_failed")) {
|
|
422
|
+
try {
|
|
423
|
+
const directory = path.join(diagnosticHome, "diagnostics", "cli");
|
|
424
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
425
|
+
const artifact = path.join(directory, `${randomUUID()}.json`);
|
|
426
|
+
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 });
|
|
427
|
+
error.details.diagnostic_artifact = artifact;
|
|
428
|
+
}
|
|
429
|
+
catch (secondary) {
|
|
430
|
+
io.stderr.write(`CLI diagnostic persistence failed: ${String(secondary)}\n`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
121
433
|
if (heartbeat)
|
|
122
434
|
clearInterval(heartbeat);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
435
|
+
if (invocationSettlement && error.details.effect !== "committed") {
|
|
436
|
+
try {
|
|
437
|
+
if (isAppError(error)) {
|
|
438
|
+
const settled = errorRecord(error);
|
|
439
|
+
settleLifecycleRejection(invocationSettlement.context, invocationSettlement.id, settled);
|
|
440
|
+
// Serialization owns a copy. Publish the committed settlement, not
|
|
441
|
+
// the original rejection which has no successor authority.
|
|
442
|
+
Object.assign(error.details, settled.details);
|
|
443
|
+
}
|
|
444
|
+
else
|
|
445
|
+
settleLifecycleInvocation(invocationSettlement.context, invocationSettlement.id, { error: { code: "unexpected", message: String(error), exitCode: 1, details: {} } });
|
|
446
|
+
}
|
|
447
|
+
catch (persistenceError) {
|
|
448
|
+
io.stderr.write(`Invocation settlement persistence failed: ${String(persistenceError)}\n`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (settleHook) {
|
|
452
|
+
try {
|
|
453
|
+
settleHook({ error: isAppError(error) ? error : { code: "unexpected", message: String(error), details: {} } });
|
|
454
|
+
}
|
|
455
|
+
catch (persistence) {
|
|
456
|
+
io.stderr.write(`${JSON.stringify({ primary: String(error), lifecycle_persistence_error: String(persistence) })}\n`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const payload = { ok: false, error: errorRecord(error) };
|
|
460
|
+
progress.splice(0, progress.length, ...progress.filter(event => event.phase !== "done"));
|
|
461
|
+
if (progress.length)
|
|
462
|
+
progress.push({ phase: "failed", message: payload.error.message, at: new Date().toISOString() });
|
|
126
463
|
emitHumanProgress(io, output, progress, "failed");
|
|
127
464
|
if (output.progressJsonl)
|
|
128
465
|
writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
|
|
129
466
|
const errorPayload = attachProgress(payload, progress, output);
|
|
130
|
-
|
|
467
|
+
let visible = errorPayload;
|
|
468
|
+
try {
|
|
469
|
+
if (error.details.parameter !== "response-file")
|
|
470
|
+
visible = writeResponseFile(errorPayload, output);
|
|
471
|
+
}
|
|
472
|
+
catch (publication) {
|
|
473
|
+
error.details.response_publication_error = { code: publication?.code ?? "response_publication_failed", message: publication instanceof Error ? publication.message : String(publication) };
|
|
474
|
+
}
|
|
131
475
|
if (output.json && output.progressJsonl)
|
|
132
476
|
writeJson(io.stdout, errorPayload);
|
|
133
477
|
else if (output.json)
|
|
@@ -136,22 +480,50 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
136
480
|
io.stderr.write(renderHumanError(payload));
|
|
137
481
|
return isAppError(error) ? error.exitCode : 1;
|
|
138
482
|
}
|
|
483
|
+
finally {
|
|
484
|
+
maintenanceDiagnostics?.close();
|
|
485
|
+
}
|
|
139
486
|
}
|
|
140
|
-
function resolveProjectRootBeforeRouting(args, env) {
|
|
487
|
+
function resolveProjectRootBeforeRouting(args, env, deadlineAt) {
|
|
141
488
|
const explicit = resolveExplicitProjectRoot(args);
|
|
142
489
|
if (explicit)
|
|
143
490
|
return explicit;
|
|
144
491
|
try {
|
|
145
492
|
// Router identity lookup is read-only; it must not initialize or migrate runtime storage.
|
|
146
|
-
|
|
493
|
+
const context = createContext(env, "read_existing", undefined, deadlineAt === undefined ? {} : { databaseDeadlineAt: deadlineAt });
|
|
494
|
+
try {
|
|
495
|
+
return resolveOperationProjectRoot(context, args);
|
|
496
|
+
}
|
|
497
|
+
finally {
|
|
498
|
+
context.db.close?.();
|
|
499
|
+
}
|
|
147
500
|
}
|
|
148
501
|
catch {
|
|
149
502
|
return null;
|
|
150
503
|
}
|
|
151
504
|
}
|
|
152
505
|
function databaseModeForCommand(args, classification) {
|
|
506
|
+
if (isNativeHookIngress(args))
|
|
507
|
+
return "hook";
|
|
508
|
+
if (isRuntimeMaintenance(args))
|
|
509
|
+
return "read_existing";
|
|
153
510
|
const [family, command] = args;
|
|
511
|
+
// Managed attempts are issued only from a prepared store. Opening that
|
|
512
|
+
// existing writer must not run migrations before their input is admitted.
|
|
513
|
+
if (parseArgs(args.slice(2)).options.has("invocation-id"))
|
|
514
|
+
return "hook";
|
|
515
|
+
// Native hook ingress must remain synchronous, but it must not run schema
|
|
516
|
+
// migration/VACUUM while the provider is waiting for its decision. Startup
|
|
517
|
+
// prepares the store; the hook mode only verifies the prepared contract and
|
|
518
|
+
// records the receipt.
|
|
519
|
+
if (family === "codex" && command === "hook" && args[2] === "handle") {
|
|
520
|
+
// Only the participating PreToolUse ingress needs the prepared hot-path
|
|
521
|
+
// contract. SessionStart/Stop/PostToolUse are observational no-ops here
|
|
522
|
+
// and retain the ordinary startup path for backwards compatibility.
|
|
523
|
+
return optionalOption(parseArgs(args.slice(3)), "event") === "PreToolUse" ? "hook" : "initialize";
|
|
524
|
+
}
|
|
154
525
|
if (family === "engine" ||
|
|
526
|
+
family === "storage" && command === "migrate-writer" ||
|
|
155
527
|
family === "version" ||
|
|
156
528
|
family === "schema" ||
|
|
157
529
|
family === "canon" && command === "resolve" ||
|
|
@@ -207,8 +579,10 @@ function parseOutputOptions(args) {
|
|
|
207
579
|
continue;
|
|
208
580
|
}
|
|
209
581
|
if (arg === "--response-file") {
|
|
582
|
+
if (responseFile !== null)
|
|
583
|
+
throw new AppError("usage", "--response-file may be supplied only once", 2);
|
|
210
584
|
const file = args[++index];
|
|
211
|
-
if (!file)
|
|
585
|
+
if (!file?.trim() || file.startsWith("--"))
|
|
212
586
|
throw new AppError("usage", "--response-file requires a path", 2);
|
|
213
587
|
responseFile = file;
|
|
214
588
|
continue;
|
|
@@ -220,6 +594,33 @@ function parseOutputOptions(args) {
|
|
|
220
594
|
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
221
595
|
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
|
|
222
596
|
}
|
|
597
|
+
/** Validate the response destination without creating it. The response is an
|
|
598
|
+
* optional publication, but a known-bad destination should never be found
|
|
599
|
+
* only after a successful lifecycle mutation. */
|
|
600
|
+
function prepareResponseDestination(value) {
|
|
601
|
+
const file = path.resolve(value);
|
|
602
|
+
const parent = path.dirname(file);
|
|
603
|
+
try {
|
|
604
|
+
let ancestor = parent;
|
|
605
|
+
for (;;) {
|
|
606
|
+
try {
|
|
607
|
+
if (!fs.statSync(ancestor).isDirectory())
|
|
608
|
+
throw new AppError("response_file_parent_invalid", "--response-file parent must be a directory", 2, { file, parent });
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
if (error.code !== "ENOENT" || path.dirname(ancestor) === ancestor)
|
|
613
|
+
throw error;
|
|
614
|
+
ancestor = path.dirname(ancestor);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (fs.existsSync(file) && !fs.statSync(file).isFile())
|
|
618
|
+
throw new AppError("response_file_invalid", "--response-file must name a regular file", 2, { file });
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
return prepareError(error, { parameter: "response-file", file, parent });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
223
624
|
/** Persist the complete machine response for harnesses that cap tool output.
|
|
224
625
|
* The visible response keeps routing fields but omits the potentially large
|
|
225
626
|
* rendered prompt; an agent can safely read the named file without rerunning
|
|
@@ -500,27 +901,636 @@ function stringValue(value) {
|
|
|
500
901
|
function shortSha(value) {
|
|
501
902
|
return value ? value.slice(0, 12) : "unknown";
|
|
502
903
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
904
|
+
/** All caller supplied file/stdin data is consumed here, before a managed
|
|
905
|
+
* invocation is allowed to claim execution or a stage claims its hook event. */
|
|
906
|
+
async function prepareCliInput(...args) {
|
|
907
|
+
try {
|
|
908
|
+
return await prepareCliInputValue(...args);
|
|
909
|
+
}
|
|
910
|
+
catch (error) {
|
|
911
|
+
return prepareError(error);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args)) {
|
|
915
|
+
const { family, command, parsed } = route;
|
|
916
|
+
const prepared = { route };
|
|
917
|
+
if (optionalOption(parsed, "reason-file")) {
|
|
918
|
+
const value = prepareJsonFile(requiredOption(parsed, "reason-file"), "Check recovery evidence").value;
|
|
919
|
+
if (!value || typeof value.reason !== "string" || !value.reason.trim() || Object.keys(value).some(key => key !== "reason"))
|
|
920
|
+
throw new AppError("validation", "Recovery evidence must be a JSON object with one nonempty reason", 2);
|
|
921
|
+
prepared.retryReason = value.reason;
|
|
922
|
+
}
|
|
923
|
+
else if (optionalOption(parsed, "retry-check"))
|
|
924
|
+
prepared.retryReason = optionalOption(parsed, "reason");
|
|
925
|
+
if (route.key === "project register")
|
|
926
|
+
resolveProjectRoot(requiredOption(parsed, "root"));
|
|
927
|
+
if (route.key === "project archive")
|
|
928
|
+
prepareProjectArchive(context, { idOrAlias: parsed.positional[0], root: optionalOption(parsed, "root"), reason: requiredOption(parsed, "reason") });
|
|
929
|
+
if (route.key === "protocol register")
|
|
930
|
+
prepareProtocolRegistration(context, { handshakeId: requiredPosition(parsed, 0, "handshake-id"), projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "workspace-path") });
|
|
931
|
+
if (route.key === "canon register")
|
|
932
|
+
prepared.canonRegistration = prepareCanonRegistration(context, { root: requiredOption(parsed, "root") });
|
|
933
|
+
if (route.key === "run attach-stage")
|
|
934
|
+
prepareFlowRunStageAttachment(context, stageAttachmentInput(parsed));
|
|
935
|
+
if (route.key === "run complete-stage")
|
|
936
|
+
prepareFlowRunStageCompletion(context, stageCompletionInput(parsed));
|
|
937
|
+
if (route.key === "run override")
|
|
938
|
+
prepareFlowRunCompletion(context, runOverrideInput(parsed));
|
|
939
|
+
if (route.key === "engine bind-run")
|
|
940
|
+
prepared.engineBind = await prepareRunEngineBind(context, engineBindInput(parsed));
|
|
941
|
+
if (route.key === "worktree create")
|
|
942
|
+
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") });
|
|
943
|
+
if (route.key === "worktree bootstrap")
|
|
944
|
+
prepared.worktreeBootstrap = prepareWorktreeBootstrap(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree bootstrap") });
|
|
945
|
+
if (route.key === "worktree close")
|
|
946
|
+
prepareWorktreeClose(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree close"), mode: requiredOption(parsed, "mode") });
|
|
947
|
+
if (route.key.startsWith("plan item "))
|
|
948
|
+
preparePlanItemMutation(context, planItemInput(parsed, requiredScopeProjectRoot(scopeProjectRoot, route.key)));
|
|
949
|
+
if (route.key === "lane workspace set")
|
|
950
|
+
prepareLaneWorkspace(context, laneWorkspaceInput(parsed));
|
|
951
|
+
if (route.key === "lane waiter cancel")
|
|
952
|
+
prepareLaneWaiterCancellation(context, laneWaiterCancelInput(parsed));
|
|
953
|
+
if (route.key === "merge-queue complete")
|
|
954
|
+
prepareMergeJobCompletion(context, { ...mergeJobTarget(parsed, scopeProjectRoot), summary: requiredOption(parsed, "summary") });
|
|
955
|
+
if (route.key === "merge-queue cancel")
|
|
956
|
+
prepareMergeQueueCancellation(context, mergeCancellationInput(parsed, scopeProjectRoot));
|
|
957
|
+
if (route.key === "merge-queue wait-next")
|
|
958
|
+
prepareMergeQueueWait(context, mergeWaitInput(parsed, scopeProjectRoot));
|
|
959
|
+
if (route.key === "merge-queue next" || route.key === "merge-queue claim")
|
|
960
|
+
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") } : {}) });
|
|
961
|
+
if (route.key === "merge-queue note")
|
|
962
|
+
prepareMergeJobNote(context, { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
|
|
963
|
+
if (route.key === "merge bundle complete")
|
|
964
|
+
prepareMergeBundleCompletion(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
|
|
965
|
+
if (route.key === "merge bundle claim")
|
|
966
|
+
prepareMergeBundleClaim(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed) });
|
|
967
|
+
if (route.key === "merge bundle fail")
|
|
968
|
+
prepareMergeBundleFailure(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
|
|
969
|
+
if (route.key === "merge-queue fail")
|
|
970
|
+
prepareMergeJobFailure(context, { ...mergeJobTarget(parsed, scopeProjectRoot), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
|
|
971
|
+
if (route.key.startsWith("lane lock ") && route.key !== "lane lock status") {
|
|
972
|
+
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
973
|
+
prepareLaneLock(context, {
|
|
974
|
+
projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"),
|
|
975
|
+
workspacePath: workspacePathOption(parsed), leaseToken: optionalOption(parsed, "lease-token"), reason: optionalOption(parsed, "reason"),
|
|
976
|
+
ttlSeconds: optionalNumber(parsed, "ttl"), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
|
|
977
|
+
}, action);
|
|
978
|
+
}
|
|
979
|
+
if (route.key === "run recovery begin")
|
|
980
|
+
prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "interruption-json"), "interruption");
|
|
981
|
+
if (route.key === "run recovery seal")
|
|
982
|
+
prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "settlement-json"), "settlement");
|
|
983
|
+
if (route.key === "run recovery capture")
|
|
984
|
+
prepared.recoveryCapture = prepareRecoveryCapture(context, recoveryCaptureInput(parsed));
|
|
985
|
+
if (route.key === "run recovery resume")
|
|
986
|
+
prepared.recoveryResume = prepareRunRecoveryResume(context, recoveryResumeInput(parsed));
|
|
987
|
+
if (route.key === "run drive answer")
|
|
988
|
+
prepared.controllerAnswer = prepareRunControllerAnswer(context, controllerAnswerInput(parsed));
|
|
989
|
+
if (route.key === "run drive launch")
|
|
990
|
+
prepared.controllerLaunch = prepareRunControllerLaunch(context, controllerLaunchInput(parsed));
|
|
991
|
+
if (route.key === "run drive context")
|
|
992
|
+
prepared.controllerContext = prepareRunControllerContext(context, controllerContextInput(parsed));
|
|
993
|
+
if (route.key === "protocol transition" || route.key === "transition")
|
|
994
|
+
prepared.protocolTransition = prepareProtocolTransition(context, protocolTransitionInput(parsed, scopeProjectRoot ?? resolveOperationProjectRoot(context, args)));
|
|
995
|
+
if (route.key === "codex home init")
|
|
996
|
+
prepared.codexHome = prepareCodexHome(context, codexHomeInput(parsed));
|
|
997
|
+
if (route.key === "codex home remove")
|
|
998
|
+
prepareCodexHomeRemoval(context, { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), mode: requiredOption(parsed, "mode") });
|
|
999
|
+
if (["codex hooks install", "codex hooks remove"].includes(route.key))
|
|
1000
|
+
prepared.codexHooks = prepareCodexHooks(context, { projectRoot: requiredOption(parsed, "project-root"), target: optionalOption(parsed, "target"), profile: optionalOption(parsed, "profile"), yes: hasOption(parsed, "yes") });
|
|
1001
|
+
if (route.key === "migration apply")
|
|
1002
|
+
prepared.migrationApply = prepareMigrationApply(context, migrationApplyInput(parsed, classifyCliOperation(args, context.env)));
|
|
1003
|
+
if (route.key === "migration verify")
|
|
1004
|
+
prepared.migrationVerification = verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
1005
|
+
if (responseFile)
|
|
1006
|
+
prepareResponseDestination(responseFile);
|
|
1007
|
+
if (family === "dashboard") {
|
|
1008
|
+
const action = dashboardAction(command);
|
|
1009
|
+
prepareDashboardOptions({ format: optionalOption(parsed, "format"), viewer: optionalOption(parsed, "viewer"), open: optionalOption(parsed, "open") });
|
|
1010
|
+
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") });
|
|
1011
|
+
if (action === "refresh" && target.kind !== "global" && optionalOption(parsed, "output") !== undefined) {
|
|
1012
|
+
throw new AppError("validation", "--output is supported only for global dashboard refresh; use dashboard render for a custom project/protocol output", 2, { parameter: "output" });
|
|
1013
|
+
}
|
|
1014
|
+
if (target.kind === "protocol")
|
|
1015
|
+
requireProtocol(context, target.protocolId, target.project.id);
|
|
1016
|
+
if (action !== "data") {
|
|
1017
|
+
if (target.kind === "global" || target.kind === "all_projects") {
|
|
1018
|
+
prepareGlobalDashboardRender(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? (command?.endsWith("-global") ? undefined : "html") });
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
const format = optionalOption(parsed, "format") ?? (action === "open" || hasOption(parsed, "project") ? "html" : undefined);
|
|
1022
|
+
const input = { projectRoot: target.project.root, format, ...(target.kind === "protocol" ? { protocol: target.protocolId } : {}) };
|
|
1023
|
+
if (action === "refresh")
|
|
1024
|
+
prepareDashboardRefresh(context, { ...input, open: optionalOption(parsed, "open") });
|
|
1025
|
+
else if (action === "render")
|
|
1026
|
+
prepareDashboardRender(context, { ...input, output: optionalOption(parsed, "output") });
|
|
1027
|
+
else if (target.kind === "protocol") {
|
|
1028
|
+
if (format !== "html")
|
|
1029
|
+
throw new AppError("validation", "Protocol dashboard open supports --format html", 2, { protocol: target.protocolId });
|
|
1030
|
+
prepareDashboardRender(context, input);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (route.key === "prompt render") {
|
|
1036
|
+
const input = workerPromptInput(parsed);
|
|
1037
|
+
prepared.workerPrompt = { input, value: prepareWorkerPrompt(context, input) };
|
|
1038
|
+
}
|
|
1039
|
+
if (route.key === "run snapshot restore" || route.key === "run snapshot bootstrap restore") {
|
|
1040
|
+
try {
|
|
1041
|
+
const input = snapshotRestoreInput(parsed);
|
|
1042
|
+
if (route.key === "run snapshot restore")
|
|
1043
|
+
prepared.snapshotRestore = prepareEvalRunRestore(context, input);
|
|
1044
|
+
else
|
|
1045
|
+
prepared.bootstrapRestore = prepareEvalBootstrapRestore(input);
|
|
1046
|
+
}
|
|
1047
|
+
catch (error) {
|
|
1048
|
+
return prepareError(error, { parameter: "snapshot/project-root" });
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (route.key === "run snapshot bootstrap create") {
|
|
1052
|
+
try {
|
|
1053
|
+
prepareEvalBootstrapSnapshot(context, { projectRoot: requiredOption(parsed, "project-root"), output: requiredOption(parsed, "output") });
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
return prepareError(error, { parameter: "project-root/output" });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (route.key === "run snapshot create") {
|
|
1060
|
+
prepareEvalRunSnapshotCreation(context, snapshotCreateInput(parsed));
|
|
1061
|
+
}
|
|
1062
|
+
if (route.key === "run start") {
|
|
1063
|
+
try {
|
|
1064
|
+
const input = runStartInput(parsed);
|
|
1065
|
+
prepared.runStart = { input, value: prepareFlowRunStart(context, input) };
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
return prepareError(error, { parameter: "run start" });
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (route.key === "run fork") {
|
|
1072
|
+
try {
|
|
1073
|
+
prepared.runFork = prepareRunFork(runForkInput(parsed));
|
|
1074
|
+
}
|
|
1075
|
+
catch (error) {
|
|
1076
|
+
return prepareError(error, { parameter: "from/output" });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (["runtime scope fence", "runtime scope stop"].includes(route.key))
|
|
1080
|
+
prepareRuntimeScopeFence(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"));
|
|
1081
|
+
if (route.key === "runtime scope resume")
|
|
1082
|
+
prepareRuntimeScopeResumeRequest(context, runtimeScopeResumeInput(parsed));
|
|
1083
|
+
if (route.key === "runtime scope reconcile")
|
|
1084
|
+
prepareRuntimeScopeReconciliation(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id"));
|
|
1085
|
+
if (route.key === "runtime scope serve")
|
|
1086
|
+
prepareRuntimeScopeServing(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")));
|
|
1087
|
+
if (route.key === "runtime scope control") {
|
|
1088
|
+
try {
|
|
1089
|
+
const raw = optionalOption(parsed, "manifest-json");
|
|
1090
|
+
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);
|
|
1091
|
+
}
|
|
1092
|
+
catch (error) {
|
|
1093
|
+
return prepareError(error, { parameter: "scope-id/request-id/manifest-json" });
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (family === "runtime" && command === "process")
|
|
1097
|
+
Object.assign(prepared, prepareRuntimeProcess(context, route.key, parsed));
|
|
1098
|
+
if (route.key === "runtime process start") {
|
|
1099
|
+
try {
|
|
1100
|
+
prepareRuntimeService(context, runtimeServiceInput(parsed));
|
|
1101
|
+
}
|
|
1102
|
+
catch (error) {
|
|
1103
|
+
return prepareError(error, { parameter: "runtime process start" });
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (family === "work" && command === "retry") {
|
|
1107
|
+
try {
|
|
1108
|
+
prepareWorkRetry(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
1109
|
+
}
|
|
1110
|
+
catch (error) {
|
|
1111
|
+
return prepareError(error, { parameter: "work-id/reason" });
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (family === "work" && (command === "fail" || command === "cancel")) {
|
|
1115
|
+
try {
|
|
1116
|
+
prepareWorkSettlement(context, requiredPosition(parsed, 0, "work-id"), command === "fail" ? "failed" : "cancelled", requiredOption(parsed, "reason"));
|
|
1117
|
+
}
|
|
1118
|
+
catch (error) {
|
|
1119
|
+
return prepareError(error, { parameter: "work-id/reason" });
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (family === "work" && (command === "deps" || command === "delete")) {
|
|
1123
|
+
try {
|
|
1124
|
+
if (command === "delete")
|
|
1125
|
+
prepareWorkDelete(context, requiredPosition(parsed, 0, "work-id"));
|
|
1126
|
+
else
|
|
1127
|
+
prepareWorkDeps(context, { workId: requiredPosition(parsed, 1, "work-id"), action: requiredPosition(parsed, 0, "action"), on: parsed.options.get("on") ?? [] });
|
|
1128
|
+
}
|
|
1129
|
+
catch (error) {
|
|
1130
|
+
return prepareError(error, { parameter: "work-id/on" });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (route.key === "cleanup apply") {
|
|
1134
|
+
const planFile = requiredOption(parsed, "plan-file");
|
|
1135
|
+
try {
|
|
1136
|
+
prepared.cleanup = prepareCleanupApply(context, { projectRoot: requiredOption(parsed, "project-root"), planFile, reason: requiredOption(parsed, "reason"), force: hasOption(parsed, "force") });
|
|
1137
|
+
}
|
|
1138
|
+
catch (error) {
|
|
1139
|
+
return prepareError(error, { parameter: "plan-file", file: planFile });
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (route.key === "stage block" || route.key === "stage unblock") {
|
|
1143
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
|
|
1144
|
+
try {
|
|
1145
|
+
if (route.key === "stage block") {
|
|
1146
|
+
const summaryFile = optionalOption(parsed, "summary-file"), summaryStdin = hasOption(parsed, "summary-stdin");
|
|
1147
|
+
if (Boolean(summaryFile) === summaryStdin)
|
|
1148
|
+
throw new AppError("usage", "stage block requires exactly one of --summary-file or --summary-stdin", 2);
|
|
1149
|
+
prepared.interactionText = summaryFile ? prepareTextFile(summaryFile, "Runtime block summary").text : await readStdin(io.stdin);
|
|
1150
|
+
prepareStageRuntimeBlock(context, { ...input, kind: requiredOption(parsed, "kind"), code: requiredOption(parsed, "code"), summary: prepared.interactionText, retryable: hasOption(parsed, "retryable") });
|
|
1151
|
+
}
|
|
1152
|
+
else
|
|
1153
|
+
prepareStageRuntimeUnblock(context, input);
|
|
1154
|
+
}
|
|
1155
|
+
catch (error) {
|
|
1156
|
+
return prepareError(error, { parameter: "stage/work/summary" });
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (route.key === "zcode invocation prepare") {
|
|
1160
|
+
const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label: "invocation issuance" }).value;
|
|
1161
|
+
const payload = value && typeof value === "object" ? value : {};
|
|
1162
|
+
try {
|
|
1163
|
+
prepared.lifecycleIssuance = prepareLifecycleIssuance(payload.command, payload.scope);
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
if (error instanceof AppError && ["invocation_command_invalid", "invocation_scope_invalid"].includes(error.code))
|
|
1167
|
+
return prepareError(new AppError("usage", error.message, 2, { cause_code: error.code }), { parameter: "stdin" });
|
|
1168
|
+
throw error;
|
|
1169
|
+
}
|
|
1170
|
+
assertLifecycleInvocationCurrent(context, prepared.lifecycleIssuance.scope, prepared.lifecycleIssuance.command);
|
|
1171
|
+
}
|
|
1172
|
+
if (route.key === "work add-batch") {
|
|
1173
|
+
const file = requiredOption(parsed, "file");
|
|
1174
|
+
try {
|
|
1175
|
+
prepared.workBatch = prepareWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file });
|
|
1176
|
+
}
|
|
1177
|
+
catch (error) {
|
|
1178
|
+
return prepareError(error, { parameter: "file/parent", file });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (route.key === "work repair add") {
|
|
1182
|
+
const taskFile = optionalOption(parsed, "task-file"), taskStdin = hasOption(parsed, "task-stdin");
|
|
1183
|
+
if (Boolean(taskFile) === taskStdin)
|
|
1184
|
+
throw new AppError("usage", "work repair add requires exactly one of --task-file or --task-stdin", 2);
|
|
1185
|
+
if (taskStdin && io.stdin?.isTTY)
|
|
1186
|
+
throw new AppError("usage", "--task-stdin requires piped or redirected input", 2);
|
|
1187
|
+
const task = taskFile ? prepareTextFile(taskFile, "Repair task").text : await readStdin(io.stdin);
|
|
1188
|
+
prepared.workRepair = prepareCliWorkRepair(context, parsed, task, scopeProjectRoot);
|
|
1189
|
+
}
|
|
1190
|
+
if (route.key === "run config set" || route.key === "run vars set") {
|
|
1191
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), key: requiredOption(parsed, "key"), value: requiredOption(parsed, "value") };
|
|
1192
|
+
try {
|
|
1193
|
+
if (route.key === "run config set")
|
|
1194
|
+
prepareFlowRunConfig(context, { ...input, reason: requiredOption(parsed, "reason") });
|
|
1195
|
+
else
|
|
1196
|
+
prepareFlowRunVariable(context, input);
|
|
1197
|
+
}
|
|
1198
|
+
catch (error) {
|
|
1199
|
+
return prepareError(error, { parameter: "run config/vars" });
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (route.key === "schema validate") {
|
|
1203
|
+
try {
|
|
1204
|
+
prepared.schemaValidation = dispatchSchema(context, command, parsed);
|
|
1205
|
+
}
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
return prepareError(error, { parameter: "file", file: requiredOption(parsed, "file") });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (route.key === "project config set") {
|
|
1211
|
+
try {
|
|
1212
|
+
prepareProjectConfigValue(requiredOption(parsed, "key"), requiredOption(parsed, "value"));
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
return prepareError(error, { parameter: "key/value" });
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (family === "session" && command === "register") {
|
|
1219
|
+
try {
|
|
1220
|
+
prepared.sessionPayload = prepareFlowSessionPayload({ payloadBase64: optionalOption(parsed, "payload-base64"), payloadFile: optionalOption(parsed, "payload-file"), payloadJson: optionalOption(parsed, "payload-json") });
|
|
1221
|
+
}
|
|
1222
|
+
catch (error) {
|
|
1223
|
+
return prepareError(error, { parameter: "session payload" });
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const run = (projectRoot, runId) => {
|
|
1227
|
+
try {
|
|
1228
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
|
|
1229
|
+
const row = context.db.get("SELECT run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
|
|
1230
|
+
if (!row)
|
|
1231
|
+
throw new AppError("not_found", "RUN is not registered", 2, { run_id: runId });
|
|
1232
|
+
if (!row.run_root)
|
|
1233
|
+
throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: runId });
|
|
1234
|
+
return { runRoot: row.run_root, workspaceRoot: row.workspace_root, indexJson: row.index_json };
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
return prepareError(error, { parameter: "run-id", run_id: runId });
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
const json = (file, stdin, label, schema) => {
|
|
1241
|
+
if (stdin) {
|
|
1242
|
+
// The caller awaits this helper; the synchronous facade below is only
|
|
1243
|
+
// used for file inputs. Keep stdin handling in the stage branch.
|
|
1244
|
+
throw new AppError("usage", `${label} stdin preparation must be awaited`, 2);
|
|
1245
|
+
}
|
|
1246
|
+
if (!file)
|
|
1247
|
+
throw new AppError("usage", `${label} input is required`, 2);
|
|
1248
|
+
return prepareJsonSchema(file, label, schema);
|
|
1249
|
+
};
|
|
1250
|
+
if (family === "stage" && command === "start") {
|
|
1251
|
+
const stage = requiredOption(parsed, "stage");
|
|
1252
|
+
if (stage !== "specify" && ["bootstrap", "intake-file", "intake-stdin"].some(option => hasOption(parsed, option)))
|
|
1253
|
+
throw new AppError("usage", "--bootstrap and intake inputs are only supported for SPECIFY", 2);
|
|
1254
|
+
requiredOption(parsed, "project-root");
|
|
1255
|
+
if (!hasOption(parsed, "bootstrap"))
|
|
1256
|
+
requiredPosition(parsed, 0, "run-id");
|
|
1257
|
+
if (hasOption(parsed, "session-id"))
|
|
1258
|
+
throw new AppError("usage", "--session-id is managed by the harness lifecycle adapter and is not accepted", 2);
|
|
1259
|
+
const contextFile = optionalOption(parsed, "context-file"), contextSha256 = optionalOption(parsed, "context-sha256");
|
|
1260
|
+
if (Boolean(contextFile) !== Boolean(contextSha256))
|
|
1261
|
+
throw new AppError("usage", "stage start requires --context-file together with --context-sha256", 2);
|
|
1262
|
+
if ([hasOption(parsed, "intake-file"), hasOption(parsed, "intake-stdin"), Boolean(contextFile)].filter(Boolean).length > 1)
|
|
1263
|
+
throw new AppError("usage", "Use exactly one of --intake-file, --intake-stdin or --context-file", 2);
|
|
1264
|
+
if (contextFile && contextSha256) {
|
|
1265
|
+
try {
|
|
1266
|
+
prepared.stageStart = { externalContext: loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) };
|
|
1267
|
+
}
|
|
1268
|
+
catch (error) {
|
|
1269
|
+
return prepareError(error, { file: contextFile, parameter: "context-file" });
|
|
1270
|
+
}
|
|
1271
|
+
if (!hasOption(parsed, "bootstrap")) {
|
|
1272
|
+
const { runRoot } = run(requiredOption(parsed, "project-root"), requiredPosition(parsed, 0, "run-id"));
|
|
1273
|
+
prepareExternalStageContext({ stageRoot: path.join(runRoot, vnextStageDirectory(stage)), loaded: prepared.stageStart.externalContext });
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (stage === "specify") {
|
|
1277
|
+
const intakeFile = optionalOption(parsed, "intake-file");
|
|
1278
|
+
if (intakeFile)
|
|
1279
|
+
prepared.stageStart = { intakeMarkdown: prepareTextFile(intakeFile, "SPECIFY intake").text };
|
|
1280
|
+
else if (hasOption(parsed, "intake-stdin"))
|
|
1281
|
+
prepared.stageStart = { intakeMarkdown: await readStdin(io.stdin) };
|
|
1282
|
+
else if (prepared.stageStart?.externalContext)
|
|
1283
|
+
prepared.stageStart.intakeMarkdown = runnerIntake(prepared.stageStart.externalContext);
|
|
1284
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1285
|
+
const bootstrap = hasOption(parsed, "bootstrap");
|
|
1286
|
+
if (isVnextSpecifyFlow(projectRoot) && (bootstrap || isVnextSpecifyRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") }))) {
|
|
1287
|
+
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") } : {}) });
|
|
1288
|
+
}
|
|
1289
|
+
else if (bootstrap) {
|
|
1290
|
+
if (hasOption(parsed, "intake-stdin") || contextFile)
|
|
1291
|
+
throw new AppError("usage", "Legacy SPECIFY bootstrap supports --intake-file, not stdin or runner context", 2);
|
|
1292
|
+
prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot, stage, bootstrap: { subject: requiredOption(parsed, "subject"), ...(intakeFile ? { intakeFile } : {}) }, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) }, prepared.stageStart?.intakeMarkdown);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (stage === "merge" && isVnextMergeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1296
|
+
prepared.mergeStart = prepareVnextMergeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1297
|
+
}
|
|
1298
|
+
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1299
|
+
prepared.protocolizeStart = prepareVnextProtocolizeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1300
|
+
}
|
|
1301
|
+
if (stage === "plan" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1302
|
+
prepared.planStart = prepareVnextPlanStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1303
|
+
}
|
|
1304
|
+
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1305
|
+
prepared.planReviewStart = prepareVnextPlanReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1306
|
+
}
|
|
1307
|
+
if (stage === "code" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1308
|
+
prepared.codeStart = prepareVnextCodeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1309
|
+
}
|
|
1310
|
+
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1311
|
+
prepared.codeReviewStart = prepareVnextCodeReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1312
|
+
}
|
|
1313
|
+
if (![prepared.specifyStart, prepared.protocolizeStart, prepared.planStart, prepared.planReviewStart, prepared.codeStart, prepared.codeReviewStart, prepared.mergeStart, prepared.legacyStageStart].some(Boolean)) {
|
|
1314
|
+
if (prepared.stageStart?.externalContext)
|
|
1315
|
+
throw new AppError("usage", "--context-file is currently supported only by vNext flow stages", 2);
|
|
1316
|
+
prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) });
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (family === "stage" && (command === "pause" || command === "resume")) {
|
|
1320
|
+
requiredPosition(parsed, 0, "run-id");
|
|
1321
|
+
requiredOption(parsed, "project-root");
|
|
1322
|
+
requiredOption(parsed, "stage");
|
|
1323
|
+
requiredOption(parsed, "work");
|
|
1324
|
+
const fileParameter = command === "pause" ? "question-file" : "answer-file";
|
|
1325
|
+
const stdinParameter = command === "pause" ? "question-stdin" : "answer-stdin";
|
|
1326
|
+
const inputFile = optionalOption(parsed, fileParameter);
|
|
1327
|
+
const fromStdin = hasOption(parsed, stdinParameter);
|
|
1328
|
+
if (Boolean(inputFile) === fromStdin)
|
|
1329
|
+
throw new AppError("usage", `stage ${command} requires exactly one of --${fileParameter} or --${stdinParameter}`, 2);
|
|
1330
|
+
if (fromStdin && io.stdin?.isTTY)
|
|
1331
|
+
throw new AppError("usage", `--${stdinParameter} requires piped or redirected input`, 2);
|
|
1332
|
+
prepared.interactionText = inputFile ? prepareTextFile(inputFile, command === "pause" ? "User question" : "User answer").text : await readStdin(io.stdin);
|
|
1333
|
+
if (!prepared.interactionText.trim())
|
|
1334
|
+
return prepareError(new AppError("validation", `stage ${command} requires non-empty input`, 2), { parameter: inputFile ? fileParameter : stdinParameter });
|
|
1335
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
|
|
1336
|
+
try {
|
|
1337
|
+
if (command === "pause")
|
|
1338
|
+
prepareStagePause(context, { ...input, question: prepared.interactionText });
|
|
1339
|
+
else
|
|
1340
|
+
prepared.resumeEvidence = prepareStageResume(context, { ...input, answer: prepared.interactionText }).evidence;
|
|
1341
|
+
}
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
return prepareError(error, { parameter: "stage/work" });
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (family === "work" && command === "finish") {
|
|
1347
|
+
const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = prepared.retryReason;
|
|
1348
|
+
if (Boolean(retryReceiptId) !== Boolean(retryReason))
|
|
1349
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1350
|
+
const resultFile = optionalOption(parsed, "result-file");
|
|
1351
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1352
|
+
if (Boolean(resultFile) === resultStdin)
|
|
1353
|
+
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
1354
|
+
prepared.workFinish = { result: resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin) };
|
|
1355
|
+
prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result);
|
|
1356
|
+
}
|
|
1357
|
+
if (family === "stage" && command === "fanout" && requiredPosition(parsed, 0, "fanout action") === "reconcile") {
|
|
1358
|
+
prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
|
|
1359
|
+
}
|
|
1360
|
+
if (family === "stage" && command === "native" && requiredPosition(parsed, 0, "native action") === "observe") {
|
|
1361
|
+
prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
|
|
1362
|
+
}
|
|
1363
|
+
if (family !== "stage" || command !== "finish")
|
|
1364
|
+
return prepared;
|
|
1365
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1366
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
1367
|
+
const stage = requiredOption(parsed, "stage");
|
|
1368
|
+
if (hasOption(parsed, "status"))
|
|
1369
|
+
throw new AppError("usage", "Stage status belongs in the semantic input file", 2);
|
|
1370
|
+
if (["result-file", "semantic-file", "data", "result-stdin"].filter(key => hasOption(parsed, key)).length > 1)
|
|
1371
|
+
throw new AppError("usage", "Use exactly one result input: --result-file, --semantic-file, --data or --result-stdin", 2);
|
|
1372
|
+
if (Boolean(optionalOption(parsed, "retry-check")) !== Boolean(prepared.retryReason))
|
|
1373
|
+
throw new AppError("usage", "--retry-check requires recovery evidence", 2);
|
|
1374
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1375
|
+
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
1376
|
+
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
1377
|
+
const runtime = run(projectRoot, runId);
|
|
1378
|
+
const terminalReplay = (name) => {
|
|
1379
|
+
let index;
|
|
1380
|
+
try {
|
|
1381
|
+
index = JSON.parse(runtime.indexJson);
|
|
1382
|
+
}
|
|
1383
|
+
catch (error) {
|
|
1384
|
+
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) });
|
|
1385
|
+
}
|
|
1386
|
+
return index.stage_runs?.some(entry => entry.stage === name && entry.status === "done") === true
|
|
1387
|
+
&& fs.existsSync(path.join(runtime.runRoot, vnextStageDirectory(name), "stage-report.json"));
|
|
1388
|
+
};
|
|
1389
|
+
const acceptStageInputs = (allowed) => {
|
|
1390
|
+
const options = allowed.split(" ");
|
|
1391
|
+
for (const name of ["dir", "semantic-file", "data", "result-file", "result-stdin", "outcome", "decision-file", "verification-file", "retry-check", "reason", "reason-file", "request", "work"]) {
|
|
1392
|
+
if (hasOption(parsed, name) && !options.includes(name === "reason-file" ? "reason" : name))
|
|
1393
|
+
throw new AppError("usage", `stage finish ${stage} does not accept --${name} for this flow`, 2, { parameter: name });
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
const fromStdin = async (label, schema) => {
|
|
1397
|
+
const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label });
|
|
1398
|
+
try {
|
|
1399
|
+
validateSchema({ ...schema, file: value.file, data: value.value });
|
|
1400
|
+
}
|
|
1401
|
+
catch (error) {
|
|
1402
|
+
return prepareError(error, { file: value.file, label });
|
|
1403
|
+
}
|
|
1404
|
+
return value;
|
|
1405
|
+
};
|
|
1406
|
+
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
1407
|
+
acceptStageInputs("result-file semantic-file data result-stdin outcome");
|
|
1408
|
+
const outcome = optionalOption(parsed, "outcome");
|
|
1409
|
+
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome))
|
|
1410
|
+
throw new AppError("usage", "vNext SPECIFY requires --outcome specified|failed|cancelled; use stage pause for a user question", 2);
|
|
1411
|
+
if (Boolean(resultFile) === resultStdin)
|
|
1412
|
+
throw new AppError("usage", "vNext SPECIFY finish requires exactly one result input", 2);
|
|
1413
|
+
const schema = { schemaName: "vnext-specify", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot };
|
|
1414
|
+
const input = resultStdin ? await fromStdin("SPECIFY result", schema) : json(resultFile, false, "SPECIFY result", schema);
|
|
1415
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1416
|
+
}
|
|
1417
|
+
else if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
1418
|
+
acceptStageInputs("result-file semantic-file data");
|
|
1419
|
+
if (!resultFile || resultStdin || hasOption(parsed, "outcome"))
|
|
1420
|
+
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
1421
|
+
let absolute;
|
|
1422
|
+
try {
|
|
1423
|
+
absolute = prepareProtocolizeResultPath(runtime.runRoot, resultFile);
|
|
1424
|
+
}
|
|
1425
|
+
catch (error) {
|
|
1426
|
+
return prepareError(error, { file: resultFile });
|
|
1427
|
+
}
|
|
1428
|
+
const input = json(absolute, false, "PROTOCOLIZE result", { schemaName: "vnext-protocolize-result", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1429
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1430
|
+
}
|
|
1431
|
+
else if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
1432
|
+
acceptStageInputs("");
|
|
1433
|
+
if (hasOption(parsed, "code-work-batch-file"))
|
|
1434
|
+
throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
|
|
1435
|
+
try {
|
|
1436
|
+
prepared.planFinish = prepareVnextPlanFinish(context, { projectRoot, runId });
|
|
1437
|
+
}
|
|
1438
|
+
catch (error) {
|
|
1439
|
+
return prepareError(error, { parameter: "PLAN artifacts" });
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
else if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
1443
|
+
acceptStageInputs("request work retry-check reason");
|
|
1444
|
+
if (resultStdin || resultFile || hasOption(parsed, "outcome"))
|
|
1445
|
+
throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
|
|
1446
|
+
requiredOption(parsed, "request");
|
|
1447
|
+
requiredOption(parsed, "work");
|
|
1448
|
+
try {
|
|
1449
|
+
prepared.mergeFinish = prepareVnextMergeFinish(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(optionalOption(parsed, "retry-check") ? { retryCheckId: optionalOption(parsed, "retry-check"), retryReason: prepared.retryReason } : {}) });
|
|
1450
|
+
}
|
|
1451
|
+
catch (error) {
|
|
1452
|
+
return prepareError(error, { parameter: "MERGE finish" });
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
else if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
1456
|
+
acceptStageInputs("decision-file");
|
|
1457
|
+
if (resultStdin || hasOption(parsed, "outcome"))
|
|
1458
|
+
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
1459
|
+
const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "04-plan-review", "decision.json");
|
|
1460
|
+
const input = json(decision, false, "PLAN-REVIEW decision", { schemaName: "plan-review-decision", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1461
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1462
|
+
}
|
|
1463
|
+
else if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
1464
|
+
acceptStageInputs("verification-file retry-check reason");
|
|
1465
|
+
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome"))
|
|
1466
|
+
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
1467
|
+
const verification = optionalOption(parsed, "verification-file");
|
|
1468
|
+
if (!verification)
|
|
1469
|
+
throw new AppError("usage", "vNext CODE finish requires --verification-file", 2);
|
|
1470
|
+
if (!terminalReplay(stage)) {
|
|
1471
|
+
const input = json(verification, false, "CODE verification", { schemaName: "code-verification", projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1472
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
else if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
1476
|
+
acceptStageInputs("decision-file retry-check reason");
|
|
1477
|
+
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
1478
|
+
throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
|
|
1479
|
+
const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "06-code-review", "decision.json");
|
|
1480
|
+
if (!terminalReplay(stage)) {
|
|
1481
|
+
const input = prepareCodeReviewDecision(runtime.runRoot, optionalOption(parsed, "decision-file"));
|
|
1482
|
+
try {
|
|
1483
|
+
validateSchema({ schemaName: "code-review-decision", file: decision, data: input.value, projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1484
|
+
}
|
|
1485
|
+
catch (error) {
|
|
1486
|
+
return prepareError(error, { file: decision, label: "CODE-REVIEW decision" });
|
|
1487
|
+
}
|
|
1488
|
+
prepared.stageFinish = input;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
else {
|
|
1492
|
+
acceptStageInputs("dir semantic-file data");
|
|
1493
|
+
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin)
|
|
1494
|
+
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
1495
|
+
prepared.legacyStageFinish = prepareStageFinish(context, { projectRoot, runId, stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}), ...(semanticFile ? { semanticFile } : {}) });
|
|
1496
|
+
}
|
|
1497
|
+
return prepared;
|
|
1498
|
+
}
|
|
1499
|
+
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args), prepared = {}) {
|
|
1500
|
+
if (!prepared.route)
|
|
1501
|
+
throw new AppError("command_not_prepared", "Command dispatch requires prepared arguments", 1);
|
|
1502
|
+
const { family, command, parsed } = prepared.route;
|
|
1503
|
+
if (family === "zcode" && command === "invocation" && parsed.positional[0] === "prepare") {
|
|
1504
|
+
const payload = prepared.lifecycleIssuance;
|
|
1505
|
+
if (!payload)
|
|
1506
|
+
throw new AppError("command_not_prepared", "Invocation issuance requires prepared input", 1);
|
|
1507
|
+
assertLifecycleInvocationCurrent(context, payload.scope, payload.command);
|
|
1508
|
+
return { ok: true, ...issueLifecycleInvocation(context, payload.command, payload.scope) };
|
|
1509
|
+
}
|
|
1510
|
+
if (family === "storage" && command === "migrate-writer") {
|
|
1511
|
+
if (parsed.positional.length || [...parsed.options.keys()].some(key => !["database", "backup", "offline"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1512
|
+
throw new AppError("usage", "Usage: dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline", 2);
|
|
1513
|
+
if (parsed.options.get("offline")?.[0] !== flagValue)
|
|
1514
|
+
throw new AppError("usage", "Writer migration requires --offline after draining all local and external runtime owners", 2);
|
|
1515
|
+
return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
|
|
1516
|
+
}
|
|
506
1517
|
if (family === "engine") {
|
|
507
|
-
return dispatchEngine(context, command, parsed, classification);
|
|
1518
|
+
return dispatchEngine(context, command, parsed, classification, prepared);
|
|
508
1519
|
}
|
|
509
1520
|
if (family === "status") {
|
|
510
|
-
const rootParsed = parseArgs([command ?? "", ...rest]);
|
|
511
1521
|
return getRuntimeStatus(context, {
|
|
512
|
-
projectRoot: optionalOption(
|
|
513
|
-
checkRegistry: hasOption(
|
|
1522
|
+
projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root"),
|
|
1523
|
+
checkRegistry: hasOption(parsed, "check-registry")
|
|
514
1524
|
});
|
|
515
1525
|
}
|
|
516
1526
|
if (family === "version") {
|
|
517
1527
|
return getCliVersionReport();
|
|
518
1528
|
}
|
|
519
1529
|
if (family === "runtime") {
|
|
520
|
-
return await dispatchRuntime(context, command, parsed);
|
|
1530
|
+
return await dispatchRuntime(context, command, parsed, commandProgress(io, output), prepared);
|
|
521
1531
|
}
|
|
522
1532
|
if (family === "canon") {
|
|
523
|
-
return dispatchCanon(context, command, parsed);
|
|
1533
|
+
return dispatchCanon(context, command, parsed, prepared);
|
|
524
1534
|
}
|
|
525
1535
|
if (family === "project" && command === "register") {
|
|
526
1536
|
return registerProject(context, { root: requiredOption(parsed, "root") });
|
|
@@ -597,16 +1607,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
597
1607
|
return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
|
|
598
1608
|
}
|
|
599
1609
|
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
|
-
});
|
|
1610
|
+
return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
|
|
610
1611
|
}
|
|
611
1612
|
if (family === "protocol" && command === "sync-from-run") {
|
|
612
1613
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
@@ -631,17 +1632,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
631
1632
|
});
|
|
632
1633
|
}
|
|
633
1634
|
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
|
-
});
|
|
1635
|
+
return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
|
|
645
1636
|
}
|
|
646
1637
|
if (family === "plan" && command === "status") {
|
|
647
1638
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
@@ -660,12 +1651,19 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
660
1651
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "stage fanout");
|
|
661
1652
|
if (action === "status")
|
|
662
1653
|
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
1654
|
+
if (action === "reconcile")
|
|
1655
|
+
return reconcileVnextFanout(context, { projectRoot, runId, stage, observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
|
|
663
1656
|
if (action === "dispatch") {
|
|
664
1657
|
if (stage === "plan-review")
|
|
665
1658
|
return dispatchVnextPlanReview(context, { projectRoot, runId });
|
|
666
1659
|
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
667
1660
|
}
|
|
668
|
-
throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch> <RUN> --stage <stage> --project-root <path>", 2);
|
|
1661
|
+
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);
|
|
1662
|
+
}
|
|
1663
|
+
if (family === "stage" && command === "native") {
|
|
1664
|
+
if (requiredPosition(parsed, 0, "native action") !== "observe")
|
|
1665
|
+
throw new AppError("usage", "Use: dd-flow stage native observe <RUN> --stage <stage> --observations-file <native-observations.json> --project-root <path>", 2);
|
|
1666
|
+
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
1667
|
}
|
|
670
1668
|
if (family === "run" && command === "capacity") {
|
|
671
1669
|
if (requiredPosition(parsed, 0, "capacity action") !== "record")
|
|
@@ -677,16 +1675,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
677
1675
|
return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
|
|
678
1676
|
}
|
|
679
1677
|
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
|
-
});
|
|
1678
|
+
return renderWorkerPrompt(context, prepared.workerPrompt?.input ?? workerPromptInput(parsed), prepared.workerPrompt?.value);
|
|
690
1679
|
}
|
|
691
1680
|
if (family === "work" && command === "status") {
|
|
692
1681
|
return getVnextWorkStatus(context, {
|
|
@@ -695,7 +1684,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
695
1684
|
});
|
|
696
1685
|
}
|
|
697
1686
|
if (family === "work" && command === "add-batch") {
|
|
698
|
-
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file") });
|
|
1687
|
+
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file"), prepared: prepared.workBatch });
|
|
699
1688
|
}
|
|
700
1689
|
if (family === "work" && command === "ls") {
|
|
701
1690
|
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 +1692,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
703
1692
|
if (family === "work" && command === "show")
|
|
704
1693
|
return showWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
705
1694
|
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
|
-
});
|
|
1695
|
+
if (!prepared.workRepair)
|
|
1696
|
+
throw new AppError("cli_input_unprepared", "Repair input was not prepared", 1);
|
|
1697
|
+
return addVnextCodeRepair(context, prepared.workRepair.input, prepared.workRepair.value);
|
|
748
1698
|
}
|
|
749
1699
|
if (family === "work" && command === "deps") {
|
|
750
1700
|
const on = parsed.options.get("on") ?? [];
|
|
@@ -752,21 +1702,34 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
752
1702
|
}
|
|
753
1703
|
if (family === "work" && command === "delete")
|
|
754
1704
|
return deleteWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
1705
|
+
if (family === "work" && command === "launch")
|
|
1706
|
+
return await launchExternalWork(context, { workId: requiredPosition(parsed, 0, "work-id"), ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}), progress: commandProgress(io, output) });
|
|
755
1707
|
if (family === "work" && command === "start") {
|
|
756
1708
|
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
1709
|
+
const recoveryId = optionalOption(parsed, "recovery-id");
|
|
757
1710
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
|
|
758
|
-
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), projectRoot });
|
|
1711
|
+
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot });
|
|
759
1712
|
}
|
|
760
1713
|
if (family === "work" && command === "finish") {
|
|
761
1714
|
const resultFile = optionalOption(parsed, "result-file");
|
|
762
1715
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
763
1716
|
if (Boolean(resultFile) === resultStdin)
|
|
764
1717
|
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
765
|
-
const
|
|
766
|
-
|
|
1718
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
1719
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work finish");
|
|
1720
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_finish", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1721
|
+
const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = prepared.retryReason;
|
|
1722
|
+
if (Boolean(retryReceiptId) !== Boolean(retryReason))
|
|
1723
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1724
|
+
const result = prepared.workFinish?.result ?? (resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin));
|
|
1725
|
+
return await finishWork(context, workId, result, commandProgress(io, output), retryReceiptId ? { receiptId: retryReceiptId, reason: retryReason } : undefined);
|
|
1726
|
+
}
|
|
1727
|
+
if (family === "work" && command === "fail") {
|
|
1728
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
1729
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work fail");
|
|
1730
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_fail", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1731
|
+
return failWork(context, workId, requiredOption(parsed, "reason"));
|
|
767
1732
|
}
|
|
768
|
-
if (family === "work" && command === "fail")
|
|
769
|
-
return failWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
770
1733
|
if (family === "work" && command === "cancel")
|
|
771
1734
|
return cancelWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
772
1735
|
if (family === "work" && command === "retry")
|
|
@@ -788,12 +1751,12 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
788
1751
|
const requireSessionBinding = hasOption(parsed, "require-session-binding");
|
|
789
1752
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
790
1753
|
const stage = requiredOption(parsed, "stage");
|
|
791
|
-
const externalContext =
|
|
1754
|
+
const externalContext = prepared.stageStart?.externalContext;
|
|
792
1755
|
const claimVnextStageStart = (runId) => {
|
|
793
1756
|
registerProject(context, { root: projectRoot });
|
|
794
1757
|
return findRecentMatchingHookEvent(context, {
|
|
795
1758
|
projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
|
|
796
|
-
matchKey: stageStartMatchKey(
|
|
1759
|
+
matchKey: entityReferenceVariants(runId).map((reference) => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
|
|
797
1760
|
errorCode: "trusted_session_binding_required",
|
|
798
1761
|
operation: `vNext ${stage.toUpperCase()} start`
|
|
799
1762
|
}).eventKey;
|
|
@@ -819,37 +1782,37 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
819
1782
|
}
|
|
820
1783
|
const result = launchVnextSpecify(context, {
|
|
821
1784
|
projectRoot,
|
|
822
|
-
...(externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
1785
|
+
...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
823
1786
|
hookEventId,
|
|
824
1787
|
...(subject ? { slug: subject } : {}),
|
|
825
1788
|
...(externalContext ? { externalContext } : {}),
|
|
826
1789
|
...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {})
|
|
827
|
-
});
|
|
1790
|
+
}, prepared.specifyStart);
|
|
828
1791
|
return attachExternalStageContext(result, externalContext);
|
|
829
1792
|
}
|
|
830
1793
|
if (!bootstrap && stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
831
1794
|
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);
|
|
1795
|
+
return attachExternalStageContext(await startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.protocolizeStart), externalContext);
|
|
833
1796
|
}
|
|
834
1797
|
if (!bootstrap && stage === "plan" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
835
1798
|
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);
|
|
1799
|
+
return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planStart), externalContext);
|
|
837
1800
|
}
|
|
838
1801
|
if (!bootstrap && stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
839
1802
|
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);
|
|
1803
|
+
return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planReviewStart), externalContext);
|
|
841
1804
|
}
|
|
842
1805
|
if (!bootstrap && stage === "code" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
843
1806
|
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);
|
|
1807
|
+
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
1808
|
}
|
|
846
1809
|
if (!bootstrap && stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
847
1810
|
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);
|
|
1811
|
+
return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeReviewStart), externalContext);
|
|
849
1812
|
}
|
|
850
1813
|
if (!bootstrap && stage === "merge" && isVnextMergeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
851
1814
|
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);
|
|
1815
|
+
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
1816
|
}
|
|
854
1817
|
if (intakeStdin) {
|
|
855
1818
|
throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
|
|
@@ -864,7 +1827,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
864
1827
|
...(hookEventId ? { hookEventId } : {}),
|
|
865
1828
|
...(requireSessionBinding ? { requireSessionBinding: true } : {}),
|
|
866
1829
|
...(dir ? { dir } : {})
|
|
867
|
-
});
|
|
1830
|
+
}, prepared.legacyStageStart);
|
|
868
1831
|
}
|
|
869
1832
|
if (family === "stage" && command === "finish") {
|
|
870
1833
|
const dir = optionalOption(parsed, "dir");
|
|
@@ -874,9 +1837,23 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
874
1837
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
875
1838
|
const runId = requiredPosition(parsed, 0, "run-id");
|
|
876
1839
|
const stage = requiredOption(parsed, "stage");
|
|
1840
|
+
const controlledReply = (result) => controlLifecycleReply(context, { projectRoot, runId }, result);
|
|
877
1841
|
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
878
1842
|
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
879
1843
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1844
|
+
const retryCheckId = optionalOption(parsed, "retry-check");
|
|
1845
|
+
const retryReason = prepared.retryReason;
|
|
1846
|
+
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
1847
|
+
if (retryCheckId && !retryReason?.trim())
|
|
1848
|
+
throw new AppError("usage", "--retry-check requires --reason explaining the recovered environment", 2);
|
|
1849
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
1850
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
1851
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1852
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
1853
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1854
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
1855
|
+
if (protectedLifecycle)
|
|
1856
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, operation: "stage_finish", ...(hookEventId ? { hookEventId } : {}) });
|
|
880
1857
|
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
881
1858
|
const outcome = optionalOption(parsed, "outcome");
|
|
882
1859
|
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome)) {
|
|
@@ -888,84 +1865,92 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
888
1865
|
if (!resultFile && !resultStdin) {
|
|
889
1866
|
throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
|
|
890
1867
|
}
|
|
891
|
-
return finishVnextSpecifyStage(context, {
|
|
1868
|
+
return controlledReply(await finishVnextSpecifyStage(context, {
|
|
892
1869
|
projectRoot,
|
|
893
1870
|
runId,
|
|
894
|
-
...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
|
|
1871
|
+
...(resultFile ? { resultFile } : prepared.stageFinish?.value ? {} : { resultJson: await readStdin(io.stdin) }),
|
|
1872
|
+
...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}),
|
|
895
1873
|
outcome: outcome
|
|
896
|
-
});
|
|
1874
|
+
}));
|
|
897
1875
|
}
|
|
898
1876
|
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
899
1877
|
if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
900
1878
|
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
901
1879
|
}
|
|
902
|
-
return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
|
|
1880
|
+
return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile, ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}) }));
|
|
903
1881
|
}
|
|
904
1882
|
if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
905
1883
|
if (optionalOption(parsed, "code-work-batch-file"))
|
|
906
1884
|
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 });
|
|
1885
|
+
return controlledReply(await finishVnextPlan(context, { projectRoot, runId, ...(prepared.planFinish ? { prepared: prepared.planFinish } : {}) }));
|
|
908
1886
|
}
|
|
909
1887
|
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
910
1888
|
if (resultStdin || hasOption(parsed, "outcome"))
|
|
911
1889
|
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
912
1890
|
const decisionFile = optionalOption(parsed, "decision-file");
|
|
913
|
-
return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
|
|
1891
|
+
return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}) }));
|
|
914
1892
|
}
|
|
915
1893
|
if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
916
1894
|
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
917
1895
|
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
918
1896
|
}
|
|
919
|
-
return await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), progress: commandProgress(io, output) });
|
|
1897
|
+
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
1898
|
}
|
|
921
1899
|
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
922
1900
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
923
1901
|
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) });
|
|
1902
|
+
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
1903
|
}
|
|
926
1904
|
if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
927
1905
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
928
1906
|
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) });
|
|
1907
|
+
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
1908
|
}
|
|
931
1909
|
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
|
|
932
1910
|
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
933
1911
|
}
|
|
934
|
-
return finishStage(context, {
|
|
1912
|
+
return controlledReply(await finishStage(context, {
|
|
935
1913
|
projectRoot,
|
|
936
1914
|
runId,
|
|
937
1915
|
stage,
|
|
938
1916
|
...(dir ? { dir } : {}),
|
|
939
1917
|
...(semanticFile ? { semanticFile } : {})
|
|
940
|
-
});
|
|
1918
|
+
}, prepared.legacyStageFinish));
|
|
941
1919
|
}
|
|
942
1920
|
if (family === "stage" && command === "pause") {
|
|
943
|
-
|
|
944
|
-
|
|
1921
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1922
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
1923
|
+
const stage = requiredOption(parsed, "stage");
|
|
1924
|
+
const workId = requiredOption(parsed, "work");
|
|
1925
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
1926
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
1927
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1928
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
1929
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1930
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
1931
|
+
if (protectedLifecycle)
|
|
1932
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, workId, operation: "stage_pause", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
945
1933
|
return pauseStageForUser(context, {
|
|
946
|
-
projectRoot
|
|
947
|
-
runId
|
|
948
|
-
stage
|
|
949
|
-
workId
|
|
950
|
-
question: await readStdin(io.stdin)
|
|
1934
|
+
projectRoot,
|
|
1935
|
+
runId,
|
|
1936
|
+
stage,
|
|
1937
|
+
workId,
|
|
1938
|
+
question: prepared.interactionText ?? await readStdin(io.stdin)
|
|
951
1939
|
});
|
|
952
1940
|
}
|
|
953
1941
|
if (family === "stage" && command === "resume") {
|
|
954
|
-
if (!hasOption(parsed, "answer-stdin"))
|
|
955
|
-
throw new AppError("usage", "stage resume requires --answer-stdin", 2);
|
|
956
1942
|
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
957
1943
|
return resumeStageAfterUser(context, {
|
|
958
1944
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
959
1945
|
runId: requiredPosition(parsed, 0, "run-id"),
|
|
960
1946
|
stage: requiredOption(parsed, "stage"),
|
|
961
1947
|
workId: requiredOption(parsed, "work"),
|
|
962
|
-
answer: await readStdin(io.stdin),
|
|
1948
|
+
answer: prepared.interactionText ?? await readStdin(io.stdin),
|
|
1949
|
+
evidence: prepared.resumeEvidence,
|
|
963
1950
|
...(hookEventId ? { hookEventId } : {})
|
|
964
1951
|
});
|
|
965
1952
|
}
|
|
966
1953
|
if (family === "stage" && command === "block") {
|
|
967
|
-
if (!hasOption(parsed, "summary-stdin"))
|
|
968
|
-
throw new AppError("usage", "stage block requires --summary-stdin", 2);
|
|
969
1954
|
const kind = requiredOption(parsed, "kind");
|
|
970
1955
|
if (kind !== "engine" && kind !== "harness" && kind !== "environment")
|
|
971
1956
|
throw new AppError("usage", "--kind must be engine, harness or environment", 2);
|
|
@@ -976,7 +1961,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
976
1961
|
workId: requiredOption(parsed, "work"),
|
|
977
1962
|
kind,
|
|
978
1963
|
code: requiredOption(parsed, "code"),
|
|
979
|
-
summary: await readStdin(io.stdin),
|
|
1964
|
+
summary: prepared.interactionText ?? await readStdin(io.stdin),
|
|
980
1965
|
retryable: hasOption(parsed, "retryable")
|
|
981
1966
|
});
|
|
982
1967
|
}
|
|
@@ -1001,13 +1986,55 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1001
1986
|
return dispatchMergeWorker(context, command, parsed);
|
|
1002
1987
|
}
|
|
1003
1988
|
if (family === "cleanup") {
|
|
1004
|
-
return dispatchCleanup(context, command, parsed);
|
|
1989
|
+
return dispatchCleanup(context, command, parsed, prepared.cleanup);
|
|
1005
1990
|
}
|
|
1006
1991
|
if (family === "session") {
|
|
1007
|
-
return dispatchSession(context, command, parsed);
|
|
1992
|
+
return dispatchSession(context, command, parsed, prepared.sessionPayload);
|
|
1008
1993
|
}
|
|
1009
1994
|
if (family === "run") {
|
|
1010
|
-
|
|
1995
|
+
if (command === "control") {
|
|
1996
|
+
const action = requiredPosition(parsed, 0, "control action");
|
|
1997
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1998
|
+
if (action === "status")
|
|
1999
|
+
return runControlStatus(context, scope);
|
|
2000
|
+
if (action === "reconcile") {
|
|
2001
|
+
if ([...parsed.options.keys()].some(key => !["run", "project-root", "control-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2002
|
+
throw new AppError("usage", "Use run control reconcile --run ID --project-root PATH --control-id ID --request-id ID", 2);
|
|
2003
|
+
return await reconcileRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id"), requestId: requiredOption(parsed, "request-id") });
|
|
2004
|
+
}
|
|
2005
|
+
if (action === "resume") {
|
|
2006
|
+
if (["force", "grace-ms", "escalate"].some(option => hasOption(parsed, option)))
|
|
2007
|
+
throw new AppError("usage", "Resume does not accept stop or pause escalation options", 2);
|
|
2008
|
+
return await resumeRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), controlId: requiredOption(parsed, "from"), ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {}) });
|
|
2009
|
+
}
|
|
2010
|
+
if (action === "serve")
|
|
2011
|
+
return await serveRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id") });
|
|
2012
|
+
if (action === "pause" || action === "stop")
|
|
2013
|
+
return await requestRunControl(context, {
|
|
2014
|
+
...scope, action, requestId: requiredOption(parsed, "request-id"),
|
|
2015
|
+
...(hasOption(parsed, "force") ? { force: true } : {}),
|
|
2016
|
+
...(hasOption(parsed, "grace-ms") ? { graceMs: Number(requiredOption(parsed, "grace-ms")) } : {}),
|
|
2017
|
+
...(hasOption(parsed, "escalate") ? { escalate: requiredOption(parsed, "escalate") } : {}),
|
|
2018
|
+
...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {})
|
|
2019
|
+
});
|
|
2020
|
+
throw new AppError("usage", "Use: dd-flow run control pause|stop|resume|reconcile|status", 2);
|
|
2021
|
+
}
|
|
2022
|
+
if (command === "drive") {
|
|
2023
|
+
const action = requiredPosition(parsed, 0, "drive action");
|
|
2024
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
2025
|
+
if (action === "status")
|
|
2026
|
+
return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
|
|
2027
|
+
if (action === "launch")
|
|
2028
|
+
return await launchRunController(context, controllerLaunchInput(parsed), prepared.controllerLaunch);
|
|
2029
|
+
if (action === "serve")
|
|
2030
|
+
return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
|
|
2031
|
+
if (action === "answer")
|
|
2032
|
+
return answerRunController(context, controllerAnswerInput(parsed), prepared.controllerAnswer);
|
|
2033
|
+
if (action === "context")
|
|
2034
|
+
return supplyRunControllerContext(context, controllerContextInput(parsed), prepared.controllerContext);
|
|
2035
|
+
throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
|
|
2036
|
+
}
|
|
2037
|
+
return dispatchRun(context, command, parsed, prepared);
|
|
1011
2038
|
}
|
|
1012
2039
|
if (family === "stat") {
|
|
1013
2040
|
return dispatchStat(context, command, parsed);
|
|
@@ -1022,22 +2049,26 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1022
2049
|
return dispatchDashboard(context, command, parsed);
|
|
1023
2050
|
}
|
|
1024
2051
|
if (family === "schema") {
|
|
1025
|
-
return
|
|
2052
|
+
return prepared.schemaValidation;
|
|
1026
2053
|
}
|
|
1027
2054
|
if (family === "memory") {
|
|
1028
2055
|
return dispatchMemory(command, parsed);
|
|
1029
2056
|
}
|
|
1030
2057
|
if (family === "migration") {
|
|
1031
|
-
return dispatchMigration(context, command, parsed, classification);
|
|
2058
|
+
return dispatchMigration(context, command, parsed, classification, prepared);
|
|
1032
2059
|
}
|
|
1033
2060
|
if (family === "codex" && command === "hooks") {
|
|
1034
|
-
return dispatchCodexHooks(context, parsed);
|
|
2061
|
+
return dispatchCodexHooks(context, parsed, prepared);
|
|
1035
2062
|
}
|
|
1036
2063
|
if (family === "codex" && command === "home") {
|
|
1037
|
-
return dispatchCodexHome(context, parsed);
|
|
2064
|
+
return dispatchCodexHome(context, parsed, prepared);
|
|
1038
2065
|
}
|
|
1039
2066
|
if (family === "codex" && command === "hook") {
|
|
1040
|
-
|
|
2067
|
+
// `handle` is owned by the pre-context ingress above, never generic dispatch.
|
|
2068
|
+
throw new AppError("usage", "Use: dd-flow codex hook handle --event EVENT", 2);
|
|
2069
|
+
}
|
|
2070
|
+
if (family === "zcode" && command === "hook") {
|
|
2071
|
+
return dispatchZcodeHook(context, parsed, await readStdin(io.stdin));
|
|
1041
2072
|
}
|
|
1042
2073
|
if (family === "zcode" && command === "event") {
|
|
1043
2074
|
return dispatchZcodeEvent(context, parsed, await readStdin(io.stdin));
|
|
@@ -1063,6 +2094,16 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1063
2094
|
throw new AppError("usage", "Unknown opencode usage action", 2);
|
|
1064
2095
|
return ingestOpenCodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1065
2096
|
}
|
|
2097
|
+
if (family === "droid" && command === "event") {
|
|
2098
|
+
if (requiredPosition(parsed, 0, "droid event action") !== "handle")
|
|
2099
|
+
throw new AppError("usage", "Unknown droid event action", 2);
|
|
2100
|
+
return handleDroidEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
2101
|
+
}
|
|
2102
|
+
if (family === "droid" && command === "usage") {
|
|
2103
|
+
if (requiredPosition(parsed, 0, "droid usage action") !== "ingest")
|
|
2104
|
+
throw new AppError("usage", "Unknown droid usage action", 2);
|
|
2105
|
+
return ingestDroidUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
2106
|
+
}
|
|
1066
2107
|
if (family === "agy" && command === "event")
|
|
1067
2108
|
return dispatchAgyEvent(context, parsed, await readStdin(io.stdin));
|
|
1068
2109
|
if (family === "agy" && command === "usage") {
|
|
@@ -1071,11 +2112,14 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1071
2112
|
return ingestAgyUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1072
2113
|
}
|
|
1073
2114
|
if (family === "worktree") {
|
|
1074
|
-
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
|
|
2115
|
+
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`), prepared);
|
|
1075
2116
|
}
|
|
1076
2117
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
1077
2118
|
}
|
|
1078
|
-
function
|
|
2119
|
+
function engineBindInput(parsed) {
|
|
2120
|
+
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") };
|
|
2121
|
+
}
|
|
2122
|
+
function dispatchEngine(context, command, parsed, classification, prepared) {
|
|
1079
2123
|
if (command === "install") {
|
|
1080
2124
|
return installCurrentEngine(context, { force: hasOption(parsed, "force") });
|
|
1081
2125
|
}
|
|
@@ -1100,18 +2144,11 @@ function dispatchEngine(context, command, parsed, classification) {
|
|
|
1100
2144
|
});
|
|
1101
2145
|
}
|
|
1102
2146
|
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
|
-
});
|
|
2147
|
+
return bindRunEngine(context, engineBindInput(parsed), prepared.engineBind);
|
|
1111
2148
|
}
|
|
1112
2149
|
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
1113
2150
|
}
|
|
1114
|
-
function dispatchMigration(context, command, parsed, classification) {
|
|
2151
|
+
function dispatchMigration(context, command, parsed, classification, prepared) {
|
|
1115
2152
|
if (command === "impact")
|
|
1116
2153
|
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
2154
|
if (command === "plan" || command === "report") {
|
|
@@ -1127,26 +2164,42 @@ function dispatchMigration(context, command, parsed, classification) {
|
|
|
1127
2164
|
});
|
|
1128
2165
|
}
|
|
1129
2166
|
if (command === "verify") {
|
|
1130
|
-
return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
2167
|
+
return prepared.migrationVerification ?? verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
1131
2168
|
}
|
|
1132
2169
|
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
|
-
});
|
|
2170
|
+
return applyMigration(context, migrationApplyInput(parsed, classification), prepared.migrationApply);
|
|
1144
2171
|
}
|
|
1145
2172
|
throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
|
|
1146
2173
|
}
|
|
1147
|
-
function
|
|
2174
|
+
function protocolTransitionInput(parsed, scopeProjectRoot) {
|
|
2175
|
+
const reason = optionalOption(parsed, "reason");
|
|
2176
|
+
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 } : {}) };
|
|
2177
|
+
}
|
|
2178
|
+
function controllerAnswerInput(parsed) {
|
|
2179
|
+
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") };
|
|
2180
|
+
}
|
|
2181
|
+
function controllerLaunchInput(parsed) {
|
|
2182
|
+
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") } : {}) };
|
|
2183
|
+
}
|
|
2184
|
+
function controllerContextInput(parsed) {
|
|
2185
|
+
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") };
|
|
2186
|
+
}
|
|
2187
|
+
function migrationApplyInput(parsed, classification) {
|
|
2188
|
+
return {
|
|
2189
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
2190
|
+
sourceVersion: optionalOption(parsed, "source-version"),
|
|
2191
|
+
targetVersion: optionalOption(parsed, "target-version"),
|
|
2192
|
+
runId: optionalOption(parsed, "run"),
|
|
2193
|
+
backupPath: optionalOption(parsed, "backup-path"),
|
|
2194
|
+
backupCreatedAt: optionalOption(parsed, "backup-created-at"),
|
|
2195
|
+
reportFile: optionalOption(parsed, "file"),
|
|
2196
|
+
allowActive: false,
|
|
2197
|
+
classification
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
function dispatchCanon(context, command, parsed, prepared) {
|
|
1148
2201
|
if (command === "register") {
|
|
1149
|
-
return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
|
|
2202
|
+
return registerCanonRoot(context, { root: requiredOption(parsed, "root") }, prepared.canonRegistration);
|
|
1150
2203
|
}
|
|
1151
2204
|
if (command === "status") {
|
|
1152
2205
|
return getCanonStatus(context, { root: optionalOption(parsed, "root") });
|
|
@@ -1156,6 +2209,15 @@ function dispatchCanon(context, command, parsed) {
|
|
|
1156
2209
|
}
|
|
1157
2210
|
throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
|
|
1158
2211
|
}
|
|
2212
|
+
function mergeJobTarget(parsed, scopeProjectRoot) {
|
|
2213
|
+
return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed) };
|
|
2214
|
+
}
|
|
2215
|
+
function mergeCancellationInput(parsed, scopeProjectRoot) {
|
|
2216
|
+
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") };
|
|
2217
|
+
}
|
|
2218
|
+
function mergeWaitInput(parsed, scopeProjectRoot) {
|
|
2219
|
+
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 };
|
|
2220
|
+
}
|
|
1159
2221
|
function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
1160
2222
|
if (hasOption(parsed, "session-id")) {
|
|
1161
2223
|
throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
|
|
@@ -1179,32 +2241,17 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
|
1179
2241
|
});
|
|
1180
2242
|
}
|
|
1181
2243
|
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
|
-
});
|
|
2244
|
+
return waitNextMergeJob(context, mergeWaitInput(parsed, scopeProjectRoot));
|
|
1190
2245
|
}
|
|
1191
2246
|
if (command === "complete") {
|
|
1192
|
-
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
1193
2247
|
return completeMergeJob(context, {
|
|
1194
|
-
|
|
1195
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
|
|
1196
|
-
workerId: requiredWorkerId(parsed),
|
|
1197
|
-
workspacePath: workspacePathOption(parsed),
|
|
2248
|
+
...mergeJobTarget(parsed, scopeProjectRoot),
|
|
1198
2249
|
summary: requiredOption(parsed, "summary")
|
|
1199
2250
|
});
|
|
1200
2251
|
}
|
|
1201
2252
|
if (command === "fail") {
|
|
1202
|
-
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
1203
2253
|
return failMergeJob(context, {
|
|
1204
|
-
|
|
1205
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
|
|
1206
|
-
workerId: requiredWorkerId(parsed),
|
|
1207
|
-
workspacePath: workspacePathOption(parsed),
|
|
2254
|
+
...mergeJobTarget(parsed, scopeProjectRoot),
|
|
1208
2255
|
reason: requiredOption(parsed, "reason"),
|
|
1209
2256
|
requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
|
|
1210
2257
|
});
|
|
@@ -1219,15 +2266,7 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
|
1219
2266
|
});
|
|
1220
2267
|
}
|
|
1221
2268
|
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
|
-
});
|
|
2269
|
+
return cancelMergeQueueJob(context, mergeCancellationInput(parsed, scopeProjectRoot));
|
|
1231
2270
|
}
|
|
1232
2271
|
throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
|
|
1233
2272
|
}
|
|
@@ -1345,7 +2384,7 @@ function completedCommandExitCode(payload) {
|
|
|
1345
2384
|
const exitCode = payload.exit_code;
|
|
1346
2385
|
return typeof exitCode === "number" && Number.isInteger(exitCode) ? exitCode : 0;
|
|
1347
2386
|
}
|
|
1348
|
-
function dispatchCleanup(context, command, parsed) {
|
|
2387
|
+
function dispatchCleanup(context, command, parsed, prepared) {
|
|
1349
2388
|
if (command === "scan") {
|
|
1350
2389
|
return cleanupScan(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
1351
2390
|
}
|
|
@@ -1353,6 +2392,7 @@ function dispatchCleanup(context, command, parsed) {
|
|
|
1353
2392
|
return cleanupApply(context, {
|
|
1354
2393
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
1355
2394
|
planFile: requiredOption(parsed, "plan-file"),
|
|
2395
|
+
prepared,
|
|
1356
2396
|
reason: requiredOption(parsed, "reason"),
|
|
1357
2397
|
force: hasOption(parsed, "force")
|
|
1358
2398
|
});
|
|
@@ -1373,12 +2413,14 @@ function dispatchProjectConfig(context, parsed) {
|
|
|
1373
2413
|
}
|
|
1374
2414
|
throw new AppError("usage", `Unknown project config action: ${action}`, 2);
|
|
1375
2415
|
}
|
|
1376
|
-
function dispatchSession(context, command, parsed) {
|
|
2416
|
+
function dispatchSession(context, command, parsed, preparedPayload) {
|
|
1377
2417
|
if (command === "register") {
|
|
1378
2418
|
return registerFlowSession(context, {
|
|
2419
|
+
...(preparedPayload ? { preparedPayload } : {}),
|
|
1379
2420
|
payloadBase64: optionalOption(parsed, "payload-base64"),
|
|
1380
2421
|
payloadJson: optionalOption(parsed, "payload-json"),
|
|
1381
2422
|
payloadFile: optionalOption(parsed, "payload-file"),
|
|
2423
|
+
hookEventId: optionalOption(parsed, "hook-event-id"),
|
|
1382
2424
|
sessionId: optionalOption(parsed, "session-id")
|
|
1383
2425
|
});
|
|
1384
2426
|
}
|
|
@@ -1411,7 +2453,45 @@ function dispatchSession(context, command, parsed) {
|
|
|
1411
2453
|
}
|
|
1412
2454
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
1413
2455
|
}
|
|
1414
|
-
function
|
|
2456
|
+
function snapshotCreateInput(parsed) {
|
|
2457
|
+
const recoveryId = optionalOption(parsed, "recovery-id"), stageEntry = optionalOption(parsed, "stage-entry");
|
|
2458
|
+
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 } : {}) };
|
|
2459
|
+
}
|
|
2460
|
+
function snapshotRestoreInput(parsed) {
|
|
2461
|
+
return { snapshot: requiredOption(parsed, "snapshot"), projectRoot: requiredOption(parsed, "project-root"), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), operationalDecisionFile: optionalOption(parsed, "operational-decision-file"), ...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}) };
|
|
2462
|
+
}
|
|
2463
|
+
function runStartInput(parsed) {
|
|
2464
|
+
const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
|
|
2465
|
+
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")) };
|
|
2466
|
+
}
|
|
2467
|
+
function runForkInput(parsed) {
|
|
2468
|
+
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") } : {}), ...(optionalOption(parsed, "operational-decision-file") ? { operationalDecisionFile: optionalOption(parsed, "operational-decision-file") } : {}) };
|
|
2469
|
+
}
|
|
2470
|
+
function recoveryCaptureInput(parsed) {
|
|
2471
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") };
|
|
2472
|
+
}
|
|
2473
|
+
function recoveryResumeInput(parsed) {
|
|
2474
|
+
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") };
|
|
2475
|
+
}
|
|
2476
|
+
function dispatchRun(context, command, parsed, prepared = {}) {
|
|
2477
|
+
if (command === "recovery") {
|
|
2478
|
+
const action = requiredPosition(parsed, 0, "recovery action");
|
|
2479
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
2480
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
2481
|
+
if (action === "inspect")
|
|
2482
|
+
return inspectRunRecovery(context, { projectRoot, runId });
|
|
2483
|
+
if (action === "begin")
|
|
2484
|
+
return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") }, prepared.recoveryPayload);
|
|
2485
|
+
if (action === "seal")
|
|
2486
|
+
return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") }, prepared.recoveryPayload);
|
|
2487
|
+
if (action === "capture")
|
|
2488
|
+
return recordRecoveryCapture(context, recoveryCaptureInput(parsed), prepared.recoveryCapture);
|
|
2489
|
+
if (action === "resume")
|
|
2490
|
+
return resumeRunRecovery(context, recoveryResumeInput(parsed), prepared.recoveryResume);
|
|
2491
|
+
if (action === "accept")
|
|
2492
|
+
return acceptRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
2493
|
+
throw new AppError("usage", "Use: dd-flow run recovery inspect|begin|seal|capture|resume|accept", 2);
|
|
2494
|
+
}
|
|
1415
2495
|
if (command === "snapshot") {
|
|
1416
2496
|
const action = requiredPosition(parsed, 0, "snapshot action");
|
|
1417
2497
|
if (action === "bootstrap") {
|
|
@@ -1422,56 +2502,27 @@ function dispatchRun(context, command, parsed) {
|
|
|
1422
2502
|
output: requiredOption(parsed, "output")
|
|
1423
2503
|
});
|
|
1424
2504
|
if (bootstrapAction === "restore")
|
|
1425
|
-
return restoreEvalBootstrapSnapshot(context,
|
|
1426
|
-
snapshot: requiredOption(parsed, "snapshot"),
|
|
1427
|
-
projectRoot: requiredOption(parsed, "project-root")
|
|
1428
|
-
});
|
|
2505
|
+
return restoreEvalBootstrapSnapshot(context, snapshotRestoreInput(parsed), prepared.bootstrapRestore);
|
|
1429
2506
|
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore", 2);
|
|
1430
2507
|
}
|
|
1431
2508
|
if (action === "create") {
|
|
1432
|
-
|
|
1433
|
-
const stageEntry = optionalOption(parsed, "stage-entry");
|
|
1434
|
-
if (candidate && stageEntry !== undefined)
|
|
1435
|
-
throw new AppError("usage", "Use either --candidate or --stage-entry, not both", 2);
|
|
1436
|
-
if (!candidate && stageEntry === undefined)
|
|
1437
|
-
throw new AppError("usage", "Snapshot create requires --candidate or --stage-entry", 2);
|
|
1438
|
-
const input = {
|
|
1439
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1440
|
-
runId: requiredPosition(parsed, 1, "run-id"),
|
|
1441
|
-
output: requiredOption(parsed, "output")
|
|
1442
|
-
};
|
|
1443
|
-
return candidate
|
|
1444
|
-
? createEvalRunSnapshot(context, { ...input, candidate: true })
|
|
1445
|
-
: createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
|
|
2509
|
+
return createEvalRunSnapshot(context, snapshotCreateInput(parsed));
|
|
1446
2510
|
}
|
|
1447
2511
|
if (action === "restore")
|
|
1448
|
-
return restoreEvalRunSnapshot(context,
|
|
1449
|
-
snapshot: requiredOption(parsed, "snapshot"),
|
|
1450
|
-
projectRoot: requiredOption(parsed, "project-root")
|
|
1451
|
-
});
|
|
2512
|
+
return restoreEvalRunSnapshot(context, snapshotRestoreInput(parsed), prepared.snapshotRestore);
|
|
1452
2513
|
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);
|
|
1453
2514
|
}
|
|
2515
|
+
if (command === "fork") {
|
|
2516
|
+
return forkRunFromSnapshot(context, runForkInput(parsed), prepared.runFork);
|
|
2517
|
+
}
|
|
1454
2518
|
if (command === "prepare-vnext-specify") {
|
|
1455
|
-
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
|
|
2519
|
+
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
|
|
1456
2520
|
}
|
|
1457
2521
|
if (command === "start") {
|
|
1458
2522
|
if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
|
|
1459
2523
|
assertUpgradeRunStorageReady(context, requiredOption(parsed, "project-root"));
|
|
1460
2524
|
}
|
|
1461
|
-
|
|
1462
|
-
return startFlowRun(context, {
|
|
1463
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1464
|
-
workspaceRoot: optionalOption(parsed, "workspace-root"),
|
|
1465
|
-
flowKind: requiredOption(parsed, "flow-kind"),
|
|
1466
|
-
subjectType: requiredOption(parsed, "subject-type"),
|
|
1467
|
-
subjectId: requiredOption(parsed, "subject-id"),
|
|
1468
|
-
slug: requiredOption(parsed, "slug"),
|
|
1469
|
-
nextAction: optionalOption(parsed, "next-action"),
|
|
1470
|
-
preset: optionalOption(parsed, "preset"),
|
|
1471
|
-
taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
|
|
1472
|
-
...(protocolOverrides ? { protocolOverrides } : {}),
|
|
1473
|
-
runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
|
|
1474
|
-
});
|
|
2525
|
+
return startFlowRun(context, prepared.runStart?.input ?? runStartInput(parsed), prepared.runStart?.value);
|
|
1475
2526
|
}
|
|
1476
2527
|
if (command === "status") {
|
|
1477
2528
|
return getFlowRunStatus(context, {
|
|
@@ -1541,83 +2592,79 @@ function dispatchRun(context, command, parsed) {
|
|
|
1541
2592
|
throw new AppError("usage", `Unknown run flags command: ${action}`, 2);
|
|
1542
2593
|
}
|
|
1543
2594
|
if (command === "attach-stage") {
|
|
1544
|
-
return attachFlowRunStage(context,
|
|
1545
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1546
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1547
|
-
stage: requiredOption(parsed, "stage"),
|
|
1548
|
-
dir: requiredOption(parsed, "dir"),
|
|
1549
|
-
status: requiredOption(parsed, "status"),
|
|
1550
|
-
dataSchemaId: optionalOption(parsed, "data-schema-id")
|
|
1551
|
-
});
|
|
2595
|
+
return attachFlowRunStage(context, stageAttachmentInput(parsed));
|
|
1552
2596
|
}
|
|
1553
2597
|
if (command === "complete-stage") {
|
|
1554
|
-
|
|
1555
|
-
return completeFlowRunStage(context, {
|
|
1556
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1557
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1558
|
-
stage: requiredOption(parsed, "stage"),
|
|
1559
|
-
status: requiredOption(parsed, "status"),
|
|
1560
|
-
stageReport,
|
|
1561
|
-
data: optionalOption(parsed, "data"),
|
|
1562
|
-
dataSchemaId: optionalOption(parsed, "data-schema-id"),
|
|
1563
|
-
report: optionalOption(parsed, "report"),
|
|
1564
|
-
aliases: allOptions(parsed, "alias")
|
|
1565
|
-
});
|
|
2598
|
+
return completeFlowRunStage(context, stageCompletionInput(parsed));
|
|
1566
2599
|
}
|
|
1567
2600
|
if (command === "complete") {
|
|
1568
2601
|
throw new AppError("usage", "run complete is retired; only an operator may use run override", 2);
|
|
1569
2602
|
}
|
|
1570
2603
|
if (command === "override") {
|
|
1571
|
-
|
|
1572
|
-
if (!['cancelled', 'failed'].includes(status)) {
|
|
1573
|
-
throw new AppError("usage", "run override status must be cancelled or failed", 2, { status });
|
|
1574
|
-
}
|
|
1575
|
-
return completeFlowRun(context, {
|
|
1576
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1577
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1578
|
-
status,
|
|
1579
|
-
verdict: optionalOption(parsed, "verdict"),
|
|
1580
|
-
nextAction: optionalOption(parsed, "next-action"),
|
|
1581
|
-
manualOverrideReason: requiredOption(parsed, "reason")
|
|
1582
|
-
});
|
|
2604
|
+
return completeFlowRun(context, runOverrideInput(parsed));
|
|
1583
2605
|
}
|
|
1584
2606
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1585
2607
|
}
|
|
1586
|
-
|
|
2608
|
+
function stageAttachmentInput(parsed) {
|
|
2609
|
+
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") };
|
|
2610
|
+
}
|
|
2611
|
+
function runOverrideInput(parsed) {
|
|
2612
|
+
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") };
|
|
2613
|
+
}
|
|
2614
|
+
function stageCompletionInput(parsed) {
|
|
2615
|
+
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") };
|
|
2616
|
+
}
|
|
2617
|
+
function runtimeServiceInput(parsed) {
|
|
2618
|
+
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 };
|
|
2619
|
+
}
|
|
2620
|
+
function runtimeScopeResumeInput(parsed) {
|
|
2621
|
+
return { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") };
|
|
2622
|
+
}
|
|
2623
|
+
async function dispatchRuntime(context, command, parsed, progress, prepared = {}) {
|
|
2624
|
+
if (command === "scope") {
|
|
2625
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "reconcile") {
|
|
2626
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2627
|
+
throw new AppError("usage", "Use runtime scope reconcile --scope-id ID --generation N --request-id ID", 2);
|
|
2628
|
+
return { ok: true, ...await reconcileRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id")) };
|
|
2629
|
+
}
|
|
2630
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
|
|
2631
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2632
|
+
throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
|
|
2633
|
+
return { ok: true, ...await launchRuntimeScopeResume(context, runtimeScopeResumeInput(parsed)) };
|
|
2634
|
+
}
|
|
2635
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
|
|
2636
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2637
|
+
throw new AppError("usage", "Use runtime scope control --scope-id ID --request-id ID --mode pause|stop [--manifest-json JSON]", 2);
|
|
2638
|
+
const mode = requiredOption(parsed, "mode");
|
|
2639
|
+
if (mode !== "pause" && mode !== "stop")
|
|
2640
|
+
throw new AppError("usage", "Scope control mode must be pause or stop", 2);
|
|
2641
|
+
return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, undefined, undefined, undefined, prepared.runtimeManifest) };
|
|
2642
|
+
}
|
|
2643
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
|
|
2644
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2645
|
+
throw new AppError("usage", "Invalid internal scope worker arguments", 2);
|
|
2646
|
+
return { ok: true, ...await serveRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation"))) };
|
|
2647
|
+
}
|
|
2648
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "stop") {
|
|
2649
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2650
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID", 2);
|
|
2651
|
+
return { ok: true, ...await stopRuntimeScope(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
2652
|
+
}
|
|
2653
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "status") {
|
|
2654
|
+
if ([...parsed.options.keys()].some(key => key !== "scope-id") || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2655
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope status --scope-id EVAL_ID", 2);
|
|
2656
|
+
return { ok: true, ...runtimeBudgetStatus(context, requiredOption(parsed, "scope-id")) };
|
|
2657
|
+
}
|
|
2658
|
+
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))
|
|
2659
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID", 2);
|
|
2660
|
+
return { ok: true, ...fenceRuntimeBudget(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
2661
|
+
}
|
|
1587
2662
|
if (command !== "process")
|
|
1588
|
-
throw new AppError("usage", "Usage: dd-flow runtime process
|
|
2663
|
+
throw new AppError("usage", "Usage: dd-flow runtime <scope|process>; see dd-flow runtime scope --help", 2);
|
|
1589
2664
|
const action = requiredPosition(parsed, 0, "runtime process action");
|
|
1590
|
-
if (action === "
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
|
|
1594
|
-
projectId: optionalOption(parsed, "project-id") ?? null, runId: optionalOption(parsed, "run") ?? null,
|
|
1595
|
-
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
1596
|
-
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
1597
|
-
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
|
|
1598
|
-
});
|
|
1599
|
-
return { ok: true, process };
|
|
1600
|
-
}
|
|
1601
|
-
if (action === "confirm") {
|
|
1602
|
-
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1603
|
-
const processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
|
|
1604
|
-
return { ok: true, process: confirmManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(processGroupId ? { processGroupId } : {}), ...(leaseMs ? { leaseMs } : {}) }) };
|
|
1605
|
-
}
|
|
1606
|
-
if (action === "heartbeat") {
|
|
1607
|
-
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
1608
|
-
return { ok: heartbeatManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs ? { leaseMs } : {}) }) };
|
|
1609
|
-
}
|
|
1610
|
-
if (action === "finish") {
|
|
1611
|
-
const state = requiredOption(parsed, "state");
|
|
1612
|
-
if (state !== "stopped" && state !== "failed")
|
|
1613
|
-
throw new AppError("validation", "--state must be stopped or failed", 2);
|
|
1614
|
-
return { ok: finishManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state: state, reason: optionalOption(parsed, "reason") ?? null }) };
|
|
1615
|
-
}
|
|
1616
|
-
if (action === "status")
|
|
1617
|
-
return { ok: true, processes: managedProcessStatus(context) };
|
|
1618
|
-
if (action === "reconcile")
|
|
1619
|
-
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalPositiveNumber(parsed, "grace-ms", true)) };
|
|
1620
|
-
throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
|
|
2665
|
+
if (action === "start")
|
|
2666
|
+
return await startRuntimeService(context, { ...runtimeServiceInput(parsed), progress });
|
|
2667
|
+
return await dispatchRuntimeProcess(context, action, parsed, prepared);
|
|
1621
2668
|
}
|
|
1622
2669
|
function dispatchStat(context, command, parsed) {
|
|
1623
2670
|
if (command === "usage") {
|
|
@@ -1651,7 +2698,7 @@ function dispatchCmux(context, parsed) {
|
|
|
1651
2698
|
}
|
|
1652
2699
|
throw new AppError("usage", `Unknown integration cmux action: ${action}`, 2);
|
|
1653
2700
|
}
|
|
1654
|
-
function dispatchDashboard(context, command, parsed) {
|
|
2701
|
+
async function dispatchDashboard(context, command, parsed) {
|
|
1655
2702
|
const action = dashboardAction(command);
|
|
1656
2703
|
if (command === "data") {
|
|
1657
2704
|
const target = resolveDashboardTarget(context, {
|
|
@@ -1686,7 +2733,7 @@ function dispatchDashboard(context, command, parsed) {
|
|
|
1686
2733
|
projectRoot: optionalOption(parsed, "project-root"),
|
|
1687
2734
|
protocol: optionalOption(parsed, "protocol")
|
|
1688
2735
|
});
|
|
1689
|
-
return withDashboardGuidance(action, target, openDashboardForResolvedTarget(context, target, parsed));
|
|
2736
|
+
return withDashboardGuidance(action, target, await openDashboardForResolvedTarget(context, target, parsed));
|
|
1690
2737
|
}
|
|
1691
2738
|
if (command === "refresh") {
|
|
1692
2739
|
const open = optionalOption(parsed, "open");
|
|
@@ -1700,7 +2747,7 @@ function dispatchDashboard(context, command, parsed) {
|
|
|
1700
2747
|
protocol: optionalOption(parsed, "protocol"),
|
|
1701
2748
|
all: hasOption(parsed, "all")
|
|
1702
2749
|
});
|
|
1703
|
-
return withDashboardGuidance(action, target, refreshDashboardForResolvedTarget(context, target, parsed, open));
|
|
2750
|
+
return withDashboardGuidance(action, target, await refreshDashboardForResolvedTarget(context, target, parsed, open));
|
|
1704
2751
|
}
|
|
1705
2752
|
if (command === "refresh-global") {
|
|
1706
2753
|
const target = resolveDashboardTarget(context, { action: "refresh", global: true });
|
|
@@ -1804,7 +2851,9 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1804
2851
|
// searching project directories: run_id is a structural field in the
|
|
1805
2852
|
// submitted artifact and the database owns its materialized run_root.
|
|
1806
2853
|
const explicitRunId = optionalOption(parsed, "run");
|
|
1807
|
-
const
|
|
2854
|
+
const data = prepareJsonFile(file, "schema input").value;
|
|
2855
|
+
const declared = data && typeof data === "object" && "run_id" in data ? data.run_id : undefined;
|
|
2856
|
+
const declaredRunId = explicitRunId ?? (typeof declared === "string" && declared.trim() ? declared : undefined);
|
|
1808
2857
|
const fileRunRoot = path.basename(file) === "run.json" ? path.dirname(path.resolve(file)) : undefined;
|
|
1809
2858
|
const bound = projectRoot
|
|
1810
2859
|
? 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 ?? ""])
|
|
@@ -1812,6 +2861,7 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1812
2861
|
return validateSchema({
|
|
1813
2862
|
schemaName: requiredOption(parsed, "schema"),
|
|
1814
2863
|
file,
|
|
2864
|
+
data,
|
|
1815
2865
|
...(schemaDir ? { schemaDir } : {}),
|
|
1816
2866
|
...(projectRoot ? { projectRoot } : {}),
|
|
1817
2867
|
...(declaredRunId || bound?.id ? { runId: declaredRunId ?? bound.id } : {}),
|
|
@@ -1821,14 +2871,11 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1821
2871
|
}
|
|
1822
2872
|
throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
|
|
1823
2873
|
}
|
|
1824
|
-
function
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
}
|
|
1829
|
-
catch {
|
|
1830
|
-
return undefined;
|
|
1831
|
-
}
|
|
2874
|
+
function laneWorkspaceInput(parsed) {
|
|
2875
|
+
return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workspacePath: requiredOption(parsed, "path"), branch: optionalOption(parsed, "branch") };
|
|
2876
|
+
}
|
|
2877
|
+
function laneWaiterCancelInput(parsed) {
|
|
2878
|
+
return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"), reason: requiredOption(parsed, "reason") };
|
|
1832
2879
|
}
|
|
1833
2880
|
async function dispatchLane(context, command, parsed) {
|
|
1834
2881
|
if (command === "status") {
|
|
@@ -1840,24 +2887,14 @@ async function dispatchLane(context, command, parsed) {
|
|
|
1840
2887
|
if (command === "waiter") {
|
|
1841
2888
|
const action = requiredPosition(parsed, 0, "lane waiter action");
|
|
1842
2889
|
if (action === "cancel") {
|
|
1843
|
-
return cancelLaneWaiter(context,
|
|
1844
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1845
|
-
lane: requiredOption(parsed, "lane"),
|
|
1846
|
-
workerId: requiredOption(parsed, "worker-id"),
|
|
1847
|
-
reason: requiredOption(parsed, "reason")
|
|
1848
|
-
});
|
|
2890
|
+
return cancelLaneWaiter(context, laneWaiterCancelInput(parsed));
|
|
1849
2891
|
}
|
|
1850
2892
|
throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
|
|
1851
2893
|
}
|
|
1852
2894
|
if (command === "workspace") {
|
|
1853
2895
|
const action = requiredPosition(parsed, 0, "lane workspace action");
|
|
1854
2896
|
if (action === "set") {
|
|
1855
|
-
return setLaneWorkspace(context,
|
|
1856
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1857
|
-
lane: requiredOption(parsed, "lane"),
|
|
1858
|
-
workspacePath: requiredOption(parsed, "path"),
|
|
1859
|
-
branch: optionalOption(parsed, "branch")
|
|
1860
|
-
});
|
|
2897
|
+
return setLaneWorkspace(context, laneWorkspaceInput(parsed));
|
|
1861
2898
|
}
|
|
1862
2899
|
if (action === "check") {
|
|
1863
2900
|
return checkLaneWorkspace(context, {
|
|
@@ -1929,7 +2966,7 @@ async function dispatchLane(context, command, parsed) {
|
|
|
1929
2966
|
}
|
|
1930
2967
|
throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
|
|
1931
2968
|
}
|
|
1932
|
-
function dispatchCodexHooks(context, parsed) {
|
|
2969
|
+
function dispatchCodexHooks(context, parsed, prepared) {
|
|
1933
2970
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
1934
2971
|
if (action === "print") {
|
|
1935
2972
|
return printCodexHooks(context, {
|
|
@@ -1951,7 +2988,7 @@ function dispatchCodexHooks(context, parsed) {
|
|
|
1951
2988
|
target: optionalOption(parsed, "target"),
|
|
1952
2989
|
profile: optionalOption(parsed, "profile"),
|
|
1953
2990
|
yes: hasOption(parsed, "yes")
|
|
1954
|
-
});
|
|
2991
|
+
}, prepared.codexHooks);
|
|
1955
2992
|
}
|
|
1956
2993
|
if (action === "remove") {
|
|
1957
2994
|
return removeCodexHooks(context, {
|
|
@@ -1959,11 +2996,14 @@ function dispatchCodexHooks(context, parsed) {
|
|
|
1959
2996
|
target: optionalOption(parsed, "target"),
|
|
1960
2997
|
profile: optionalOption(parsed, "profile"),
|
|
1961
2998
|
yes: hasOption(parsed, "yes")
|
|
1962
|
-
});
|
|
2999
|
+
}, prepared.codexHooks);
|
|
1963
3000
|
}
|
|
1964
3001
|
throw new AppError("usage", `Unknown codex hooks action: ${action}`, 2);
|
|
1965
3002
|
}
|
|
1966
|
-
function
|
|
3003
|
+
function codexHomeInput(parsed) {
|
|
3004
|
+
return { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), sourceHome: optionalOption(parsed, "source-home"), targetHome: optionalOption(parsed, "target-home") };
|
|
3005
|
+
}
|
|
3006
|
+
function dispatchCodexHome(context, parsed, prepared) {
|
|
1967
3007
|
const action = requiredPosition(parsed, 0, "codex home action");
|
|
1968
3008
|
if (action === "plan") {
|
|
1969
3009
|
return planCodexHome(context, {
|
|
@@ -1974,12 +3014,7 @@ function dispatchCodexHome(context, parsed) {
|
|
|
1974
3014
|
});
|
|
1975
3015
|
}
|
|
1976
3016
|
if (action === "init") {
|
|
1977
|
-
return initCodexHome(context,
|
|
1978
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1979
|
-
profile: optionalOption(parsed, "profile"),
|
|
1980
|
-
sourceHome: optionalOption(parsed, "source-home"),
|
|
1981
|
-
targetHome: optionalOption(parsed, "target-home")
|
|
1982
|
-
});
|
|
3017
|
+
return initCodexHome(context, codexHomeInput(parsed), prepared.codexHome);
|
|
1983
3018
|
}
|
|
1984
3019
|
if (action === "status") {
|
|
1985
3020
|
return getCodexHomeStatus(context, {
|
|
@@ -2002,23 +3037,6 @@ function dispatchCodexHome(context, parsed) {
|
|
|
2002
3037
|
}
|
|
2003
3038
|
throw new AppError("usage", `Unknown codex home action: ${action}`, 2);
|
|
2004
3039
|
}
|
|
2005
|
-
function dispatchCodexHook(context, parsed, stdin) {
|
|
2006
|
-
const action = requiredPosition(parsed, 0, "codex hook action");
|
|
2007
|
-
if (action === "handle") {
|
|
2008
|
-
const isolatedHome = isolatedDdFlowHomeFromHookStdin(stdin);
|
|
2009
|
-
const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }) : context;
|
|
2010
|
-
const result = handleCodexHook(hookContext, {
|
|
2011
|
-
event: requiredOption(parsed, "event"),
|
|
2012
|
-
projectRoot: optionalOption(parsed, "project-root"),
|
|
2013
|
-
stdin
|
|
2014
|
-
});
|
|
2015
|
-
if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
|
|
2016
|
-
return { hookSpecificOutput: result.hookSpecificOutput };
|
|
2017
|
-
}
|
|
2018
|
-
return {};
|
|
2019
|
-
}
|
|
2020
|
-
throw new AppError("usage", `Unknown codex hook action: ${action}`, 2);
|
|
2021
|
-
}
|
|
2022
3040
|
function dispatchZcodeEvent(context, parsed, stdin) {
|
|
2023
3041
|
const action = requiredPosition(parsed, 0, "zcode event action");
|
|
2024
3042
|
if (action === "handle") {
|
|
@@ -2029,6 +3047,15 @@ function dispatchZcodeEvent(context, parsed, stdin) {
|
|
|
2029
3047
|
}
|
|
2030
3048
|
throw new AppError("usage", `Unknown zcode event action: ${action}`, 2);
|
|
2031
3049
|
}
|
|
3050
|
+
function dispatchZcodeHook(context, parsed, stdin) {
|
|
3051
|
+
if (requiredPosition(parsed, 0, "zcode hook action") !== "handle")
|
|
3052
|
+
throw new AppError("usage", "Unknown zcode hook action", 2);
|
|
3053
|
+
const result = handleZcodeHook(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
3054
|
+
if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
|
|
3055
|
+
return { hookSpecificOutput: result.hookSpecificOutput };
|
|
3056
|
+
}
|
|
3057
|
+
return {};
|
|
3058
|
+
}
|
|
2032
3059
|
function dispatchGrokEvent(context, parsed, stdin) {
|
|
2033
3060
|
const action = requiredPosition(parsed, 0, "grok event action");
|
|
2034
3061
|
if (action === "handle") {
|
|
@@ -2052,7 +3079,7 @@ function dispatchAgyEvent(context, parsed, stdin) {
|
|
|
2052
3079
|
return handleAgyEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
2053
3080
|
throw new AppError("usage", `Unknown agy event action: ${action}`, 2);
|
|
2054
3081
|
}
|
|
2055
|
-
function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
3082
|
+
function dispatchWorktree(context, command, parsed, scopeProjectRoot, prepared) {
|
|
2056
3083
|
if (command === "plan") {
|
|
2057
3084
|
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
2058
3085
|
}
|
|
@@ -2063,13 +3090,13 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
|
2063
3090
|
branch: requiredOption(parsed, "branch"),
|
|
2064
3091
|
base: requiredOption(parsed, "base"),
|
|
2065
3092
|
path: requiredOption(parsed, "path")
|
|
2066
|
-
});
|
|
3093
|
+
}, prepared.worktreeCreate);
|
|
2067
3094
|
}
|
|
2068
3095
|
if (command === "status") {
|
|
2069
3096
|
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
2070
3097
|
}
|
|
2071
3098
|
if (command === "bootstrap") {
|
|
2072
|
-
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
3099
|
+
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot }, prepared.worktreeBootstrap);
|
|
2073
3100
|
}
|
|
2074
3101
|
if (command === "close") {
|
|
2075
3102
|
return closeWorktree(context, {
|
|
@@ -2081,52 +3108,50 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
|
2081
3108
|
throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
|
|
2082
3109
|
}
|
|
2083
3110
|
function dispatchPlanItem(context, parsed, projectRoot) {
|
|
3111
|
+
return updatePlanItem(context, planItemInput(parsed, projectRoot));
|
|
3112
|
+
}
|
|
3113
|
+
function planItemInput(parsed, projectRoot) {
|
|
2084
3114
|
const action = requiredPosition(parsed, 0, "plan item action");
|
|
2085
3115
|
const protocolId = requiredPosition(parsed, 1, "protocol-id");
|
|
2086
3116
|
const itemId = requiredPosition(parsed, 2, "item-id");
|
|
2087
3117
|
if (action === "start") {
|
|
2088
|
-
return
|
|
3118
|
+
return { protocolId, projectRoot, itemId, action };
|
|
2089
3119
|
}
|
|
2090
3120
|
if (action === "done") {
|
|
2091
|
-
return
|
|
3121
|
+
return {
|
|
3122
|
+
action,
|
|
2092
3123
|
protocolId,
|
|
2093
3124
|
projectRoot,
|
|
2094
3125
|
itemId,
|
|
2095
3126
|
summary: requiredOption(parsed, "summary"),
|
|
2096
3127
|
evidence: parsed.options.get("evidence") ?? []
|
|
2097
|
-
}
|
|
3128
|
+
};
|
|
2098
3129
|
}
|
|
2099
3130
|
if (action === "block") {
|
|
2100
|
-
return
|
|
3131
|
+
return {
|
|
3132
|
+
action,
|
|
2101
3133
|
protocolId,
|
|
2102
3134
|
projectRoot,
|
|
2103
3135
|
itemId,
|
|
2104
3136
|
reason: requiredOption(parsed, "reason"),
|
|
2105
3137
|
userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
|
|
2106
|
-
}
|
|
3138
|
+
};
|
|
2107
3139
|
}
|
|
2108
3140
|
if (action === "skip") {
|
|
2109
|
-
return
|
|
3141
|
+
return {
|
|
3142
|
+
action,
|
|
2110
3143
|
protocolId,
|
|
2111
3144
|
projectRoot,
|
|
2112
3145
|
itemId,
|
|
2113
3146
|
reason: requiredOption(parsed, "reason")
|
|
2114
|
-
}
|
|
3147
|
+
};
|
|
2115
3148
|
}
|
|
2116
3149
|
throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
|
|
2117
3150
|
}
|
|
2118
3151
|
function readOptionalJsonFile(file) {
|
|
2119
3152
|
if (!file)
|
|
2120
3153
|
return undefined;
|
|
2121
|
-
|
|
2122
|
-
throw new AppError("not_found", `JSON file does not exist: ${file}`, 1);
|
|
2123
|
-
}
|
|
2124
|
-
try {
|
|
2125
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
2126
|
-
}
|
|
2127
|
-
catch {
|
|
2128
|
-
throw new AppError("validation", `JSON file is malformed: ${file}`, 2);
|
|
2129
|
-
}
|
|
3154
|
+
return prepareJsonFile(file, "JSON input").value;
|
|
2130
3155
|
}
|
|
2131
3156
|
function parseFlowFlagOverrides(values) {
|
|
2132
3157
|
const result = {};
|
|
@@ -2158,21 +3183,9 @@ function requiredPosition(parsed, index, label) {
|
|
|
2158
3183
|
}
|
|
2159
3184
|
return value;
|
|
2160
3185
|
}
|
|
2161
|
-
function optionalOption(parsed, key) {
|
|
2162
|
-
const values = parsed.options.get(key);
|
|
2163
|
-
const value = values?.[values.length - 1];
|
|
2164
|
-
return value === flagValue ? undefined : value;
|
|
2165
|
-
}
|
|
2166
3186
|
function allOptions(parsed, key) {
|
|
2167
3187
|
return (parsed.options.get(key) ?? []).filter((value) => value !== flagValue);
|
|
2168
3188
|
}
|
|
2169
|
-
function requiredOption(parsed, key) {
|
|
2170
|
-
const value = optionalOption(parsed, key);
|
|
2171
|
-
if (!value) {
|
|
2172
|
-
throw new AppError("usage", `Missing required option: --${key}`, 2);
|
|
2173
|
-
}
|
|
2174
|
-
return value;
|
|
2175
|
-
}
|
|
2176
3189
|
function requiredScopeProjectRoot(root, operation) {
|
|
2177
3190
|
if (root)
|
|
2178
3191
|
return root;
|
|
@@ -2212,26 +3225,6 @@ function parseBoolean(value, key) {
|
|
|
2212
3225
|
function parseOptionalBoolean(value, key) {
|
|
2213
3226
|
return typeof value === "string" ? parseBoolean(value, key) : undefined;
|
|
2214
3227
|
}
|
|
2215
|
-
function optionalNumber(parsed, key) {
|
|
2216
|
-
const value = optionalOption(parsed, key);
|
|
2217
|
-
if (typeof value === "undefined") {
|
|
2218
|
-
return undefined;
|
|
2219
|
-
}
|
|
2220
|
-
const parsedNumber = Number(value);
|
|
2221
|
-
if (!Number.isFinite(parsedNumber)) {
|
|
2222
|
-
throw new AppError("validation", `--${key} must be a number`, 2);
|
|
2223
|
-
}
|
|
2224
|
-
return parsedNumber;
|
|
2225
|
-
}
|
|
2226
|
-
function optionalPositiveNumber(parsed, key, integer = false) {
|
|
2227
|
-
const value = optionalNumber(parsed, key);
|
|
2228
|
-
if (typeof value === "undefined")
|
|
2229
|
-
return undefined;
|
|
2230
|
-
if (value <= 0 || (integer && !Number.isInteger(value))) {
|
|
2231
|
-
throw new AppError("validation", `--${key} must be a positive${integer ? " integer" : ""} number`, 2);
|
|
2232
|
-
}
|
|
2233
|
-
return value;
|
|
2234
|
-
}
|
|
2235
3228
|
function requiredWorkerId(parsed) {
|
|
2236
3229
|
return requiredOption(parsed, "worker-id");
|
|
2237
3230
|
}
|
|
@@ -2327,7 +3320,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2327
3320
|
return projectRootForMergeQueueMutation(context, command, parsed);
|
|
2328
3321
|
}
|
|
2329
3322
|
if (family === "merge" && ["one-shot", "apply", "repair", "request"].includes(command ?? "")) {
|
|
2330
|
-
return requiredOption(parsed, "project-root");
|
|
3323
|
+
return resolvedScope ?? requiredOption(parsed, "project-root");
|
|
2331
3324
|
}
|
|
2332
3325
|
if (family === "merge-worker" && ["start", "stop"].includes(command ?? "")) {
|
|
2333
3326
|
return requiredOption(parsed, "project-root");
|
|
@@ -2345,7 +3338,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2345
3338
|
return requiredOption(parsed, "project-root");
|
|
2346
3339
|
}
|
|
2347
3340
|
if (family === "stage" && ["start", "pause", "resume", "finish"].includes(command ?? "")) {
|
|
2348
|
-
return requiredOption(parsed, "project-root");
|
|
3341
|
+
return resolvedScope ?? requiredOption(parsed, "project-root");
|
|
2349
3342
|
}
|
|
2350
3343
|
if (family === "worktree" && ["create", "bootstrap", "close"].includes(command ?? "")) {
|
|
2351
3344
|
return protocolProjectRoot(context, requiredOption(parsed, "protocol-id"));
|
|
@@ -2359,7 +3352,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2359
3352
|
if (family === "opencode" && ((command === "event" && requiredPosition(parsed, 0, "opencode event action") === "handle") || command === "usage")) {
|
|
2360
3353
|
return requiredOption(parsed, "project-root");
|
|
2361
3354
|
}
|
|
2362
|
-
if (family
|
|
3355
|
+
if (["agy", "droid"].includes(family ?? "") && ((command === "event" && requiredPosition(parsed, 0, `${family} event action`) === "handle") || command === "usage"))
|
|
2363
3356
|
return requiredOption(parsed, "project-root");
|
|
2364
3357
|
if (family === "codex" && command === "hooks") {
|
|
2365
3358
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
@@ -2396,14 +3389,36 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
|
2396
3389
|
function protocolProjectRoot(context, protocolId) {
|
|
2397
3390
|
return requireProtocol(context, protocolId).project_root;
|
|
2398
3391
|
}
|
|
3392
|
+
function prepareCliWorkRepair(context, parsed, objective, scopeProjectRoot) {
|
|
3393
|
+
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") ?? [];
|
|
3394
|
+
if ([findings.length > 0, unresolved.length > 0, receipts.length > 0].filter(Boolean).length !== 1)
|
|
3395
|
+
throw new AppError("usage", "Repair requires exactly one source: --from-check, --from-finding or --from-unresolved", 2);
|
|
3396
|
+
const scope = { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"), runId: requiredOption(parsed, "run"), objective };
|
|
3397
|
+
if (findings.length) {
|
|
3398
|
+
if (origins.length || hasOption(parsed, "verification-file"))
|
|
3399
|
+
throw new AppError("usage", "Finding repair derives its origins and cannot accept --origin-work or --verification-file", 2);
|
|
3400
|
+
return prepareVnextCodeReviewRepair(context, { ...scope, findingIds: findings, checkRefsByFinding: Object.fromEntries(findings.map(finding => [finding, parsed.options.get("check-ref") ?? []])) });
|
|
3401
|
+
}
|
|
3402
|
+
if (!origins.length)
|
|
3403
|
+
throw new AppError("usage", "Repair requires at least one --origin-work", 2);
|
|
3404
|
+
if (hasOption(parsed, "check-ref"))
|
|
3405
|
+
throw new AppError("usage", "--check-ref is only supported with --from-finding", 2);
|
|
3406
|
+
if (receipts.length > 1)
|
|
3407
|
+
throw new AppError("usage", "Create one repair Work per --from-check receipt", 2);
|
|
3408
|
+
if (!unresolved.length && hasOption(parsed, "verification-file"))
|
|
3409
|
+
throw new AppError("usage", "--verification-file is only supported with --from-unresolved", 2);
|
|
3410
|
+
const input = { ...scope, originWorkIds: origins, ...(unresolved.length ? { semanticUnresolved: unresolved, verificationPath: requiredOption(parsed, "verification-file") } : { checkReceiptId: receipts[0] }) };
|
|
3411
|
+
return { input, value: prepareVnextCodeRepair(context, input) };
|
|
3412
|
+
}
|
|
3413
|
+
function workerPromptInput(parsed) {
|
|
3414
|
+
const workspaceRoot = optionalOption(parsed, "workspace-root"), taskFile = optionalOption(parsed, "task-file");
|
|
3415
|
+
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 } : {}) };
|
|
3416
|
+
}
|
|
2399
3417
|
function runnerIntake(context) {
|
|
2400
3418
|
const input = context.value.task_input?.[0];
|
|
2401
3419
|
if (!input?.path)
|
|
2402
3420
|
throw new AppError("validation", "Runner SPECIFY context requires one ordered task_input file", 2);
|
|
2403
|
-
|
|
2404
|
-
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
2405
|
-
throw new AppError("not_found", "Runner SPECIFY task input is missing", 2, { task_input: file });
|
|
2406
|
-
return fs.readFileSync(file, "utf8");
|
|
3421
|
+
return prepareTextFile(input.path, "Runner SPECIFY task input").text;
|
|
2407
3422
|
}
|
|
2408
3423
|
/** Stage services install context before their lifecycle transition. Keeping
|
|
2409
3424
|
* this check here makes an incomplete service migration fail closed instead of
|