@deksden-com/dd-flow-cli 0.9.0-beta.9 → 0.9.0-beta.90
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 +460 -0
- package/README.md +65 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +304 -0
- package/dist/cli/help.js +89 -13
- package/dist/cli/hook-ingress.js +82 -0
- package/dist/cli/input-preparation.js +115 -0
- package/dist/cli/run-cli.js +1409 -382
- package/dist/cli.js +6 -2
- package/dist/harness-runtime/bin/dd-agy.mjs +37 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +423 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +471 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +910 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +260 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +70 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +2 -2
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +4 -4
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +57 -13
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +543 -96
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +132 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +121 -76
- package/dist/services/eval-snapshots.js +750 -73
- package/dist/services/execution-policy.js +170 -0
- package/dist/services/external-work-launch.js +110 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +587 -299
- package/dist/services/lanes.js +61 -53
- package/dist/services/lifecycle-command.js +118 -7
- package/dist/services/lifecycle-invocations.js +1055 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +283 -27
- package/dist/services/merge-queue.js +179 -104
- package/dist/services/merge-server.js +73 -28
- package/dist/services/migrations.js +14 -9
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +19 -8
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +18 -5
- package/dist/services/prompts.js +23 -16
- package/dist/services/protocols.js +64 -20
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -0
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +363 -0
- package/dist/services/run-control.js +876 -0
- package/dist/services/run-controller-adapter.js +222 -0
- package/dist/services/run-controller-capture.js +134 -0
- package/dist/services/run-controller-process.js +189 -0
- package/dist/services/run-controller-recovery.js +222 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +789 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +138 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +343 -0
- package/dist/services/runs.js +368 -74
- package/dist/services/runtime-budget.js +434 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +450 -0
- package/dist/services/runtime-scope-resume.js +565 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +243 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +44 -18
- package/dist/services/stage-lifecycle.js +99 -102
- package/dist/services/stage-pause.js +84 -55
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/usage.js +37 -93
- package/dist/services/vnext-code-review.js +195 -88
- package/dist/services/vnext-code.js +285 -252
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +160 -17
- package/dist/services/vnext-merge.js +291 -104
- package/dist/services/vnext-plan-review.js +139 -81
- package/dist/services/vnext-plan.js +138 -69
- package/dist/services/vnext-protocolize.js +129 -56
- package/dist/services/vnext-specify.js +88 -65
- package/dist/services/work-registry.js +716 -168
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/worktrees.js +85 -35
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +459 -32
- package/dist/storage/paths.js +17 -4
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +24 -13
- package/tools/repair-paused-run-status.mjs +59 -0
package/dist/cli/run-cli.js
CHANGED
|
@@ -1,56 +1,82 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { resolveCommandPaths, resolveCommandProjectRoot } from "../services/command-context.js";
|
|
3
|
+
import { assertAuxiliaryCommandScope } from "../services/lifecycle-invocations.js";
|
|
2
4
|
import path from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { quote } from "shell-quote";
|
|
7
|
+
import { observeLifecycleCommand, observedLifecycleNativeEvent } from "../services/lifecycle-invocations.js";
|
|
8
|
+
import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
|
|
9
|
+
import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleDiagnostic, observedLifecycleInvocation, resolveObservedLifecycleArgs, settleLifecycleInvocation, settleLifecyclePreparationRejection, settleLifecycleRejection, validateLifecyclePathAliases } from "../services/lifecycle-invocations.js";
|
|
3
10
|
import { createContext, createRouterContext } from "../runtime/context.js";
|
|
11
|
+
import { migrateStoreWriter } from "../storage/writer-migration.js";
|
|
4
12
|
import { helpForArgs } from "./help.js";
|
|
5
13
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
14
|
+
import { entityReferenceVariants } from "../shared/entity-references.js";
|
|
6
15
|
import { writeJson } from "../shared/json.js";
|
|
7
|
-
import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
8
|
-
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, requireProtocol, syncProtocolFromRun, transitionProtocol } from "../services/protocols.js";
|
|
9
|
-
import {
|
|
10
|
-
import { cancelMergeQueueJob, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
|
|
16
|
+
import { archiveProject, prepareProjectArchive, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
|
|
17
|
+
import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, prepareProtocolRegistration, requireProtocol, syncProtocolFromRun, prepareProtocolTransition, transitionProtocol } from "../services/protocols.js";
|
|
18
|
+
import { getPlanStatus, preparePlanItemMutation, updatePlanItem } from "../services/plans.js";
|
|
19
|
+
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
20
|
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";
|
|
21
|
+
import { cleanupApply, cleanupScan, prepareCleanupApply } from "../services/cleanup.js";
|
|
22
|
+
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";
|
|
23
|
+
import { bootstrapWorktree, closeWorktree, createWorktreeRecord, prepareWorktreeCreate, prepareWorktreeBootstrap, prepareWorktreeClose, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
|
|
24
|
+
import { acquireLaneLock, cancelLaneWaiter, prepareLaneWaiterCancellation, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, prepareLaneWorkspace, prepareLaneLock, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
|
|
25
|
+
import { getProjectConfigStatus, prepareProjectConfigValue, setProjectConfigValue } from "../services/config.js";
|
|
26
|
+
import { getCanonStatus, registerCanonRoot, resolveCanonRoot, prepareCanonRegistration } from "../services/canon.js";
|
|
18
27
|
import { getRuntimeStatus } from "../services/status.js";
|
|
19
28
|
import { getCliVersionReport } from "../services/build-info.js";
|
|
20
29
|
import { parseCommandArgs } from "../services/lifecycle-command.js";
|
|
21
|
-
import {
|
|
30
|
+
import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile } from "./input-preparation.js";
|
|
31
|
+
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, prepareDashboardRefresh, prepareDashboardRender, prepareGlobalDashboardRender, prepareDashboardOptions, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
22
32
|
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
23
|
-
import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
33
|
+
import { getFlowSessionStatus, prepareFlowSessionPayload, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
24
34
|
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";
|
|
35
|
+
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
36
|
import { previewNextEntityId } from "../services/ids.js";
|
|
27
37
|
import { validateSchema } from "../services/schema-validation.js";
|
|
28
38
|
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";
|
|
39
|
+
import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
|
|
40
|
+
import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, prepareMigrationApply, verifyMigrationReport } from "../services/migrations.js";
|
|
31
41
|
import { publishProjectSummary } from "../services/project-summary.js";
|
|
32
42
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
33
43
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
34
|
-
import { doctorEngines, bindRunEngine, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
44
|
+
import { doctorEngines, bindRunEngine, prepareRunEngineBind, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
|
|
35
45
|
import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
|
|
36
|
-
import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify } from "../services/vnext-specify.js";
|
|
37
|
-
import { finishVnextProtocolize, isVnextProtocolizeRun, startVnextProtocolize } from "../services/vnext-protocolize.js";
|
|
38
|
-
import { finishVnextPlan, isVnextPlanRun, startVnextPlan } from "../services/vnext-plan.js";
|
|
39
|
-
import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
|
|
40
|
-
import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services/vnext-code.js";
|
|
41
|
-
import {
|
|
42
|
-
import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
46
|
+
import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify, prepareVnextSpecifyStart } from "../services/vnext-specify.js";
|
|
47
|
+
import { finishVnextProtocolize, isVnextProtocolizeRun, prepareProtocolizeResultPath, prepareVnextProtocolizeStart, startVnextProtocolize } from "../services/vnext-protocolize.js";
|
|
48
|
+
import { finishVnextPlan, isVnextPlanRun, startVnextPlan, prepareVnextPlanStart, prepareVnextPlanFinish } from "../services/vnext-plan.js";
|
|
49
|
+
import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, prepareVnextPlanReviewStart, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
|
|
50
|
+
import { addVnextCodeRepair, finishVnextCode, prepareVnextCodeRepair, prepareVnextCodeStart, startVnextCode } from "../services/vnext-code.js";
|
|
51
|
+
import { finishVnextCodeReview, isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReviewRepair, prepareVnextCodeReviewStart, startVnextCodeReview } from "../services/vnext-code-review.js";
|
|
52
|
+
import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
|
|
43
53
|
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";
|
|
54
|
+
import { getVnextFanoutStatus, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
|
|
55
|
+
import { pauseStageForUser, resumeStageAfterUser, prepareStagePause, prepareStageResume } from "../services/stage-pause.js";
|
|
56
|
+
import { blockStageForRuntime, unblockStageAfterRuntimeRepair, prepareStageRuntimeBlock, prepareStageRuntimeUnblock } from "../services/stage-blocker.js";
|
|
47
57
|
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 {
|
|
58
|
+
import { prepareWorkerPrompt, renderWorkerPrompt } from "../services/prompts.js";
|
|
59
|
+
import { finishStage, prepareStageFinish, startStage, prepareLegacyStageStart } from "../services/stage-lifecycle.js";
|
|
60
|
+
import { addWorkBatch, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, retryWork, showWork, startWork } from "../services/work-registry.js";
|
|
61
|
+
import { launchExternalWork } from "../services/external-work-launch.js";
|
|
62
|
+
import { createEvalBootstrapSnapshot, prepareEvalBootstrapSnapshot, createEvalRunSnapshot, prepareEvalRunSnapshotCreation, prepareVnextSpecifyRun, prepareEvalRunRestore, prepareEvalBootstrapRestore, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
|
|
63
|
+
import { forkRunFromSnapshot, prepareRunFork } from "../services/run-fork.js";
|
|
64
|
+
import { acceptRunRecovery, assertRunMutationAllowed, beginRunRecovery, inspectRunRecovery, prepareRecoveryCapture, prepareRecoveryPayload, prepareRunRecoveryResume, recordRecoveryCapture, recoveryGuard, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
|
|
65
|
+
import { recoveryRuntimeAdmission } from "../services/run-recovery-runtime.js";
|
|
66
|
+
import { loadExternalStageContext, prepareExternalStageContext } from "../services/stage-context.js";
|
|
67
|
+
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
68
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
69
|
+
import { parseLifecycleCommand } from "../services/lifecycle-command.js";
|
|
70
|
+
import { prepareManagedProcessRegistration, prepareManagedProcessConfirmation, prepareManagedProcessHeartbeat, prepareManagedProcessFinish, prepareManagedProcessStopOperation, prepareManagedProcessReconcile, stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
|
|
71
|
+
import { fenceRuntimeBudget, runtimeBudgetStatus, admitRuntimeCheck, parseRuntimeBudget, prepareRuntimeScopeControl, prepareRuntimeScopeFence, reserveProviderTurn, releaseProviderTurns, prepareProviderTurnRelease } from "../services/runtime-budget.js";
|
|
72
|
+
import { stopRuntimeScope } from "../services/runtime-scope-stop.js";
|
|
73
|
+
import { launchRuntimeScopeControl, launchRuntimeScopeResume, prepareRuntimeScopeReconciliation, prepareRuntimeScopeServing, reconcileRuntimeScopeControl, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
|
|
74
|
+
import { prepareRuntimeScopeResumeRequest } from "../services/runtime-scope-resume.js";
|
|
75
|
+
import { launchRunController, prepareRunControllerLaunch, resumeRunController, serveRunController, runControllerStatus, answerRunController, prepareRunControllerAnswer, supplyRunControllerContext, prepareRunControllerContext } from "../services/run-controller.js";
|
|
76
|
+
import { controllerHitlAdmission } from "../services/run-controller-adapter.js";
|
|
77
|
+
import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
|
|
78
|
+
import { reconcileRunControl, requestRunControl, serveRunControl } from "../services/run-control-worker.js";
|
|
79
|
+
import { startRuntimeService, prepareRuntimeService } from "../services/runtime-service.js";
|
|
54
80
|
const defaultIo = {
|
|
55
81
|
stdout: process.stdout,
|
|
56
82
|
stderr: process.stderr,
|
|
@@ -58,9 +84,20 @@ const defaultIo = {
|
|
|
58
84
|
};
|
|
59
85
|
const flagValue = "__dd_flow_flag__";
|
|
60
86
|
export async function runCli(args, io = defaultIo, env = process.env) {
|
|
61
|
-
|
|
87
|
+
let output;
|
|
88
|
+
try {
|
|
89
|
+
output = parseOutputOptions(args);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
93
|
+
writeJson(io.stderr, { ok: false, error: { code: "usage", message } });
|
|
94
|
+
return 2;
|
|
95
|
+
}
|
|
62
96
|
const progress = progressForCommand(output.args);
|
|
63
97
|
let heartbeat;
|
|
98
|
+
let invocationSettlement;
|
|
99
|
+
let settleHook;
|
|
100
|
+
let diagnosticHome;
|
|
64
101
|
try {
|
|
65
102
|
if (output.args.length === 1 && output.args[0] === "--version") {
|
|
66
103
|
const payload = getCliVersionReport();
|
|
@@ -75,6 +112,16 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
75
112
|
io.stdout.write(`${help}\n`);
|
|
76
113
|
return 0;
|
|
77
114
|
}
|
|
115
|
+
const suppliedInvocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
|
|
116
|
+
if (output.args[0] === "run" && ["drive", "control"].includes(output.args[1] ?? "")) {
|
|
117
|
+
const controlArgs = parseArgs(output.args.slice(2));
|
|
118
|
+
requiredOption(controlArgs, "run");
|
|
119
|
+
requiredOption(controlArgs, "project-root");
|
|
120
|
+
}
|
|
121
|
+
if (io.stdin?.isTTY && ["work", "stage"].includes(output.args[0] ?? "") && output.args[1] === "finish"
|
|
122
|
+
&& hasOption(parseArgs(output.args.slice(2)), "result-stdin")) {
|
|
123
|
+
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);
|
|
124
|
+
}
|
|
78
125
|
const classification = classifyCliOperation(output.args, env);
|
|
79
126
|
if (requiresExplicitUpgradeAuthorization(output.args) && classification.mode !== "mb_upgrade") {
|
|
80
127
|
throw new AppError("upgrade_authorization_required", "This mb-upgrade command requires the explicit mb-upgrade authorization marker and allowlisted target context", 1, {
|
|
@@ -83,25 +130,133 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
83
130
|
});
|
|
84
131
|
}
|
|
85
132
|
const routerContext = createRouterContext(env);
|
|
133
|
+
const publicCommand = quote(["dd-flow", ...output.args]);
|
|
134
|
+
const lifecycleCommand = parseLifecycleCommand(publicCommand);
|
|
135
|
+
if (lifecycleCommand.kind === "standalone")
|
|
136
|
+
validateLifecyclePathAliases(output.args, lifecycleCommand.invocation.operation);
|
|
137
|
+
const observedInvocation = observedLifecycleInvocation(routerContext, publicCommand, env.DD_FLOW_DAEMON_ID);
|
|
138
|
+
if (!observedInvocation && !suppliedInvocationId && env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone") {
|
|
139
|
+
const diagnostic = observedLifecycleDiagnostic(routerContext, publicCommand, env.DD_FLOW_DAEMON_ID);
|
|
140
|
+
if (diagnostic)
|
|
141
|
+
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 });
|
|
142
|
+
const nativeEvent = observedLifecycleNativeEvent(routerContext, publicCommand, env.DD_FLOW_DAEMON_ID);
|
|
143
|
+
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 });
|
|
144
|
+
}
|
|
145
|
+
const invocationId = observedInvocation?.id ?? suppliedInvocationId;
|
|
146
|
+
let boundInvocationArgs;
|
|
147
|
+
try {
|
|
148
|
+
boundInvocationArgs = observedInvocation
|
|
149
|
+
? resolveObservedLifecycleArgs(routerContext, observedInvocation.id, output.args)
|
|
150
|
+
: invocationId && invocationId !== suppliedInvocationId
|
|
151
|
+
? [...output.args.filter((_, index, values) => values[index - 1] !== "--invocation-id" && values[index] !== "--invocation-id"), "--invocation-id", invocationId]
|
|
152
|
+
: output.args;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (!(observedInvocation && error instanceof AppError))
|
|
156
|
+
throw error;
|
|
157
|
+
const rejectionContext = createContext({ ...env }, "hook");
|
|
158
|
+
try {
|
|
159
|
+
Object.assign(error.details, settleLifecyclePreparationRejection(rejectionContext, observedInvocation.id, error));
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
rejectionContext.db.close?.();
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
const observedOperation = lifecycleCommand.kind === "standalone" ? lifecycleCommand.invocation.operation : null;
|
|
167
|
+
if (observedInvocation && !observedOperation)
|
|
168
|
+
throw new AppError("invocation_command_invalid", "Observed lifecycle command could not be parsed", 1);
|
|
169
|
+
const invocationScope = invocationId ? lifecycleInvocationScope({ ...routerContext, env }, invocationId) : undefined;
|
|
170
|
+
const expandedInvocationArgs = invocationScope && observedOperation
|
|
171
|
+
? expandLifecycleInvocationArgs(routerContext, boundInvocationArgs, invocationScope, observedOperation)
|
|
172
|
+
: boundInvocationArgs;
|
|
173
|
+
const retainedOutput = observedInvocation ? parseOutputOptions(expandedInvocationArgs) : null;
|
|
174
|
+
if (retainedOutput?.responseFile)
|
|
175
|
+
output.responseFile = retainedOutput.responseFile;
|
|
176
|
+
let invocationArgs = retainedOutput?.args ?? expandedInvocationArgs;
|
|
86
177
|
emitHumanProgress(io, output, progress, "start");
|
|
87
178
|
if (output.progressJsonl) {
|
|
88
179
|
writeProgressJsonl(io, "start", `Starting ${output.args.slice(0, 3).join(" ")}`);
|
|
89
180
|
heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
|
|
90
181
|
}
|
|
91
|
-
|
|
92
|
-
|
|
182
|
+
// Route by retained authority, so a mistyped project path reaches the CLI
|
|
183
|
+
// validator instead of selecting a different engine or failing preflight.
|
|
184
|
+
const scopeProjectRoot = invocationScope?.projectRoot ?? resolveCommandProjectRoot(routerContext, output.args, resolveProjectRootBeforeRouting(output.args, env));
|
|
185
|
+
assertAuxiliaryCommandScope({ ...routerContext, env }, publicCommand, scopeProjectRoot);
|
|
186
|
+
invocationArgs = resolveCommandPaths(routerContext, invocationArgs, scopeProjectRoot);
|
|
187
|
+
const routedArgs = output.responseFile ? [...invocationArgs, "--response-file", output.responseFile] : invocationArgs;
|
|
93
188
|
const routed = await routeArgsThroughEngine(routerContext, output.json ? [...routedArgs, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : routedArgs, io, io.stdin, env, scopeProjectRoot);
|
|
94
189
|
if (routed?.routed && !routed.inProcess) {
|
|
95
190
|
if (heartbeat)
|
|
96
191
|
clearInterval(heartbeat);
|
|
97
|
-
emitHumanProgress(io, output, progress, "done");
|
|
192
|
+
emitHumanProgress(io, output, progress, routed.exitCode === 0 ? "done" : "failed");
|
|
193
|
+
if (output.progressJsonl && routed.exitCode !== 0)
|
|
194
|
+
writeProgressJsonl(io, "failed", "Routed engine failed", { exit_code: routed.exitCode });
|
|
98
195
|
return routed.exitCode;
|
|
99
196
|
}
|
|
197
|
+
let preparedInput;
|
|
198
|
+
let preparedRoute;
|
|
199
|
+
if (!invocationId) {
|
|
200
|
+
const preparationContext = createContext({ ...env }, "read_existing");
|
|
201
|
+
try {
|
|
202
|
+
preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
preparationContext.db.close?.();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
100
208
|
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
|
-
|
|
209
|
+
? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(invocationArgs, classification))
|
|
210
|
+
: createContext({ ...env }, databaseModeForCommand(invocationArgs, classification));
|
|
211
|
+
diagnosticHome = dispatchContext.ddFlowHome;
|
|
212
|
+
dispatchContext.env.DD_FLOW_INVOCATION_READONLY = classification.mode === "read_only_diagnostics" ? "1" : undefined;
|
|
213
|
+
let dispatchArgs = invocationArgs;
|
|
214
|
+
let retainedOutcome;
|
|
215
|
+
// Issued transports may deliver their receipt asynchronously. Observe only
|
|
216
|
+
// after admission; retained outcomes must not need a fresh hook receipt.
|
|
217
|
+
if (invocationId) {
|
|
218
|
+
const command = quote(["dd-flow", ...invocationArgs]);
|
|
219
|
+
const admission = await awaitLifecycleInvocation(dispatchContext, { id: invocationId, command,
|
|
220
|
+
validateArgs: () => { preparedRoute = prepareCommandRoute(invocationArgs); },
|
|
221
|
+
prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute); },
|
|
222
|
+
assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command) });
|
|
223
|
+
if (admission.replay) {
|
|
224
|
+
retainedOutcome = admission.outcome;
|
|
225
|
+
settleHook = undefined;
|
|
226
|
+
delete dispatchContext.observeLifecycleHook;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
invocationSettlement = { context: dispatchContext, id: invocationId };
|
|
230
|
+
dispatchArgs = [...invocationArgs, "--hook-event-id", admission.eventKey];
|
|
231
|
+
preparedInput?.route?.parsed.options.set("hook-event-id", [admission.eventKey]);
|
|
232
|
+
dispatchContext.env.DD_FLOW_INVOCATION_SCOPE = JSON.stringify(lifecycleInvocationScope(dispatchContext, invocationId));
|
|
233
|
+
dispatchContext.env.DD_FLOW_CURRENT_INVOCATION = invocationId;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (retainedOutcome?.error) {
|
|
237
|
+
const error = retainedOutcome.error;
|
|
238
|
+
throw new AppError(error.code, error.message, error.exitCode, error.details);
|
|
239
|
+
}
|
|
240
|
+
if (!retainedOutcome && !preparedInput)
|
|
241
|
+
preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile);
|
|
242
|
+
if (!retainedOutcome)
|
|
243
|
+
preflightCliCompatibility(dispatchContext, invocationArgs, env, scopeProjectRoot);
|
|
244
|
+
if (!retainedOutcome) {
|
|
245
|
+
const command = lifecycleCommand.kind === "none" ? publicCommand : quote(["dd-flow", ...dispatchArgs]);
|
|
246
|
+
const event = lifecycleCommand.kind === "none" && env.DD_FLOW_DAEMON_ID ? observedLifecycleNativeEvent(dispatchContext, command, env.DD_FLOW_DAEMON_ID) : null;
|
|
247
|
+
settleHook = observeLifecycleCommand(dispatchContext, command, event ?? undefined);
|
|
248
|
+
}
|
|
249
|
+
const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput);
|
|
250
|
+
const completedHook = settleHook;
|
|
251
|
+
settleHook = undefined;
|
|
252
|
+
completedHook?.({ result });
|
|
253
|
+
if (invocationSettlement) {
|
|
254
|
+
const completed = invocationSettlement;
|
|
255
|
+
invocationSettlement = undefined;
|
|
256
|
+
// A failure to persist an already completed result is an unknown reply,
|
|
257
|
+
// not a failed operation eligible for a new productive retry.
|
|
258
|
+
settleLifecycleInvocation(completed.context, completed.id, { result });
|
|
259
|
+
}
|
|
105
260
|
const refreshWarnings = refreshDashboardsAfterMutation(dispatchContext, output.args, result, scopeProjectRoot);
|
|
106
261
|
const resultWithWarnings = addWarnings(result, refreshWarnings);
|
|
107
262
|
if (heartbeat)
|
|
@@ -117,17 +272,76 @@ export async function runCli(args, io = defaultIo, env = process.env) {
|
|
|
117
272
|
io.stdout.write(renderHumanResult(output.args, payload));
|
|
118
273
|
return completedCommandExitCode(payload);
|
|
119
274
|
}
|
|
120
|
-
catch (
|
|
275
|
+
catch (caught) {
|
|
276
|
+
const error = isAppError(caught) ? caught : new AppError("unexpected", caught instanceof Error ? caught.message : String(caught), 1, { effect: "unknown", cause: { code: caught?.code ?? null, sqlite_extended_code: caught?.errcode ?? null } });
|
|
277
|
+
if (!settleHook && !invocationSettlement && env.DD_FLOW_DAEMON_ID) {
|
|
278
|
+
let earlyContext;
|
|
279
|
+
try {
|
|
280
|
+
const command = quote(["dd-flow", ...output.args]);
|
|
281
|
+
const event = observedLifecycleNativeEvent(createRouterContext(env), command, env.DD_FLOW_DAEMON_ID);
|
|
282
|
+
if (event) {
|
|
283
|
+
earlyContext = createContext({ ...env }, "hook");
|
|
284
|
+
observeLifecycleCommand(earlyContext, command, event)({ error });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (persistence) {
|
|
288
|
+
error.details.lifecycle_diagnostic_error = String(persistence);
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
earlyContext?.db.close?.();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (diagnosticHome && (!isAppError(caught) || error.code === "storage_write_failed")) {
|
|
295
|
+
try {
|
|
296
|
+
const directory = path.join(diagnosticHome, "diagnostics", "cli");
|
|
297
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
298
|
+
const artifact = path.join(directory, `${randomUUID()}.json`);
|
|
299
|
+
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 });
|
|
300
|
+
error.details.diagnostic_artifact = artifact;
|
|
301
|
+
}
|
|
302
|
+
catch (secondary) {
|
|
303
|
+
io.stderr.write(`CLI diagnostic persistence failed: ${String(secondary)}\n`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
121
306
|
if (heartbeat)
|
|
122
307
|
clearInterval(heartbeat);
|
|
308
|
+
if (invocationSettlement && error.details.effect !== "committed") {
|
|
309
|
+
try {
|
|
310
|
+
if (isAppError(error))
|
|
311
|
+
settleLifecycleRejection(invocationSettlement.context, invocationSettlement.id, { code: error.code, message: error.message, exitCode: error.exitCode, details: error.details });
|
|
312
|
+
else
|
|
313
|
+
settleLifecycleInvocation(invocationSettlement.context, invocationSettlement.id, { error: { code: "unexpected", message: String(error), exitCode: 1, details: {} } });
|
|
314
|
+
}
|
|
315
|
+
catch (persistenceError) {
|
|
316
|
+
io.stderr.write(`Invocation settlement persistence failed: ${String(persistenceError)}\n`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (settleHook) {
|
|
320
|
+
try {
|
|
321
|
+
settleHook({ error: isAppError(error) ? error : { code: "unexpected", message: String(error), details: {} } });
|
|
322
|
+
}
|
|
323
|
+
catch (persistence) {
|
|
324
|
+
io.stderr.write(`${JSON.stringify({ primary: String(error), lifecycle_persistence_error: String(persistence) })}\n`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
123
327
|
const payload = isAppError(error)
|
|
124
328
|
? { ok: false, error: { code: error.code, message: error.message, details: error.details } }
|
|
125
329
|
: { ok: false, error: { code: "unexpected", message: String(error) } };
|
|
330
|
+
progress.splice(0, progress.length, ...progress.filter(event => event.phase !== "done"));
|
|
331
|
+
if (progress.length)
|
|
332
|
+
progress.push({ phase: "failed", message: payload.error.message, at: new Date().toISOString() });
|
|
126
333
|
emitHumanProgress(io, output, progress, "failed");
|
|
127
334
|
if (output.progressJsonl)
|
|
128
335
|
writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
|
|
129
336
|
const errorPayload = attachProgress(payload, progress, output);
|
|
130
|
-
|
|
337
|
+
let visible = errorPayload;
|
|
338
|
+
try {
|
|
339
|
+
if (error.details.parameter !== "response-file")
|
|
340
|
+
visible = writeResponseFile(errorPayload, output);
|
|
341
|
+
}
|
|
342
|
+
catch (publication) {
|
|
343
|
+
error.details.response_publication_error = { code: publication?.code ?? "response_publication_failed", message: publication instanceof Error ? publication.message : String(publication) };
|
|
344
|
+
}
|
|
131
345
|
if (output.json && output.progressJsonl)
|
|
132
346
|
writeJson(io.stdout, errorPayload);
|
|
133
347
|
else if (output.json)
|
|
@@ -151,7 +365,22 @@ function resolveProjectRootBeforeRouting(args, env) {
|
|
|
151
365
|
}
|
|
152
366
|
function databaseModeForCommand(args, classification) {
|
|
153
367
|
const [family, command] = args;
|
|
368
|
+
// Managed attempts are issued only from a prepared store. Opening that
|
|
369
|
+
// existing writer must not run migrations before their input is admitted.
|
|
370
|
+
if (parseArgs(args.slice(2)).options.has("invocation-id"))
|
|
371
|
+
return "hook";
|
|
372
|
+
// Native hook ingress must remain synchronous, but it must not run schema
|
|
373
|
+
// migration/VACUUM while the provider is waiting for its decision. Startup
|
|
374
|
+
// prepares the store; the hook mode only verifies the prepared contract and
|
|
375
|
+
// records the receipt.
|
|
376
|
+
if (family === "codex" && command === "hook" && args[2] === "handle") {
|
|
377
|
+
// Only the participating PreToolUse ingress needs the prepared hot-path
|
|
378
|
+
// contract. SessionStart/Stop/PostToolUse are observational no-ops here
|
|
379
|
+
// and retain the ordinary startup path for backwards compatibility.
|
|
380
|
+
return optionalOption(parseArgs(args.slice(3)), "event") === "PreToolUse" ? "hook" : "initialize";
|
|
381
|
+
}
|
|
154
382
|
if (family === "engine" ||
|
|
383
|
+
family === "storage" && command === "migrate-writer" ||
|
|
155
384
|
family === "version" ||
|
|
156
385
|
family === "schema" ||
|
|
157
386
|
family === "canon" && command === "resolve" ||
|
|
@@ -207,8 +436,10 @@ function parseOutputOptions(args) {
|
|
|
207
436
|
continue;
|
|
208
437
|
}
|
|
209
438
|
if (arg === "--response-file") {
|
|
439
|
+
if (responseFile !== null)
|
|
440
|
+
throw new AppError("usage", "--response-file may be supplied only once", 2);
|
|
210
441
|
const file = args[++index];
|
|
211
|
-
if (!file)
|
|
442
|
+
if (!file?.trim() || file.startsWith("--"))
|
|
212
443
|
throw new AppError("usage", "--response-file requires a path", 2);
|
|
213
444
|
responseFile = file;
|
|
214
445
|
continue;
|
|
@@ -220,6 +451,33 @@ function parseOutputOptions(args) {
|
|
|
220
451
|
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
221
452
|
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
|
|
222
453
|
}
|
|
454
|
+
/** Validate the response destination without creating it. The response is an
|
|
455
|
+
* optional publication, but a known-bad destination should never be found
|
|
456
|
+
* only after a successful lifecycle mutation. */
|
|
457
|
+
function prepareResponseDestination(value) {
|
|
458
|
+
const file = path.resolve(value);
|
|
459
|
+
const parent = path.dirname(file);
|
|
460
|
+
try {
|
|
461
|
+
let ancestor = parent;
|
|
462
|
+
for (;;) {
|
|
463
|
+
try {
|
|
464
|
+
if (!fs.statSync(ancestor).isDirectory())
|
|
465
|
+
throw new AppError("response_file_parent_invalid", "--response-file parent must be a directory", 2, { file, parent });
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
if (error.code !== "ENOENT" || path.dirname(ancestor) === ancestor)
|
|
470
|
+
throw error;
|
|
471
|
+
ancestor = path.dirname(ancestor);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (fs.existsSync(file) && !fs.statSync(file).isFile())
|
|
475
|
+
throw new AppError("response_file_invalid", "--response-file must name a regular file", 2, { file });
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
return prepareError(error, { parameter: "response-file", file, parent });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
223
481
|
/** Persist the complete machine response for harnesses that cap tool output.
|
|
224
482
|
* The visible response keeps routing fields but omits the potentially large
|
|
225
483
|
* rendered prompt; an agent can safely read the named file without rerunning
|
|
@@ -500,27 +758,650 @@ function stringValue(value) {
|
|
|
500
758
|
function shortSha(value) {
|
|
501
759
|
return value ? value.slice(0, 12) : "unknown";
|
|
502
760
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
761
|
+
/** All caller supplied file/stdin data is consumed here, before a managed
|
|
762
|
+
* invocation is allowed to claim execution or a stage claims its hook event. */
|
|
763
|
+
async function prepareCliInput(...args) {
|
|
764
|
+
try {
|
|
765
|
+
return await prepareCliInputValue(...args);
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
return prepareError(error);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args)) {
|
|
772
|
+
const { family, command, parsed } = route;
|
|
773
|
+
const prepared = { route };
|
|
774
|
+
if (optionalOption(parsed, "reason-file")) {
|
|
775
|
+
const value = prepareJsonFile(requiredOption(parsed, "reason-file"), "Check recovery evidence").value;
|
|
776
|
+
if (!value || typeof value.reason !== "string" || !value.reason.trim() || Object.keys(value).some(key => key !== "reason"))
|
|
777
|
+
throw new AppError("validation", "Recovery evidence must be a JSON object with one nonempty reason", 2);
|
|
778
|
+
prepared.retryReason = value.reason;
|
|
779
|
+
}
|
|
780
|
+
else if (optionalOption(parsed, "retry-check"))
|
|
781
|
+
prepared.retryReason = optionalOption(parsed, "reason");
|
|
782
|
+
if (route.key === "project register")
|
|
783
|
+
resolveProjectRoot(requiredOption(parsed, "root"));
|
|
784
|
+
if (route.key === "project archive")
|
|
785
|
+
prepareProjectArchive(context, { idOrAlias: parsed.positional[0], root: optionalOption(parsed, "root"), reason: requiredOption(parsed, "reason") });
|
|
786
|
+
if (route.key === "protocol register")
|
|
787
|
+
prepareProtocolRegistration(context, { handshakeId: requiredPosition(parsed, 0, "handshake-id"), projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "workspace-path") });
|
|
788
|
+
if (route.key === "canon register")
|
|
789
|
+
prepared.canonRegistration = prepareCanonRegistration(context, { root: requiredOption(parsed, "root") });
|
|
790
|
+
if (route.key === "run attach-stage")
|
|
791
|
+
prepareFlowRunStageAttachment(context, stageAttachmentInput(parsed));
|
|
792
|
+
if (route.key === "run complete-stage")
|
|
793
|
+
prepareFlowRunStageCompletion(context, stageCompletionInput(parsed));
|
|
794
|
+
if (route.key === "run override")
|
|
795
|
+
prepareFlowRunCompletion(context, runOverrideInput(parsed));
|
|
796
|
+
if (route.key === "engine bind-run")
|
|
797
|
+
prepared.engineBind = prepareRunEngineBind(context, engineBindInput(parsed));
|
|
798
|
+
if (route.key === "worktree create")
|
|
799
|
+
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") });
|
|
800
|
+
if (route.key === "worktree bootstrap")
|
|
801
|
+
prepared.worktreeBootstrap = prepareWorktreeBootstrap(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree bootstrap") });
|
|
802
|
+
if (route.key === "worktree close")
|
|
803
|
+
prepareWorktreeClose(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree close"), mode: requiredOption(parsed, "mode") });
|
|
804
|
+
if (route.key.startsWith("plan item "))
|
|
805
|
+
preparePlanItemMutation(context, planItemInput(parsed, requiredScopeProjectRoot(scopeProjectRoot, route.key)));
|
|
806
|
+
if (route.key === "lane workspace set")
|
|
807
|
+
prepareLaneWorkspace(context, laneWorkspaceInput(parsed));
|
|
808
|
+
if (route.key === "lane waiter cancel")
|
|
809
|
+
prepareLaneWaiterCancellation(context, laneWaiterCancelInput(parsed));
|
|
810
|
+
if (route.key === "merge-queue complete")
|
|
811
|
+
prepareMergeJobCompletion(context, { ...mergeJobTarget(parsed, scopeProjectRoot), summary: requiredOption(parsed, "summary") });
|
|
812
|
+
if (route.key === "merge-queue cancel")
|
|
813
|
+
prepareMergeQueueCancellation(context, mergeCancellationInput(parsed, scopeProjectRoot));
|
|
814
|
+
if (route.key === "merge-queue wait-next")
|
|
815
|
+
prepareMergeQueueWait(context, mergeWaitInput(parsed, scopeProjectRoot));
|
|
816
|
+
if (route.key === "merge-queue next" || route.key === "merge-queue claim")
|
|
817
|
+
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") } : {}) });
|
|
818
|
+
if (route.key === "merge-queue note")
|
|
819
|
+
prepareMergeJobNote(context, { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
|
|
820
|
+
if (route.key === "merge bundle complete")
|
|
821
|
+
prepareMergeBundleCompletion(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
|
|
822
|
+
if (route.key === "merge bundle claim")
|
|
823
|
+
prepareMergeBundleClaim(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed) });
|
|
824
|
+
if (route.key === "merge bundle fail")
|
|
825
|
+
prepareMergeBundleFailure(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
|
|
826
|
+
if (route.key === "merge-queue fail")
|
|
827
|
+
prepareMergeJobFailure(context, { ...mergeJobTarget(parsed, scopeProjectRoot), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
|
|
828
|
+
if (route.key.startsWith("lane lock ") && route.key !== "lane lock status") {
|
|
829
|
+
const action = requiredPosition(parsed, 0, "lane lock action");
|
|
830
|
+
prepareLaneLock(context, {
|
|
831
|
+
projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"),
|
|
832
|
+
workspacePath: workspacePathOption(parsed), leaseToken: optionalOption(parsed, "lease-token"), reason: optionalOption(parsed, "reason"),
|
|
833
|
+
ttlSeconds: optionalNumber(parsed, "ttl"), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
|
|
834
|
+
}, action);
|
|
835
|
+
}
|
|
836
|
+
if (route.key === "run recovery begin")
|
|
837
|
+
prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "interruption-json"), "interruption");
|
|
838
|
+
if (route.key === "run recovery seal")
|
|
839
|
+
prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "settlement-json"), "settlement");
|
|
840
|
+
if (route.key === "run recovery capture")
|
|
841
|
+
prepared.recoveryCapture = prepareRecoveryCapture(context, recoveryCaptureInput(parsed));
|
|
842
|
+
if (route.key === "run recovery resume")
|
|
843
|
+
prepared.recoveryResume = prepareRunRecoveryResume(context, recoveryResumeInput(parsed));
|
|
844
|
+
if (route.key === "run drive answer")
|
|
845
|
+
prepared.controllerAnswer = prepareRunControllerAnswer(context, controllerAnswerInput(parsed));
|
|
846
|
+
if (route.key === "run drive launch")
|
|
847
|
+
prepared.controllerLaunch = prepareRunControllerLaunch(context, controllerLaunchInput(parsed));
|
|
848
|
+
if (route.key === "run drive context")
|
|
849
|
+
prepared.controllerContext = prepareRunControllerContext(context, controllerContextInput(parsed));
|
|
850
|
+
if (route.key === "protocol transition" || route.key === "transition")
|
|
851
|
+
prepared.protocolTransition = prepareProtocolTransition(context, protocolTransitionInput(parsed, scopeProjectRoot ?? resolveOperationProjectRoot(context, args)));
|
|
852
|
+
if (route.key === "codex home init")
|
|
853
|
+
prepared.codexHome = prepareCodexHome(context, codexHomeInput(parsed));
|
|
854
|
+
if (route.key === "codex home remove")
|
|
855
|
+
prepareCodexHomeRemoval(context, { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), mode: requiredOption(parsed, "mode") });
|
|
856
|
+
if (["codex hooks install", "codex hooks remove"].includes(route.key))
|
|
857
|
+
prepared.codexHooks = prepareCodexHooks(context, { projectRoot: requiredOption(parsed, "project-root"), target: optionalOption(parsed, "target"), profile: optionalOption(parsed, "profile"), yes: hasOption(parsed, "yes") });
|
|
858
|
+
if (route.key === "migration apply")
|
|
859
|
+
prepared.migrationApply = prepareMigrationApply(context, migrationApplyInput(parsed, classifyCliOperation(args, context.env)));
|
|
860
|
+
if (route.key === "migration verify")
|
|
861
|
+
prepared.migrationVerification = verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
862
|
+
if (responseFile)
|
|
863
|
+
prepareResponseDestination(responseFile);
|
|
864
|
+
if (family === "dashboard") {
|
|
865
|
+
const action = dashboardAction(command);
|
|
866
|
+
prepareDashboardOptions({ format: optionalOption(parsed, "format"), viewer: optionalOption(parsed, "viewer"), open: optionalOption(parsed, "open") });
|
|
867
|
+
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") });
|
|
868
|
+
if (action === "refresh" && target.kind !== "global" && optionalOption(parsed, "output") !== undefined) {
|
|
869
|
+
throw new AppError("validation", "--output is supported only for global dashboard refresh; use dashboard render for a custom project/protocol output", 2, { parameter: "output" });
|
|
870
|
+
}
|
|
871
|
+
if (target.kind === "protocol")
|
|
872
|
+
requireProtocol(context, target.protocolId, target.project.id);
|
|
873
|
+
if (action !== "data") {
|
|
874
|
+
if (target.kind === "global" || target.kind === "all_projects") {
|
|
875
|
+
prepareGlobalDashboardRender(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? (command?.endsWith("-global") ? undefined : "html") });
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
const format = optionalOption(parsed, "format") ?? (action === "open" || hasOption(parsed, "project") ? "html" : undefined);
|
|
879
|
+
const input = { projectRoot: target.project.root, format, ...(target.kind === "protocol" ? { protocol: target.protocolId } : {}) };
|
|
880
|
+
if (action === "refresh")
|
|
881
|
+
prepareDashboardRefresh(context, { ...input, open: optionalOption(parsed, "open") });
|
|
882
|
+
else if (action === "render")
|
|
883
|
+
prepareDashboardRender(context, { ...input, output: optionalOption(parsed, "output") });
|
|
884
|
+
else if (target.kind === "protocol") {
|
|
885
|
+
if (format !== "html")
|
|
886
|
+
throw new AppError("validation", "Protocol dashboard open supports --format html", 2, { protocol: target.protocolId });
|
|
887
|
+
prepareDashboardRender(context, input);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (route.key === "prompt render") {
|
|
893
|
+
const input = workerPromptInput(parsed);
|
|
894
|
+
prepared.workerPrompt = { input, value: prepareWorkerPrompt(context, input) };
|
|
895
|
+
}
|
|
896
|
+
if (route.key === "run snapshot restore" || route.key === "run snapshot bootstrap restore") {
|
|
897
|
+
try {
|
|
898
|
+
const input = snapshotRestoreInput(parsed);
|
|
899
|
+
if (route.key === "run snapshot restore")
|
|
900
|
+
prepared.snapshotRestore = prepareEvalRunRestore(context, input);
|
|
901
|
+
else
|
|
902
|
+
prepared.bootstrapRestore = prepareEvalBootstrapRestore(input);
|
|
903
|
+
}
|
|
904
|
+
catch (error) {
|
|
905
|
+
return prepareError(error, { parameter: "snapshot/project-root" });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (route.key === "run snapshot bootstrap create") {
|
|
909
|
+
try {
|
|
910
|
+
prepareEvalBootstrapSnapshot(context, { projectRoot: requiredOption(parsed, "project-root"), output: requiredOption(parsed, "output") });
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
return prepareError(error, { parameter: "project-root/output" });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (route.key === "run snapshot create") {
|
|
917
|
+
prepareEvalRunSnapshotCreation(context, snapshotCreateInput(parsed));
|
|
918
|
+
}
|
|
919
|
+
if (route.key === "run start") {
|
|
920
|
+
try {
|
|
921
|
+
const input = runStartInput(parsed);
|
|
922
|
+
prepared.runStart = { input, value: prepareFlowRunStart(context, input) };
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
return prepareError(error, { parameter: "run start" });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (route.key === "run fork") {
|
|
929
|
+
try {
|
|
930
|
+
prepared.runFork = prepareRunFork(runForkInput(parsed));
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
return prepareError(error, { parameter: "from/output" });
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (["runtime scope fence", "runtime scope stop"].includes(route.key))
|
|
937
|
+
prepareRuntimeScopeFence(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"));
|
|
938
|
+
if (route.key === "runtime scope resume")
|
|
939
|
+
prepareRuntimeScopeResumeRequest(context, runtimeScopeResumeInput(parsed));
|
|
940
|
+
if (route.key === "runtime scope reconcile")
|
|
941
|
+
prepareRuntimeScopeReconciliation(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id"));
|
|
942
|
+
if (route.key === "runtime scope serve")
|
|
943
|
+
prepareRuntimeScopeServing(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")));
|
|
944
|
+
if (route.key === "runtime scope control") {
|
|
945
|
+
try {
|
|
946
|
+
const raw = optionalOption(parsed, "manifest-json");
|
|
947
|
+
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);
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
return prepareError(error, { parameter: "scope-id/request-id/manifest-json" });
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (route.key === "runtime process register") {
|
|
954
|
+
prepareManagedProcessRegistration(context, runtimeProcessRegistrationInput(context, parsed));
|
|
955
|
+
}
|
|
956
|
+
if (route.key === "runtime process heartbeat")
|
|
957
|
+
prepareManagedProcessHeartbeat(context, runtimeProcessHeartbeatInput(parsed));
|
|
958
|
+
if (route.key === "runtime process finish")
|
|
959
|
+
prepareManagedProcessFinish(context, runtimeProcessFinishInput(parsed));
|
|
960
|
+
if (route.key === "runtime process confirm")
|
|
961
|
+
prepareManagedProcessConfirmation(context, runtimeProcessConfirmationInput(parsed));
|
|
962
|
+
if (route.key === "runtime process stop")
|
|
963
|
+
prepareManagedProcessStopOperation(context, runtimeProcessStopInput(parsed));
|
|
964
|
+
if (route.key === "runtime process reconcile")
|
|
965
|
+
prepareManagedProcessReconcile(requiredOption(parsed, "owner"), optionalNumber(parsed, "grace-ms"));
|
|
966
|
+
if (route.key === "runtime process release-turn") {
|
|
967
|
+
prepared.providerTurnRelease = prepareProviderTurnRelease({ daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") });
|
|
968
|
+
}
|
|
969
|
+
if (route.key === "runtime process start") {
|
|
970
|
+
try {
|
|
971
|
+
prepareRuntimeService(context, runtimeServiceInput(parsed));
|
|
972
|
+
}
|
|
973
|
+
catch (error) {
|
|
974
|
+
return prepareError(error, { parameter: "runtime process start" });
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (family === "work" && command === "retry") {
|
|
978
|
+
try {
|
|
979
|
+
prepareWorkRetry(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
980
|
+
}
|
|
981
|
+
catch (error) {
|
|
982
|
+
return prepareError(error, { parameter: "work-id/reason" });
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (family === "work" && (command === "fail" || command === "cancel")) {
|
|
986
|
+
try {
|
|
987
|
+
prepareWorkSettlement(context, requiredPosition(parsed, 0, "work-id"), command === "fail" ? "failed" : "cancelled", requiredOption(parsed, "reason"));
|
|
988
|
+
}
|
|
989
|
+
catch (error) {
|
|
990
|
+
return prepareError(error, { parameter: "work-id/reason" });
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if (family === "work" && (command === "deps" || command === "delete")) {
|
|
994
|
+
try {
|
|
995
|
+
if (command === "delete")
|
|
996
|
+
prepareWorkDelete(context, requiredPosition(parsed, 0, "work-id"));
|
|
997
|
+
else
|
|
998
|
+
prepareWorkDeps(context, { workId: requiredPosition(parsed, 1, "work-id"), action: requiredPosition(parsed, 0, "action"), on: parsed.options.get("on") ?? [] });
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
return prepareError(error, { parameter: "work-id/on" });
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (route.key === "cleanup apply") {
|
|
1005
|
+
const planFile = requiredOption(parsed, "plan-file");
|
|
1006
|
+
try {
|
|
1007
|
+
prepared.cleanup = prepareCleanupApply(context, { projectRoot: requiredOption(parsed, "project-root"), planFile, reason: requiredOption(parsed, "reason"), force: hasOption(parsed, "force") });
|
|
1008
|
+
}
|
|
1009
|
+
catch (error) {
|
|
1010
|
+
return prepareError(error, { parameter: "plan-file", file: planFile });
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (route.key === "stage block" || route.key === "stage unblock") {
|
|
1014
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
|
|
1015
|
+
try {
|
|
1016
|
+
if (route.key === "stage block") {
|
|
1017
|
+
const summaryFile = optionalOption(parsed, "summary-file"), summaryStdin = hasOption(parsed, "summary-stdin");
|
|
1018
|
+
if (Boolean(summaryFile) === summaryStdin)
|
|
1019
|
+
throw new AppError("usage", "stage block requires exactly one of --summary-file or --summary-stdin", 2);
|
|
1020
|
+
prepared.interactionText = summaryFile ? prepareTextFile(summaryFile, "Runtime block summary").text : await readStdin(io.stdin);
|
|
1021
|
+
prepareStageRuntimeBlock(context, { ...input, kind: requiredOption(parsed, "kind"), code: requiredOption(parsed, "code"), summary: prepared.interactionText, retryable: hasOption(parsed, "retryable") });
|
|
1022
|
+
}
|
|
1023
|
+
else
|
|
1024
|
+
prepareStageRuntimeUnblock(context, input);
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
return prepareError(error, { parameter: "stage/work/summary" });
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (route.key === "zcode invocation prepare") {
|
|
1031
|
+
const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label: "invocation issuance" }).value;
|
|
1032
|
+
const payload = value && typeof value === "object" ? value : {};
|
|
1033
|
+
try {
|
|
1034
|
+
prepared.lifecycleIssuance = prepareLifecycleIssuance(payload.command, payload.scope);
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
if (error instanceof AppError && ["invocation_command_invalid", "invocation_scope_invalid"].includes(error.code))
|
|
1038
|
+
return prepareError(new AppError("usage", error.message, 2, { cause_code: error.code }), { parameter: "stdin" });
|
|
1039
|
+
throw error;
|
|
1040
|
+
}
|
|
1041
|
+
assertLifecycleInvocationCurrent(context, prepared.lifecycleIssuance.scope, prepared.lifecycleIssuance.command);
|
|
1042
|
+
}
|
|
1043
|
+
if (route.key === "work add-batch") {
|
|
1044
|
+
const file = requiredOption(parsed, "file");
|
|
1045
|
+
try {
|
|
1046
|
+
prepared.workBatch = prepareWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file });
|
|
1047
|
+
}
|
|
1048
|
+
catch (error) {
|
|
1049
|
+
return prepareError(error, { parameter: "file/parent", file });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (route.key === "work repair add") {
|
|
1053
|
+
const taskFile = optionalOption(parsed, "task-file"), taskStdin = hasOption(parsed, "task-stdin");
|
|
1054
|
+
if (Boolean(taskFile) === taskStdin)
|
|
1055
|
+
throw new AppError("usage", "work repair add requires exactly one of --task-file or --task-stdin", 2);
|
|
1056
|
+
if (taskStdin && io.stdin?.isTTY)
|
|
1057
|
+
throw new AppError("usage", "--task-stdin requires piped or redirected input", 2);
|
|
1058
|
+
const task = taskFile ? prepareTextFile(taskFile, "Repair task").text : await readStdin(io.stdin);
|
|
1059
|
+
prepared.workRepair = prepareCliWorkRepair(context, parsed, task, scopeProjectRoot);
|
|
1060
|
+
}
|
|
1061
|
+
if (route.key === "run config set" || route.key === "run vars set") {
|
|
1062
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), key: requiredOption(parsed, "key"), value: requiredOption(parsed, "value") };
|
|
1063
|
+
try {
|
|
1064
|
+
if (route.key === "run config set")
|
|
1065
|
+
prepareFlowRunConfig(context, { ...input, reason: requiredOption(parsed, "reason") });
|
|
1066
|
+
else
|
|
1067
|
+
prepareFlowRunVariable(context, input);
|
|
1068
|
+
}
|
|
1069
|
+
catch (error) {
|
|
1070
|
+
return prepareError(error, { parameter: "run config/vars" });
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (route.key === "schema validate") {
|
|
1074
|
+
try {
|
|
1075
|
+
prepared.schemaValidation = dispatchSchema(context, command, parsed);
|
|
1076
|
+
}
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
return prepareError(error, { parameter: "file", file: requiredOption(parsed, "file") });
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (route.key === "project config set") {
|
|
1082
|
+
try {
|
|
1083
|
+
prepareProjectConfigValue(requiredOption(parsed, "key"), requiredOption(parsed, "value"));
|
|
1084
|
+
}
|
|
1085
|
+
catch (error) {
|
|
1086
|
+
return prepareError(error, { parameter: "key/value" });
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (family === "session" && command === "register") {
|
|
1090
|
+
try {
|
|
1091
|
+
prepared.sessionPayload = prepareFlowSessionPayload({ payloadBase64: optionalOption(parsed, "payload-base64"), payloadFile: optionalOption(parsed, "payload-file"), payloadJson: optionalOption(parsed, "payload-json") });
|
|
1092
|
+
}
|
|
1093
|
+
catch (error) {
|
|
1094
|
+
return prepareError(error, { parameter: "session payload" });
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
const run = (projectRoot, runId) => {
|
|
1098
|
+
try {
|
|
1099
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
|
|
1100
|
+
const row = context.db.get("SELECT run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
|
|
1101
|
+
if (!row)
|
|
1102
|
+
throw new AppError("not_found", "RUN is not registered", 2, { run_id: runId });
|
|
1103
|
+
if (!row.run_root)
|
|
1104
|
+
throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: runId });
|
|
1105
|
+
return { runRoot: row.run_root, workspaceRoot: row.workspace_root, indexJson: row.index_json };
|
|
1106
|
+
}
|
|
1107
|
+
catch (error) {
|
|
1108
|
+
return prepareError(error, { parameter: "run-id", run_id: runId });
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
const json = (file, stdin, label, schema) => {
|
|
1112
|
+
if (stdin) {
|
|
1113
|
+
// The caller awaits this helper; the synchronous facade below is only
|
|
1114
|
+
// used for file inputs. Keep stdin handling in the stage branch.
|
|
1115
|
+
throw new AppError("usage", `${label} stdin preparation must be awaited`, 2);
|
|
1116
|
+
}
|
|
1117
|
+
if (!file)
|
|
1118
|
+
throw new AppError("usage", `${label} input is required`, 2);
|
|
1119
|
+
return prepareJsonSchema(file, label, schema);
|
|
1120
|
+
};
|
|
1121
|
+
if (family === "stage" && command === "start") {
|
|
1122
|
+
const stage = requiredOption(parsed, "stage");
|
|
1123
|
+
if (stage !== "specify" && ["bootstrap", "intake-file", "intake-stdin"].some(option => hasOption(parsed, option)))
|
|
1124
|
+
throw new AppError("usage", "--bootstrap and intake inputs are only supported for SPECIFY", 2);
|
|
1125
|
+
requiredOption(parsed, "project-root");
|
|
1126
|
+
if (!hasOption(parsed, "bootstrap"))
|
|
1127
|
+
requiredPosition(parsed, 0, "run-id");
|
|
1128
|
+
if (hasOption(parsed, "session-id"))
|
|
1129
|
+
throw new AppError("usage", "--session-id is managed by the harness lifecycle adapter and is not accepted", 2);
|
|
1130
|
+
const contextFile = optionalOption(parsed, "context-file"), contextSha256 = optionalOption(parsed, "context-sha256");
|
|
1131
|
+
if (Boolean(contextFile) !== Boolean(contextSha256))
|
|
1132
|
+
throw new AppError("usage", "stage start requires --context-file together with --context-sha256", 2);
|
|
1133
|
+
if ([hasOption(parsed, "intake-file"), hasOption(parsed, "intake-stdin"), Boolean(contextFile)].filter(Boolean).length > 1)
|
|
1134
|
+
throw new AppError("usage", "Use exactly one of --intake-file, --intake-stdin or --context-file", 2);
|
|
1135
|
+
if (contextFile && contextSha256) {
|
|
1136
|
+
try {
|
|
1137
|
+
prepared.stageStart = { externalContext: loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) };
|
|
1138
|
+
}
|
|
1139
|
+
catch (error) {
|
|
1140
|
+
return prepareError(error, { file: contextFile, parameter: "context-file" });
|
|
1141
|
+
}
|
|
1142
|
+
if (!hasOption(parsed, "bootstrap")) {
|
|
1143
|
+
const { runRoot } = run(requiredOption(parsed, "project-root"), requiredPosition(parsed, 0, "run-id"));
|
|
1144
|
+
prepareExternalStageContext({ stageRoot: path.join(runRoot, vnextStageDirectory(stage)), loaded: prepared.stageStart.externalContext });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (stage === "specify") {
|
|
1148
|
+
const intakeFile = optionalOption(parsed, "intake-file");
|
|
1149
|
+
if (intakeFile)
|
|
1150
|
+
prepared.stageStart = { intakeMarkdown: prepareTextFile(intakeFile, "SPECIFY intake").text };
|
|
1151
|
+
else if (hasOption(parsed, "intake-stdin"))
|
|
1152
|
+
prepared.stageStart = { intakeMarkdown: await readStdin(io.stdin) };
|
|
1153
|
+
else if (prepared.stageStart?.externalContext)
|
|
1154
|
+
prepared.stageStart.intakeMarkdown = runnerIntake(prepared.stageStart.externalContext);
|
|
1155
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1156
|
+
const bootstrap = hasOption(parsed, "bootstrap");
|
|
1157
|
+
if (isVnextSpecifyFlow(projectRoot) && (bootstrap || isVnextSpecifyRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") }))) {
|
|
1158
|
+
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") } : {}) });
|
|
1159
|
+
}
|
|
1160
|
+
else if (bootstrap) {
|
|
1161
|
+
if (hasOption(parsed, "intake-stdin") || contextFile)
|
|
1162
|
+
throw new AppError("usage", "Legacy SPECIFY bootstrap supports --intake-file, not stdin or runner context", 2);
|
|
1163
|
+
prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot, stage, bootstrap: { subject: requiredOption(parsed, "subject"), ...(intakeFile ? { intakeFile } : {}) }, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) }, prepared.stageStart?.intakeMarkdown);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (stage === "merge" && isVnextMergeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1167
|
+
prepared.mergeStart = prepareVnextMergeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1168
|
+
}
|
|
1169
|
+
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1170
|
+
prepared.protocolizeStart = prepareVnextProtocolizeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1171
|
+
}
|
|
1172
|
+
if (stage === "plan" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1173
|
+
prepared.planStart = prepareVnextPlanStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1174
|
+
}
|
|
1175
|
+
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1176
|
+
prepared.planReviewStart = prepareVnextPlanReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1177
|
+
}
|
|
1178
|
+
if (stage === "code" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1179
|
+
prepared.codeStart = prepareVnextCodeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1180
|
+
}
|
|
1181
|
+
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
1182
|
+
prepared.codeReviewStart = prepareVnextCodeReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
|
|
1183
|
+
}
|
|
1184
|
+
if (![prepared.specifyStart, prepared.protocolizeStart, prepared.planStart, prepared.planReviewStart, prepared.codeStart, prepared.codeReviewStart, prepared.mergeStart, prepared.legacyStageStart].some(Boolean)) {
|
|
1185
|
+
if (prepared.stageStart?.externalContext)
|
|
1186
|
+
throw new AppError("usage", "--context-file is currently supported only by vNext flow stages", 2);
|
|
1187
|
+
prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) });
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (family === "stage" && (command === "pause" || command === "resume")) {
|
|
1191
|
+
requiredPosition(parsed, 0, "run-id");
|
|
1192
|
+
requiredOption(parsed, "project-root");
|
|
1193
|
+
requiredOption(parsed, "stage");
|
|
1194
|
+
requiredOption(parsed, "work");
|
|
1195
|
+
const fileParameter = command === "pause" ? "question-file" : "answer-file";
|
|
1196
|
+
const stdinParameter = command === "pause" ? "question-stdin" : "answer-stdin";
|
|
1197
|
+
const inputFile = optionalOption(parsed, fileParameter);
|
|
1198
|
+
const fromStdin = hasOption(parsed, stdinParameter);
|
|
1199
|
+
if (Boolean(inputFile) === fromStdin)
|
|
1200
|
+
throw new AppError("usage", `stage ${command} requires exactly one of --${fileParameter} or --${stdinParameter}`, 2);
|
|
1201
|
+
if (fromStdin && io.stdin?.isTTY)
|
|
1202
|
+
throw new AppError("usage", `--${stdinParameter} requires piped or redirected input`, 2);
|
|
1203
|
+
prepared.interactionText = inputFile ? prepareTextFile(inputFile, command === "pause" ? "User question" : "User answer").text : await readStdin(io.stdin);
|
|
1204
|
+
if (!prepared.interactionText.trim())
|
|
1205
|
+
return prepareError(new AppError("validation", `stage ${command} requires non-empty input`, 2), { parameter: inputFile ? fileParameter : stdinParameter });
|
|
1206
|
+
const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
|
|
1207
|
+
try {
|
|
1208
|
+
if (command === "pause")
|
|
1209
|
+
prepareStagePause(context, { ...input, question: prepared.interactionText });
|
|
1210
|
+
else
|
|
1211
|
+
prepared.resumeEvidence = prepareStageResume(context, { ...input, answer: prepared.interactionText }).evidence;
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
return prepareError(error, { parameter: "stage/work" });
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
if (family === "work" && command === "finish") {
|
|
1218
|
+
const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = prepared.retryReason;
|
|
1219
|
+
if (Boolean(retryReceiptId) !== Boolean(retryReason))
|
|
1220
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1221
|
+
const resultFile = optionalOption(parsed, "result-file");
|
|
1222
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1223
|
+
if (Boolean(resultFile) === resultStdin)
|
|
1224
|
+
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
1225
|
+
prepared.workFinish = { result: resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin) };
|
|
1226
|
+
prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result);
|
|
1227
|
+
}
|
|
1228
|
+
if (family === "stage" && command === "fanout" && requiredPosition(parsed, 0, "fanout action") === "reconcile") {
|
|
1229
|
+
prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
|
|
1230
|
+
}
|
|
1231
|
+
if (family === "stage" && command === "native" && requiredPosition(parsed, 0, "native action") === "observe") {
|
|
1232
|
+
prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
|
|
1233
|
+
}
|
|
1234
|
+
if (family !== "stage" || command !== "finish")
|
|
1235
|
+
return prepared;
|
|
1236
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1237
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
1238
|
+
const stage = requiredOption(parsed, "stage");
|
|
1239
|
+
if (hasOption(parsed, "status"))
|
|
1240
|
+
throw new AppError("usage", "Stage status belongs in the semantic input file", 2);
|
|
1241
|
+
if (["result-file", "semantic-file", "data", "result-stdin"].filter(key => hasOption(parsed, key)).length > 1)
|
|
1242
|
+
throw new AppError("usage", "Use exactly one result input: --result-file, --semantic-file, --data or --result-stdin", 2);
|
|
1243
|
+
if (Boolean(optionalOption(parsed, "retry-check")) !== Boolean(prepared.retryReason))
|
|
1244
|
+
throw new AppError("usage", "--retry-check requires recovery evidence", 2);
|
|
1245
|
+
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1246
|
+
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
1247
|
+
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
1248
|
+
const runtime = run(projectRoot, runId);
|
|
1249
|
+
const terminalReplay = (name) => {
|
|
1250
|
+
let index;
|
|
1251
|
+
try {
|
|
1252
|
+
index = JSON.parse(runtime.indexJson);
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
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) });
|
|
1256
|
+
}
|
|
1257
|
+
return index.stage_runs?.some(entry => entry.stage === name && entry.status === "done") === true
|
|
1258
|
+
&& fs.existsSync(path.join(runtime.runRoot, vnextStageDirectory(name), "stage-report.json"));
|
|
1259
|
+
};
|
|
1260
|
+
const acceptStageInputs = (allowed) => {
|
|
1261
|
+
const options = allowed.split(" ");
|
|
1262
|
+
for (const name of ["dir", "semantic-file", "data", "result-file", "result-stdin", "outcome", "decision-file", "verification-file", "retry-check", "reason", "reason-file", "request", "work"]) {
|
|
1263
|
+
if (hasOption(parsed, name) && !options.includes(name === "reason-file" ? "reason" : name))
|
|
1264
|
+
throw new AppError("usage", `stage finish ${stage} does not accept --${name} for this flow`, 2, { parameter: name });
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
const fromStdin = async (label, schema) => {
|
|
1268
|
+
const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label });
|
|
1269
|
+
try {
|
|
1270
|
+
validateSchema({ ...schema, file: value.file, data: value.value });
|
|
1271
|
+
}
|
|
1272
|
+
catch (error) {
|
|
1273
|
+
return prepareError(error, { file: value.file, label });
|
|
1274
|
+
}
|
|
1275
|
+
return value;
|
|
1276
|
+
};
|
|
1277
|
+
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
1278
|
+
acceptStageInputs("result-file semantic-file data result-stdin outcome");
|
|
1279
|
+
const outcome = optionalOption(parsed, "outcome");
|
|
1280
|
+
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome))
|
|
1281
|
+
throw new AppError("usage", "vNext SPECIFY requires --outcome specified|failed|cancelled; use stage pause for a user question", 2);
|
|
1282
|
+
if (Boolean(resultFile) === resultStdin)
|
|
1283
|
+
throw new AppError("usage", "vNext SPECIFY finish requires exactly one result input", 2);
|
|
1284
|
+
const schema = { schemaName: "vnext-specify", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot };
|
|
1285
|
+
const input = resultStdin ? await fromStdin("SPECIFY result", schema) : json(resultFile, false, "SPECIFY result", schema);
|
|
1286
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1287
|
+
}
|
|
1288
|
+
else if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
1289
|
+
acceptStageInputs("result-file semantic-file data");
|
|
1290
|
+
if (!resultFile || resultStdin || hasOption(parsed, "outcome"))
|
|
1291
|
+
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
1292
|
+
let absolute;
|
|
1293
|
+
try {
|
|
1294
|
+
absolute = prepareProtocolizeResultPath(runtime.runRoot, resultFile);
|
|
1295
|
+
}
|
|
1296
|
+
catch (error) {
|
|
1297
|
+
return prepareError(error, { file: resultFile });
|
|
1298
|
+
}
|
|
1299
|
+
const input = json(absolute, false, "PROTOCOLIZE result", { schemaName: "vnext-protocolize-result", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1300
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1301
|
+
}
|
|
1302
|
+
else if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
1303
|
+
acceptStageInputs("");
|
|
1304
|
+
if (hasOption(parsed, "code-work-batch-file"))
|
|
1305
|
+
throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
|
|
1306
|
+
try {
|
|
1307
|
+
prepared.planFinish = prepareVnextPlanFinish(context, { projectRoot, runId });
|
|
1308
|
+
}
|
|
1309
|
+
catch (error) {
|
|
1310
|
+
return prepareError(error, { parameter: "PLAN artifacts" });
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
else if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
1314
|
+
acceptStageInputs("request work retry-check reason");
|
|
1315
|
+
if (resultStdin || resultFile || hasOption(parsed, "outcome"))
|
|
1316
|
+
throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
|
|
1317
|
+
requiredOption(parsed, "request");
|
|
1318
|
+
requiredOption(parsed, "work");
|
|
1319
|
+
try {
|
|
1320
|
+
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 } : {}) });
|
|
1321
|
+
}
|
|
1322
|
+
catch (error) {
|
|
1323
|
+
return prepareError(error, { parameter: "MERGE finish" });
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
1327
|
+
acceptStageInputs("decision-file");
|
|
1328
|
+
if (resultStdin || hasOption(parsed, "outcome"))
|
|
1329
|
+
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
1330
|
+
const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "04-plan-review", "decision.json");
|
|
1331
|
+
const input = json(decision, false, "PLAN-REVIEW decision", { schemaName: "plan-review-decision", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1332
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1333
|
+
}
|
|
1334
|
+
else if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
1335
|
+
acceptStageInputs("verification-file retry-check reason");
|
|
1336
|
+
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome"))
|
|
1337
|
+
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
1338
|
+
const verification = optionalOption(parsed, "verification-file");
|
|
1339
|
+
if (!verification)
|
|
1340
|
+
throw new AppError("usage", "vNext CODE finish requires --verification-file", 2);
|
|
1341
|
+
if (!terminalReplay(stage)) {
|
|
1342
|
+
const input = json(verification, false, "CODE verification", { schemaName: "code-verification", projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1343
|
+
prepared.stageFinish = { value: input.value, text: input.text };
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
else if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
1347
|
+
acceptStageInputs("decision-file retry-check reason");
|
|
1348
|
+
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
1349
|
+
throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
|
|
1350
|
+
const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "06-code-review", "decision.json");
|
|
1351
|
+
if (!terminalReplay(stage)) {
|
|
1352
|
+
const input = prepareCodeReviewDecision(runtime.runRoot, optionalOption(parsed, "decision-file"));
|
|
1353
|
+
try {
|
|
1354
|
+
validateSchema({ schemaName: "code-review-decision", file: decision, data: input.value, projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
|
|
1355
|
+
}
|
|
1356
|
+
catch (error) {
|
|
1357
|
+
return prepareError(error, { file: decision, label: "CODE-REVIEW decision" });
|
|
1358
|
+
}
|
|
1359
|
+
prepared.stageFinish = input;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
acceptStageInputs("dir semantic-file data");
|
|
1364
|
+
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin)
|
|
1365
|
+
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
1366
|
+
prepared.legacyStageFinish = prepareStageFinish(context, { projectRoot, runId, stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}), ...(semanticFile ? { semanticFile } : {}) });
|
|
1367
|
+
}
|
|
1368
|
+
return prepared;
|
|
1369
|
+
}
|
|
1370
|
+
async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args), prepared = {}) {
|
|
1371
|
+
if (!prepared.route)
|
|
1372
|
+
throw new AppError("command_not_prepared", "Command dispatch requires prepared arguments", 1);
|
|
1373
|
+
const { family, command, parsed } = prepared.route;
|
|
1374
|
+
if (family === "zcode" && command === "invocation" && parsed.positional[0] === "prepare") {
|
|
1375
|
+
const payload = prepared.lifecycleIssuance;
|
|
1376
|
+
if (!payload)
|
|
1377
|
+
throw new AppError("command_not_prepared", "Invocation issuance requires prepared input", 1);
|
|
1378
|
+
assertLifecycleInvocationCurrent(context, payload.scope, payload.command);
|
|
1379
|
+
return { ok: true, ...issueLifecycleInvocation(context, payload.command, payload.scope) };
|
|
1380
|
+
}
|
|
1381
|
+
if (family === "storage" && command === "migrate-writer") {
|
|
1382
|
+
if (parsed.positional.length || [...parsed.options.keys()].some(key => !["database", "backup", "offline"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1383
|
+
throw new AppError("usage", "Usage: dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline", 2);
|
|
1384
|
+
if (parsed.options.get("offline")?.[0] !== flagValue)
|
|
1385
|
+
throw new AppError("usage", "Writer migration requires --offline after draining all local and external runtime owners", 2);
|
|
1386
|
+
return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
|
|
1387
|
+
}
|
|
506
1388
|
if (family === "engine") {
|
|
507
|
-
return dispatchEngine(context, command, parsed, classification);
|
|
1389
|
+
return dispatchEngine(context, command, parsed, classification, prepared);
|
|
508
1390
|
}
|
|
509
1391
|
if (family === "status") {
|
|
510
|
-
const rootParsed = parseArgs([command ?? "", ...rest]);
|
|
511
1392
|
return getRuntimeStatus(context, {
|
|
512
|
-
projectRoot: optionalOption(
|
|
513
|
-
checkRegistry: hasOption(
|
|
1393
|
+
projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root"),
|
|
1394
|
+
checkRegistry: hasOption(parsed, "check-registry")
|
|
514
1395
|
});
|
|
515
1396
|
}
|
|
516
1397
|
if (family === "version") {
|
|
517
1398
|
return getCliVersionReport();
|
|
518
1399
|
}
|
|
519
1400
|
if (family === "runtime") {
|
|
520
|
-
return await dispatchRuntime(context, command, parsed);
|
|
1401
|
+
return await dispatchRuntime(context, command, parsed, commandProgress(io, output), prepared);
|
|
521
1402
|
}
|
|
522
1403
|
if (family === "canon") {
|
|
523
|
-
return dispatchCanon(context, command, parsed);
|
|
1404
|
+
return dispatchCanon(context, command, parsed, prepared);
|
|
524
1405
|
}
|
|
525
1406
|
if (family === "project" && command === "register") {
|
|
526
1407
|
return registerProject(context, { root: requiredOption(parsed, "root") });
|
|
@@ -597,16 +1478,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
597
1478
|
return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
|
|
598
1479
|
}
|
|
599
1480
|
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
|
-
});
|
|
1481
|
+
return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
|
|
610
1482
|
}
|
|
611
1483
|
if (family === "protocol" && command === "sync-from-run") {
|
|
612
1484
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
@@ -631,17 +1503,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
631
1503
|
});
|
|
632
1504
|
}
|
|
633
1505
|
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
|
-
});
|
|
1506
|
+
return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
|
|
645
1507
|
}
|
|
646
1508
|
if (family === "plan" && command === "status") {
|
|
647
1509
|
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
@@ -660,12 +1522,19 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
660
1522
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "stage fanout");
|
|
661
1523
|
if (action === "status")
|
|
662
1524
|
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
1525
|
+
if (action === "reconcile")
|
|
1526
|
+
return reconcileVnextFanout(context, { projectRoot, runId, stage, observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
|
|
663
1527
|
if (action === "dispatch") {
|
|
664
1528
|
if (stage === "plan-review")
|
|
665
1529
|
return dispatchVnextPlanReview(context, { projectRoot, runId });
|
|
666
1530
|
return getVnextFanoutStatus(context, { projectRoot, runId, stage });
|
|
667
1531
|
}
|
|
668
|
-
throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch> <RUN> --stage <stage> --project-root <path>", 2);
|
|
1532
|
+
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);
|
|
1533
|
+
}
|
|
1534
|
+
if (family === "stage" && command === "native") {
|
|
1535
|
+
if (requiredPosition(parsed, 0, "native action") !== "observe")
|
|
1536
|
+
throw new AppError("usage", "Use: dd-flow stage native observe <RUN> --stage <stage> --observations-file <native-observations.json> --project-root <path>", 2);
|
|
1537
|
+
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
1538
|
}
|
|
670
1539
|
if (family === "run" && command === "capacity") {
|
|
671
1540
|
if (requiredPosition(parsed, 0, "capacity action") !== "record")
|
|
@@ -677,16 +1546,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
677
1546
|
return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
|
|
678
1547
|
}
|
|
679
1548
|
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
|
-
});
|
|
1549
|
+
return renderWorkerPrompt(context, prepared.workerPrompt?.input ?? workerPromptInput(parsed), prepared.workerPrompt?.value);
|
|
690
1550
|
}
|
|
691
1551
|
if (family === "work" && command === "status") {
|
|
692
1552
|
return getVnextWorkStatus(context, {
|
|
@@ -695,7 +1555,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
695
1555
|
});
|
|
696
1556
|
}
|
|
697
1557
|
if (family === "work" && command === "add-batch") {
|
|
698
|
-
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file") });
|
|
1558
|
+
return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file"), prepared: prepared.workBatch });
|
|
699
1559
|
}
|
|
700
1560
|
if (family === "work" && command === "ls") {
|
|
701
1561
|
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 +1563,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
703
1563
|
if (family === "work" && command === "show")
|
|
704
1564
|
return showWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
705
1565
|
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
|
-
});
|
|
1566
|
+
if (!prepared.workRepair)
|
|
1567
|
+
throw new AppError("cli_input_unprepared", "Repair input was not prepared", 1);
|
|
1568
|
+
return addVnextCodeRepair(context, prepared.workRepair.input, prepared.workRepair.value);
|
|
748
1569
|
}
|
|
749
1570
|
if (family === "work" && command === "deps") {
|
|
750
1571
|
const on = parsed.options.get("on") ?? [];
|
|
@@ -752,21 +1573,34 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
752
1573
|
}
|
|
753
1574
|
if (family === "work" && command === "delete")
|
|
754
1575
|
return deleteWork(context, requiredPosition(parsed, 0, "work-id"));
|
|
1576
|
+
if (family === "work" && command === "launch")
|
|
1577
|
+
return await launchExternalWork(context, { workId: requiredPosition(parsed, 0, "work-id"), ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}), progress: commandProgress(io, output) });
|
|
755
1578
|
if (family === "work" && command === "start") {
|
|
756
1579
|
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
1580
|
+
const recoveryId = optionalOption(parsed, "recovery-id");
|
|
757
1581
|
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
|
|
758
|
-
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), projectRoot });
|
|
1582
|
+
return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot });
|
|
759
1583
|
}
|
|
760
1584
|
if (family === "work" && command === "finish") {
|
|
761
1585
|
const resultFile = optionalOption(parsed, "result-file");
|
|
762
1586
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
763
1587
|
if (Boolean(resultFile) === resultStdin)
|
|
764
1588
|
throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
|
|
765
|
-
const
|
|
766
|
-
|
|
1589
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
1590
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work finish");
|
|
1591
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_finish", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1592
|
+
const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = prepared.retryReason;
|
|
1593
|
+
if (Boolean(retryReceiptId) !== Boolean(retryReason))
|
|
1594
|
+
throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
|
|
1595
|
+
const result = prepared.workFinish?.result ?? (resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin));
|
|
1596
|
+
return await finishWork(context, workId, result, commandProgress(io, output), retryReceiptId ? { receiptId: retryReceiptId, reason: retryReason } : undefined);
|
|
1597
|
+
}
|
|
1598
|
+
if (family === "work" && command === "fail") {
|
|
1599
|
+
const workId = requiredPosition(parsed, 0, "work-id");
|
|
1600
|
+
const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work fail");
|
|
1601
|
+
assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_fail", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
1602
|
+
return failWork(context, workId, requiredOption(parsed, "reason"));
|
|
767
1603
|
}
|
|
768
|
-
if (family === "work" && command === "fail")
|
|
769
|
-
return failWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
770
1604
|
if (family === "work" && command === "cancel")
|
|
771
1605
|
return cancelWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
|
|
772
1606
|
if (family === "work" && command === "retry")
|
|
@@ -788,12 +1622,12 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
788
1622
|
const requireSessionBinding = hasOption(parsed, "require-session-binding");
|
|
789
1623
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
790
1624
|
const stage = requiredOption(parsed, "stage");
|
|
791
|
-
const externalContext =
|
|
1625
|
+
const externalContext = prepared.stageStart?.externalContext;
|
|
792
1626
|
const claimVnextStageStart = (runId) => {
|
|
793
1627
|
registerProject(context, { root: projectRoot });
|
|
794
1628
|
return findRecentMatchingHookEvent(context, {
|
|
795
1629
|
projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
|
|
796
|
-
matchKey: stageStartMatchKey(
|
|
1630
|
+
matchKey: entityReferenceVariants(runId).map((reference) => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
|
|
797
1631
|
errorCode: "trusted_session_binding_required",
|
|
798
1632
|
operation: `vNext ${stage.toUpperCase()} start`
|
|
799
1633
|
}).eventKey;
|
|
@@ -819,37 +1653,37 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
819
1653
|
}
|
|
820
1654
|
const result = launchVnextSpecify(context, {
|
|
821
1655
|
projectRoot,
|
|
822
|
-
...(externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
1656
|
+
...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
|
|
823
1657
|
hookEventId,
|
|
824
1658
|
...(subject ? { slug: subject } : {}),
|
|
825
1659
|
...(externalContext ? { externalContext } : {}),
|
|
826
1660
|
...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {})
|
|
827
|
-
});
|
|
1661
|
+
}, prepared.specifyStart);
|
|
828
1662
|
return attachExternalStageContext(result, externalContext);
|
|
829
1663
|
}
|
|
830
1664
|
if (!bootstrap && stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
831
1665
|
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);
|
|
1666
|
+
return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.protocolizeStart), externalContext);
|
|
833
1667
|
}
|
|
834
1668
|
if (!bootstrap && stage === "plan" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
835
1669
|
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);
|
|
1670
|
+
return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planStart), externalContext);
|
|
837
1671
|
}
|
|
838
1672
|
if (!bootstrap && stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
839
1673
|
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);
|
|
1674
|
+
return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planReviewStart), externalContext);
|
|
841
1675
|
}
|
|
842
1676
|
if (!bootstrap && stage === "code" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
843
1677
|
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);
|
|
1678
|
+
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
1679
|
}
|
|
846
1680
|
if (!bootstrap && stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
847
1681
|
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);
|
|
1682
|
+
return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeReviewStart), externalContext);
|
|
849
1683
|
}
|
|
850
1684
|
if (!bootstrap && stage === "merge" && isVnextMergeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
|
|
851
1685
|
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);
|
|
1686
|
+
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
1687
|
}
|
|
854
1688
|
if (intakeStdin) {
|
|
855
1689
|
throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
|
|
@@ -864,7 +1698,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
864
1698
|
...(hookEventId ? { hookEventId } : {}),
|
|
865
1699
|
...(requireSessionBinding ? { requireSessionBinding: true } : {}),
|
|
866
1700
|
...(dir ? { dir } : {})
|
|
867
|
-
});
|
|
1701
|
+
}, prepared.legacyStageStart);
|
|
868
1702
|
}
|
|
869
1703
|
if (family === "stage" && command === "finish") {
|
|
870
1704
|
const dir = optionalOption(parsed, "dir");
|
|
@@ -874,9 +1708,23 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
874
1708
|
const projectRoot = requiredOption(parsed, "project-root");
|
|
875
1709
|
const runId = requiredPosition(parsed, 0, "run-id");
|
|
876
1710
|
const stage = requiredOption(parsed, "stage");
|
|
1711
|
+
const controlledReply = (result) => controlLifecycleReply(context, { projectRoot, runId }, result);
|
|
877
1712
|
const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
|
|
878
1713
|
const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
|
|
879
1714
|
const resultStdin = hasOption(parsed, "result-stdin");
|
|
1715
|
+
const retryCheckId = optionalOption(parsed, "retry-check");
|
|
1716
|
+
const retryReason = prepared.retryReason;
|
|
1717
|
+
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
1718
|
+
if (retryCheckId && !retryReason?.trim())
|
|
1719
|
+
throw new AppError("usage", "--retry-check requires --reason explaining the recovered environment", 2);
|
|
1720
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
1721
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
1722
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1723
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
1724
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1725
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
1726
|
+
if (protectedLifecycle)
|
|
1727
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, operation: "stage_finish", ...(hookEventId ? { hookEventId } : {}) });
|
|
880
1728
|
if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
|
|
881
1729
|
const outcome = optionalOption(parsed, "outcome");
|
|
882
1730
|
if (!outcome || !["specified", "failed", "cancelled"].includes(outcome)) {
|
|
@@ -888,84 +1736,92 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
888
1736
|
if (!resultFile && !resultStdin) {
|
|
889
1737
|
throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
|
|
890
1738
|
}
|
|
891
|
-
return finishVnextSpecifyStage(context, {
|
|
1739
|
+
return controlledReply(await finishVnextSpecifyStage(context, {
|
|
892
1740
|
projectRoot,
|
|
893
1741
|
runId,
|
|
894
|
-
...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
|
|
1742
|
+
...(resultFile ? { resultFile } : prepared.stageFinish?.value ? {} : { resultJson: await readStdin(io.stdin) }),
|
|
1743
|
+
...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}),
|
|
895
1744
|
outcome: outcome
|
|
896
|
-
});
|
|
1745
|
+
}));
|
|
897
1746
|
}
|
|
898
1747
|
if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
|
|
899
1748
|
if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
900
1749
|
throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
|
|
901
1750
|
}
|
|
902
|
-
return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
|
|
1751
|
+
return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile, ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}) }));
|
|
903
1752
|
}
|
|
904
1753
|
if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
905
1754
|
if (optionalOption(parsed, "code-work-batch-file"))
|
|
906
1755
|
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 });
|
|
1756
|
+
return controlledReply(await finishVnextPlan(context, { projectRoot, runId, ...(prepared.planFinish ? { prepared: prepared.planFinish } : {}) }));
|
|
908
1757
|
}
|
|
909
1758
|
if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
|
|
910
1759
|
if (resultStdin || hasOption(parsed, "outcome"))
|
|
911
1760
|
throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
|
|
912
1761
|
const decisionFile = optionalOption(parsed, "decision-file");
|
|
913
|
-
return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
|
|
1762
|
+
return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}) }));
|
|
914
1763
|
}
|
|
915
1764
|
if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
|
|
916
1765
|
if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
|
|
917
1766
|
throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
|
|
918
1767
|
}
|
|
919
|
-
return await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), progress: commandProgress(io, output) });
|
|
1768
|
+
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
1769
|
}
|
|
921
1770
|
if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
|
|
922
1771
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
923
1772
|
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) });
|
|
1773
|
+
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
1774
|
}
|
|
926
1775
|
if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
|
|
927
1776
|
if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
|
|
928
1777
|
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) });
|
|
1778
|
+
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
1779
|
}
|
|
931
1780
|
if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
|
|
932
1781
|
throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
|
|
933
1782
|
}
|
|
934
|
-
return finishStage(context, {
|
|
1783
|
+
return controlledReply(await finishStage(context, {
|
|
935
1784
|
projectRoot,
|
|
936
1785
|
runId,
|
|
937
1786
|
stage,
|
|
938
1787
|
...(dir ? { dir } : {}),
|
|
939
1788
|
...(semanticFile ? { semanticFile } : {})
|
|
940
|
-
});
|
|
1789
|
+
}, prepared.legacyStageFinish));
|
|
941
1790
|
}
|
|
942
1791
|
if (family === "stage" && command === "pause") {
|
|
943
|
-
|
|
944
|
-
|
|
1792
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
1793
|
+
const runId = requiredPosition(parsed, 0, "run-id");
|
|
1794
|
+
const stage = requiredOption(parsed, "stage");
|
|
1795
|
+
const workId = requiredOption(parsed, "work");
|
|
1796
|
+
const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
|
|
1797
|
+
|| (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
|
|
1798
|
+
|| (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1799
|
+
|| (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
|
|
1800
|
+
|| (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
|
|
1801
|
+
|| (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
|
|
1802
|
+
if (protectedLifecycle)
|
|
1803
|
+
assertStageLifecycleOwner(context, { projectRoot, runId, stage, workId, operation: "stage_pause", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
945
1804
|
return pauseStageForUser(context, {
|
|
946
|
-
projectRoot
|
|
947
|
-
runId
|
|
948
|
-
stage
|
|
949
|
-
workId
|
|
950
|
-
question: await readStdin(io.stdin)
|
|
1805
|
+
projectRoot,
|
|
1806
|
+
runId,
|
|
1807
|
+
stage,
|
|
1808
|
+
workId,
|
|
1809
|
+
question: prepared.interactionText ?? await readStdin(io.stdin)
|
|
951
1810
|
});
|
|
952
1811
|
}
|
|
953
1812
|
if (family === "stage" && command === "resume") {
|
|
954
|
-
if (!hasOption(parsed, "answer-stdin"))
|
|
955
|
-
throw new AppError("usage", "stage resume requires --answer-stdin", 2);
|
|
956
1813
|
const hookEventId = optionalOption(parsed, "hook-event-id");
|
|
957
1814
|
return resumeStageAfterUser(context, {
|
|
958
1815
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
959
1816
|
runId: requiredPosition(parsed, 0, "run-id"),
|
|
960
1817
|
stage: requiredOption(parsed, "stage"),
|
|
961
1818
|
workId: requiredOption(parsed, "work"),
|
|
962
|
-
answer: await readStdin(io.stdin),
|
|
1819
|
+
answer: prepared.interactionText ?? await readStdin(io.stdin),
|
|
1820
|
+
evidence: prepared.resumeEvidence,
|
|
963
1821
|
...(hookEventId ? { hookEventId } : {})
|
|
964
1822
|
});
|
|
965
1823
|
}
|
|
966
1824
|
if (family === "stage" && command === "block") {
|
|
967
|
-
if (!hasOption(parsed, "summary-stdin"))
|
|
968
|
-
throw new AppError("usage", "stage block requires --summary-stdin", 2);
|
|
969
1825
|
const kind = requiredOption(parsed, "kind");
|
|
970
1826
|
if (kind !== "engine" && kind !== "harness" && kind !== "environment")
|
|
971
1827
|
throw new AppError("usage", "--kind must be engine, harness or environment", 2);
|
|
@@ -976,7 +1832,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
976
1832
|
workId: requiredOption(parsed, "work"),
|
|
977
1833
|
kind,
|
|
978
1834
|
code: requiredOption(parsed, "code"),
|
|
979
|
-
summary: await readStdin(io.stdin),
|
|
1835
|
+
summary: prepared.interactionText ?? await readStdin(io.stdin),
|
|
980
1836
|
retryable: hasOption(parsed, "retryable")
|
|
981
1837
|
});
|
|
982
1838
|
}
|
|
@@ -1001,13 +1857,55 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1001
1857
|
return dispatchMergeWorker(context, command, parsed);
|
|
1002
1858
|
}
|
|
1003
1859
|
if (family === "cleanup") {
|
|
1004
|
-
return dispatchCleanup(context, command, parsed);
|
|
1860
|
+
return dispatchCleanup(context, command, parsed, prepared.cleanup);
|
|
1005
1861
|
}
|
|
1006
1862
|
if (family === "session") {
|
|
1007
|
-
return dispatchSession(context, command, parsed);
|
|
1863
|
+
return dispatchSession(context, command, parsed, prepared.sessionPayload);
|
|
1008
1864
|
}
|
|
1009
1865
|
if (family === "run") {
|
|
1010
|
-
|
|
1866
|
+
if (command === "control") {
|
|
1867
|
+
const action = requiredPosition(parsed, 0, "control action");
|
|
1868
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1869
|
+
if (action === "status")
|
|
1870
|
+
return runControlStatus(context, scope);
|
|
1871
|
+
if (action === "reconcile") {
|
|
1872
|
+
if ([...parsed.options.keys()].some(key => !["run", "project-root", "control-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
1873
|
+
throw new AppError("usage", "Use run control reconcile --run ID --project-root PATH --control-id ID --request-id ID", 2);
|
|
1874
|
+
return await reconcileRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id"), requestId: requiredOption(parsed, "request-id") });
|
|
1875
|
+
}
|
|
1876
|
+
if (action === "resume") {
|
|
1877
|
+
if (["force", "grace-ms", "escalate"].some(option => hasOption(parsed, option)))
|
|
1878
|
+
throw new AppError("usage", "Resume does not accept stop or pause escalation options", 2);
|
|
1879
|
+
return await resumeRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), controlId: requiredOption(parsed, "from"), ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {}) });
|
|
1880
|
+
}
|
|
1881
|
+
if (action === "serve")
|
|
1882
|
+
return await serveRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id") });
|
|
1883
|
+
if (action === "pause" || action === "stop")
|
|
1884
|
+
return await requestRunControl(context, {
|
|
1885
|
+
...scope, action, requestId: requiredOption(parsed, "request-id"),
|
|
1886
|
+
...(hasOption(parsed, "force") ? { force: true } : {}),
|
|
1887
|
+
...(hasOption(parsed, "grace-ms") ? { graceMs: Number(requiredOption(parsed, "grace-ms")) } : {}),
|
|
1888
|
+
...(hasOption(parsed, "escalate") ? { escalate: requiredOption(parsed, "escalate") } : {}),
|
|
1889
|
+
...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {})
|
|
1890
|
+
});
|
|
1891
|
+
throw new AppError("usage", "Use: dd-flow run control pause|stop|resume|reconcile|status", 2);
|
|
1892
|
+
}
|
|
1893
|
+
if (command === "drive") {
|
|
1894
|
+
const action = requiredPosition(parsed, 0, "drive action");
|
|
1895
|
+
const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
|
|
1896
|
+
if (action === "status")
|
|
1897
|
+
return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
|
|
1898
|
+
if (action === "launch")
|
|
1899
|
+
return await launchRunController(context, controllerLaunchInput(parsed), prepared.controllerLaunch);
|
|
1900
|
+
if (action === "serve")
|
|
1901
|
+
return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
|
|
1902
|
+
if (action === "answer")
|
|
1903
|
+
return answerRunController(context, controllerAnswerInput(parsed), prepared.controllerAnswer);
|
|
1904
|
+
if (action === "context")
|
|
1905
|
+
return supplyRunControllerContext(context, controllerContextInput(parsed), prepared.controllerContext);
|
|
1906
|
+
throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
|
|
1907
|
+
}
|
|
1908
|
+
return dispatchRun(context, command, parsed, prepared);
|
|
1011
1909
|
}
|
|
1012
1910
|
if (family === "stat") {
|
|
1013
1911
|
return dispatchStat(context, command, parsed);
|
|
@@ -1022,23 +1920,26 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1022
1920
|
return dispatchDashboard(context, command, parsed);
|
|
1023
1921
|
}
|
|
1024
1922
|
if (family === "schema") {
|
|
1025
|
-
return
|
|
1923
|
+
return prepared.schemaValidation;
|
|
1026
1924
|
}
|
|
1027
1925
|
if (family === "memory") {
|
|
1028
1926
|
return dispatchMemory(command, parsed);
|
|
1029
1927
|
}
|
|
1030
1928
|
if (family === "migration") {
|
|
1031
|
-
return dispatchMigration(context, command, parsed, classification);
|
|
1929
|
+
return dispatchMigration(context, command, parsed, classification, prepared);
|
|
1032
1930
|
}
|
|
1033
1931
|
if (family === "codex" && command === "hooks") {
|
|
1034
|
-
return dispatchCodexHooks(context, parsed);
|
|
1932
|
+
return dispatchCodexHooks(context, parsed, prepared);
|
|
1035
1933
|
}
|
|
1036
1934
|
if (family === "codex" && command === "home") {
|
|
1037
|
-
return dispatchCodexHome(context, parsed);
|
|
1935
|
+
return dispatchCodexHome(context, parsed, prepared);
|
|
1038
1936
|
}
|
|
1039
1937
|
if (family === "codex" && command === "hook") {
|
|
1040
1938
|
return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
|
|
1041
1939
|
}
|
|
1940
|
+
if (family === "zcode" && command === "hook") {
|
|
1941
|
+
return dispatchZcodeHook(context, parsed, await readStdin(io.stdin));
|
|
1942
|
+
}
|
|
1042
1943
|
if (family === "zcode" && command === "event") {
|
|
1043
1944
|
return dispatchZcodeEvent(context, parsed, await readStdin(io.stdin));
|
|
1044
1945
|
}
|
|
@@ -1063,6 +1964,16 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1063
1964
|
throw new AppError("usage", "Unknown opencode usage action", 2);
|
|
1064
1965
|
return ingestOpenCodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1065
1966
|
}
|
|
1967
|
+
if (family === "droid" && command === "event") {
|
|
1968
|
+
if (requiredPosition(parsed, 0, "droid event action") !== "handle")
|
|
1969
|
+
throw new AppError("usage", "Unknown droid event action", 2);
|
|
1970
|
+
return handleDroidEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1971
|
+
}
|
|
1972
|
+
if (family === "droid" && command === "usage") {
|
|
1973
|
+
if (requiredPosition(parsed, 0, "droid usage action") !== "ingest")
|
|
1974
|
+
throw new AppError("usage", "Unknown droid usage action", 2);
|
|
1975
|
+
return ingestDroidUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1976
|
+
}
|
|
1066
1977
|
if (family === "agy" && command === "event")
|
|
1067
1978
|
return dispatchAgyEvent(context, parsed, await readStdin(io.stdin));
|
|
1068
1979
|
if (family === "agy" && command === "usage") {
|
|
@@ -1071,11 +1982,14 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
|
|
|
1071
1982
|
return ingestAgyUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
|
|
1072
1983
|
}
|
|
1073
1984
|
if (family === "worktree") {
|
|
1074
|
-
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
|
|
1985
|
+
return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`), prepared);
|
|
1075
1986
|
}
|
|
1076
1987
|
throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
|
|
1077
1988
|
}
|
|
1078
|
-
function
|
|
1989
|
+
function engineBindInput(parsed) {
|
|
1990
|
+
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") };
|
|
1991
|
+
}
|
|
1992
|
+
function dispatchEngine(context, command, parsed, classification, prepared) {
|
|
1079
1993
|
if (command === "install") {
|
|
1080
1994
|
return installCurrentEngine(context, { force: hasOption(parsed, "force") });
|
|
1081
1995
|
}
|
|
@@ -1100,18 +2014,11 @@ function dispatchEngine(context, command, parsed, classification) {
|
|
|
1100
2014
|
});
|
|
1101
2015
|
}
|
|
1102
2016
|
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
|
-
});
|
|
2017
|
+
return bindRunEngine(context, engineBindInput(parsed), prepared.engineBind);
|
|
1111
2018
|
}
|
|
1112
2019
|
throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
|
|
1113
2020
|
}
|
|
1114
|
-
function dispatchMigration(context, command, parsed, classification) {
|
|
2021
|
+
function dispatchMigration(context, command, parsed, classification, prepared) {
|
|
1115
2022
|
if (command === "impact")
|
|
1116
2023
|
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
2024
|
if (command === "plan" || command === "report") {
|
|
@@ -1127,26 +2034,42 @@ function dispatchMigration(context, command, parsed, classification) {
|
|
|
1127
2034
|
});
|
|
1128
2035
|
}
|
|
1129
2036
|
if (command === "verify") {
|
|
1130
|
-
return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
2037
|
+
return prepared.migrationVerification ?? verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
|
|
1131
2038
|
}
|
|
1132
2039
|
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
|
-
});
|
|
2040
|
+
return applyMigration(context, migrationApplyInput(parsed, classification), prepared.migrationApply);
|
|
1144
2041
|
}
|
|
1145
2042
|
throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
|
|
1146
2043
|
}
|
|
1147
|
-
function
|
|
2044
|
+
function protocolTransitionInput(parsed, scopeProjectRoot) {
|
|
2045
|
+
const reason = optionalOption(parsed, "reason");
|
|
2046
|
+
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 } : {}) };
|
|
2047
|
+
}
|
|
2048
|
+
function controllerAnswerInput(parsed) {
|
|
2049
|
+
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") };
|
|
2050
|
+
}
|
|
2051
|
+
function controllerLaunchInput(parsed) {
|
|
2052
|
+
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") } : {}) };
|
|
2053
|
+
}
|
|
2054
|
+
function controllerContextInput(parsed) {
|
|
2055
|
+
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") };
|
|
2056
|
+
}
|
|
2057
|
+
function migrationApplyInput(parsed, classification) {
|
|
2058
|
+
return {
|
|
2059
|
+
projectRoot: requiredOption(parsed, "project-root"),
|
|
2060
|
+
sourceVersion: optionalOption(parsed, "source-version"),
|
|
2061
|
+
targetVersion: optionalOption(parsed, "target-version"),
|
|
2062
|
+
runId: optionalOption(parsed, "run"),
|
|
2063
|
+
backupPath: optionalOption(parsed, "backup-path"),
|
|
2064
|
+
backupCreatedAt: optionalOption(parsed, "backup-created-at"),
|
|
2065
|
+
reportFile: optionalOption(parsed, "file"),
|
|
2066
|
+
allowActive: false,
|
|
2067
|
+
classification
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
function dispatchCanon(context, command, parsed, prepared) {
|
|
1148
2071
|
if (command === "register") {
|
|
1149
|
-
return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
|
|
2072
|
+
return registerCanonRoot(context, { root: requiredOption(parsed, "root") }, prepared.canonRegistration);
|
|
1150
2073
|
}
|
|
1151
2074
|
if (command === "status") {
|
|
1152
2075
|
return getCanonStatus(context, { root: optionalOption(parsed, "root") });
|
|
@@ -1156,6 +2079,15 @@ function dispatchCanon(context, command, parsed) {
|
|
|
1156
2079
|
}
|
|
1157
2080
|
throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
|
|
1158
2081
|
}
|
|
2082
|
+
function mergeJobTarget(parsed, scopeProjectRoot) {
|
|
2083
|
+
return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed) };
|
|
2084
|
+
}
|
|
2085
|
+
function mergeCancellationInput(parsed, scopeProjectRoot) {
|
|
2086
|
+
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") };
|
|
2087
|
+
}
|
|
2088
|
+
function mergeWaitInput(parsed, scopeProjectRoot) {
|
|
2089
|
+
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 };
|
|
2090
|
+
}
|
|
1159
2091
|
function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
1160
2092
|
if (hasOption(parsed, "session-id")) {
|
|
1161
2093
|
throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
|
|
@@ -1179,32 +2111,17 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
|
1179
2111
|
});
|
|
1180
2112
|
}
|
|
1181
2113
|
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
|
-
});
|
|
2114
|
+
return waitNextMergeJob(context, mergeWaitInput(parsed, scopeProjectRoot));
|
|
1190
2115
|
}
|
|
1191
2116
|
if (command === "complete") {
|
|
1192
|
-
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
1193
2117
|
return completeMergeJob(context, {
|
|
1194
|
-
|
|
1195
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
|
|
1196
|
-
workerId: requiredWorkerId(parsed),
|
|
1197
|
-
workspacePath: workspacePathOption(parsed),
|
|
2118
|
+
...mergeJobTarget(parsed, scopeProjectRoot),
|
|
1198
2119
|
summary: requiredOption(parsed, "summary")
|
|
1199
2120
|
});
|
|
1200
2121
|
}
|
|
1201
2122
|
if (command === "fail") {
|
|
1202
|
-
const protocolId = requiredPosition(parsed, 0, "protocol-id");
|
|
1203
2123
|
return failMergeJob(context, {
|
|
1204
|
-
|
|
1205
|
-
projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
|
|
1206
|
-
workerId: requiredWorkerId(parsed),
|
|
1207
|
-
workspacePath: workspacePathOption(parsed),
|
|
2124
|
+
...mergeJobTarget(parsed, scopeProjectRoot),
|
|
1208
2125
|
reason: requiredOption(parsed, "reason"),
|
|
1209
2126
|
requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
|
|
1210
2127
|
});
|
|
@@ -1219,15 +2136,7 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
|
|
|
1219
2136
|
});
|
|
1220
2137
|
}
|
|
1221
2138
|
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
|
-
});
|
|
2139
|
+
return cancelMergeQueueJob(context, mergeCancellationInput(parsed, scopeProjectRoot));
|
|
1231
2140
|
}
|
|
1232
2141
|
throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
|
|
1233
2142
|
}
|
|
@@ -1247,6 +2156,8 @@ async function dispatchMerge(context, command, parsed, progress) {
|
|
|
1247
2156
|
}
|
|
1248
2157
|
if (command === "apply")
|
|
1249
2158
|
return applyVnextMerge(context, { projectRoot: requiredOption(parsed, "project-root"), requestId: requiredPosition(parsed, 0, "merge-request-id"), workId: requiredOption(parsed, "work"), ...(progress ? { progress } : {}) });
|
|
2159
|
+
if (command === "repair")
|
|
2160
|
+
return await repairVnextMerge(context, { projectRoot: requiredOption(parsed, "project-root"), requestId: requiredPosition(parsed, 0, "merge-request-id") });
|
|
1250
2161
|
if (command === "request") {
|
|
1251
2162
|
const action = requiredPosition(parsed, 0, "request action");
|
|
1252
2163
|
const requestId = requiredPosition(parsed, 1, "merge-request-id");
|
|
@@ -1343,7 +2254,7 @@ function completedCommandExitCode(payload) {
|
|
|
1343
2254
|
const exitCode = payload.exit_code;
|
|
1344
2255
|
return typeof exitCode === "number" && Number.isInteger(exitCode) ? exitCode : 0;
|
|
1345
2256
|
}
|
|
1346
|
-
function dispatchCleanup(context, command, parsed) {
|
|
2257
|
+
function dispatchCleanup(context, command, parsed, prepared) {
|
|
1347
2258
|
if (command === "scan") {
|
|
1348
2259
|
return cleanupScan(context, { projectRoot: requiredOption(parsed, "project-root") });
|
|
1349
2260
|
}
|
|
@@ -1351,6 +2262,7 @@ function dispatchCleanup(context, command, parsed) {
|
|
|
1351
2262
|
return cleanupApply(context, {
|
|
1352
2263
|
projectRoot: requiredOption(parsed, "project-root"),
|
|
1353
2264
|
planFile: requiredOption(parsed, "plan-file"),
|
|
2265
|
+
prepared,
|
|
1354
2266
|
reason: requiredOption(parsed, "reason"),
|
|
1355
2267
|
force: hasOption(parsed, "force")
|
|
1356
2268
|
});
|
|
@@ -1371,12 +2283,14 @@ function dispatchProjectConfig(context, parsed) {
|
|
|
1371
2283
|
}
|
|
1372
2284
|
throw new AppError("usage", `Unknown project config action: ${action}`, 2);
|
|
1373
2285
|
}
|
|
1374
|
-
function dispatchSession(context, command, parsed) {
|
|
2286
|
+
function dispatchSession(context, command, parsed, preparedPayload) {
|
|
1375
2287
|
if (command === "register") {
|
|
1376
2288
|
return registerFlowSession(context, {
|
|
2289
|
+
...(preparedPayload ? { preparedPayload } : {}),
|
|
1377
2290
|
payloadBase64: optionalOption(parsed, "payload-base64"),
|
|
1378
2291
|
payloadJson: optionalOption(parsed, "payload-json"),
|
|
1379
2292
|
payloadFile: optionalOption(parsed, "payload-file"),
|
|
2293
|
+
hookEventId: optionalOption(parsed, "hook-event-id"),
|
|
1380
2294
|
sessionId: optionalOption(parsed, "session-id")
|
|
1381
2295
|
});
|
|
1382
2296
|
}
|
|
@@ -1409,7 +2323,45 @@ function dispatchSession(context, command, parsed) {
|
|
|
1409
2323
|
}
|
|
1410
2324
|
throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
|
|
1411
2325
|
}
|
|
1412
|
-
function
|
|
2326
|
+
function snapshotCreateInput(parsed) {
|
|
2327
|
+
const recoveryId = optionalOption(parsed, "recovery-id"), stageEntry = optionalOption(parsed, "stage-entry");
|
|
2328
|
+
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 } : {}) };
|
|
2329
|
+
}
|
|
2330
|
+
function snapshotRestoreInput(parsed) {
|
|
2331
|
+
return { snapshot: requiredOption(parsed, "snapshot"), projectRoot: requiredOption(parsed, "project-root"), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), ...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}) };
|
|
2332
|
+
}
|
|
2333
|
+
function runStartInput(parsed) {
|
|
2334
|
+
const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
|
|
2335
|
+
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")) };
|
|
2336
|
+
}
|
|
2337
|
+
function runForkInput(parsed) {
|
|
2338
|
+
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") } : {}) };
|
|
2339
|
+
}
|
|
2340
|
+
function recoveryCaptureInput(parsed) {
|
|
2341
|
+
return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") };
|
|
2342
|
+
}
|
|
2343
|
+
function recoveryResumeInput(parsed) {
|
|
2344
|
+
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") };
|
|
2345
|
+
}
|
|
2346
|
+
function dispatchRun(context, command, parsed, prepared = {}) {
|
|
2347
|
+
if (command === "recovery") {
|
|
2348
|
+
const action = requiredPosition(parsed, 0, "recovery action");
|
|
2349
|
+
const projectRoot = requiredOption(parsed, "project-root");
|
|
2350
|
+
const runId = requiredPosition(parsed, 1, "run-id");
|
|
2351
|
+
if (action === "inspect")
|
|
2352
|
+
return inspectRunRecovery(context, { projectRoot, runId });
|
|
2353
|
+
if (action === "begin")
|
|
2354
|
+
return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") }, prepared.recoveryPayload);
|
|
2355
|
+
if (action === "seal")
|
|
2356
|
+
return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") }, prepared.recoveryPayload);
|
|
2357
|
+
if (action === "capture")
|
|
2358
|
+
return recordRecoveryCapture(context, recoveryCaptureInput(parsed), prepared.recoveryCapture);
|
|
2359
|
+
if (action === "resume")
|
|
2360
|
+
return resumeRunRecovery(context, recoveryResumeInput(parsed), prepared.recoveryResume);
|
|
2361
|
+
if (action === "accept")
|
|
2362
|
+
return acceptRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
|
|
2363
|
+
throw new AppError("usage", "Use: dd-flow run recovery inspect|begin|seal|capture|resume|accept", 2);
|
|
2364
|
+
}
|
|
1413
2365
|
if (command === "snapshot") {
|
|
1414
2366
|
const action = requiredPosition(parsed, 0, "snapshot action");
|
|
1415
2367
|
if (action === "bootstrap") {
|
|
@@ -1420,56 +2372,27 @@ function dispatchRun(context, command, parsed) {
|
|
|
1420
2372
|
output: requiredOption(parsed, "output")
|
|
1421
2373
|
});
|
|
1422
2374
|
if (bootstrapAction === "restore")
|
|
1423
|
-
return restoreEvalBootstrapSnapshot(context,
|
|
1424
|
-
snapshot: requiredOption(parsed, "snapshot"),
|
|
1425
|
-
projectRoot: requiredOption(parsed, "project-root")
|
|
1426
|
-
});
|
|
2375
|
+
return restoreEvalBootstrapSnapshot(context, snapshotRestoreInput(parsed), prepared.bootstrapRestore);
|
|
1427
2376
|
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore", 2);
|
|
1428
2377
|
}
|
|
1429
2378
|
if (action === "create") {
|
|
1430
|
-
|
|
1431
|
-
const stageEntry = optionalOption(parsed, "stage-entry");
|
|
1432
|
-
if (candidate && stageEntry !== undefined)
|
|
1433
|
-
throw new AppError("usage", "Use either --candidate or --stage-entry, not both", 2);
|
|
1434
|
-
if (!candidate && stageEntry === undefined)
|
|
1435
|
-
throw new AppError("usage", "Snapshot create requires --candidate or --stage-entry", 2);
|
|
1436
|
-
const input = {
|
|
1437
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1438
|
-
runId: requiredPosition(parsed, 1, "run-id"),
|
|
1439
|
-
output: requiredOption(parsed, "output")
|
|
1440
|
-
};
|
|
1441
|
-
return candidate
|
|
1442
|
-
? createEvalRunSnapshot(context, { ...input, candidate: true })
|
|
1443
|
-
: createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
|
|
2379
|
+
return createEvalRunSnapshot(context, snapshotCreateInput(parsed));
|
|
1444
2380
|
}
|
|
1445
2381
|
if (action === "restore")
|
|
1446
|
-
return restoreEvalRunSnapshot(context,
|
|
1447
|
-
snapshot: requiredOption(parsed, "snapshot"),
|
|
1448
|
-
projectRoot: requiredOption(parsed, "project-root")
|
|
1449
|
-
});
|
|
2382
|
+
return restoreEvalRunSnapshot(context, snapshotRestoreInput(parsed), prepared.snapshotRestore);
|
|
1450
2383
|
throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore | dd-flow run snapshot create <RUN> (--stage-entry <stage>|--candidate) --project-root <path> --output <directory> | dd-flow run snapshot restore --snapshot <directory> --project-root <path>", 2);
|
|
1451
2384
|
}
|
|
2385
|
+
if (command === "fork") {
|
|
2386
|
+
return forkRunFromSnapshot(context, runForkInput(parsed), prepared.runFork);
|
|
2387
|
+
}
|
|
1452
2388
|
if (command === "prepare-vnext-specify") {
|
|
1453
|
-
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
|
|
2389
|
+
return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
|
|
1454
2390
|
}
|
|
1455
2391
|
if (command === "start") {
|
|
1456
2392
|
if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
|
|
1457
2393
|
assertUpgradeRunStorageReady(context, requiredOption(parsed, "project-root"));
|
|
1458
2394
|
}
|
|
1459
|
-
|
|
1460
|
-
return startFlowRun(context, {
|
|
1461
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1462
|
-
workspaceRoot: optionalOption(parsed, "workspace-root"),
|
|
1463
|
-
flowKind: requiredOption(parsed, "flow-kind"),
|
|
1464
|
-
subjectType: requiredOption(parsed, "subject-type"),
|
|
1465
|
-
subjectId: requiredOption(parsed, "subject-id"),
|
|
1466
|
-
slug: requiredOption(parsed, "slug"),
|
|
1467
|
-
nextAction: optionalOption(parsed, "next-action"),
|
|
1468
|
-
preset: optionalOption(parsed, "preset"),
|
|
1469
|
-
taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
|
|
1470
|
-
...(protocolOverrides ? { protocolOverrides } : {}),
|
|
1471
|
-
runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
|
|
1472
|
-
});
|
|
2395
|
+
return startFlowRun(context, prepared.runStart?.input ?? runStartInput(parsed), prepared.runStart?.value);
|
|
1473
2396
|
}
|
|
1474
2397
|
if (command === "status") {
|
|
1475
2398
|
return getFlowRunStatus(context, {
|
|
@@ -1539,83 +2462,163 @@ function dispatchRun(context, command, parsed) {
|
|
|
1539
2462
|
throw new AppError("usage", `Unknown run flags command: ${action}`, 2);
|
|
1540
2463
|
}
|
|
1541
2464
|
if (command === "attach-stage") {
|
|
1542
|
-
return attachFlowRunStage(context,
|
|
1543
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1544
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1545
|
-
stage: requiredOption(parsed, "stage"),
|
|
1546
|
-
dir: requiredOption(parsed, "dir"),
|
|
1547
|
-
status: requiredOption(parsed, "status"),
|
|
1548
|
-
dataSchemaId: optionalOption(parsed, "data-schema-id")
|
|
1549
|
-
});
|
|
2465
|
+
return attachFlowRunStage(context, stageAttachmentInput(parsed));
|
|
1550
2466
|
}
|
|
1551
2467
|
if (command === "complete-stage") {
|
|
1552
|
-
|
|
1553
|
-
return completeFlowRunStage(context, {
|
|
1554
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1555
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1556
|
-
stage: requiredOption(parsed, "stage"),
|
|
1557
|
-
status: requiredOption(parsed, "status"),
|
|
1558
|
-
stageReport,
|
|
1559
|
-
data: optionalOption(parsed, "data"),
|
|
1560
|
-
dataSchemaId: optionalOption(parsed, "data-schema-id"),
|
|
1561
|
-
report: optionalOption(parsed, "report"),
|
|
1562
|
-
aliases: allOptions(parsed, "alias")
|
|
1563
|
-
});
|
|
2468
|
+
return completeFlowRunStage(context, stageCompletionInput(parsed));
|
|
1564
2469
|
}
|
|
1565
2470
|
if (command === "complete") {
|
|
1566
2471
|
throw new AppError("usage", "run complete is retired; only an operator may use run override", 2);
|
|
1567
2472
|
}
|
|
1568
2473
|
if (command === "override") {
|
|
1569
|
-
|
|
1570
|
-
if (!['cancelled', 'failed'].includes(status)) {
|
|
1571
|
-
throw new AppError("usage", "run override status must be cancelled or failed", 2, { status });
|
|
1572
|
-
}
|
|
1573
|
-
return completeFlowRun(context, {
|
|
1574
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1575
|
-
runId: requiredPosition(parsed, 0, "run-id"),
|
|
1576
|
-
status,
|
|
1577
|
-
verdict: optionalOption(parsed, "verdict"),
|
|
1578
|
-
nextAction: optionalOption(parsed, "next-action"),
|
|
1579
|
-
manualOverrideReason: requiredOption(parsed, "reason")
|
|
1580
|
-
});
|
|
2474
|
+
return completeFlowRun(context, runOverrideInput(parsed));
|
|
1581
2475
|
}
|
|
1582
2476
|
throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
|
|
1583
2477
|
}
|
|
1584
|
-
|
|
2478
|
+
function stageAttachmentInput(parsed) {
|
|
2479
|
+
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") };
|
|
2480
|
+
}
|
|
2481
|
+
function runOverrideInput(parsed) {
|
|
2482
|
+
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") };
|
|
2483
|
+
}
|
|
2484
|
+
function stageCompletionInput(parsed) {
|
|
2485
|
+
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") };
|
|
2486
|
+
}
|
|
2487
|
+
function runtimeServiceInput(parsed) {
|
|
2488
|
+
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 };
|
|
2489
|
+
}
|
|
2490
|
+
function runtimeProcessStopInput(parsed) {
|
|
2491
|
+
const graceMs = optionalNumber(parsed, "grace-ms");
|
|
2492
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(graceMs !== undefined ? { graceMs } : {}) };
|
|
2493
|
+
}
|
|
2494
|
+
function runtimeScopeResumeInput(parsed) {
|
|
2495
|
+
return { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") };
|
|
2496
|
+
}
|
|
2497
|
+
function runtimeProcessRegistrationInput(context, parsed) {
|
|
2498
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
2499
|
+
const budget = parseRuntimeBudget(optionalOption(parsed, "budget-json"));
|
|
2500
|
+
const kind = requiredOption(parsed, "kind");
|
|
2501
|
+
const projectId = optionalOption(parsed, "project-id") ?? null, runId = optionalOption(parsed, "run") ?? null;
|
|
2502
|
+
const guard = projectId && runId ? recoveryGuard(context, projectId, runId) : null;
|
|
2503
|
+
const run = projectId && runId ? context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]) : null;
|
|
2504
|
+
return {
|
|
2505
|
+
kind, ownerId: requiredOption(parsed, "owner"), uniqueActiveOperation: kind === "eval-observer",
|
|
2506
|
+
...(kind === "eval-observer" ? { pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(optionalPositiveNumber(parsed, "process-group-id", true) ? { processGroupId: optionalPositiveNumber(parsed, "process-group-id", true) } : {}) } : {}),
|
|
2507
|
+
ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), projectId, runId,
|
|
2508
|
+
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
2509
|
+
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
2510
|
+
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs !== undefined ? { leaseMs } : {}),
|
|
2511
|
+
metadata: { ...(budget ? { budget, harness: optionalOption(parsed, "harness") ?? null } : {}), ...(projectId && runId ? { run_generation: guard?.generation ?? 0, run_admitted: Boolean(run) && !["draining", "sealed", "resuming"].includes(guard?.status ?? "") } : {}), ...(optionalOption(parsed, "dispatch-barrier") ? { dispatch_barrier: optionalOption(parsed, "dispatch-barrier") } : {}), ...(optionalOption(parsed, "role") ? { role: optionalOption(parsed, "role") } : {}), ...(optionalOption(parsed, "state-dir") ? { state_dir: optionalOption(parsed, "state-dir") } : {}), ...(optionalOption(parsed, "adapter-executable") ? { adapter_executable: optionalOption(parsed, "adapter-executable") } : {}) }
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function runtimeProcessConfirmationInput(parsed) {
|
|
2515
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true), processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
|
|
2516
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(processGroupId !== undefined ? { processGroupId } : {}), ...(leaseMs !== undefined ? { leaseMs } : {}) };
|
|
2517
|
+
}
|
|
2518
|
+
function runtimeProcessHeartbeatInput(parsed) {
|
|
2519
|
+
const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
|
|
2520
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs !== undefined ? { leaseMs } : {}) };
|
|
2521
|
+
}
|
|
2522
|
+
function runtimeProcessFinishInput(parsed) {
|
|
2523
|
+
const state = requiredOption(parsed, "state");
|
|
2524
|
+
if (state !== "stopped" && state !== "failed")
|
|
2525
|
+
throw new AppError("validation", "--state must be stopped or failed", 2);
|
|
2526
|
+
return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state, reason: optionalOption(parsed, "reason") ?? null };
|
|
2527
|
+
}
|
|
2528
|
+
async function dispatchRuntime(context, command, parsed, progress, prepared = {}) {
|
|
2529
|
+
if (command === "scope") {
|
|
2530
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "reconcile") {
|
|
2531
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2532
|
+
throw new AppError("usage", "Use runtime scope reconcile --scope-id ID --generation N --request-id ID", 2);
|
|
2533
|
+
return { ok: true, ...await reconcileRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id")) };
|
|
2534
|
+
}
|
|
2535
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
|
|
2536
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2537
|
+
throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
|
|
2538
|
+
return { ok: true, ...await launchRuntimeScopeResume(context, runtimeScopeResumeInput(parsed)) };
|
|
2539
|
+
}
|
|
2540
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
|
|
2541
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2542
|
+
throw new AppError("usage", "Use runtime scope control --scope-id ID --request-id ID --mode pause|stop [--manifest-json JSON]", 2);
|
|
2543
|
+
const mode = requiredOption(parsed, "mode");
|
|
2544
|
+
if (mode !== "pause" && mode !== "stop")
|
|
2545
|
+
throw new AppError("usage", "Scope control mode must be pause or stop", 2);
|
|
2546
|
+
return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, undefined, undefined, undefined, prepared.runtimeManifest) };
|
|
2547
|
+
}
|
|
2548
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
|
|
2549
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2550
|
+
throw new AppError("usage", "Invalid internal scope worker arguments", 2);
|
|
2551
|
+
return { ok: true, ...await serveRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation"))) };
|
|
2552
|
+
}
|
|
2553
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "stop") {
|
|
2554
|
+
if ([...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2555
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID", 2);
|
|
2556
|
+
return { ok: true, ...await stopRuntimeScope(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
2557
|
+
}
|
|
2558
|
+
if (parsed.positional.length === 1 && parsed.positional[0] === "status") {
|
|
2559
|
+
if ([...parsed.options.keys()].some(key => key !== "scope-id") || [...parsed.options.values()].some(values => values.length !== 1))
|
|
2560
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope status --scope-id EVAL_ID", 2);
|
|
2561
|
+
return { ok: true, ...runtimeBudgetStatus(context, requiredOption(parsed, "scope-id")) };
|
|
2562
|
+
}
|
|
2563
|
+
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))
|
|
2564
|
+
throw new AppError("usage", "Usage: dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID", 2);
|
|
2565
|
+
return { ok: true, ...fenceRuntimeBudget(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
|
|
2566
|
+
}
|
|
1585
2567
|
if (command !== "process")
|
|
1586
|
-
throw new AppError("usage", "Usage: dd-flow runtime process
|
|
2568
|
+
throw new AppError("usage", "Usage: dd-flow runtime <scope|process>; see dd-flow runtime scope --help", 2);
|
|
1587
2569
|
const action = requiredPosition(parsed, 0, "runtime process action");
|
|
2570
|
+
if (action === "check-admission")
|
|
2571
|
+
return { ok: true, ...admitRuntimeCheck(context, requiredOption(parsed, "id"), requiredOption(parsed, "lease-token")) };
|
|
2572
|
+
if (action === "start")
|
|
2573
|
+
return await startRuntimeService(context, { ...runtimeServiceInput(parsed), progress });
|
|
2574
|
+
if (action === "stop")
|
|
2575
|
+
return { ok: true, process: await stopManagedProcess(context, runtimeProcessStopInput(parsed)) };
|
|
1588
2576
|
if (action === "register") {
|
|
1589
|
-
const
|
|
1590
|
-
const process = registerManagedProcess(context, {
|
|
1591
|
-
kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
|
|
1592
|
-
projectId: optionalOption(parsed, "project-id") ?? null, runId: optionalOption(parsed, "run") ?? null,
|
|
1593
|
-
workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
|
|
1594
|
-
operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
|
|
1595
|
-
stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
|
|
1596
|
-
});
|
|
2577
|
+
const process = registerManagedProcess(context, runtimeProcessRegistrationInput(context, parsed));
|
|
1597
2578
|
return { ok: true, process };
|
|
1598
2579
|
}
|
|
1599
|
-
if (action === "confirm")
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
return { ok:
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2580
|
+
if (action === "confirm")
|
|
2581
|
+
return { ok: true, process: confirmManagedProcess(context, runtimeProcessConfirmationInput(parsed)) };
|
|
2582
|
+
if (action === "heartbeat")
|
|
2583
|
+
return { ok: heartbeatManagedProcess(context, runtimeProcessHeartbeatInput(parsed)) };
|
|
2584
|
+
if (action === "admission") {
|
|
2585
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
2586
|
+
if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || record.state !== "running" || !record.pid || !record.pid_started_at || !processIsAlive(record))
|
|
2587
|
+
throw new AppError("process_ownership_unknown", "Productive dispatch requires its current confirmed runtime owner", 1);
|
|
2588
|
+
const metadata = JSON.parse(record.metadata_json);
|
|
2589
|
+
if (metadata.state_dir !== requiredOption(parsed, "state-dir"))
|
|
2590
|
+
throw new AppError("process_ownership_unknown", "Runtime admission belongs to another state directory", 1);
|
|
2591
|
+
if (record.project_id && record.run_id) {
|
|
2592
|
+
if (managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
2593
|
+
throw new AppError("process_runtime_scope_mismatch", "Runtime admission belongs to another or unproven flow home", 1);
|
|
2594
|
+
const recovering = recoveryRuntimeAdmission(context, record, { stateDir: requiredOption(parsed, "state-dir"), daemonId: optionalOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), operation: optionalOption(parsed, "operation"), nativeSessionId: optionalOption(parsed, "native-session-id"), promptSha256: optionalOption(parsed, "prompt-sha256") });
|
|
2595
|
+
if (!recovering) {
|
|
2596
|
+
if (metadata.run_admitted !== true || !Number.isSafeInteger(metadata.run_generation))
|
|
2597
|
+
throw new AppError("run_recovery_guarded", "Runtime was not admitted before the RUN fence; reconcile its launch", 1);
|
|
2598
|
+
assertRunMutationAllowed(context, record.project_id, record.run_id, metadata.run_generation);
|
|
2599
|
+
}
|
|
2600
|
+
const run = context.db.get("SELECT status FROM runs WHERE project_id = ? AND id = ?", [record.project_id, record.run_id]);
|
|
2601
|
+
const retainedHitlPrompt = recovering && ["paused", "waiting_for_user"].includes(run?.status ?? "") && ["session.prompt", "session.resume"].includes(optionalOption(parsed, "operation") ?? "") && context.db.get("SELECT 1 FROM run_recovery_bindings b JOIN run_recovery_guards g ON g.recovery_id = b.recovery_id WHERE g.project_id = ? AND g.run_id = ? AND g.generation = ? AND b.daemon_id = ? AND b.provider_session_id = ?", [record.project_id, record.run_id, metadata.run_generation, optionalOption(parsed, "daemon-id"), optionalOption(parsed, "native-session-id")]);
|
|
2602
|
+
const acceptedHitlPrompt = ["paused", "waiting_for_user"].includes(run?.status ?? "") && controllerHitlAdmission(context, record, { stateDir: requiredOption(parsed, "state-dir"), operationId: optionalOption(parsed, "operation-id"), operation: optionalOption(parsed, "operation"), nativeSessionId: optionalOption(parsed, "native-session-id"), promptSha256: optionalOption(parsed, "prompt-sha256") });
|
|
2603
|
+
if (!run || run.status !== "running" && !retainedHitlPrompt && !acceptedHitlPrompt && !["judge", "probe"].includes(metadata.role ?? ""))
|
|
2604
|
+
throw new AppError("invalid_run_state", "Subject dispatch requires a running RUN or its retained recovery/HITL prompt", 1);
|
|
2605
|
+
}
|
|
2606
|
+
const budget = reserveProviderTurn(context, record, { operationId: optionalOption(parsed, "operation-id") ?? "", operation: optionalOption(parsed, "operation") ?? "", sessionId: optionalOption(parsed, "native-session-id"), daemonId: optionalOption(parsed, "daemon-id") });
|
|
2607
|
+
return { ok: true, ...budget, process_id: record.id, generation: metadata.run_generation ?? null };
|
|
1607
2608
|
}
|
|
1608
|
-
if (action === "
|
|
1609
|
-
const
|
|
1610
|
-
if (
|
|
1611
|
-
throw new AppError("
|
|
1612
|
-
return
|
|
2609
|
+
if (action === "release-turn") {
|
|
2610
|
+
const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
|
|
2611
|
+
if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
|
|
2612
|
+
throw new AppError("process_ownership_unknown", "Budget release requires its retained physical owner", 1);
|
|
2613
|
+
return releaseProviderTurns(context, record, { daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") }, prepared.providerTurnRelease);
|
|
1613
2614
|
}
|
|
2615
|
+
if (action === "finish")
|
|
2616
|
+
return { ok: finishManagedProcess(context, runtimeProcessFinishInput(parsed)) };
|
|
1614
2617
|
if (action === "status")
|
|
1615
2618
|
return { ok: true, processes: managedProcessStatus(context) };
|
|
1616
2619
|
if (action === "reconcile")
|
|
1617
|
-
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"),
|
|
1618
|
-
throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
|
|
2620
|
+
return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalNumber(parsed, "grace-ms")) };
|
|
2621
|
+
throw new AppError("usage", "Usage: dd-flow runtime process <start|stop|register|confirm|heartbeat|admission|finish|status|reconcile>", 2);
|
|
1619
2622
|
}
|
|
1620
2623
|
function dispatchStat(context, command, parsed) {
|
|
1621
2624
|
if (command === "usage") {
|
|
@@ -1802,7 +2805,9 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1802
2805
|
// searching project directories: run_id is a structural field in the
|
|
1803
2806
|
// submitted artifact and the database owns its materialized run_root.
|
|
1804
2807
|
const explicitRunId = optionalOption(parsed, "run");
|
|
1805
|
-
const
|
|
2808
|
+
const data = prepareJsonFile(file, "schema input").value;
|
|
2809
|
+
const declared = data && typeof data === "object" && "run_id" in data ? data.run_id : undefined;
|
|
2810
|
+
const declaredRunId = explicitRunId ?? (typeof declared === "string" && declared.trim() ? declared : undefined);
|
|
1806
2811
|
const fileRunRoot = path.basename(file) === "run.json" ? path.dirname(path.resolve(file)) : undefined;
|
|
1807
2812
|
const bound = projectRoot
|
|
1808
2813
|
? context.db.get("SELECT r.id, r.run_root FROM runs r JOIN projects p ON p.id = r.project_id WHERE p.root = ? AND (r.id = ? OR r.run_root = ?) LIMIT 1", [fs.realpathSync(projectRoot), declaredRunId ?? "", fileRunRoot ?? ""])
|
|
@@ -1810,6 +2815,7 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1810
2815
|
return validateSchema({
|
|
1811
2816
|
schemaName: requiredOption(parsed, "schema"),
|
|
1812
2817
|
file,
|
|
2818
|
+
data,
|
|
1813
2819
|
...(schemaDir ? { schemaDir } : {}),
|
|
1814
2820
|
...(projectRoot ? { projectRoot } : {}),
|
|
1815
2821
|
...(declaredRunId || bound?.id ? { runId: declaredRunId ?? bound.id } : {}),
|
|
@@ -1819,14 +2825,11 @@ function dispatchSchema(context, command, parsed) {
|
|
|
1819
2825
|
}
|
|
1820
2826
|
throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
|
|
1821
2827
|
}
|
|
1822
|
-
function
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1827
|
-
catch {
|
|
1828
|
-
return undefined;
|
|
1829
|
-
}
|
|
2828
|
+
function laneWorkspaceInput(parsed) {
|
|
2829
|
+
return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workspacePath: requiredOption(parsed, "path"), branch: optionalOption(parsed, "branch") };
|
|
2830
|
+
}
|
|
2831
|
+
function laneWaiterCancelInput(parsed) {
|
|
2832
|
+
return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"), reason: requiredOption(parsed, "reason") };
|
|
1830
2833
|
}
|
|
1831
2834
|
async function dispatchLane(context, command, parsed) {
|
|
1832
2835
|
if (command === "status") {
|
|
@@ -1838,24 +2841,14 @@ async function dispatchLane(context, command, parsed) {
|
|
|
1838
2841
|
if (command === "waiter") {
|
|
1839
2842
|
const action = requiredPosition(parsed, 0, "lane waiter action");
|
|
1840
2843
|
if (action === "cancel") {
|
|
1841
|
-
return cancelLaneWaiter(context,
|
|
1842
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1843
|
-
lane: requiredOption(parsed, "lane"),
|
|
1844
|
-
workerId: requiredOption(parsed, "worker-id"),
|
|
1845
|
-
reason: requiredOption(parsed, "reason")
|
|
1846
|
-
});
|
|
2844
|
+
return cancelLaneWaiter(context, laneWaiterCancelInput(parsed));
|
|
1847
2845
|
}
|
|
1848
2846
|
throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
|
|
1849
2847
|
}
|
|
1850
2848
|
if (command === "workspace") {
|
|
1851
2849
|
const action = requiredPosition(parsed, 0, "lane workspace action");
|
|
1852
2850
|
if (action === "set") {
|
|
1853
|
-
return setLaneWorkspace(context,
|
|
1854
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1855
|
-
lane: requiredOption(parsed, "lane"),
|
|
1856
|
-
workspacePath: requiredOption(parsed, "path"),
|
|
1857
|
-
branch: optionalOption(parsed, "branch")
|
|
1858
|
-
});
|
|
2851
|
+
return setLaneWorkspace(context, laneWorkspaceInput(parsed));
|
|
1859
2852
|
}
|
|
1860
2853
|
if (action === "check") {
|
|
1861
2854
|
return checkLaneWorkspace(context, {
|
|
@@ -1927,7 +2920,7 @@ async function dispatchLane(context, command, parsed) {
|
|
|
1927
2920
|
}
|
|
1928
2921
|
throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
|
|
1929
2922
|
}
|
|
1930
|
-
function dispatchCodexHooks(context, parsed) {
|
|
2923
|
+
function dispatchCodexHooks(context, parsed, prepared) {
|
|
1931
2924
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
1932
2925
|
if (action === "print") {
|
|
1933
2926
|
return printCodexHooks(context, {
|
|
@@ -1949,7 +2942,7 @@ function dispatchCodexHooks(context, parsed) {
|
|
|
1949
2942
|
target: optionalOption(parsed, "target"),
|
|
1950
2943
|
profile: optionalOption(parsed, "profile"),
|
|
1951
2944
|
yes: hasOption(parsed, "yes")
|
|
1952
|
-
});
|
|
2945
|
+
}, prepared.codexHooks);
|
|
1953
2946
|
}
|
|
1954
2947
|
if (action === "remove") {
|
|
1955
2948
|
return removeCodexHooks(context, {
|
|
@@ -1957,11 +2950,14 @@ function dispatchCodexHooks(context, parsed) {
|
|
|
1957
2950
|
target: optionalOption(parsed, "target"),
|
|
1958
2951
|
profile: optionalOption(parsed, "profile"),
|
|
1959
2952
|
yes: hasOption(parsed, "yes")
|
|
1960
|
-
});
|
|
2953
|
+
}, prepared.codexHooks);
|
|
1961
2954
|
}
|
|
1962
2955
|
throw new AppError("usage", `Unknown codex hooks action: ${action}`, 2);
|
|
1963
2956
|
}
|
|
1964
|
-
function
|
|
2957
|
+
function codexHomeInput(parsed) {
|
|
2958
|
+
return { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), sourceHome: optionalOption(parsed, "source-home"), targetHome: optionalOption(parsed, "target-home") };
|
|
2959
|
+
}
|
|
2960
|
+
function dispatchCodexHome(context, parsed, prepared) {
|
|
1965
2961
|
const action = requiredPosition(parsed, 0, "codex home action");
|
|
1966
2962
|
if (action === "plan") {
|
|
1967
2963
|
return planCodexHome(context, {
|
|
@@ -1972,12 +2968,7 @@ function dispatchCodexHome(context, parsed) {
|
|
|
1972
2968
|
});
|
|
1973
2969
|
}
|
|
1974
2970
|
if (action === "init") {
|
|
1975
|
-
return initCodexHome(context,
|
|
1976
|
-
projectRoot: requiredOption(parsed, "project-root"),
|
|
1977
|
-
profile: optionalOption(parsed, "profile"),
|
|
1978
|
-
sourceHome: optionalOption(parsed, "source-home"),
|
|
1979
|
-
targetHome: optionalOption(parsed, "target-home")
|
|
1980
|
-
});
|
|
2971
|
+
return initCodexHome(context, codexHomeInput(parsed), prepared.codexHome);
|
|
1981
2972
|
}
|
|
1982
2973
|
if (action === "status") {
|
|
1983
2974
|
return getCodexHomeStatus(context, {
|
|
@@ -2000,11 +2991,14 @@ function dispatchCodexHome(context, parsed) {
|
|
|
2000
2991
|
}
|
|
2001
2992
|
throw new AppError("usage", `Unknown codex home action: ${action}`, 2);
|
|
2002
2993
|
}
|
|
2003
|
-
function dispatchCodexHook(context, parsed, stdin) {
|
|
2994
|
+
async function dispatchCodexHook(context, parsed, stdin) {
|
|
2004
2995
|
const action = requiredPosition(parsed, 0, "codex hook action");
|
|
2005
2996
|
if (action === "handle") {
|
|
2006
2997
|
const isolatedHome = isolatedDdFlowHomeFromHookStdin(stdin);
|
|
2007
|
-
const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }) : context;
|
|
2998
|
+
const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }, "hook") : context;
|
|
2999
|
+
const proof = await verifyCodexHookDelivery(hookContext.env, JSON.parse(stdin || "{}"));
|
|
3000
|
+
if (proof)
|
|
3001
|
+
hookContext.codexHookProof = proof;
|
|
2008
3002
|
const result = handleCodexHook(hookContext, {
|
|
2009
3003
|
event: requiredOption(parsed, "event"),
|
|
2010
3004
|
projectRoot: optionalOption(parsed, "project-root"),
|
|
@@ -2027,6 +3021,15 @@ function dispatchZcodeEvent(context, parsed, stdin) {
|
|
|
2027
3021
|
}
|
|
2028
3022
|
throw new AppError("usage", `Unknown zcode event action: ${action}`, 2);
|
|
2029
3023
|
}
|
|
3024
|
+
function dispatchZcodeHook(context, parsed, stdin) {
|
|
3025
|
+
if (requiredPosition(parsed, 0, "zcode hook action") !== "handle")
|
|
3026
|
+
throw new AppError("usage", "Unknown zcode hook action", 2);
|
|
3027
|
+
const result = handleZcodeHook(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
3028
|
+
if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
|
|
3029
|
+
return { hookSpecificOutput: result.hookSpecificOutput };
|
|
3030
|
+
}
|
|
3031
|
+
return {};
|
|
3032
|
+
}
|
|
2030
3033
|
function dispatchGrokEvent(context, parsed, stdin) {
|
|
2031
3034
|
const action = requiredPosition(parsed, 0, "grok event action");
|
|
2032
3035
|
if (action === "handle") {
|
|
@@ -2050,7 +3053,7 @@ function dispatchAgyEvent(context, parsed, stdin) {
|
|
|
2050
3053
|
return handleAgyEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
|
|
2051
3054
|
throw new AppError("usage", `Unknown agy event action: ${action}`, 2);
|
|
2052
3055
|
}
|
|
2053
|
-
function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
3056
|
+
function dispatchWorktree(context, command, parsed, scopeProjectRoot, prepared) {
|
|
2054
3057
|
if (command === "plan") {
|
|
2055
3058
|
return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
2056
3059
|
}
|
|
@@ -2061,13 +3064,13 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
|
2061
3064
|
branch: requiredOption(parsed, "branch"),
|
|
2062
3065
|
base: requiredOption(parsed, "base"),
|
|
2063
3066
|
path: requiredOption(parsed, "path")
|
|
2064
|
-
});
|
|
3067
|
+
}, prepared.worktreeCreate);
|
|
2065
3068
|
}
|
|
2066
3069
|
if (command === "status") {
|
|
2067
3070
|
return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
2068
3071
|
}
|
|
2069
3072
|
if (command === "bootstrap") {
|
|
2070
|
-
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
|
|
3073
|
+
return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot }, prepared.worktreeBootstrap);
|
|
2071
3074
|
}
|
|
2072
3075
|
if (command === "close") {
|
|
2073
3076
|
return closeWorktree(context, {
|
|
@@ -2079,52 +3082,50 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
|
|
|
2079
3082
|
throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
|
|
2080
3083
|
}
|
|
2081
3084
|
function dispatchPlanItem(context, parsed, projectRoot) {
|
|
3085
|
+
return updatePlanItem(context, planItemInput(parsed, projectRoot));
|
|
3086
|
+
}
|
|
3087
|
+
function planItemInput(parsed, projectRoot) {
|
|
2082
3088
|
const action = requiredPosition(parsed, 0, "plan item action");
|
|
2083
3089
|
const protocolId = requiredPosition(parsed, 1, "protocol-id");
|
|
2084
3090
|
const itemId = requiredPosition(parsed, 2, "item-id");
|
|
2085
3091
|
if (action === "start") {
|
|
2086
|
-
return
|
|
3092
|
+
return { protocolId, projectRoot, itemId, action };
|
|
2087
3093
|
}
|
|
2088
3094
|
if (action === "done") {
|
|
2089
|
-
return
|
|
3095
|
+
return {
|
|
3096
|
+
action,
|
|
2090
3097
|
protocolId,
|
|
2091
3098
|
projectRoot,
|
|
2092
3099
|
itemId,
|
|
2093
3100
|
summary: requiredOption(parsed, "summary"),
|
|
2094
3101
|
evidence: parsed.options.get("evidence") ?? []
|
|
2095
|
-
}
|
|
3102
|
+
};
|
|
2096
3103
|
}
|
|
2097
3104
|
if (action === "block") {
|
|
2098
|
-
return
|
|
3105
|
+
return {
|
|
3106
|
+
action,
|
|
2099
3107
|
protocolId,
|
|
2100
3108
|
projectRoot,
|
|
2101
3109
|
itemId,
|
|
2102
3110
|
reason: requiredOption(parsed, "reason"),
|
|
2103
3111
|
userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
|
|
2104
|
-
}
|
|
3112
|
+
};
|
|
2105
3113
|
}
|
|
2106
3114
|
if (action === "skip") {
|
|
2107
|
-
return
|
|
3115
|
+
return {
|
|
3116
|
+
action,
|
|
2108
3117
|
protocolId,
|
|
2109
3118
|
projectRoot,
|
|
2110
3119
|
itemId,
|
|
2111
3120
|
reason: requiredOption(parsed, "reason")
|
|
2112
|
-
}
|
|
3121
|
+
};
|
|
2113
3122
|
}
|
|
2114
3123
|
throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
|
|
2115
3124
|
}
|
|
2116
3125
|
function readOptionalJsonFile(file) {
|
|
2117
3126
|
if (!file)
|
|
2118
3127
|
return undefined;
|
|
2119
|
-
|
|
2120
|
-
throw new AppError("not_found", `JSON file does not exist: ${file}`, 1);
|
|
2121
|
-
}
|
|
2122
|
-
try {
|
|
2123
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
2124
|
-
}
|
|
2125
|
-
catch {
|
|
2126
|
-
throw new AppError("validation", `JSON file is malformed: ${file}`, 2);
|
|
2127
|
-
}
|
|
3128
|
+
return prepareJsonFile(file, "JSON input").value;
|
|
2128
3129
|
}
|
|
2129
3130
|
function parseFlowFlagOverrides(values) {
|
|
2130
3131
|
const result = {};
|
|
@@ -2159,7 +3160,11 @@ function requiredPosition(parsed, index, label) {
|
|
|
2159
3160
|
function optionalOption(parsed, key) {
|
|
2160
3161
|
const values = parsed.options.get(key);
|
|
2161
3162
|
const value = values?.[values.length - 1];
|
|
2162
|
-
|
|
3163
|
+
if (value === flagValue)
|
|
3164
|
+
throw new AppError("usage", `--${key} requires a value`, 2);
|
|
3165
|
+
if (value !== undefined && !value.trim())
|
|
3166
|
+
throw new AppError("usage", `--${key} requires a non-empty value`, 2);
|
|
3167
|
+
return value;
|
|
2163
3168
|
}
|
|
2164
3169
|
function allOptions(parsed, key) {
|
|
2165
3170
|
return (parsed.options.get(key) ?? []).filter((value) => value !== flagValue);
|
|
@@ -2324,7 +3329,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2324
3329
|
if (family === "merge-queue") {
|
|
2325
3330
|
return projectRootForMergeQueueMutation(context, command, parsed);
|
|
2326
3331
|
}
|
|
2327
|
-
if (family === "merge" && ["one-shot", "apply", "request"].includes(command ?? "")) {
|
|
3332
|
+
if (family === "merge" && ["one-shot", "apply", "repair", "request"].includes(command ?? "")) {
|
|
2328
3333
|
return requiredOption(parsed, "project-root");
|
|
2329
3334
|
}
|
|
2330
3335
|
if (family === "merge-worker" && ["start", "stop"].includes(command ?? "")) {
|
|
@@ -2357,7 +3362,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
|
|
|
2357
3362
|
if (family === "opencode" && ((command === "event" && requiredPosition(parsed, 0, "opencode event action") === "handle") || command === "usage")) {
|
|
2358
3363
|
return requiredOption(parsed, "project-root");
|
|
2359
3364
|
}
|
|
2360
|
-
if (family
|
|
3365
|
+
if (["agy", "droid"].includes(family ?? "") && ((command === "event" && requiredPosition(parsed, 0, `${family} event action`) === "handle") || command === "usage"))
|
|
2361
3366
|
return requiredOption(parsed, "project-root");
|
|
2362
3367
|
if (family === "codex" && command === "hooks") {
|
|
2363
3368
|
const action = requiredPosition(parsed, 0, "codex hooks action");
|
|
@@ -2394,14 +3399,36 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
|
|
|
2394
3399
|
function protocolProjectRoot(context, protocolId) {
|
|
2395
3400
|
return requireProtocol(context, protocolId).project_root;
|
|
2396
3401
|
}
|
|
3402
|
+
function prepareCliWorkRepair(context, parsed, objective, scopeProjectRoot) {
|
|
3403
|
+
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") ?? [];
|
|
3404
|
+
if ([findings.length > 0, unresolved.length > 0, receipts.length > 0].filter(Boolean).length !== 1)
|
|
3405
|
+
throw new AppError("usage", "Repair requires exactly one source: --from-check, --from-finding or --from-unresolved", 2);
|
|
3406
|
+
const scope = { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"), runId: requiredOption(parsed, "run"), objective };
|
|
3407
|
+
if (findings.length) {
|
|
3408
|
+
if (origins.length || hasOption(parsed, "verification-file"))
|
|
3409
|
+
throw new AppError("usage", "Finding repair derives its origins and cannot accept --origin-work or --verification-file", 2);
|
|
3410
|
+
return prepareVnextCodeReviewRepair(context, { ...scope, findingIds: findings, checkRefsByFinding: Object.fromEntries(findings.map(finding => [finding, parsed.options.get("check-ref") ?? []])) });
|
|
3411
|
+
}
|
|
3412
|
+
if (!origins.length)
|
|
3413
|
+
throw new AppError("usage", "Repair requires at least one --origin-work", 2);
|
|
3414
|
+
if (hasOption(parsed, "check-ref"))
|
|
3415
|
+
throw new AppError("usage", "--check-ref is only supported with --from-finding", 2);
|
|
3416
|
+
if (receipts.length > 1)
|
|
3417
|
+
throw new AppError("usage", "Create one repair Work per --from-check receipt", 2);
|
|
3418
|
+
if (!unresolved.length && hasOption(parsed, "verification-file"))
|
|
3419
|
+
throw new AppError("usage", "--verification-file is only supported with --from-unresolved", 2);
|
|
3420
|
+
const input = { ...scope, originWorkIds: origins, ...(unresolved.length ? { semanticUnresolved: unresolved, verificationPath: requiredOption(parsed, "verification-file") } : { checkReceiptId: receipts[0] }) };
|
|
3421
|
+
return { input, value: prepareVnextCodeRepair(context, input) };
|
|
3422
|
+
}
|
|
3423
|
+
function workerPromptInput(parsed) {
|
|
3424
|
+
const workspaceRoot = optionalOption(parsed, "workspace-root"), taskFile = optionalOption(parsed, "task-file");
|
|
3425
|
+
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 } : {}) };
|
|
3426
|
+
}
|
|
2397
3427
|
function runnerIntake(context) {
|
|
2398
3428
|
const input = context.value.task_input?.[0];
|
|
2399
3429
|
if (!input?.path)
|
|
2400
3430
|
throw new AppError("validation", "Runner SPECIFY context requires one ordered task_input file", 2);
|
|
2401
|
-
|
|
2402
|
-
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
2403
|
-
throw new AppError("not_found", "Runner SPECIFY task input is missing", 2, { task_input: file });
|
|
2404
|
-
return fs.readFileSync(file, "utf8");
|
|
3431
|
+
return prepareTextFile(input.path, "Runner SPECIFY task input").text;
|
|
2405
3432
|
}
|
|
2406
3433
|
/** Stage services install context before their lifecycle transition. Keeping
|
|
2407
3434
|
* this check here makes an incomplete service migration fail closed instead of
|