@deksden-com/dd-flow-cli 0.9.0-beta.136 → 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 +12 -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 +56 -3
- package/dist/cli/command-option-descriptions.js +5 -0
- package/dist/cli/executor-onboarding.js +8 -3
- package/dist/cli/flows.js +29 -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/native-hook-ingress.js +1 -1
- package/dist/cli/response-file.js +2 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/setup.js +9 -7
- package/dist/cli.js +54 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +13 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
- 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 +20 -5
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +45 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
- 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 +5 -1
- 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/code-checks.js +387 -34
- package/dist/services/controller-child-observer.js +99 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +79 -13
- 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/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/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/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +31 -3
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +7 -4
- 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/projects.js +17 -2
- package/dist/services/recovery-snapshot-database.js +416 -1
- 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 +184 -48
- 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/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +2 -1
- package/dist/services/stage-report-renderer.js +76 -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 +136 -10
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +6 -4
- package/dist/services/vnext-protocolize.js +60 -2
- 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
package/dist/cli/setup.js
CHANGED
|
@@ -15,6 +15,7 @@ import { hybridDigest } from "../services/hybrid-contracts.js";
|
|
|
15
15
|
import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
|
|
16
16
|
import { readRegistryRelease } from "../services/release-registry.js";
|
|
17
17
|
import { writePreparationError } from "./error-output.js";
|
|
18
|
+
import { publishSetup } from "../harness-runtime/lib/setup-progress.mjs";
|
|
18
19
|
/** Lightweight global diagnostic ingress; never creates a runtime context. */
|
|
19
20
|
export async function runSetupCli(args, env, io = { stdout: process.stdout, stderr: process.stderr, stdin: process.stdin }) {
|
|
20
21
|
let json = false;
|
|
@@ -96,13 +97,14 @@ export async function runSetupCli(args, env, io = { stdout: process.stdout, stde
|
|
|
96
97
|
const installed = installAuthenticatedRelease(home, { requestId, action: route.key === "setup update" ? "update" : "install",
|
|
97
98
|
envelope: bundle.envelope, trustedProducers: producers, compatibility, canonArchive: bundle.canonArchive, engineArchive: bundle.engineArchive,
|
|
98
99
|
...(deferred ? { deferredOnboarding: true } : { executor: selected.profile, ...(profileCreation ? { profileCreation } : {}) }),
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
} });
|
|
106
108
|
if (humanProgress && installed.replayed)
|
|
107
109
|
reportHumanProgress("exact request replay verified; retained selection was not reactivated");
|
|
108
110
|
if (deferred) {
|
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
|
+
});
|
|
@@ -18,6 +18,19 @@ closed transport is not proof that its native descendants stopped. The adapter
|
|
|
18
18
|
must retain causal errors, missing observations and usage completeness rather
|
|
19
19
|
than turn them into successful/zero-valued results.
|
|
20
20
|
|
|
21
|
+
## Retained child observations
|
|
22
|
+
|
|
23
|
+
Daemon capability `retained_child_observation` enables `session observe-children`
|
|
24
|
+
for its registered root. It reads retained topology and exact terminal metadata,
|
|
25
|
+
never loads/resumes a child or performs semantic work. During fanout the controller
|
|
26
|
+
records failed child facts before dispatching anything else; healthy in-flight
|
|
27
|
+
siblings may settle normally. Observation polling is not productive model activity.
|
|
28
|
+
Ordinary `session inspect` is not a substitute for this read-only operation.
|
|
29
|
+
|
|
30
|
+
Native attribution is a reported fact, not proof that an HTTP request reached a
|
|
31
|
+
provider. If the native store discarded the causal exception, retain unknown;
|
|
32
|
+
do not scrape ambient logs or infer overload/retry authority from an error message.
|
|
33
|
+
|
|
21
34
|
The bridge marks heuristic ACP completion with
|
|
22
35
|
`_meta.zcodeCompletionEvidence: "inferred"` and exhausted failed turns with
|
|
23
36
|
`"failed"`. dd-zcode rejects these before any post-turn materializing read:
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
import { adapterTimeoutOptions } from "../lib/adapter-timeouts.mjs";
|
|
3
3
|
import { durableDaemonStart, inspectDaemonOperation, interruptDaemonOperation, settleDaemonOperation } from "../lib/daemon-operations.mjs";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { callDaemon, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-opencode-daemon.mjs";
|
|
5
|
+
import { callDaemon, doctor, openCodeProfileOptions, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-opencode-daemon.mjs";
|
|
6
6
|
|
|
7
7
|
function parse(argv) { const positional = [], options = {}; for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; if (!token.startsWith("--")) { positional.push(token); continue; } const key = token.slice(2); if (["json","cancel-tree","no-flow"].includes(key)) options[key] = true; else { if (argv[i + 1] === undefined) throw new Error(`--${key} requires a value`); options[key] = argv[++i]; } } return { positional, options }; }
|
|
8
8
|
const prompt = async options => options["prompt-file"] ? await readFile(options["prompt-file"], "utf8") : options.prompt;
|
|
9
9
|
try {
|
|
10
|
-
const { positional, options } = parse(process.argv.slice(2)), [family, command, modifier] = positional; const common = { journal: options.journal, stateDir: options["state-dir"], sessionId: options["session-id"], cwd: options.cwd, bin: options["opencode-bin"], provider: options.provider, model: options.model,
|
|
10
|
+
const { positional, options } = parse(process.argv.slice(2)), [family, command, modifier] = positional; const common = { journal: options.journal, stateDir: options["state-dir"], sessionId: options["session-id"], cwd: options.cwd, bin: options["opencode-bin"], provider: options.provider, model: options.model, ...openCodeProfileOptions(options), authPath: options["auth-path"], configPath: options["config-path"], projectRoot: options["project-root"], ddFlowBin: options["dd-flow-bin"], ddFlowHome: options["dd-flow-home"], noFlow: options["no-flow"] === true, sessionArchive: options["session-archive"], ...adapterTimeoutOptions(options, family === "session" ? command : null) }; let result;
|
|
11
11
|
if (family === "daemon" && command === "start") result = await durableDaemonStart({ ...common, entryPath: process.argv[1] }, startDaemon);
|
|
12
12
|
else if (family === "daemon" && command === "serve") { await serveDaemon(common.stateDir); process.exit(0); }
|
|
13
13
|
else if (family === "daemon" && command === "status") result = await callDaemon(common.stateDir, "daemon.status");
|
|
14
14
|
else if (family === "daemon" && command === "operation" && modifier === "interrupt") result = await interruptDaemonOperation(common.stateDir, options["operation-id"], { control_id: options["control-id"], stop_operation_id: options["stop-operation-id"], daemon_id: options["daemon-id"], session_id: options["session-id"], evidence: options.evidence ? JSON.parse(options.evidence) : null });
|
|
15
15
|
else if (family === "daemon" && command === "operation" && modifier === "settle") result = await settleDaemonOperation(common.stateDir, options["operation-id"]);
|
|
16
16
|
else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(common.stateDir, options["operation-id"]);
|
|
17
|
-
else if (family === "doctor") result = await
|
|
17
|
+
else if (family === "doctor") result = await doctor(common);
|
|
18
18
|
else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: common.stateDir, cancelTree: options["cancel-tree"], timeoutMs: common.timeoutMs });
|
|
19
19
|
else if (family === "session") { const operation = command === "status" ? "inspect" : command; const params = { livenessTimeoutMs: common.livenessTimeoutMs, sessionId: options["session-id"], title: options.title, messageId: options["message-id"], archiveFile: options["archive-file"], ...(command === "prompt" ? { prompt: await prompt(options) } : {}) }; result = await callDaemon(common.stateDir, `session.${operation}`, params, common.timeoutMs ?? 1_800_000); }
|
|
20
20
|
else throw new Error("usage: dd-opencode daemon start|status|stop | session create|prompt|inspect|fork|cancel|archive [options] --json");
|
|
@@ -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";
|
|
@@ -22,18 +24,31 @@ import { prepareRuntimeOwner, cleanupFailedStart, confirmDaemonProcess, confirmD
|
|
|
22
24
|
const REQUEST_SCHEMA = "dd-opencode/daemon-request@1", RESPONSE_SCHEMA = "dd-opencode/daemon-response@1", STATE_SCHEMA = "dd-opencode/daemon-state@2", ARCHIVE_SCHEMA = "dd-opencode/session-archive@2";
|
|
23
25
|
class DaemonError extends Error { constructor(code, message, retryable = false, details) { super(message); this.code = code; this.retryable = retryable; this.details = details; } }
|
|
24
26
|
const absolute = (value, label) => { if (!value || !path.isAbsolute(value)) throw new DaemonError("invalid_path", `${label} must be an absolute path`); return path.resolve(value); };
|
|
27
|
+
export function openCodeProfileOptions(options) {
|
|
28
|
+
for (const [native, shared] of [["variant", "reasoning"], ["agent", "mode"]]) {
|
|
29
|
+
if (options[native] !== undefined && options[shared] !== undefined && options[native] !== options[shared]) throw new DaemonError("profile_conflict", `--${native} and --${shared} disagree`);
|
|
30
|
+
}
|
|
31
|
+
return { variant: options.variant ?? options.reasoning, agent: options.agent ?? options.mode };
|
|
32
|
+
}
|
|
33
|
+
export async function doctor(options) {
|
|
34
|
+
if (options.stateDir) return await callDaemon(options.stateDir, "daemon.doctor");
|
|
35
|
+
const output = (await run(options.bin ?? process.env.DD_OPENCODE_BIN ?? "opencode", ["--version"], { cwd: options.cwd ?? process.cwd() })).trim();
|
|
36
|
+
const version = output.match(/(?:^|\s)v?(2\.\d+\.\d+(?:[-+][\w.-]+)?)(?:\s|$)/)?.[1];
|
|
37
|
+
if (!version) throw new DaemonError("unsupported_version", "dd-opencode supports OpenCode v2 only");
|
|
38
|
+
return { compatible: true, native_protocol: 2, observed_runtime: { opencode: version }, productive_hooks: false, profile_observation: "pending_native_session" };
|
|
39
|
+
}
|
|
25
40
|
function locations(stateDir) { const dir = absolute(stateDir, "--state-dir"), local = path.join(dir, "daemon.sock"); return { dir, socket: Buffer.byteLength(local) < 100 ? local : `/tmp/dd-opencode-${createHash("sha256").update(dir).digest("hex").slice(0, 20)}.sock`, state: path.join(dir, "daemon.json"), journal: path.join(dir, "events.jsonl"), log: path.join(dir, "daemon.log"), data: path.join(dir, "xdg-data"), config: path.join(dir, "xdg-config"), cache: path.join(dir, "xdg-cache"), archive: path.join(dir, "import.json"), db: path.join(dir, "native.db"), xdgState: path.join(dir, "xdg-state") }; }
|
|
26
41
|
const writeJson = writeJsonAtomic;
|
|
27
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; } }
|
|
28
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; } }
|
|
29
44
|
function executable(bin, args = []) { return /\.[cm]?js$/.test(bin) ? { command: process.execPath, args: [bin, ...args] } : { command: bin, args }; }
|
|
30
|
-
function isolatedEnv(config) {
|
|
45
|
+
export function isolatedEnv(config) {
|
|
31
46
|
const env = { ...process.env };
|
|
32
47
|
for (const name of ["OPENCODE_CONFIG", "OPENCODE_CONFIG_CONTENT", "OPENCODE_PASSWORD", "OPENCODE_PTY_HANDOFF", "OPENCODE_SERVER", "OPENCODE_SIMULATE"]) delete env[name];
|
|
33
48
|
return { ...env, XDG_DATA_HOME: config.dataHome, XDG_CONFIG_HOME: config.configHome, XDG_CACHE_HOME: config.cacheHome,
|
|
34
49
|
XDG_STATE_HOME: config.stateHome, OPENCODE_DB: config.database, OPENCODE_CONFIG_DIR: config.adapterDir,
|
|
35
50
|
OPENCODE_CONFIG_PROJECT_DISABLE: "1", OPENCODE_SERVER_PASSWORD: config.password,
|
|
36
|
-
...(config
|
|
51
|
+
...managedFlowEnv(config),
|
|
37
52
|
DD_OPENCODE_PLUGIN_SHA256: config.pluginSha256 ?? "none" };
|
|
38
53
|
}
|
|
39
54
|
export async function run(bin, args, config, options = {}) {
|
|
@@ -50,7 +65,7 @@ const errorPayload = errorRecord;
|
|
|
50
65
|
|
|
51
66
|
async function writePlugin(paths, config) {
|
|
52
67
|
const directory = path.join(paths.config, "opencode", "plugins"); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
53
|
-
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`;
|
|
54
69
|
const checksum = createHash("sha256").update(source).digest("hex"); await writeFile(path.join(directory, "dd-flow.js"), source, { mode: 0o600 }); return checksum;
|
|
55
70
|
}
|
|
56
71
|
async function prepare(paths, config, authSource) {
|
|
@@ -123,7 +138,7 @@ async function importArchive(config, client) {
|
|
|
123
138
|
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
124
139
|
const state = await readState(stateDir);
|
|
125
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" });
|
|
126
|
-
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 });
|
|
127
142
|
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'opencode', timeoutMs });
|
|
128
143
|
}
|
|
129
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)); }
|
|
@@ -272,7 +287,7 @@ export class Runtime {
|
|
|
272
287
|
if (info.pid !== this.child?.pid || info.version !== this.state.versions.opencode) throw new DaemonError("foreign_server", "OpenCode endpoint is not the owned process");
|
|
273
288
|
const pluginID = this.state.config.noFlow ? "dd-flow-security" : "dd-flow";
|
|
274
289
|
if (!plugins.data.some(plugin => plugin.id === pluginID && plugin.state?.status === "active")) throw new DaemonError("opencode_plugin_missing", "Owned v2 plugin is not active");
|
|
275
|
-
return { compatible: true, native_protocol: 2, productive_hooks: !this.state.config.noFlow, versions: this.state.versions, plugin_sha256: this.state.config.pluginSha256, auth_status: this.state.auth_status };
|
|
290
|
+
return { compatible: true, native_protocol: 2, observed_runtime: this.state.versions, productive_hooks: !this.state.config.noFlow, versions: this.state.versions, plugin_sha256: this.state.config.pluginSha256, auth_status: this.state.auth_status };
|
|
276
291
|
}
|
|
277
292
|
if (operation === "session.create") return this.productive(operation, async () => {
|
|
278
293
|
const info = await this.client.create(params.title, this.state.config);
|