@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.138
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 +4 -0
- package/README.md +9 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/argument-values.js +5 -5
- package/dist/cli/command-inputs.js +89 -20
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +240 -6
- package/dist/cli/input-preparation.js +54 -5
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +54 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
- package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +6 -4
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +9 -9
- package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +2 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +22 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +71 -12
- package/dist/services/dashboard.js +8 -2
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +39 -22
- package/dist/services/eval-snapshots.js +83 -25
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +20 -16
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +91 -11
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +242 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +241 -0
- package/dist/services/memory-init-preflight.js +312 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +175 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +172 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/recovery-snapshot-database.js +416 -1
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +3 -1
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +101 -40
- package/dist/services/run-recovery-guard.js +2 -0
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +319 -25
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-report-renderer.js +63 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +70 -9
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +6 -4
- package/dist/services/work-registry.js +616 -71
- package/dist/services/workspace-files.js +1 -1
- package/dist/storage/database.js +84 -6
- package/dist/storage/writer-contract.js +11 -7
- package/package.json +13 -3
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import { Writable } from "node:stream";
|
|
5
|
+
import { once } from "node:events";
|
|
6
|
+
import { createContext } from "../runtime/context.js";
|
|
7
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
8
|
+
import { hybridDigest } from "../services/hybrid-contracts.js";
|
|
9
|
+
import { readMemoryInitStartupInput } from "../services/memory-init-startup-custody.js";
|
|
10
|
+
import { readMemoryInitRequestInput } from "../services/memory-init-request-input.js";
|
|
11
|
+
import { prepareMemoryInitFreshRequest, prepareMemoryInitSelection, startMemoryInitRun } from "../services/memory-init-start.js";
|
|
12
|
+
import { PreparedMemoryInitLintCapability } from "../services/memory-init-lint-capability.js";
|
|
13
|
+
import { PreparedMemoryInitWorktrunkCapability } from "../services/memory-init-worktrunk-capability.js";
|
|
14
|
+
import { produceMemoryInitNativeCapacity } from "../services/memory-init-native-capacity.js";
|
|
15
|
+
import { openMemoryInitStores, assertMemoryInitStorePrerequisites } from "../services/memory-init-stores.js";
|
|
16
|
+
import { prepareMemoryInitControllerStart } from "../services/memory-init-controller-start.js";
|
|
17
|
+
import { runControllerStatus } from "../services/run-controller.js";
|
|
18
|
+
import { requireRun } from "../services/run-controller-state.js";
|
|
19
|
+
import { requireProjectByRoot } from "../services/projects.js";
|
|
20
|
+
import { routeArgsThroughEngine } from "../services/engines.js";
|
|
21
|
+
import { parseRuntimeBudget } from "../services/runtime-budget.js";
|
|
22
|
+
import { prepareFileBytes } from "./input-preparation.js";
|
|
23
|
+
import { prepareMemoryInitInvocation, assertMemoryInitInvocationReplay } from "./memory-init-invocation.js";
|
|
24
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
25
|
+
import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
|
|
26
|
+
import { writePreparationError } from "./error-output.js";
|
|
27
|
+
import { onboardExecutor } from "../services/executor-onboarding.js";
|
|
28
|
+
import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
|
|
29
|
+
import { resolveDdFlowHome } from "../storage/paths.js";
|
|
30
|
+
import { askExecutorChoice } from "./executor-onboarding.js";
|
|
31
|
+
/** Backpressure bounds the observing client's output; closing it never controls the RUN. */
|
|
32
|
+
export async function writeMemoryInitObservation(stdout, text) {
|
|
33
|
+
const disconnected = () => Boolean(stdout.destroyed);
|
|
34
|
+
if (disconnected())
|
|
35
|
+
return false;
|
|
36
|
+
const waiting = new AbortController();
|
|
37
|
+
try {
|
|
38
|
+
// Observe errors even when write returns true: a short pipe write can
|
|
39
|
+
// report EPIPE asynchronously. Writable emits callback errors as events.
|
|
40
|
+
const closed = once(stdout, "close", { signal: waiting.signal });
|
|
41
|
+
const written = new Promise(resolve => {
|
|
42
|
+
stdout.write(text, error => { if (!error)
|
|
43
|
+
resolve(); });
|
|
44
|
+
});
|
|
45
|
+
await Promise.race([written, closed]);
|
|
46
|
+
return !disconnected();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error.code === "EPIPE")
|
|
50
|
+
return false;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
waiting.abort();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Both public spellings enter this owner. The controller, never this observer,
|
|
58
|
+
* owns Sessions and productive dispatch; disconnect does not request stop. */
|
|
59
|
+
export async function runMemoryInitCli(args, env = process.env, io = process) {
|
|
60
|
+
let context;
|
|
61
|
+
let effects = false;
|
|
62
|
+
try {
|
|
63
|
+
const output = parseOutputOptions(args), invocation = prepareMemoryInitInvocation(output.args);
|
|
64
|
+
parseRuntimeBudget(env.DD_FLOW_RUNTIME_BUDGET);
|
|
65
|
+
if (output.responseFile)
|
|
66
|
+
prepareResponseDestination(output.responseFile);
|
|
67
|
+
const reader = createContext(env, "read_existing");
|
|
68
|
+
let retained;
|
|
69
|
+
try {
|
|
70
|
+
retained = readMemoryInitStartupInput(reader, invocation.scope);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
reader.db.close?.();
|
|
74
|
+
}
|
|
75
|
+
let prepared;
|
|
76
|
+
if (retained) {
|
|
77
|
+
if (retained.payload.schema_id !== "dd-flow/memory-init-startup-input@3")
|
|
78
|
+
throw new AppError("memory_init_original_request_required", "Public init replay requires independently retained original input", 1, { effect: "no_effect" });
|
|
79
|
+
const raw = readMemoryInitRequestInput(retained.payload.request_input, invocation.scope, retained.payload.objective);
|
|
80
|
+
// A supplied file is an explicit selector and must still match. Omitted
|
|
81
|
+
// files never reopen mutable profile inputs during exact replay.
|
|
82
|
+
if (invocation.profileFile)
|
|
83
|
+
invocation.execution.rootProfile = readOperatorProfileInput(invocation.profileFile);
|
|
84
|
+
assertMemoryInitInvocationReplay(invocation, raw.selection, retained.payload.request_input.intake_sha256);
|
|
85
|
+
if (invocation.nativeConfigHome) {
|
|
86
|
+
const reference = retained.payload.execution.snapshot.settings.delegation.native_capacity_receipt;
|
|
87
|
+
const encoded = retained.payload.execution.receipt_blobs?.find(([checksum]) => checksum === reference?.sha256)?.[1];
|
|
88
|
+
if (!encoded || JSON.parse(Buffer.from(encoded, "base64").toString("utf8")).input.native_config_home !== invocation.nativeConfigHome)
|
|
89
|
+
throw new AppError("memory_init_request_conflict", "Native private configuration selection changed", 1, { effect: "no_effect" });
|
|
90
|
+
}
|
|
91
|
+
context = openMemoryInitStores(env, { scope: invocation.scope, selectionSha256: hybridDigest(raw.selection) }).context;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
if (!invocation.intakeFile || !invocation.intakeSha256 || !invocation.gitPolicy)
|
|
95
|
+
throw new AppError("usage", "Fresh init requires original intake file/hash and explicit Git route/integration branch", 2, { phase: "prepare", effect: "no_effect" });
|
|
96
|
+
const raw = prepareFileBytes(invocation.intakeFile, "original init intake", 64 * 1024).bytes;
|
|
97
|
+
if (createHash("sha256").update(raw).digest("hex") !== invocation.intakeSha256)
|
|
98
|
+
throw new AppError("memory_init_intake_changed", "Original intake bytes differ from --intake-sha256", 2, { phase: "prepare", effect: "no_effect" });
|
|
99
|
+
let objective;
|
|
100
|
+
try {
|
|
101
|
+
objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new AppError("usage", "Original init intake must be UTF-8", 2, { phase: "prepare", effect: "no_effect" });
|
|
105
|
+
}
|
|
106
|
+
// Controller birth/lease confirmation uses this OS observation. Known
|
|
107
|
+
// inability must refuse before candidate, stores or project publication.
|
|
108
|
+
const identity = spawnSync("ps", ["-o", "lstart=", "-p", String(process.pid)], { env: { ...env, TZ: "UTC", LC_ALL: "C" }, encoding: "utf8", timeout: 1000, shell: false, windowsHide: true });
|
|
109
|
+
if (identity.error || identity.signal || identity.status !== 0 || !identity.stdout.trim())
|
|
110
|
+
throw new AppError("memory_init_runtime_unavailable", "Controller physical identity observation is unavailable", 1, { phase: "prepare", effect: "no_effect", next_action: "restore_system_process_observation" });
|
|
111
|
+
const stdin = io.stdin ?? process.stdin;
|
|
112
|
+
const nonInteractive = invocation.nonInteractive || output.json || output.progressJsonl
|
|
113
|
+
|| !stdin.isTTY || !io.stderr.isTTY;
|
|
114
|
+
const selected = await onboardExecutor({
|
|
115
|
+
...(typeof invocation.execution.rootProfile === "string" ? { profileId: invocation.execution.rootProfile } : {}),
|
|
116
|
+
...(invocation.profileFile ? { profileFile: invocation.profileFile } : {})
|
|
117
|
+
}, () => readOperatorProfiles(resolveDdFlowHome(env)), { allowSkip: false, nonInteractive }, settings => askExecutorChoice(settings, { stdin, stderr: io.stderr }, { allowSave: false, allowSkip: false }));
|
|
118
|
+
if (selected.source === "explicit-file" || selected.source === "explicit-profile")
|
|
119
|
+
invocation.execution.rootProfile = selected.profile;
|
|
120
|
+
else if (invocation.execution.rootProfile === undefined && selected.source === "explicit-id")
|
|
121
|
+
invocation.execution.rootProfile = selected.profile.id;
|
|
122
|
+
let native;
|
|
123
|
+
if (invocation.execution.delegation?.mode === "native") {
|
|
124
|
+
if (!invocation.allowLiveProbe)
|
|
125
|
+
throw new AppError("usage", "Fresh native init requires --allow-live-probe", 2, { effect: "no_effect" });
|
|
126
|
+
const admissionReader = createContext(env, "read_existing");
|
|
127
|
+
let profile;
|
|
128
|
+
try {
|
|
129
|
+
const baseline = prepareMemoryInitSelection(admissionReader, invocation.scope, { execution: {
|
|
130
|
+
...invocation.execution, delegation: { mode: "external", max_parallel: invocation.execution.delegation.max_parallel ?? 1 }
|
|
131
|
+
}, gitPolicy: invocation.gitPolicy, objective });
|
|
132
|
+
PreparedMemoryInitLintCapability.prepare(baseline.target, env);
|
|
133
|
+
PreparedMemoryInitWorktrunkCapability.prepare(baseline.target, env);
|
|
134
|
+
baseline.engine.admitMemoryInitFlow(baseline.source, "dd-flow/memory-init-startup-input@3");
|
|
135
|
+
assertMemoryInitStorePrerequisites(env, invocation.scope);
|
|
136
|
+
const snapshot = baseline.execution.snapshot();
|
|
137
|
+
profile = snapshot.profiles.find(entry => entry.id === snapshot.settings.root_profile).profile;
|
|
138
|
+
if (snapshot.profiles.some(entry => hybridDigest({ ...entry.profile, id: profile.id }) !== hybridDigest(profile)))
|
|
139
|
+
throw new AppError("generic_execution_invalid", "Native delegation cannot change execution profiles", 2, { effect: "no_effect" });
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
admissionReader.db.close?.();
|
|
143
|
+
}
|
|
144
|
+
// From here a failed reply cannot imply no effect: daemon/child custody
|
|
145
|
+
// is independent from later startup publication and RUN allocation.
|
|
146
|
+
effects = true;
|
|
147
|
+
native = await produceMemoryInitNativeCapacity({ home: resolveDdFlowHome(env), scope: invocation.scope, profile,
|
|
148
|
+
maximum: invocation.execution.delegation.max_parallel ?? 1,
|
|
149
|
+
...(invocation.nativeConfigHome !== undefined ? { nativeConfigHome: invocation.nativeConfigHome } : {}),
|
|
150
|
+
allowLiveProbe: invocation.allowLiveProbe, env, requestSha256: hybridDigest({ scope: invocation.scope,
|
|
151
|
+
execution: invocation.execution, gitPolicy: invocation.gitPolicy, objective, intake: invocation.intakeSha256 }) });
|
|
152
|
+
invocation.execution.delegation.nativeCapacityReceipt = native.reference;
|
|
153
|
+
}
|
|
154
|
+
const owner = prepareMemoryInitFreshRequest(env, invocation.scope, { execution: invocation.execution, gitPolicy: invocation.gitPolicy, objective,
|
|
155
|
+
...(native ? { receipts: native.receipts, receiptBytes: native.receiptBytes } : {}) }, raw);
|
|
156
|
+
context = owner.context;
|
|
157
|
+
prepared = owner.prepared;
|
|
158
|
+
}
|
|
159
|
+
// Beyond this boundary, absence of a success receipt is not no-effect.
|
|
160
|
+
effects = true;
|
|
161
|
+
const started = startMemoryInitRun(context, invocation.scope, prepared ? () => prepared : undefined);
|
|
162
|
+
if (invocation.prepareOnly) {
|
|
163
|
+
const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
|
|
164
|
+
const response = { ok: true, action: "flow_mb_init_prepared", flow_id: "mb-init", ...started,
|
|
165
|
+
run_id: started.runId, request_id: invocation.scope.requestId, run_root: run.run_root,
|
|
166
|
+
project_root: run.project_root, workspace_root: run.workspace_root };
|
|
167
|
+
await writeMemoryInitObservation(io.stdout, `${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
const launch = prepareMemoryInitControllerStart(context, invocation.scope);
|
|
171
|
+
// Use the existing RUN-bound router: a global CLI must not launch its own
|
|
172
|
+
// controller code in place of the independently frozen RUN executable.
|
|
173
|
+
const receiptChunks = [];
|
|
174
|
+
let receiptSize = 0, oversizedReceipt = false;
|
|
175
|
+
const capture = new Writable({ write(chunk, _, next) {
|
|
176
|
+
receiptSize += Buffer.byteLength(chunk);
|
|
177
|
+
if (receiptSize > 1024 * 1024)
|
|
178
|
+
oversizedReceipt = true;
|
|
179
|
+
else
|
|
180
|
+
receiptChunks.push(Buffer.from(chunk));
|
|
181
|
+
next();
|
|
182
|
+
} });
|
|
183
|
+
const routed = await routeArgsThroughEngine(context, ["run", "drive", "launch", "--project-root", launch.input.projectRoot,
|
|
184
|
+
"--run", launch.input.runId, "--request-id", launch.input.requestId, "--context-file", launch.input.contextFile,
|
|
185
|
+
"--context-sha256", launch.input.contextSha256, "--json"], { stdout: capture, stderr: io.stderr }, undefined, { ...env, DD_FLOW_ENGINE_MODE: undefined }, invocation.scope.projectRoot);
|
|
186
|
+
if (!routed?.routed || routed.inProcess || routed.exitCode !== 0 || oversizedReceipt)
|
|
187
|
+
throw new AppError("memory_init_controller_launch_unknown", "Observe or reconcile the exact RUN-bound controller launch", 1, { effect: "unknown" });
|
|
188
|
+
let receipt;
|
|
189
|
+
try {
|
|
190
|
+
receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(receiptChunks)));
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned no valid controller receipt", 1, { effect: "unknown" });
|
|
194
|
+
}
|
|
195
|
+
const accepted = receipt.controller;
|
|
196
|
+
if (receipt.ok !== true || !accepted || accepted.run_id !== started.runId || accepted.request_id !== invocation.scope.requestId)
|
|
197
|
+
throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned a foreign controller receipt", 1, { effect: "unknown" });
|
|
198
|
+
const emit = (result) => writeMemoryInitObservation(io.stdout, `${JSON.stringify(result, null, output.json || output.progressJsonl ? undefined : 2)}\n`);
|
|
199
|
+
if (output.progressJsonl && !await emit({ ok: true, action: "flow_accepted", flow_id: "mb-init", ...started, ...receipt }))
|
|
200
|
+
return 0;
|
|
201
|
+
let result = receipt;
|
|
202
|
+
if (!invocation.detach) {
|
|
203
|
+
let after = 0;
|
|
204
|
+
let recentEvents = [];
|
|
205
|
+
for (;;) {
|
|
206
|
+
// The client has no ownership of the detached controller process.
|
|
207
|
+
if (io.stdout.destroyed)
|
|
208
|
+
return 0;
|
|
209
|
+
result = runControllerStatus(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId, after });
|
|
210
|
+
const events = result.events, controller = result.controller;
|
|
211
|
+
after = result.next_cursor;
|
|
212
|
+
recentEvents = [...recentEvents, ...events].slice(-100);
|
|
213
|
+
if (output.progressJsonl && events.length && !await emit({ ok: true, action: "flow_progress", flow_id: "mb-init", ...result }))
|
|
214
|
+
return 0;
|
|
215
|
+
if (!controller)
|
|
216
|
+
throw new AppError("memory_init_controller_receipt_missing", "Observe or reconcile the retained controller", 1, { effect: "unknown" });
|
|
217
|
+
if (["completed", "cancelled", "superseded", "recovery_required", "failed", "waiting_for_user", "waiting_for_context"].includes(controller.status)) {
|
|
218
|
+
result = { ...result, events: recentEvents };
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
await delay(250);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const response = { ...result, action: "flow_mb_init", flow_id: "mb-init", run_id: started.runId, request_id: invocation.scope.requestId };
|
|
225
|
+
if (!await emit(writeResponseFile(response, output)))
|
|
226
|
+
return 0;
|
|
227
|
+
const status = result.controller?.status;
|
|
228
|
+
return ["recovery_required", "failed", "cancelled", "superseded"].includes(status ?? "") ? 1 : 0;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const record = errorRecord(error);
|
|
232
|
+
if (effects)
|
|
233
|
+
record.details = { ...record.details, effect: record.details.effect === "partial" ? "partial" : "unknown", next_action: "observe_or_reconcile_exact_request" };
|
|
234
|
+
writePreparationError(io, args, record);
|
|
235
|
+
return record.exitCode;
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
context?.db.close?.();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
4
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
5
|
+
import { createContext } from "../runtime/context.js";
|
|
6
|
+
import { getResourceDatabase } from "../storage/database.js";
|
|
7
|
+
import { withStoreCleanup } from "../storage/native-cleanup.js";
|
|
8
|
+
import { PreparedMemoryInitRuntimeStorage } from "../services/memory-init-storage.js";
|
|
9
|
+
import { PreparedMemoryInitResourceStorage } from "../services/memory-init-resource-storage.js";
|
|
10
|
+
import { hybridDigest } from "../services/hybrid-contracts.js";
|
|
11
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
12
|
+
import { writePreparationError } from "./error-output.js";
|
|
13
|
+
/** Selected engine's non-migrating operation admission. No AppContext initialize. */
|
|
14
|
+
export function runMemoryStorageAdmissionCli(args, env = process.env, io = process) {
|
|
15
|
+
try {
|
|
16
|
+
const output = parseOutputOptions(args), route = prepareCommandRoute(output.args);
|
|
17
|
+
if (route.key !== "runtime storage admit" || output.responseFile || output.progressJsonl) {
|
|
18
|
+
throw new AppError("usage", "Storage admission accepts stdout output only", 2, { effect: "no_effect" });
|
|
19
|
+
}
|
|
20
|
+
const home = env.DD_FLOW_HOME;
|
|
21
|
+
if (!home || !path.isAbsolute(home) || path.resolve(home) !== home || fs.realpathSync(home) !== home) {
|
|
22
|
+
throw new AppError("usage", "Storage admission requires an explicit canonical absolute DD_FLOW_HOME", 2, { effect: "no_effect" });
|
|
23
|
+
}
|
|
24
|
+
const file = path.join(home, "db.sqlite"), owner = fs.lstatSync(file);
|
|
25
|
+
if (!owner.isFile() || owner.nlink !== 1)
|
|
26
|
+
throw new AppError("run_storage_unprepared", "RUN storage must be a regular single-linked file", 1, { effect: "no_effect" });
|
|
27
|
+
const resource = PreparedMemoryInitResourceStorage.prepare({ ddFlowHome: home, env });
|
|
28
|
+
const context = createContext(env, "prepared_existing");
|
|
29
|
+
const facts = withStoreCleanup(() => {
|
|
30
|
+
const run = PreparedMemoryInitRuntimeStorage.prepare(context);
|
|
31
|
+
const resourceWriter = getResourceDatabase(path.dirname(resource.facts().store_path), "prepared_existing");
|
|
32
|
+
return withStoreCleanup(() => {
|
|
33
|
+
if (resourceWriter.get("SELECT dd_flow_writer_version() AS version")?.version !== resource.facts().executing_writer_version) {
|
|
34
|
+
throw new AppError("store_writer_incompatible", "Resource connection writer differs from admitted storage", 1, { effect: "no_effect" });
|
|
35
|
+
}
|
|
36
|
+
resource.revalidate({ ddFlowHome: home, env });
|
|
37
|
+
const current = fs.lstatSync(file);
|
|
38
|
+
if (!current.isFile() || current.nlink !== 1 || current.dev !== owner.dev || current.ino !== owner.ino) {
|
|
39
|
+
throw new AppError("run_storage_changed", "RUN store changed during admission", 1, { effect: "no_effect" });
|
|
40
|
+
}
|
|
41
|
+
return { schema_id: "dd-flow/memory-init-engine-storage@1", operation: "memory-init", route: "verified-no-op", outcome: "no-op",
|
|
42
|
+
run: { ...run.facts(), store_dev: owner.dev, store_ino: owner.ino }, resource: resource.facts() };
|
|
43
|
+
}, () => resourceWriter.close?.());
|
|
44
|
+
}, () => context.db.close?.());
|
|
45
|
+
io.stdout.write(`${JSON.stringify({ ok: true, action: "runtime_storage_admit", facts, sha256: hybridDigest(facts) }, null, output.json ? undefined : 2)}\n`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const record = errorRecord(error);
|
|
50
|
+
writePreparationError(io, args, record);
|
|
51
|
+
return record.exitCode;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
2
|
+
import { optionalOption } from "./argument-values.js";
|
|
3
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
4
|
+
import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
|
|
5
|
+
import { listOperatorModels } from "../services/model-catalog.js";
|
|
6
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
7
|
+
import { writePreparationError } from "./error-output.js";
|
|
8
|
+
/** Model listing never opens an AppContext, starts a probe or reads credentials. */
|
|
9
|
+
export function runModelsCli(args, io = process) {
|
|
10
|
+
let json = false;
|
|
11
|
+
try {
|
|
12
|
+
const output = parseOutputOptions(args);
|
|
13
|
+
json = output.json;
|
|
14
|
+
if (output.responseFile)
|
|
15
|
+
prepareResponseDestination(output.responseFile);
|
|
16
|
+
const route = prepareCommandRoute(output.args);
|
|
17
|
+
if (!["models list", "models show"].includes(route.key))
|
|
18
|
+
throw new AppError("usage", "Unknown models command; see dd-flow models --help", 2, { effect: "no_effect" });
|
|
19
|
+
const input = {};
|
|
20
|
+
for (const [option, field] of [["harness", "harness"], ["provider", "provider"], ["account-sha256", "accountSha256"],
|
|
21
|
+
["seed-file", "seedFile"], ["observation-file", "observationFile"]]) {
|
|
22
|
+
const value = optionalOption(route.parsed, option);
|
|
23
|
+
if (value !== undefined)
|
|
24
|
+
input[field] = value;
|
|
25
|
+
}
|
|
26
|
+
if (route.key === "models show")
|
|
27
|
+
input.model = route.parsed.positional[0];
|
|
28
|
+
const models = listOperatorModels(input);
|
|
29
|
+
if (route.key === "models show" && models.entries.length === 0)
|
|
30
|
+
throw new AppError("model_not_found", "Model is absent from the selected seed and scoped observation", 1, { effect: "no_effect", help: "models" });
|
|
31
|
+
io.stdout.write(`${JSON.stringify(writeResponseFile({ ok: true, action: route.key.replaceAll(" ", "_"), ...models }, output), null, json ? undefined : 2)}\n`);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const record = errorRecord(error);
|
|
36
|
+
writePreparationError(io, args, record);
|
|
37
|
+
return record.exitCode;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -8,7 +8,7 @@ export function isNativeHookIngress(args) {
|
|
|
8
8
|
}
|
|
9
9
|
export async function dispatchNativeHook(args, stdin, env) {
|
|
10
10
|
const started = performance.now();
|
|
11
|
-
const deadline = env.DD_FLOW_HOOK_DEADLINE_AT
|
|
11
|
+
const deadline = env.DD_FLOW_HOOK_DEADLINE_AT ? Number(env.DD_FLOW_HOOK_DEADLINE_AT) : undefined;
|
|
12
12
|
if (deadline !== undefined && !Number.isSafeInteger(deadline))
|
|
13
13
|
throw new AppError("hook_deadline_invalid", "Native hook deadline must be an absolute millisecond timestamp", 1, { effect: "unknown" });
|
|
14
14
|
const phase = (name) => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { requiredOption, optionalOption } from "./argument-values.js";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { errorRecord } from "../shared/errors.js";
|
|
4
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
5
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
6
|
+
import { writePreparationError } from "./error-output.js";
|
|
7
|
+
import { resolveDdFlowHome } from "../storage/paths.js";
|
|
8
|
+
import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
|
|
9
|
+
import { readOperatorProfiles, resolveOperatorProfile, mutateOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
|
|
10
|
+
/** Home-only entrypoint; does not create AppContext, project registration or runtime DB. */
|
|
11
|
+
export function dispatchOperatorProfiles(home, route) {
|
|
12
|
+
const action = route.command, parsed = route.parsed;
|
|
13
|
+
if (action === "list")
|
|
14
|
+
return { ok: true, action: "profiles_list", settings: readOperatorProfiles(home) };
|
|
15
|
+
if (action === "show") {
|
|
16
|
+
const settings = readOperatorProfiles(home), id = parsed.positional[0];
|
|
17
|
+
return { ok: true, action: "profiles_show", selected: id, revision: settings.revision, profile: resolveOperatorProfile(settings, id) };
|
|
18
|
+
}
|
|
19
|
+
let mutation;
|
|
20
|
+
if (action === "create")
|
|
21
|
+
mutation = { action, profile: readOperatorProfileInput(requiredOption(parsed, "file")), setDefault: parsed.options.has("set-default") };
|
|
22
|
+
else if (action === "edit")
|
|
23
|
+
mutation = { action, profile: readOperatorProfileInput(requiredOption(parsed, "file")) };
|
|
24
|
+
else if (action === "remove")
|
|
25
|
+
mutation = { action, id: parsed.positional[0] };
|
|
26
|
+
else if (action === "alias") {
|
|
27
|
+
const target = optionalOption(parsed, "target"), remove = parsed.options.has("remove");
|
|
28
|
+
if (Boolean(target) === remove)
|
|
29
|
+
throw new AppError("usage", "Use exactly one of --target ID or --remove; see dd-flow profiles alias --help", 2, { effect: "no_effect" });
|
|
30
|
+
mutation = { action, alias: parsed.positional[0], target: target ?? null };
|
|
31
|
+
}
|
|
32
|
+
else if (action === "set-default") {
|
|
33
|
+
const id = parsed.positional[0], clear = parsed.options.has("clear");
|
|
34
|
+
if (Boolean(id) === clear)
|
|
35
|
+
throw new AppError("usage", "Select one profile ID or --clear; see dd-flow profiles set-default --help", 2, { effect: "no_effect" });
|
|
36
|
+
mutation = { action, id: id ?? null };
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
throw new AppError("usage", "Unknown profiles command; use dd-flow profiles --help", 2, { effect: "no_effect" });
|
|
40
|
+
const settings = mutateOperatorProfiles(home, requiredOption(parsed, "expected-revision"), mutation);
|
|
41
|
+
return { ok: true, action: `profiles_${action}`, effect: "committed", settings };
|
|
42
|
+
}
|
|
43
|
+
/** Lightweight executable ingress; never imports the project/runtime business router. */
|
|
44
|
+
export function runOperatorProfilesCli(args, env, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
45
|
+
let json = false;
|
|
46
|
+
try {
|
|
47
|
+
const output = parseOutputOptions(args);
|
|
48
|
+
json = output.json;
|
|
49
|
+
if (output.responseFile)
|
|
50
|
+
prepareResponseDestination(output.responseFile);
|
|
51
|
+
const route = prepareCommandRoute(output.args);
|
|
52
|
+
const result = dispatchOperatorProfiles(resolveDdFlowHome(env), route);
|
|
53
|
+
io.stdout.write(`${JSON.stringify(writeResponseFile(result, output), null, json ? undefined : 2)}\n`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const record = errorRecord(error);
|
|
58
|
+
writePreparationError(io, args, record);
|
|
59
|
+
return record.exitCode;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
/** Shared execution transport grammar. Help resolves before this function. */
|
|
3
|
+
export function parseOutputOptions(args) {
|
|
4
|
+
const stripped = [];
|
|
5
|
+
let json = false, progressJsonl = false, responseFile = null;
|
|
6
|
+
for (let index = 0; index < args.length; index++) {
|
|
7
|
+
const arg = args[index];
|
|
8
|
+
if (arg === "--") {
|
|
9
|
+
stripped.push(...args.slice(index));
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
if (arg === "--json") {
|
|
13
|
+
json = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (arg === "--progress-jsonl") {
|
|
17
|
+
progressJsonl = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (arg === "--response-file" || arg.startsWith("--response-file=")) {
|
|
21
|
+
if (responseFile !== null)
|
|
22
|
+
throw new AppError("usage", "--response-file may be supplied only once", 2);
|
|
23
|
+
const file = arg === "--response-file" ? args[++index] : arg.slice("--response-file=".length);
|
|
24
|
+
if (!file?.trim() || file.startsWith("--"))
|
|
25
|
+
throw new AppError("usage", "--response-file requires a path", 2);
|
|
26
|
+
responseFile = file;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
stripped.push(arg);
|
|
30
|
+
}
|
|
31
|
+
if (responseFile && !json)
|
|
32
|
+
throw new AppError("usage", "--response-file requires --json", 2);
|
|
33
|
+
const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
|
|
34
|
+
return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
|
|
35
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
/** Preflight does not create a parent, destination or runtime home. */
|
|
6
|
+
export function prepareResponseDestination(value) {
|
|
7
|
+
const file = path.resolve(value);
|
|
8
|
+
try {
|
|
9
|
+
const inspect = (target) => {
|
|
10
|
+
try {
|
|
11
|
+
return fs.lstatSync(target);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.code === "ENOENT")
|
|
15
|
+
return null;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
let parent = path.dirname(file);
|
|
20
|
+
let parentStat = inspect(parent);
|
|
21
|
+
while (!parentStat) {
|
|
22
|
+
const next = path.dirname(parent);
|
|
23
|
+
if (next === parent)
|
|
24
|
+
break;
|
|
25
|
+
parent = next;
|
|
26
|
+
parentStat = inspect(parent);
|
|
27
|
+
}
|
|
28
|
+
if (parentStat?.isSymbolicLink())
|
|
29
|
+
parentStat = fs.statSync(parent);
|
|
30
|
+
const targetStat = inspect(file);
|
|
31
|
+
if (!parentStat?.isDirectory() || targetStat && !targetStat.isFile())
|
|
32
|
+
throw new Error();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new AppError("response_file_invalid", "Response destination must be a regular file under a directory", 2, { parameter: "response-file", effect: "no_effect" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Optional publication failure never changes the accepted operation outcome. */
|
|
39
|
+
export function writeResponseFile(payload, output) {
|
|
40
|
+
if (!output.responseFile)
|
|
41
|
+
return payload;
|
|
42
|
+
const file = path.resolve(output.responseFile), temporary = `${file}.tmp-${randomUUID()}`;
|
|
43
|
+
const visible = payload && typeof payload === "object" && !Array.isArray(payload) ? { ...payload } : { ok: true, result: payload };
|
|
44
|
+
let ownsTemporary = false;
|
|
45
|
+
try {
|
|
46
|
+
prepareResponseDestination(file);
|
|
47
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
48
|
+
const fd = fs.openSync(temporary, "wx", 0o600);
|
|
49
|
+
ownsTemporary = true;
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}\n`);
|
|
52
|
+
fs.fsyncSync(fd);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
fs.closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
fs.renameSync(temporary, file);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { ...visible, response_file: file, response_file_error: "Response publication failed; reconcile the accepted operation before retrying" };
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
try {
|
|
64
|
+
if (ownsTemporary && fs.existsSync(temporary))
|
|
65
|
+
fs.unlinkSync(temporary);
|
|
66
|
+
}
|
|
67
|
+
catch { /* Own temporary only; never mask the operation outcome. */ }
|
|
68
|
+
}
|
|
69
|
+
delete visible.worker_prompt_markdown;
|
|
70
|
+
return { ...visible, response_file: file };
|
|
71
|
+
}
|