@deksden-com/dd-flow-cli 0.9.0-beta.13 → 0.9.0-beta.133
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 +666 -0
- package/README.md +101 -1
- package/dist/build-info.json +10 -10
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +306 -0
- package/dist/cli/help.js +88 -12
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/run-cli.js +1465 -450
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +52 -0
- package/dist/cli/runtime-process.js +153 -0
- package/dist/cli.js +17 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/harness-runtime/DD-ZCODE.md +99 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +10 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +370 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +693 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +190 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +805 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +128 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +561 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +490 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +255 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +128 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +703 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1108 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +13 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +144 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +36 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +272 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +12 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +619 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +13 -0
- package/dist/harness-runtime/lib/native-children.mjs +78 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
- package/dist/harness-runtime/lib/process-json.d.mts +5 -0
- package/dist/harness-runtime/lib/process-json.mjs +201 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/flow-run.schema.json +2 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +121 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +851 -109
- package/dist/services/codex-capacity.js +367 -0
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +119 -0
- package/dist/services/dashboard.js +135 -56
- package/dist/services/engines.js +245 -119
- package/dist/services/eval-snapshots.js +1146 -105
- package/dist/services/execution-policy.js +181 -0
- package/dist/services/external-work-launch.js +230 -0
- package/dist/services/harness-adapter.js +329 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +747 -325
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +1 -288
- package/dist/services/lifecycle-contract.js +1 -0
- package/dist/services/lifecycle-invocations.js +1774 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-lease-monitor.js +133 -0
- package/dist/services/managed-processes.js +409 -51
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +163 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +20 -15
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +145 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +157 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +985 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +179 -0
- package/dist/services/run-controller-process.js +187 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1035 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +349 -0
- package/dist/services/runs.js +558 -217
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +180 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +185 -19
- package/dist/services/stage-lifecycle.js +98 -107
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +158 -6
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/tooling.js +32 -23
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +374 -0
- package/dist/services/vnext-code-review.js +222 -87
- package/dist/services/vnext-code.js +324 -251
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +79 -4
- package/dist/services/vnext-fanout.js +177 -17
- package/dist/services/vnext-merge.js +335 -113
- package/dist/services/vnext-plan-review.js +276 -126
- package/dist/services/vnext-plan.js +227 -102
- package/dist/services/vnext-protocolize.js +335 -82
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/work-registry.js +1038 -192
- package/dist/services/workspace-bootstrap.js +87 -0
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +98 -44
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +17 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +781 -37
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +30 -13
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -1,59 +1,352 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
6
|
+
import { stopProcessGroup } from "../harness-runtime/lib/managed-daemon.mjs";
|
|
7
|
+
import { recoveryObservationBudget, recoveryRpcTimeout, withRecoveryObservation } from "./recovery-observation-budget.js";
|
|
8
|
+
import { ObservationClock, validateObservationDuration } from "../harness-runtime/lib/observation-clock.mjs";
|
|
9
|
+
import { canonicalNativeOperation, nativeOperationWait } from "../harness-runtime/lib/adapter-timeouts.mjs";
|
|
10
|
+
import { readOperationProgress, readOperationRecord, readOperationWaiting, suspendOperationClock } from "../harness-runtime/lib/operation-progress.mjs";
|
|
11
|
+
import { recoverDaemonReply } from "../harness-runtime/lib/daemon-observation.mjs";
|
|
12
|
+
import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
|
|
13
|
+
const maxAdapterOutputBytes = 1_000_000;
|
|
14
|
+
/** Native hooks need a real executable, not a PATH-dependent shell expression. */
|
|
15
|
+
export function prepareHarnessFlowExecutable(context, stateDir) {
|
|
16
|
+
const configured = context.env.DD_FLOW_BIN;
|
|
17
|
+
if (configured !== undefined) {
|
|
18
|
+
if (!path.isAbsolute(configured) || !fs.existsSync(configured))
|
|
19
|
+
throw new AppError("harness_runtime_missing", "DD_FLOW_BIN must identify an existing absolute executable", 2);
|
|
20
|
+
if (path.basename(configured) === "dd-flow")
|
|
21
|
+
return configured;
|
|
22
|
+
}
|
|
23
|
+
const isolated = path.join(context.ddFlowHome, "bin", "dd-flow");
|
|
24
|
+
if (!configured && fs.existsSync(isolated))
|
|
25
|
+
return isolated;
|
|
26
|
+
const entry = fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
27
|
+
if (!configured && !fs.existsSync(entry))
|
|
28
|
+
throw new AppError("harness_runtime_unbuilt", "Build the CLI or supply its pinned DD_FLOW_BIN before launching a harness", 1);
|
|
29
|
+
// The trusted lifecycle parser recognizes this basename, including for a
|
|
30
|
+
// custom pinned executable. Do not broaden that parser to arbitrary scripts.
|
|
31
|
+
const executable = path.join(stateDir, "dd-flow");
|
|
32
|
+
const dispatch = configured
|
|
33
|
+
? `const { spawnSync } = await import("node:child_process");\nconst child = spawnSync(${JSON.stringify(configured)}, process.argv.slice(2), { stdio: "inherit", env: process.env });\nif (child.error) throw child.error;\nif (child.signal) process.kill(process.pid, child.signal);\nprocess.exit(child.status ?? 1);\n`
|
|
34
|
+
: `await import(${JSON.stringify(pathToFileURL(entry).href)});\n`;
|
|
35
|
+
const contents = `#!${process.execPath}\nprocess.env.DD_FLOW_HOME = ${JSON.stringify(context.ddFlowHome)};\n${dispatch}`;
|
|
36
|
+
try {
|
|
37
|
+
fs.writeFileSync(executable, contents, { flag: "wx", mode: 0o700 });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code !== "EEXIST" || fs.readFileSync(executable, "utf8") !== contents)
|
|
41
|
+
throw new AppError("harness_runtime_binding_conflict", "Launch runtime executable already has different content", 1, { path: executable });
|
|
42
|
+
}
|
|
43
|
+
return executable;
|
|
44
|
+
}
|
|
4
45
|
export function runHarnessAdapter(input) {
|
|
5
|
-
const
|
|
46
|
+
const index = input.args.indexOf("--state-dir"), stateDir = index >= 0 ? input.args[index + 1] : undefined;
|
|
47
|
+
const operation = canonicalNativeOperation(input.harness ?? "", `${input.args[0]}.${input.args[1]}`);
|
|
48
|
+
const boundOperation = ["session.create", "session.fork", "session.start", "session.resume"].includes(operation);
|
|
49
|
+
const binding = { operation, ...(input.runtimeOwner ? { owner_id: input.runtimeOwner.owner_id } : {}) };
|
|
50
|
+
let retainedOutcome;
|
|
51
|
+
const onOutcome = (value) => {
|
|
52
|
+
const fingerprint = JSON.stringify(value);
|
|
53
|
+
if (fingerprint !== retainedOutcome) {
|
|
54
|
+
input.onNativeOutcome?.(value);
|
|
55
|
+
retainedOutcome = fingerprint;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const dispatched = runAdapterTransport(input, stateDir, binding);
|
|
59
|
+
return dispatched.catch(async (error) => {
|
|
60
|
+
if (!stateDir || !boundOperation || input.signal?.aborted || !(isObservationLoss(error) || isObservationLoss(error?.details?.cause)))
|
|
61
|
+
throw error;
|
|
62
|
+
try {
|
|
63
|
+
return await recoverDaemonReply(stateDir, input.operationId, { timeoutMs: recoveryRpcTimeout(30_000), ...(input.signal ? { signal: input.signal } : {}), ...(error?.observationClock ? { observationClock: error.observationClock } : {}), binding, onOutcome,
|
|
64
|
+
// Reconcile only original bookkeeping. Work/MERGE share this path and
|
|
65
|
+
// must not replay creation merely because its release receipt is pending.
|
|
66
|
+
onPendingSettlement: async (retained) => {
|
|
67
|
+
input.signal?.throwIfAborted();
|
|
68
|
+
await withRecoveryObservation(recoveryObservationBudget(retained), () => runAdapterTransport({ ...input, args: ["daemon", "operation", "settle", "--state-dir", stateDir, "--operation-id", input.operationId, "--json"], operationId: `${input.operationId}:settlement`, timeoutMs: recoveryRpcTimeout(30_000) }, stateDir, {}), true);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (recovery) {
|
|
73
|
+
if (recovery && typeof recovery === "object")
|
|
74
|
+
Object.assign(recovery, { cause: error });
|
|
75
|
+
throw recovery;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function runAdapterTransport(input, stateDir, binding = {}) {
|
|
80
|
+
// `null` is deliberate: a productive Turn has no adapter-wall-clock limit.
|
|
81
|
+
// Preserve it instead of treating it like an omitted short control request.
|
|
82
|
+
if (input.timeoutMs != null)
|
|
83
|
+
validateObservationDuration(input.timeoutMs);
|
|
84
|
+
const operation = canonicalNativeOperation(input.harness ?? "", `${input.args[0]}.${input.args[1]}`);
|
|
85
|
+
const productive = input.harness !== undefined && nativeOperationWait(input.harness, operation) === "native-work";
|
|
86
|
+
const timeoutMs = recoveryRpcTimeout(productive ? null : input.timeoutMs === undefined ? 120_000 : input.timeoutMs);
|
|
87
|
+
if (timeoutMs !== null)
|
|
88
|
+
validateObservationDuration(timeoutMs);
|
|
89
|
+
if (!input.operationId.trim())
|
|
90
|
+
throw new AppError("operation_id_required", "Harness calls require a scoped operation id", 2);
|
|
91
|
+
// The current adapter transport uses this name; its identity is runtime-owned,
|
|
92
|
+
// never the operation or trusted daemon identity of the calling coordinator.
|
|
93
|
+
const env = { ...input.env, DD_EVAL_OPERATION_ID: input.operationId };
|
|
94
|
+
delete env.DD_FLOW_DAEMON_ID;
|
|
95
|
+
delete env.DD_FLOW_SCOPE_CONTROL_TOKEN;
|
|
96
|
+
delete env.DD_FLOW_RUNTIME_OWNER;
|
|
97
|
+
if (input.runtimeOwner) {
|
|
98
|
+
if (!path.isAbsolute(input.executable) || input.runtimeOwner.adapter_executable !== undefined && input.runtimeOwner.adapter_executable !== input.executable)
|
|
99
|
+
throw new AppError("harness_runtime_binding_conflict", "Owned harness call must use its frozen absolute adapter executable", 1);
|
|
100
|
+
env.DD_FLOW_RUNTIME_OWNER = JSON.stringify({ ...input.runtimeOwner, adapter_executable: input.executable });
|
|
101
|
+
}
|
|
6
102
|
return new Promise((resolve, reject) => {
|
|
103
|
+
if (input.signal?.aborted)
|
|
104
|
+
return reject(new AppError("harness_adapter_aborted", "Harness adapter call was cancelled before dispatch", 1));
|
|
7
105
|
const detached = process.platform !== "win32";
|
|
8
|
-
const child = spawn(input.executable, input.args, { env: input.
|
|
106
|
+
const child = spawn(input.executable, input.args, { env, ...(input.cwd ? { cwd: input.cwd } : {}), detached, stdio: ["ignore", "pipe", "pipe"] });
|
|
9
107
|
let stdout = "";
|
|
10
108
|
let stderr = "";
|
|
109
|
+
let outputTooLarge = false;
|
|
11
110
|
let timedOut = false;
|
|
12
|
-
let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
111
|
+
let aborted = false;
|
|
112
|
+
let cleanup;
|
|
113
|
+
let pendingDiagnostic = "", droppingDiagnostic = false, progressCursor = 0;
|
|
114
|
+
let prettyDiagnostic = "", retainedError;
|
|
115
|
+
const phases = new Set();
|
|
116
|
+
let operationSequence = 0;
|
|
117
|
+
let diagnosticReads = Promise.resolve();
|
|
118
|
+
const clock = timeoutMs === null ? null : new ObservationClock({ timeoutMs });
|
|
119
|
+
clock?.sample(progressCursor);
|
|
120
|
+
let settled = false;
|
|
121
|
+
let callbackError;
|
|
122
|
+
let spawnError;
|
|
123
|
+
const stopTransport = () => {
|
|
124
|
+
cleanup ??= stopProcessGroup(child, 5_000, { detached }).catch(error => {
|
|
125
|
+
if (timer)
|
|
126
|
+
clearInterval(timer);
|
|
127
|
+
if (heartbeat)
|
|
128
|
+
clearInterval(heartbeat);
|
|
129
|
+
input.signal?.removeEventListener("abort", abort);
|
|
130
|
+
const details = { cleanup_error: errorRecord(error) };
|
|
131
|
+
if (callbackError instanceof Error) {
|
|
132
|
+
Object.assign(callbackError, { cleanup_error: details.cleanup_error, details: { ...callbackError.details, ...details } });
|
|
133
|
+
reject(callbackError);
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
reject(new AppError(outputTooLarge ? "harness_adapter_output_limit" : timedOut ? "harness_adapter_timeout" : aborted ? "harness_adapter_aborted" : "harness_adapter_cleanup_unconfirmed", "Adapter transport cleanup could not be confirmed", 1, details));
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
const deliver = (callback) => {
|
|
140
|
+
try {
|
|
141
|
+
const result = callback();
|
|
142
|
+
if (result && typeof result.then === "function") {
|
|
143
|
+
void Promise.resolve(result).catch(() => { });
|
|
144
|
+
throw new AppError("progress_callback_async", "Harness callbacks must be synchronous", 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
callbackError ??= error;
|
|
149
|
+
if (!settled)
|
|
150
|
+
stopTransport();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const heartbeat = input.progress && input.progressMessage ? setInterval(() => deliver(() => input.progress?.(`${input.progressMessage}; waiting for adapter completion`)), 15_000) : undefined;
|
|
154
|
+
let observing = false;
|
|
155
|
+
const timer = timeoutMs === null ? undefined : setInterval(async () => {
|
|
156
|
+
if (settled || cleanup)
|
|
157
|
+
return;
|
|
158
|
+
if (observing)
|
|
159
|
+
return;
|
|
160
|
+
observing = true;
|
|
161
|
+
// Helper phase progress must never replenish its owner's independent
|
|
162
|
+
// physical-settlement episode. ALS retains that existing budget here.
|
|
163
|
+
try {
|
|
164
|
+
recoveryRpcTimeout(timeoutMs);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
callbackError ??= error;
|
|
168
|
+
stopTransport();
|
|
169
|
+
observing = false;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const waiting = stateDir && await readOperationWaiting(stateDir, input.operationId, binding);
|
|
174
|
+
if (settled || cleanup)
|
|
175
|
+
return;
|
|
176
|
+
if (waiting)
|
|
177
|
+
suspendOperationClock(clock);
|
|
178
|
+
else if (clock.sample(progressCursor)) {
|
|
179
|
+
timedOut = true;
|
|
180
|
+
stopTransport();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
callbackError ??= error;
|
|
185
|
+
stopTransport();
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
observing = false;
|
|
189
|
+
}
|
|
190
|
+
}, Math.max(1, Math.min(1000, timeoutMs / 4)));
|
|
191
|
+
const abort = () => { aborted = true; stopTransport(); };
|
|
192
|
+
if (input.signal?.aborted)
|
|
193
|
+
abort();
|
|
194
|
+
else
|
|
195
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
196
|
+
const evidence = (value) => input.onEvidence?.({ operation_id: input.operationId, executable: input.executable, args: input.args, ...value, at: new Date().toISOString() });
|
|
197
|
+
const append = (current, chunk) => {
|
|
198
|
+
if (Buffer.byteLength(current) + Buffer.byteLength(chunk) > maxAdapterOutputBytes) {
|
|
199
|
+
outputTooLarge = true;
|
|
200
|
+
stopTransport();
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
return current + chunk;
|
|
204
|
+
};
|
|
205
|
+
const observeDiagnostic = (line) => {
|
|
206
|
+
if (prettyDiagnostic === null) {
|
|
207
|
+
if (line.trimEnd() === "}")
|
|
208
|
+
prettyDiagnostic = "";
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const candidate = prettyDiagnostic ? `${prettyDiagnostic}\n${line}` : line;
|
|
212
|
+
if (candidate.length > 64_000) {
|
|
213
|
+
prettyDiagnostic = null;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let value;
|
|
217
|
+
try {
|
|
218
|
+
value = JSON.parse(candidate);
|
|
219
|
+
prettyDiagnostic = "";
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
prettyDiagnostic = prettyDiagnostic || line.trimStart().startsWith("{") ? candidate : "";
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
226
|
+
return;
|
|
227
|
+
if (value.kind === "operation_progress" && stateDir && value.operation_id === input.operationId) {
|
|
228
|
+
// The diagnostic is only a wake-up: durable, bound evidence owns liveness.
|
|
229
|
+
diagnosticReads = diagnosticReads.then(async () => {
|
|
230
|
+
const record = await readOperationProgress(stateDir, input.operationId, binding);
|
|
231
|
+
if (!record || record.sequence <= operationSequence)
|
|
232
|
+
return;
|
|
233
|
+
const requested = await readOperationRecord(stateDir, input.operationId, "requested.json");
|
|
234
|
+
if (!requested)
|
|
235
|
+
throw new AppError("operation_progress_invalid", "Operation request disappeared while observing progress", 1);
|
|
236
|
+
for (const key of ["daemon_id", "params_sha256", "generation"])
|
|
237
|
+
binding[key] ??= requested[key] ?? null;
|
|
238
|
+
operationSequence = record.sequence;
|
|
239
|
+
clock?.sample(++progressCursor, Date.parse(record.observed_at));
|
|
240
|
+
}).catch(error => { callbackError ??= error; if (!settled)
|
|
241
|
+
stopTransport(); });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const cause = adapterCause(candidate);
|
|
245
|
+
if (typeof cause.code === "string")
|
|
246
|
+
retainedError = cause;
|
|
247
|
+
if (clock && !(stateDir && ["session.create", "session.fork", "session.start", "session.resume", "session.prompt"].includes(operation))
|
|
248
|
+
&& ["native_hook_phase", "process_maintenance_phase"].includes(String(value.kind))
|
|
249
|
+
&& typeof value.phase === "string" && /^[a-z_]{1,48}$/.test(value.phase)
|
|
250
|
+
&& !["heartbeat", "poll", "elapsed", "wait", "waiting", "status"].includes(value.phase)) {
|
|
251
|
+
const marker = JSON.stringify([value.kind, value.phase, value.operation_id ?? value.process_id]);
|
|
252
|
+
if (!phases.has(marker)) {
|
|
253
|
+
phases.add(marker);
|
|
254
|
+
clock.sample(++progressCursor);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout = append(stdout, chunk); });
|
|
259
|
+
// Diagnostics have no lifetime quota. Stream to the existing evidence sink
|
|
260
|
+
// and carry only a bounded tail in the disposable RPC receipt.
|
|
261
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
262
|
+
stderr = (stderr + chunk).slice(-64_000);
|
|
263
|
+
for (const piece of chunk.split(/(?<=\n)/)) {
|
|
264
|
+
if (!droppingDiagnostic)
|
|
265
|
+
pendingDiagnostic += piece;
|
|
266
|
+
if (pendingDiagnostic.length > 64_000) {
|
|
267
|
+
prettyDiagnostic = prettyDiagnostic || pendingDiagnostic.trimStart().startsWith("{") ? null : "";
|
|
268
|
+
pendingDiagnostic = "";
|
|
269
|
+
droppingDiagnostic = true;
|
|
270
|
+
}
|
|
271
|
+
if (!piece.endsWith("\n"))
|
|
272
|
+
continue;
|
|
273
|
+
if (!droppingDiagnostic)
|
|
274
|
+
observeDiagnostic(pendingDiagnostic);
|
|
275
|
+
pendingDiagnostic = "";
|
|
276
|
+
droppingDiagnostic = false;
|
|
277
|
+
}
|
|
278
|
+
if (input.onEvidence)
|
|
279
|
+
deliver(() => evidence({ type: "adapter_diagnostic", stderr: chunk }));
|
|
280
|
+
});
|
|
281
|
+
child.once("error", (error) => { spawnError = error; });
|
|
282
|
+
child.once("close", async (status, signal) => {
|
|
283
|
+
if (settled)
|
|
284
|
+
return;
|
|
285
|
+
settled = true;
|
|
286
|
+
if (timer)
|
|
287
|
+
clearInterval(timer);
|
|
288
|
+
input.signal?.removeEventListener("abort", abort);
|
|
30
289
|
if (heartbeat)
|
|
31
290
|
clearInterval(heartbeat);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
291
|
+
await cleanup;
|
|
292
|
+
if (!droppingDiagnostic && pendingDiagnostic)
|
|
293
|
+
observeDiagnostic(pendingDiagnostic);
|
|
294
|
+
await diagnosticReads;
|
|
295
|
+
const progressFailure = callbackError;
|
|
296
|
+
deliver(() => evidence({ status, signal, timed_out: timedOut, aborted, stdout, stderr, ...(spawnError ? { error: spawnError.message } : {}) }));
|
|
297
|
+
if (spawnError)
|
|
298
|
+
return reject(new AppError("harness_adapter_failed", "Harness adapter call failed", 1, { cause: adapterCause(spawnError.message), ...(callbackError ? { evidence_error: String(callbackError) } : {}) }));
|
|
299
|
+
if (outputTooLarge)
|
|
300
|
+
return reject(new AppError("harness_adapter_output_limit", "Harness adapter exceeded the bounded reply limit", 1, { executable: input.executable, args: input.args.slice(0, 3), max_output_bytes: maxAdapterOutputBytes }));
|
|
35
301
|
if (timedOut)
|
|
36
|
-
return reject(new AppError("
|
|
302
|
+
return reject(Object.assign(new AppError("harness_adapter_timeout", "Harness adapter call timed out", 1, { executable: input.executable, args: input.args.slice(0, 3), timeout_ms: timeoutMs }), { observationClock: clock }));
|
|
303
|
+
if (aborted)
|
|
304
|
+
return reject(new AppError("harness_adapter_aborted", "Harness adapter call was cancelled", 1, { executable: input.executable, args: input.args.slice(0, 3) }));
|
|
37
305
|
if (status !== 0)
|
|
38
|
-
return reject(new AppError("
|
|
306
|
+
return reject(progressFailure ?? new AppError("harness_adapter_failed", "Harness adapter call failed", 1, { executable: input.executable, args: input.args.slice(0, 3), status, signal, stderr, cause: adapterCause(stdout, stderr, retainedError ? JSON.stringify(retainedError) : ""), ...(callbackError ? { evidence_error: String(callbackError) } : {}) }));
|
|
307
|
+
if (callbackError)
|
|
308
|
+
return reject(callbackError);
|
|
39
309
|
try {
|
|
40
|
-
|
|
310
|
+
const reply = JSON.parse(stdout);
|
|
311
|
+
if (!reply || typeof reply !== "object" || Array.isArray(reply))
|
|
312
|
+
throw new Error("not object");
|
|
313
|
+
const value = reply;
|
|
314
|
+
if (value.ok === false)
|
|
315
|
+
return reject(new AppError("harness_adapter_failed", "Harness adapter reported a runtime failure", 1, { cause: adapterCause(stdout) }));
|
|
316
|
+
resolve(reply);
|
|
41
317
|
}
|
|
42
318
|
catch {
|
|
43
|
-
reject(new AppError("
|
|
319
|
+
reject(new AppError("harness_adapter_invalid", "Harness adapter returned one invalid JSON object", 1, { executable: input.executable }));
|
|
44
320
|
}
|
|
45
321
|
});
|
|
46
322
|
});
|
|
47
323
|
}
|
|
324
|
+
function adapterCause(...outputs) {
|
|
325
|
+
for (const output of outputs)
|
|
326
|
+
for (const line of [output.trim(), ...output.trim().split("\n").reverse()]) {
|
|
327
|
+
try {
|
|
328
|
+
const value = JSON.parse(line);
|
|
329
|
+
const nested = value.error && typeof value.error === "object" && !Array.isArray(value.error) ? value.error : null;
|
|
330
|
+
const code = typeof nested?.code === "string" ? nested.code : typeof value.code === "string" ? value.code : null;
|
|
331
|
+
const message = typeof nested?.message === "string" ? nested.message : typeof value.error === "string" ? value.error : typeof value.message === "string" ? value.message : code;
|
|
332
|
+
if (code)
|
|
333
|
+
return errorRecord({ ...(nested ?? value), code, message });
|
|
334
|
+
}
|
|
335
|
+
catch { /* Adapter diagnostics need not be JSON. */ }
|
|
336
|
+
}
|
|
337
|
+
return { message: outputs.map(output => output.trim()).filter(Boolean).join("\n") || "adapter process failed" };
|
|
338
|
+
}
|
|
48
339
|
export function adapterSessionId(value) {
|
|
49
340
|
if (!value || typeof value !== "object")
|
|
50
341
|
return null;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
342
|
+
const reply = value;
|
|
343
|
+
for (const key of ["provider_session_id", "adapter_session_id", "session_id"])
|
|
344
|
+
if (typeof reply[key] === "string" && reply[key].trim())
|
|
345
|
+
return reply[key];
|
|
346
|
+
const session = reply.session;
|
|
347
|
+
if (session && typeof session === "object" && !Array.isArray(session))
|
|
348
|
+
for (const key of ["provider_session_id", "session_id"])
|
|
349
|
+
if (typeof session[key] === "string" && String(session[key]).trim())
|
|
350
|
+
return session[key];
|
|
58
351
|
return null;
|
|
59
352
|
}
|
|
@@ -10,6 +10,7 @@ export function harnessKey(value) {
|
|
|
10
10
|
zcode: "zcode-acp", "zcode-acp": "zcode-acp",
|
|
11
11
|
grok: "grok-acp", "grok-acp": "grok-acp",
|
|
12
12
|
agy: "antigravity-cli", "antigravity-cli": "antigravity-cli",
|
|
13
|
+
droid: "droid-cli", "droid-cli": "droid-cli",
|
|
13
14
|
opencode: "opencode-server", "opencode-server": "opencode-server"
|
|
14
15
|
};
|
|
15
16
|
const resolved = keys[value];
|
|
@@ -60,6 +61,7 @@ export function harnessRuntimeArguments(ddFlowHome, harness) {
|
|
|
60
61
|
"zcode-acp": "--zcode-acp-bin",
|
|
61
62
|
"grok-acp": "--grok-bin",
|
|
62
63
|
"antigravity-cli": "--agy-bin",
|
|
64
|
+
"droid-cli": "--droid-bin",
|
|
63
65
|
"opencode-server": "--opencode-bin"
|
|
64
66
|
};
|
|
65
67
|
return [option[key], runtime];
|