@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,149 @@
|
|
|
1
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
4
|
+
import { resolveDdFlowHome } from "../storage/paths.js";
|
|
5
|
+
import { readSetupStatus, installAuthenticatedRelease, readRetainedSetupExecutor, hasDeferredSetupOnboarding, readSetupOnboarding, commitSetupOnboarding } from "../services/setup.js";
|
|
6
|
+
import { readTrustedReleaseProducers, readReleaseBundle } from "../services/release-source.js";
|
|
7
|
+
import { createReleaseCompatibility, installedCatalogVersions } from "../services/release-compatibility.js";
|
|
8
|
+
import { getCliVersionReport } from "../services/build-info.js";
|
|
9
|
+
import { readOperatorProfiles, readProfileCreationIntent } from "../services/operator-profiles.js";
|
|
10
|
+
import { onboardExecutor, selectOnboardingExecutor } from "../services/executor-onboarding.js";
|
|
11
|
+
import { askExecutorChoice } from "./executor-onboarding.js";
|
|
12
|
+
import { requiredOption, optionalOption } from "./argument-values.js";
|
|
13
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
14
|
+
import { hybridDigest } from "../services/hybrid-contracts.js";
|
|
15
|
+
import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
|
|
16
|
+
import { readRegistryRelease } from "../services/release-registry.js";
|
|
17
|
+
import { writePreparationError } from "./error-output.js";
|
|
18
|
+
import { publishSetup } from "../harness-runtime/lib/setup-progress.mjs";
|
|
19
|
+
/** Lightweight global diagnostic ingress; never creates a runtime context. */
|
|
20
|
+
export async function runSetupCli(args, env, io = { stdout: process.stdout, stderr: process.stderr, stdin: process.stdin }) {
|
|
21
|
+
let json = false;
|
|
22
|
+
try {
|
|
23
|
+
const output = parseOutputOptions(args);
|
|
24
|
+
json = output.json;
|
|
25
|
+
if (output.responseFile)
|
|
26
|
+
prepareResponseDestination(output.responseFile);
|
|
27
|
+
const route = prepareCommandRoute(output.args);
|
|
28
|
+
const humanProgress = !output.json && !output.progressJsonl;
|
|
29
|
+
const reportHumanProgress = (message) => {
|
|
30
|
+
// Diagnostic delivery cannot turn a completed installation into failure.
|
|
31
|
+
try {
|
|
32
|
+
io.stderr.write(`setup: ${message}\n`);
|
|
33
|
+
}
|
|
34
|
+
catch { /* Best-effort human progress only. */ }
|
|
35
|
+
};
|
|
36
|
+
const home = resolveDdFlowHome(env);
|
|
37
|
+
let result;
|
|
38
|
+
if (route.key === "setup status")
|
|
39
|
+
result = { ok: true, action: "setup_status", observed_cli: getCliVersionReport().cli, ...readSetupStatus(home) };
|
|
40
|
+
else if (route.key === "setup" || route.key === "setup update") {
|
|
41
|
+
const parsed = route.parsed;
|
|
42
|
+
const source = optionalOption(parsed, "canon-source"), version = optionalOption(parsed, "canon-version");
|
|
43
|
+
const registryFile = optionalOption(parsed, "release-registry");
|
|
44
|
+
if (source && (version || registryFile))
|
|
45
|
+
throw new AppError("usage", "--canon-source is exclusive with --canon-version and --release-registry; see dd-flow setup --help", 2, { effect: "no_effect" });
|
|
46
|
+
const channel = (optionalOption(parsed, "channel") ?? "stable");
|
|
47
|
+
const selection = source ? null : readRegistryRelease(registryFile ?? env.DD_FLOW_RELEASE_REGISTRY ?? path.join(home, "release-registry.json"), channel, version);
|
|
48
|
+
const profile = optionalOption(parsed, "profile"), skip = parsed.options.has("skip-agent-setup");
|
|
49
|
+
const intentFile = optionalOption(parsed, "save-profile-intent");
|
|
50
|
+
if (intentFile && skip)
|
|
51
|
+
throw new AppError("usage", "--save-profile-intent conflicts with --skip-agent-setup", 2, { effect: "no_effect" });
|
|
52
|
+
let profileCreation = intentFile ? readProfileCreationIntent(intentFile) : undefined;
|
|
53
|
+
const stdin = io.stdin ?? process.stdin;
|
|
54
|
+
const nonInteractive = parsed.options.has("non-interactive") || output.json || output.progressJsonl
|
|
55
|
+
|| !stdin.isTTY || !io.stderr.isTTY;
|
|
56
|
+
const profileFile = optionalOption(parsed, "profile-file");
|
|
57
|
+
const requestId = requiredOption(parsed, "request-id");
|
|
58
|
+
const retained = profile === undefined && profileFile === undefined && !intentFile && !skip ? readRetainedSetupExecutor(home, requestId) : null;
|
|
59
|
+
if (retained?.profileCreation)
|
|
60
|
+
profileCreation = retained.profileCreation;
|
|
61
|
+
const choice = { ...(profile !== undefined ? { profileId: profile } : {}),
|
|
62
|
+
...(profileFile !== undefined ? { profileFile } : {}),
|
|
63
|
+
...(retained?.executor ? { profile: retained.executor } : profileCreation && profile === undefined && profileFile === undefined ? { profile: profileCreation.profile } : {}),
|
|
64
|
+
skip: skip || retained !== null && retained.executor === null };
|
|
65
|
+
let deferred = hasDeferredSetupOnboarding(home, requestId);
|
|
66
|
+
let selected;
|
|
67
|
+
if (!deferred) {
|
|
68
|
+
try {
|
|
69
|
+
selected = selectOnboardingExecutor(choice, () => readOperatorProfiles(home), { allowSkip: true });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!(error instanceof AppError) || error.code !== "agent_profile_missing" || nonInteractive
|
|
73
|
+
|| profile !== undefined || profileFile !== undefined || intentFile || skip)
|
|
74
|
+
throw error;
|
|
75
|
+
deferred = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (profile !== undefined || profileFile !== undefined || intentFile || skip) {
|
|
79
|
+
// Explicit continuation inputs still fail before downloading or replaying.
|
|
80
|
+
selected = selectOnboardingExecutor(choice, () => readOperatorProfiles(home), { allowSkip: true });
|
|
81
|
+
}
|
|
82
|
+
const observedCli = getCliVersionReport().cli;
|
|
83
|
+
const compatibility = createReleaseCompatibility({ channel,
|
|
84
|
+
catalogVersions: installedCatalogVersions, routerVersion: observedCli.version });
|
|
85
|
+
const producers = readTrustedReleaseProducers(requiredOption(parsed, "trust-file"));
|
|
86
|
+
const bundle = await readReleaseBundle(source ?? selection.envelope_url, producers, compatibility, {
|
|
87
|
+
...(selection ? { expectedVersion: selection.version } : {}),
|
|
88
|
+
...((output.progressJsonl || humanProgress) ? { onProgress: (progress) => {
|
|
89
|
+
if (output.progressJsonl)
|
|
90
|
+
io.stdout.write(`${JSON.stringify({ type: "setup_download_progress", request_id: requestId, ...progress })}\n`);
|
|
91
|
+
else if (progress.received_bytes === progress.expected_bytes)
|
|
92
|
+
reportHumanProgress(`${progress.artifact} archive received (${progress.received_bytes} bytes); integrity validation follows`);
|
|
93
|
+
} } : {})
|
|
94
|
+
});
|
|
95
|
+
if (selection && bundle.authenticated.manifest.version !== selection.version)
|
|
96
|
+
throw new AppError("release_registry", "Authenticated release differs from selected registry version", 1, { reason: "selected_version_mismatch", phase: "release_selection", effect: "no_effect", help: "setup" });
|
|
97
|
+
const installed = installAuthenticatedRelease(home, { requestId, action: route.key === "setup update" ? "update" : "install",
|
|
98
|
+
envelope: bundle.envelope, trustedProducers: producers, compatibility, canonArchive: bundle.canonArchive, engineArchive: bundle.engineArchive,
|
|
99
|
+
...(deferred ? { deferredOnboarding: true } : { executor: selected.profile, ...(profileCreation ? { profileCreation } : {}) }),
|
|
100
|
+
onCheckpoint: phase => {
|
|
101
|
+
publishSetup("dependency_check_completed", `release-setup:${phase}`);
|
|
102
|
+
if (output.progressJsonl)
|
|
103
|
+
io.stdout.write(`${JSON.stringify({ type: "setup_progress", request_id: requestId, phase })}\n`);
|
|
104
|
+
else if (humanProgress)
|
|
105
|
+
reportHumanProgress({ prepared: "verified release request retained", canon_cached: "canon cache verified", engine_cached: "engine cache verified",
|
|
106
|
+
profiles_saved: "profile settings saved", committed: "installation committed; runtime qualification remains unknown" }[phase]);
|
|
107
|
+
} });
|
|
108
|
+
if (humanProgress && installed.replayed)
|
|
109
|
+
reportHumanProgress("exact request replay verified; retained selection was not reactivated");
|
|
110
|
+
if (deferred) {
|
|
111
|
+
const saved = readSetupOnboarding(home, requestId);
|
|
112
|
+
const explicit = profile !== undefined || profileFile !== undefined || intentFile !== undefined || skip;
|
|
113
|
+
const deferredChoice = saved && !explicit ? { ...(saved.executor ? { profile: saved.executor } : { skip: true }),
|
|
114
|
+
...(saved.profileCreation ? { profileCreation: saved.profileCreation } : {}) } : choice;
|
|
115
|
+
try {
|
|
116
|
+
selected ??= await onboardExecutor(deferredChoice, () => readOperatorProfiles(home), { allowSkip: true, nonInteractive }, settings => askExecutorChoice(settings, { stdin, stderr: io.stderr }));
|
|
117
|
+
const consent = commitSetupOnboarding(home, requestId, { executor: selected.profile,
|
|
118
|
+
...(profileCreation ?? selected.profileCreation ? { profileCreation: profileCreation ?? selected.profileCreation } : {}) });
|
|
119
|
+
installed.executor = consent.executor;
|
|
120
|
+
if (humanProgress)
|
|
121
|
+
reportHumanProgress("executor onboarding committed after installation");
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error instanceof AppError) {
|
|
125
|
+
error.details.installation = "committed";
|
|
126
|
+
error.details.request_id = requestId;
|
|
127
|
+
error.details.next_action = "Replay the same setup request to resume executor onboarding; installation remains retained";
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
result = { ...installed, action: route.key === "setup update" ? "setup_update" : "setup_install", observed_cli: observedCli,
|
|
133
|
+
executor: installed.executor ? { profile_id: installed.executor.id, revision: hybridDigest(installed.executor), readiness: "unknown_requires_qualification" } : { profile_id: null, readiness: "not_configured" },
|
|
134
|
+
...readSetupStatus(home) };
|
|
135
|
+
}
|
|
136
|
+
else
|
|
137
|
+
throw new AppError("usage", "Unknown setup command; see dd-flow setup --help", 2, { effect: "no_effect" });
|
|
138
|
+
io.stdout.write(`${JSON.stringify(writeResponseFile(result, output), null, json ? undefined : 2)}\n`);
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const record = errorRecord(error);
|
|
143
|
+
writePreparationError(io, args, record);
|
|
144
|
+
if (!json && error instanceof AppError && error.details.installation === "committed") {
|
|
145
|
+
io.stderr.write("setup: installation remains committed; replay the same request to resume executor onboarding\n");
|
|
146
|
+
}
|
|
147
|
+
return record.exitCode;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,66 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { isNativeHookIngress } from "./cli/native-hook-ingress.js";
|
|
3
3
|
import { isRuntimeMaintenanceIngress, runRuntimeMaintenanceIngress } from "./cli/runtime-maintenance-ingress.js";
|
|
4
|
+
import { resolveHelp } from "./cli/help.js";
|
|
5
|
+
import { errorRecord } from "./shared/errors.js";
|
|
6
|
+
import { writePreparationError } from "./cli/error-output.js";
|
|
4
7
|
import { publishSetup } from './harness-runtime/lib/setup-progress.mjs';
|
|
5
8
|
const args = process.argv.slice(2);
|
|
6
|
-
|
|
9
|
+
let help;
|
|
10
|
+
try {
|
|
11
|
+
help = resolveHelp(args);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const record = errorRecord(error);
|
|
15
|
+
writePreparationError({ stderr: process.stderr }, args, record);
|
|
16
|
+
process.exit(record.exitCode);
|
|
17
|
+
}
|
|
18
|
+
if (help) {
|
|
19
|
+
process.stdout.write(`${help.json ? JSON.stringify(help.data) : help.text}\n`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (args[0] === "completion") {
|
|
23
|
+
const { runCompletionCli } = await import("./cli/completion-cli.js");
|
|
24
|
+
process.exit(runCompletionCli(args));
|
|
25
|
+
}
|
|
26
|
+
if (args[0] === "profiles") {
|
|
27
|
+
const { runOperatorProfilesCli } = await import("./cli/operator-profiles.js");
|
|
28
|
+
process.exit(runOperatorProfilesCli(args, process.env));
|
|
29
|
+
}
|
|
30
|
+
if (args[0] === "setup") {
|
|
31
|
+
const { runSetupCli } = await import("./cli/setup.js");
|
|
32
|
+
process.exit(await runSetupCli(args, process.env));
|
|
33
|
+
}
|
|
34
|
+
if (args[0] === "harnesses") {
|
|
35
|
+
const { runHarnessesCli } = await import("./cli/harnesses.js");
|
|
36
|
+
process.exit(await runHarnessesCli(args, process.env));
|
|
37
|
+
}
|
|
38
|
+
if (args[0] === "models") {
|
|
39
|
+
const { runModelsCli } = await import("./cli/models.js");
|
|
40
|
+
process.exit(runModelsCli(args));
|
|
41
|
+
}
|
|
42
|
+
if (args[0] === "flow" && ["list", "show"].includes(args[1] ?? "")) {
|
|
43
|
+
const { runFlowCatalogCli } = await import("./cli/flows.js");
|
|
44
|
+
process.exit(runFlowCatalogCli(args));
|
|
45
|
+
}
|
|
46
|
+
if (args[0] === "init" || args[0] === "flow" && args[1] === "mb-init") {
|
|
47
|
+
const { runMemoryInitCli } = await import("./cli/memory-init.js");
|
|
48
|
+
process.exit(await runMemoryInitCli(args, process.env));
|
|
49
|
+
}
|
|
50
|
+
if (args.slice(0, 3).join(" ") === "runtime storage admit") {
|
|
51
|
+
const { runMemoryStorageAdmissionCli } = await import("./cli/memory-storage-admission.js");
|
|
52
|
+
process.exit(runMemoryStorageAdmissionCli(args));
|
|
53
|
+
}
|
|
54
|
+
if (args.slice(0, 3).join(" ") === "runtime flow admit") {
|
|
55
|
+
const { runMemoryFlowAdmissionCli } = await import("./cli/memory-flow-admission.js");
|
|
56
|
+
process.exit(await runMemoryFlowAdmissionCli(args));
|
|
57
|
+
}
|
|
58
|
+
const managedHook = Boolean(process.env.DD_FLOW_CODEX_STATE_DIR) && args.slice(0, 3).join(" ") === "codex hook handle";
|
|
7
59
|
const exitCode = isRuntimeMaintenanceIngress(args, process.env)
|
|
8
60
|
? await runRuntimeMaintenanceIngress(args)
|
|
9
61
|
: managedHook
|
|
10
62
|
? await (await import("./cli/hook-ingress.js")).runHookIngress(args)
|
|
11
|
-
: isNativeHookIngress(args)
|
|
63
|
+
: isNativeHookIngress(args)
|
|
12
64
|
? await (await import("./cli/native-hook-ingress.js")).runNativeHookIngress(args)
|
|
13
65
|
: await (async () => {
|
|
14
66
|
publishSetup('ingress_entered');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Engine-owned identities shared by inspection and executable registration.
|
|
2
|
+
* Keep this module dependency-free: catalog inspection must not load handlers.
|
|
3
|
+
*/
|
|
4
|
+
export const sdlcHandlerIdentities = Object.freeze({
|
|
5
|
+
specify: Object.freeze({ stage: "specify", entryId: "sdlc-specify-entry", finishId: "sdlc-specify-finish" }),
|
|
6
|
+
protocolize: Object.freeze({ stage: "protocolize", entryId: "sdlc-protocolize-entry", finishId: "sdlc-protocolize-finish" }),
|
|
7
|
+
plan: Object.freeze({ stage: "plan", entryId: "sdlc-plan-entry", finishId: "sdlc-plan-finish" }),
|
|
8
|
+
"plan-review": Object.freeze({ stage: "plan-review", entryId: "sdlc-plan-review-entry", finishId: "sdlc-plan-review-finish" }),
|
|
9
|
+
code: Object.freeze({ stage: "code", entryId: "sdlc-code-entry", finishId: "sdlc-code-finish" }),
|
|
10
|
+
"code-review": Object.freeze({ stage: "code-review", entryId: "sdlc-code-review-entry", finishId: "sdlc-code-review-finish" }),
|
|
11
|
+
merge: Object.freeze({ stage: "merge", entryId: "sdlc-merge-entry", finishId: "sdlc-merge-finish" })
|
|
12
|
+
});
|
|
13
|
+
export const memoryInitHandlerIdentities = Object.freeze({
|
|
14
|
+
preflight: Object.freeze({ stage: "preflight", entryId: "memory-stage-entry", finishId: "memory-stage-finish" }),
|
|
15
|
+
discover: Object.freeze({ stage: "discover", entryId: "memory-stage-entry", finishId: "memory-stage-finish" }),
|
|
16
|
+
compose: Object.freeze({ stage: "compose", entryId: "memory-stage-entry", finishId: "memory-stage-finish" }),
|
|
17
|
+
verify: Object.freeze({ stage: "verify", entryId: "memory-stage-entry", finishId: "memory-stage-finish" }),
|
|
18
|
+
review: Object.freeze({ stage: "review", entryId: "memory-stage-entry", finishId: "memory-stage-finish" }),
|
|
19
|
+
finish: Object.freeze({ stage: "finish", entryId: "memory-stage-entry", finishId: "memory-stage-finish" })
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function inspectDaemonOperation(root: string, id: string): Promise<Record<string, unknown>>;
|
|
@@ -8,6 +8,7 @@ import { errorRecord, isObservationLoss, isProvenShellCorrection, reportedError
|
|
|
8
8
|
import { assertDaemonOwnership, budgetObservationScope, releaseBudgetObservation, completeBudgetOperation } from "./managed-daemon.mjs";
|
|
9
9
|
import { readModelObservations, modelAttribution } from "./model-observations.mjs";
|
|
10
10
|
import { publishOperationProgress, publishOperationWaiting, withOperationProgress } from './operation-progress.mjs';
|
|
11
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
11
12
|
|
|
12
13
|
const productive = new Set(["session.create", "session.prompt", "session.start", "session.fork", "session.resume"]);
|
|
13
14
|
const controls = new Map();
|
|
@@ -301,7 +302,7 @@ export async function durableDaemonStart(options, action) {
|
|
|
301
302
|
// scope can reuse this exact adapter launch after retiring the old daemon.
|
|
302
303
|
const launch = { schema_id: "dd-flow/native-launch@1", executable: options.entryPath ?? null, args: process.argv.slice(2), cwd: process.cwd(), runtime_owner: process.env.DD_FLOW_RUNTIME_OWNER ?? null,
|
|
303
304
|
env: Object.fromEntries(["CODEX_HOME", "DD_FLOW_CONFIG_HOME", "DD_FLOW_HOME", "DD_FLOW_BIN", "DD_FLOW_RESOURCE_HOME"].map(key => [key, process.env[key] ?? null])) };
|
|
304
|
-
const request = { id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation: "daemon.start", params: { ...options, runtimeOwner: process.env.DD_FLOW_RUNTIME_OWNER ?? null, resourceHome: process.env.DD_FLOW_RESOURCE_HOME ?? null, launch } };
|
|
305
|
+
const request = prepareDaemonRequest({ id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation: "daemon.start", params: { ...options, runtimeOwner: process.env.DD_FLOW_RUNTIME_OWNER ?? null, resourceHome: process.env.DD_FLOW_RESOURCE_HOME ?? null, launch } });
|
|
305
306
|
return await journalDispatch(root, request, () => action(options));
|
|
306
307
|
}
|
|
307
308
|
|
|
@@ -314,13 +315,13 @@ async function journalDispatch(root, request, action) {
|
|
|
314
315
|
const daemon = await read(path.join(root, "daemon.json"));
|
|
315
316
|
const startup = request.operation === "daemon.start";
|
|
316
317
|
const daemonId = daemon?.daemon_id ?? daemon?.config?.daemonId ?? null;
|
|
317
|
-
await writeJsonAtomic(temporary, { schema_id: "dd-flow/daemon-operation@1", operation_id: request.id, operation: request.operation, session_id: request.params?.sessionId ?? null, daemon_id: startup ? null : daemonId, owner_id: daemon?.config?.runtime_owner?.owner_id ?? null, generation: dispatchGenerations.get(root) ?? 0, ...(startup ? { predecessor_daemon_id: daemonId, launch: request.params?.launch } : {}), params_sha256: fingerprint, requested_at: new Date().toISOString() });
|
|
318
|
+
await writeJsonAtomic(temporary, { schema_id: "dd-flow/daemon-operation@1", operation_id: request.id, operation: request.operation, session_id: request.params?.sessionId ?? null, daemon_id: startup ? null : daemonId, owner_id: daemon?.config?.runtime_owner?.owner_id ?? null, generation: dispatchGenerations.get(root) ?? 0, ...(startup ? { predecessor_daemon_id: daemonId, launch: request.params?.launch } : {}), params_sha256: fingerprint, ...(request.invocation_sha256 ? { invocation_sha256: request.invocation_sha256 } : {}), requested_at: new Date().toISOString() });
|
|
318
319
|
try {
|
|
319
320
|
await link(temporary, path.join(directory, "requested.json"));
|
|
320
321
|
} catch (error) {
|
|
321
322
|
if (error.code !== "EEXIST") throw error;
|
|
322
323
|
const saved = await inspectDaemonOperation(root, request.id);
|
|
323
|
-
if (saved.params_sha256 !== fingerprint) throw Object.assign(new Error("operation id was reused with different parameters"), { code: "operation_conflict", details: { operation_id: request.id } });
|
|
324
|
+
if (saved.params_sha256 !== fingerprint || (saved.invocation_sha256 ?? null) !== (request.invocation_sha256 ?? null)) throw Object.assign(new Error("operation id was reused with different parameters or invocation"), { code: "operation_conflict", details: { operation_id: request.id } });
|
|
324
325
|
if (saved.state === "completed") return saved.result;
|
|
325
326
|
if (["failed", "interrupted"].includes(saved.state)) throw reportedError(saved.error, "provider operation failed");
|
|
326
327
|
throw Object.assign(new Error("provider operation has no confirmed terminal result; inspect the existing operation instead of resending it"), { code: "operation_observation_lost", details: { operation_id: request.id, receipt_directory: directory } });
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
1
2
|
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
2
3
|
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
3
4
|
import { operationProgressReporter } from './operation-progress.mjs';
|
|
5
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
4
6
|
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
5
7
|
import { errorRecord } from "./operation-errors.mjs";
|
|
6
8
|
import { ToolObservations, agyToolObservation, agyTranscriptObservations } from "./tool-observations.mjs";
|
|
@@ -58,7 +60,7 @@ async function writeJson(file, value) { const temporary = `${file}.${process.pid
|
|
|
58
60
|
async function readState(dir) { try { return JSON.parse(await readFile(locations(dir).state, "utf8")); } catch (error) { if (error.code === "ENOENT") return null; throw error; } }
|
|
59
61
|
async function removeSocket(paths) { try { if (!(await lstat(paths.socket)).isSocket()) throw new DaemonError("unsafe_socket", "refusing to replace a non-socket daemon path"); await unlink(paths.socket); } catch (error) { if (error.code !== "ENOENT") throw error; } }
|
|
60
62
|
const shell = value => `'${String(value).replaceAll("'", `'"'"'`)}'`;
|
|
61
|
-
export function sanitizedEnv(config) { const env = { ...process.env, TMPDIR: config.temporary, DD_AGY_HOOK_NODE: process.execPath, DD_AGY_HOOK_ENTRY: config.entryPath, DD_AGY_STATE_DIR: config.stateDir, DD_AGY_DAEMON_ID: config.daemonId }; for (const key of ["GEMINI_API_KEY","GOOGLE_API_KEY","ANTHROPIC_API_KEY","OPENAI_API_KEY"
|
|
63
|
+
export function sanitizedEnv(config) { const env = { ...process.env, TMPDIR: config.temporary, DD_AGY_HOOK_NODE: process.execPath, DD_AGY_HOOK_ENTRY: config.entryPath, DD_AGY_STATE_DIR: config.stateDir, DD_AGY_DAEMON_ID: config.daemonId }; for (const key of ["GEMINI_API_KEY","GOOGLE_API_KEY","ANTHROPIC_API_KEY","OPENAI_API_KEY"]) env[key] = undefined; return { ...env, ...managedFlowEnv(config) }; }
|
|
62
64
|
const errorPayload = errorRecord;
|
|
63
65
|
async function forwardUsage(config, sessionId, result, tools, descendants) {
|
|
64
66
|
if (!config.ddFlowBin || !config.projectRoot) return;
|
|
@@ -164,7 +166,7 @@ export async function qualifyAgyHooks(projectRoot, { stateDir = null, install =
|
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000, requestId = null) {
|
|
167
|
-
const request = { schema_id: REQUEST_SCHEMA, id: requestId ?? process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params };
|
|
169
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST_SCHEMA, id: requestId ?? process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
168
170
|
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'agy', timeoutMs });
|
|
169
171
|
}
|
|
170
172
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
1
2
|
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
2
3
|
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
3
4
|
import { admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
5
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
4
6
|
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
5
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
8
|
import { chmod, lstat, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
@@ -12,9 +14,11 @@ import path from "node:path";
|
|
|
12
14
|
import { spawn } from "node:child_process";
|
|
13
15
|
import { waitForSettlement } from "./session-settlement.mjs";
|
|
14
16
|
import { errorRecord } from "./operation-errors.mjs";
|
|
15
|
-
import { CodexBridge, cancelSessionWithBridge, createSessionWithBridge, inspectSessionWithBridge, promptSessionWithBridge, startSessionWithBridge } from "./dd-codex.mjs";
|
|
17
|
+
import { CODEX_REQUEST_TIMEOUT_MS, LATE_RESPONSE_GRACE_MS, CodexBridge, cancelSessionWithBridge, createSessionWithBridge, inspectSessionWithBridge, promptSessionWithBridge, startSessionWithBridge } from "./dd-codex.mjs";
|
|
16
18
|
import { assertDaemonOwnership, prepareRuntimeOwner, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, startManagedBridge, stopProcessGroup } from "./managed-daemon.mjs";
|
|
17
19
|
|
|
20
|
+
export function codexBridgeEnv(config, daemonId) { return { ...config.env, ...managedFlowEnv(config, daemonId) }; }
|
|
21
|
+
|
|
18
22
|
const requestSchema = "dd-codex/daemon-request@1";
|
|
19
23
|
const responseSchema = "dd-codex/daemon-response@1";
|
|
20
24
|
const stateSchema = "dd-codex/daemon-state@1";
|
|
@@ -72,13 +76,15 @@ export async function cancelCodexTree(bridge, options, { remember = () => {}, ac
|
|
|
72
76
|
return { harness: "codex-desktop", provider_session_id: options.sessionId, adapter_session_id: options.sessionId, cancelled: true, settled: true, tree_session_ids: [...sessions], cleanup_errors: settlement.cleanup_errors, tree_observations: settlement.observations.map(item => ({ provider_session_id: item.provider_session_id, status: item.status, settled: item.settled })) };
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
76
|
-
const request = { schema_id: requestSchema, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params };
|
|
79
|
+
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = operation === "session.create" ? params.timeoutMs ?? CODEX_REQUEST_TIMEOUT_MS + LATE_RESPONSE_GRACE_MS : 30_000) {
|
|
80
|
+
const request = prepareDaemonRequest({ schema_id: requestSchema, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
77
81
|
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: responseSchema, harness: 'codex', timeoutMs });
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
export async function startDaemon(options) {
|
|
81
|
-
|
|
85
|
+
if (!options.noFlow && Boolean(options.ddFlowHome) !== Boolean(options.ddFlowBin)) throw Object.assign(new Error("--dd-flow-home and --dd-flow-bin must be provided together"), { code: "flow_context_incomplete" });
|
|
86
|
+
if (options.noFlow) options = { ...options, ddFlowHome: undefined, ddFlowBin: undefined };
|
|
87
|
+
const paths = locations(options.stateDir); const cwd = absolute(options.cwd, "--cwd"); const config = { cwd, journal: absolute(options.journal, "--journal"), bin: options.bin ?? null, ddFlowBin: options.ddFlowBin ? absolute(options.ddFlowBin, "--dd-flow-bin") : null, ddFlowHome: options.ddFlowHome ? absolute(options.ddFlowHome, "--dd-flow-home") : null, projectRoot: options.projectRoot ? absolute(options.projectRoot, "--project-root") : cwd, resourceHome: !options.noFlow && process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null, env: options.ddFlowHome ? { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home"), ...(options.ddFlowBin ? { DD_FLOW_BIN: absolute(options.ddFlowBin, "--dd-flow-bin") } : {}) } : {} };
|
|
82
88
|
await prepareRuntimeOwner(config, paths.dir);
|
|
83
89
|
await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
84
90
|
let previous = await readState(paths.state); assertDaemonRestartable(previous);
|
|
@@ -95,7 +101,7 @@ export async function stopDaemon(options) { const timeoutMs = options.timeoutMs
|
|
|
95
101
|
|
|
96
102
|
export async function serveDaemon(stateDir) {
|
|
97
103
|
const paths = locations(stateDir); const state = await readState(paths.state); if (!state || state.schema_id !== stateSchema) throw Object.assign(new Error("daemon state missing"), { code: "daemon_state_missing" });
|
|
98
|
-
const bridge = new CodexBridge({ ...state.config, env: { ...state.config.
|
|
104
|
+
const bridge = new CodexBridge({ ...state.config, env: { ...codexBridgeEnv(state.config, state.daemon_id), CODEX_HOME: process.env.CODEX_HOME, DD_FLOW_CODEX_STATE_DIR: paths.dir } }); await startManagedBridge({ config: state.config, state, bridge, kind: "codex-provider", publish: snapshot => writeState(paths.state, snapshot) }); const sessions = new Set(state.sessions ?? []); let active = null; let server; let persist = Promise.resolve();
|
|
99
105
|
scheduleDaemonProcessHeartbeat(state.config, state.resource_process);
|
|
100
106
|
const save = (patch = {}) => { Object.assign(state, patch, { pid: process.pid, updated_at: new Date().toISOString(), sessions: [...sessions] }); const snapshot = structuredClone(state); persist = persist.catch(() => {}).then(() => writeState(paths.state, snapshot)); return persist; };
|
|
101
107
|
const dispatch = async (operation, params, operationId) => {
|
|
@@ -13,7 +13,8 @@ import { readNativeLines, streamNativeDiagnostics, nativeObservationError } from
|
|
|
13
13
|
import { operationProgressReporter } from "./operation-progress.mjs";
|
|
14
14
|
import { publishSetup } from './setup-progress.mjs';
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
export const CODEX_REQUEST_TIMEOUT_MS = 120_000;
|
|
17
|
+
export const LATE_RESPONSE_GRACE_MS = 5_000;
|
|
17
18
|
const CODEX_SESSION_CONFIG = Object.freeze({
|
|
18
19
|
bypass_hook_trust: true,
|
|
19
20
|
"features.plugins": false,
|
|
@@ -409,7 +410,7 @@ export class CodexBridge {
|
|
|
409
410
|
this.send({ jsonrpc: "2.0", id: message.id, error });
|
|
410
411
|
}
|
|
411
412
|
|
|
412
|
-
request(method, params = {}, timeoutMs = this.options.timeoutMs ??
|
|
413
|
+
request(method, params = {}, timeoutMs = this.options.timeoutMs ?? CODEX_REQUEST_TIMEOUT_MS) {
|
|
413
414
|
const turnFloor = method === "turn/start" ? new Set([this.currentTurns?.get(params.threadId), ...[...this.turns].filter(([, value]) => value.threadId === params.threadId).map(([id]) => id)].filter(Boolean)) : null;
|
|
414
415
|
if (method === "turn/start" && params.threadId) {
|
|
415
416
|
// Only one productive root Turn is admitted by the daemon fence. Drop
|
|
@@ -576,7 +577,8 @@ export async function promptSessionWithBridge(bridge, options) {
|
|
|
576
577
|
const started = await bridge.request("turn/start", { threadId: sessionId, input: [{ type: "text", text: prompt }], cwd, model: options.model ?? null, effort: options.reasoning ?? null, approvalPolicy: "never", sandboxPolicy: { type: "dangerFullAccess" } }, 30_000);
|
|
577
578
|
const turn = started?.turn ?? started; const turnId = turn?.id ?? null;
|
|
578
579
|
let completed;
|
|
579
|
-
try { completed =
|
|
580
|
+
try { completed = isTerminalTurnStatus(turn?.status) ? terminalTurn({ turn }, sessionId, turnId, bridge)
|
|
581
|
+
: await waitForTurn(bridge, sessionId, turnId, options.livenessTimeoutMs ?? options.timeoutMs ?? 1_800_000, options, cwd, clock, turn?.status); }
|
|
580
582
|
catch (error) {
|
|
581
583
|
const retained = bridge.turns?.get(turnId);
|
|
582
584
|
if (retained?.value?.threadId === sessionId && retained.value.turn?.id === turnId && retained.value.turn.status === "completed") {
|
|
@@ -624,11 +626,20 @@ async function ensureThreadLoaded(bridge, sessionId, options, cwd) {
|
|
|
624
626
|
}, 30_000);
|
|
625
627
|
}
|
|
626
628
|
|
|
627
|
-
async function waitForTurn(bridge, sessionId, turnId, timeoutMs, options, cwd, clock = new ObservationClock({ timeoutMs, onGap: gap => bridge.journal?.write("observation_gap", { ...gap, session_id: sessionId, turn_id: turnId }) })) {
|
|
629
|
+
async function waitForTurn(bridge, sessionId, turnId, timeoutMs, options, cwd, clock = new ObservationClock({ timeoutMs, onGap: gap => bridge.journal?.write("observation_gap", { ...gap, session_id: sessionId, turn_id: turnId }) }), startStatus) {
|
|
628
630
|
// app-server can briefly expose the new Turn as `interrupted` between the
|
|
629
631
|
// turn/start response and its turn/started notification. Give that snapshot
|
|
630
632
|
// one event-loop grace window before treating failure as authoritative.
|
|
631
633
|
const failureNotBefore = Date.now() + 1_000;
|
|
634
|
+
const reconcileSnapshot = async (status, threadStatus) => {
|
|
635
|
+
if (!isAuthoritativeTerminal(bridge, turnId, status, threadStatus, failureNotBefore)) return false;
|
|
636
|
+
if (status === "completed" || startStatus !== "inProgress" || bridge.turns.get(turnId)?.status === "completed") return true;
|
|
637
|
+
// A provisional failure can contradict the accepted start before its
|
|
638
|
+
// delayed notification arrives. Recheck this Turn, never start another.
|
|
639
|
+
const current = await readTurn(bridge, sessionId, turnId, true);
|
|
640
|
+
return bridge.turns.get(turnId)?.status === "completed" || current?.turn?.status === status
|
|
641
|
+
&& isAuthoritativeTerminal(bridge, turnId, status, current.threadStatus, failureNotBefore);
|
|
642
|
+
};
|
|
632
643
|
let nextStoredReadAt = Date.now();
|
|
633
644
|
while (true) {
|
|
634
645
|
bridge.assertHookHealthy?.(sessionId);
|
|
@@ -636,20 +647,20 @@ async function waitForTurn(bridge, sessionId, turnId, timeoutMs, options, cwd, c
|
|
|
636
647
|
const expired = clock.sample(bridge.activityObservations?.get(sessionId) ?? bridge.activity?.get(sessionId));
|
|
637
648
|
if (expired || Date.now() >= nextStoredReadAt) {
|
|
638
649
|
const stored = await readTurn(bridge, sessionId, turnId, false);
|
|
639
|
-
if (stored?.turn &&
|
|
650
|
+
if (stored?.turn && await reconcileSnapshot(stored.turn.status, stored.threadStatus)) return terminalTurn({ turn: stored.turn }, sessionId, turnId, bridge);
|
|
640
651
|
// A loaded app-server can retain a stale `active` Thread state after a
|
|
641
652
|
// provider-side interruption. The persisted turn page is independent of
|
|
642
653
|
// that live subscription and is the compact authority for its terminal
|
|
643
654
|
// status; unlike `includeTurns`, it never hydrates the whole history.
|
|
644
655
|
const persisted = await readPersistedTurn(bridge, sessionId, turnId);
|
|
645
|
-
if (persisted &&
|
|
656
|
+
if (persisted && await reconcileSnapshot(persisted.status, stored?.threadStatus)) return terminalTurn({ turn: persisted }, sessionId, turnId, bridge);
|
|
646
657
|
// Desktop may unload an idle Thread immediately after an interrupted
|
|
647
658
|
// Turn. Reload it once before polling again: otherwise the compact
|
|
648
659
|
// status remains `notLoaded` forever and hides the terminal Turn.
|
|
649
660
|
if (stored?.threadStatus === "notLoaded") {
|
|
650
661
|
await ensureThreadLoaded(bridge, sessionId, options, cwd);
|
|
651
662
|
const hydrated = await readTurn(bridge, sessionId, turnId, true);
|
|
652
|
-
if (hydrated?.turn &&
|
|
663
|
+
if (hydrated?.turn && await reconcileSnapshot(hydrated.turn.status, hydrated.threadStatus)) return terminalTurn({ turn: hydrated.turn }, sessionId, turnId, bridge);
|
|
653
664
|
}
|
|
654
665
|
// Some app-server versions correctly omit turns for a compact read.
|
|
655
666
|
// Once the thread is idle, hydrate exactly once to recover the terminal
|
|
@@ -660,7 +671,7 @@ async function waitForTurn(bridge, sessionId, turnId, timeoutMs, options, cwd, c
|
|
|
660
671
|
// an inProgress Turn after emitting the final agent message; treating
|
|
661
672
|
// that stale Turn as terminal incorrectly aborts an otherwise usable
|
|
662
673
|
// worker session.
|
|
663
|
-
if (hydrated?.turn &&
|
|
674
|
+
if (hydrated?.turn && await reconcileSnapshot(hydrated.turn.status, hydrated.threadStatus)) return terminalTurn({ turn: hydrated.turn }, sessionId, turnId, bridge);
|
|
664
675
|
}
|
|
665
676
|
// Some app-server builds emit a final agent message but omit both the
|
|
666
677
|
// terminal event and the compact turn record. Wait briefly, hydrate once,
|
|
@@ -671,8 +682,9 @@ async function waitForTurn(bridge, sessionId, turnId, timeoutMs, options, cwd, c
|
|
|
671
682
|
// app-server reports the Thread idle.
|
|
672
683
|
if (final?.turnId === turnId && stored?.threadStatus === "idle" && Date.now() - final.completedAt >= 1_000) {
|
|
673
684
|
const hydrated = await readTurn(bridge, sessionId, turnId, true);
|
|
674
|
-
if (hydrated?.turn &&
|
|
675
|
-
|
|
685
|
+
if (hydrated?.turn && await reconcileSnapshot(hydrated.turn.status, hydrated.threadStatus)) return terminalTurn({ turn: hydrated.turn }, sessionId, turnId, bridge);
|
|
686
|
+
if (hydrated?.threadStatus === "idle" && !isTerminalTurnStatus(hydrated.turn?.status))
|
|
687
|
+
return terminalTurn({ turn: { id: turnId, status: "completed", synthetic: true, final_message: final.item } }, sessionId, turnId, bridge);
|
|
676
688
|
}
|
|
677
689
|
nextStoredReadAt = Date.now() < failureNotBefore ? failureNotBefore : Date.now() + 15_000;
|
|
678
690
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
2
2
|
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
3
3
|
import { admitNativeRequestBuffer, nativeObservationError } from "./native-streams.mjs";
|
|
4
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
4
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
6
|
import { chmod, lstat, mkdir, open, unlink, realpath } from "node:fs/promises";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
@@ -16,7 +17,7 @@ import { prepareRuntimeOwner, cleanupFailedStart, confirmDaemonProcess, confirmD
|
|
|
16
17
|
const REQUEST = "dd-droid/daemon-request@1", RESPONSE = "dd-droid/daemon-response@1";
|
|
17
18
|
async function removeSocket(file) { try { if (!(await lstat(file)).isSocket()) throw new DroidError("unsafe_socket", "Refusing to replace non-socket daemon path"); await unlink(file); } catch (error) { if (error.code !== "ENOENT") throw error; } }
|
|
18
19
|
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000, operationId = process.env.DD_EVAL_OPERATION_ID ?? randomUUID()) {
|
|
19
|
-
const request = { schema_id: REQUEST, id: operationId, operation, params };
|
|
20
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST, id: operationId, operation, params });
|
|
20
21
|
return await callDaemonObserved({ stateDir, socket: droidPaths(stateDir).socket, request, responseSchema: RESPONSE, harness: 'droid', timeoutMs });
|
|
21
22
|
}
|
|
22
23
|
function sameConfig(a, b) { return ["cwd", "bin", "model", "reasoning", "mode", "provider", "ddFlowBin", "ddFlowHome", "resourceHome", "projectRoot", "noFlow","inactivityMs"].every(key => a?.[key] === b[key]); }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
1
2
|
import { processSnapshot } from "./process-snapshot.mjs";
|
|
2
3
|
import { ToolObservations, droidToolObservations, journalToolObservations } from "./tool-observations.mjs";
|
|
3
4
|
export { processSnapshot } from "./process-snapshot.mjs";
|
|
@@ -44,7 +45,7 @@ export function droidEnvironment(config) {
|
|
|
44
45
|
// This adapter deliberately uses the operator's existing Factory login.
|
|
45
46
|
// Undefined is a tombstone: downstream env overlays must not resurrect keys.
|
|
46
47
|
for (const key of Object.keys(env)) if (key.endsWith("API_KEY")) env[key] = undefined;
|
|
47
|
-
return env;
|
|
48
|
+
return { ...env, ...managedFlowEnv(config) };
|
|
48
49
|
}
|
|
49
50
|
export async function prepareDroidHome(config) {
|
|
50
51
|
const paths = droidPaths(config.stateDir);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
1
2
|
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
2
3
|
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
3
4
|
import { canonicalNativeOperation } from './adapter-timeouts.mjs';
|
|
4
5
|
import { admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
6
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
5
7
|
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
6
8
|
import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
|
|
7
9
|
import { ToolObservations, acpToolObservation } from "./tool-observations.mjs";
|
|
@@ -39,7 +41,7 @@ const errorPayload = errorRecord;
|
|
|
39
41
|
function shellQuote(value) { return `'${String(value).replace(/'/g, "'\\''")}'`; }
|
|
40
42
|
|
|
41
43
|
function executable(command, args = []) { const resolved = command ?? process.env.DD_GROK_BIN ?? "grok"; return /\.[cm]?js$/.test(resolved) ? { command: process.execPath, args: [resolved, ...args] } : { command: resolved, args }; }
|
|
42
|
-
export function isolatedEnv(config) {
|
|
44
|
+
export function isolatedEnv(config) { return { ...process.env, HOME: config.grokHome, GROK_HOME: config.grokHome, ...(config.authPath ? { GROK_AUTH_PATH: config.authPath } : {}), ...managedFlowEnv(config) }; }
|
|
43
45
|
function within(candidate, root) { const resolved = path.resolve(candidate); const parent = path.resolve(root); return resolved === parent || resolved.startsWith(`${parent}${path.sep}`); }
|
|
44
46
|
|
|
45
47
|
async function writeIsolatedConfig(paths) {
|
|
@@ -151,7 +153,7 @@ async function materializeSessionArchive(paths, archiveDir) {
|
|
|
151
153
|
|
|
152
154
|
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
153
155
|
operation = canonicalNativeOperation('grok', operation);
|
|
154
|
-
const request = { schema_id: REQUEST_SCHEMA, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params };
|
|
156
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST_SCHEMA, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
155
157
|
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'grok', timeoutMs });
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
1
2
|
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
2
3
|
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
3
4
|
import { admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
5
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
4
6
|
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
5
7
|
import { openCodeToolObservation, journalToolObservations } from "./tool-observations.mjs";
|
|
6
8
|
import { ObservationClock } from "./observation-clock.mjs";
|
|
@@ -40,13 +42,13 @@ const writeJson = writeJsonAtomic;
|
|
|
40
42
|
async function readState(dir) { try { return JSON.parse(await readFile(locations(dir).state, "utf8")); } catch (error) { if (error.code === "ENOENT") return null; throw error; } }
|
|
41
43
|
async function removeSocket(paths) { try { if (!(await lstat(paths.socket)).isSocket()) throw new DaemonError("unsafe_socket", "refusing to replace non-socket daemon path"); await unlink(paths.socket); } catch (error) { if (error.code !== "ENOENT") throw error; } }
|
|
42
44
|
function executable(bin, args = []) { return /\.[cm]?js$/.test(bin) ? { command: process.execPath, args: [bin, ...args] } : { command: bin, args }; }
|
|
43
|
-
function isolatedEnv(config) {
|
|
45
|
+
export function isolatedEnv(config) {
|
|
44
46
|
const env = { ...process.env };
|
|
45
47
|
for (const name of ["OPENCODE_CONFIG", "OPENCODE_CONFIG_CONTENT", "OPENCODE_PASSWORD", "OPENCODE_PTY_HANDOFF", "OPENCODE_SERVER", "OPENCODE_SIMULATE"]) delete env[name];
|
|
46
48
|
return { ...env, XDG_DATA_HOME: config.dataHome, XDG_CONFIG_HOME: config.configHome, XDG_CACHE_HOME: config.cacheHome,
|
|
47
49
|
XDG_STATE_HOME: config.stateHome, OPENCODE_DB: config.database, OPENCODE_CONFIG_DIR: config.adapterDir,
|
|
48
50
|
OPENCODE_CONFIG_PROJECT_DISABLE: "1", OPENCODE_SERVER_PASSWORD: config.password,
|
|
49
|
-
...(config
|
|
51
|
+
...managedFlowEnv(config),
|
|
50
52
|
DD_OPENCODE_PLUGIN_SHA256: config.pluginSha256 ?? "none" };
|
|
51
53
|
}
|
|
52
54
|
export async function run(bin, args, config, options = {}) {
|
|
@@ -63,7 +65,7 @@ const errorPayload = errorRecord;
|
|
|
63
65
|
|
|
64
66
|
async function writePlugin(paths, config) {
|
|
65
67
|
const directory = path.join(paths.config, "opencode", "plugins"); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
66
|
-
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot })});\n`;
|
|
68
|
+
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot, resourceHome: config.resourceHome })});\n`;
|
|
67
69
|
const checksum = createHash("sha256").update(source).digest("hex"); await writeFile(path.join(directory, "dd-flow.js"), source, { mode: 0o600 }); return checksum;
|
|
68
70
|
}
|
|
69
71
|
async function prepare(paths, config, authSource) {
|
|
@@ -136,7 +138,7 @@ async function importArchive(config, client) {
|
|
|
136
138
|
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
137
139
|
const state = await readState(stateDir);
|
|
138
140
|
if (state && state.schema_id !== STATE_SCHEMA) throw new DaemonError("legacy_opencode_state", "Use archive/opencode-v1-final-beta134 for v1 states; the v2 adapter cannot dispatch to a legacy daemon", false, { effect: "no_effect" });
|
|
139
|
-
const request = { schema_id: REQUEST_SCHEMA, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params };
|
|
141
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST_SCHEMA, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
140
142
|
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'opencode', timeoutMs });
|
|
141
143
|
}
|
|
142
144
|
function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","variant","agent","projectRoot","ddFlowBin","ddFlowHome","sessionArchive","resourceHome","noFlow","configSource","authSource"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|