@deksden-com/dd-flow-cli 0.9.0-beta.155 → 0.9.0-beta.158
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 +23 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/error-output.js +11 -1
- package/dist/cli/input-preparation.js +32 -4
- package/dist/cli/run-cli.js +26 -8
- package/dist/harness-runtime/lib/daemon-operations.mjs +46 -11
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +19 -6
- package/dist/harness-runtime/lib/dd-agy.mjs +19 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +1 -1
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +2 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +3 -3
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +1 -1
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +2 -1
- package/dist/harness-runtime/lib/delegation-instructions.mjs +8 -2
- package/dist/schemas/code-work-batch.schema.json +1 -1
- package/dist/schemas/vnext-protocol-plan.schema.json +1 -1
- package/dist/services/codex-capacity.js +367 -0
- package/dist/services/controller-child-repair.js +117 -0
- package/dist/services/controller-fanout.js +33 -20
- package/dist/services/controller-result-authority.js +13 -8
- package/dist/services/controller-work-failure.js +198 -0
- package/dist/services/lifecycle-invocations.js +117 -25
- package/dist/services/managed-daemon-binding.js +40 -0
- package/dist/services/retained-native-hook.js +34 -5
- package/dist/services/run-control.js +18 -36
- package/dist/services/run-controller-process.js +19 -6
- package/dist/services/run-controller.js +154 -22
- package/dist/services/vnext-code.js +3 -1
- package/dist/services/vnext-plan-review.js +2 -2
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/work-registry.js +62 -15
- package/dist/services/work-retry-context.js +55 -0
- package/dist/verification-components.json +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.158
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Resolve proven explicit Work refusals through coordinator retry/block/pause/fail decisions. Retain accepted corrections and launch history, recover lost acceptance/pause acknowledgements, and keep native/provider failures fenced. Align PLAN stop conditions with accepted intent (plan 112).
|
|
8
|
+
- Register focused PLAN/canon verification ownership so release selection never falls back to an unrelated full gate.
|
|
9
|
+
|
|
10
|
+
## 0.9.0-beta.157
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- b7555a7: Preserve actionable lifecycle output and actual Work inputs through preparation retries, persist refusal-to-successor links, and allow distinct same-child correction episodes without duplicate dispatch. Reconcile consistent observations of one technical pause without losing its accepted source.
|
|
15
|
+
|
|
16
|
+
Retire owned execution after native shutdown failures without fabricating native success. Preserve AGY terminal task evidence and reject capture when native inventory changes during physical observation.
|
|
17
|
+
|
|
18
|
+
## 0.9.0-beta.156
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- b59c32b: Preserve current lifecycle successors, restore OpenCode retained child admission, and separate Work correction from technical pause and native resource cleanup. Continue an eligible completed child once per Work without replaying unknown effects or treating descriptive deviations as unfinished obligations.
|
|
23
|
+
|
|
24
|
+
Observe a concurrent controller-launch winner instead of rejecting an already accepted duplicate resume; retain physical ownership and launch CAS fences.
|
|
25
|
+
|
|
3
26
|
## 0.9.0-beta.155
|
|
4
27
|
|
|
5
28
|
Versions beta.153 and beta.154 were used by unpublished preparation candidates; this release uses a new immutable identity. Older pending Changesets below reconcile release accounting rather than claim their functionality is new.
|
package/dist/build-info.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-10-
|
|
3
|
+
"cli_version": "0.9.0-beta.158",
|
|
4
|
+
"cli_commit": "d0da77c661fa63daf690214f177105c0c0892262",
|
|
5
|
+
"built_at": "2026-10-29T05:58:19.667Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
|
-
"version": "4.1.
|
|
8
|
-
"commit": "
|
|
7
|
+
"version": "4.1.4",
|
|
8
|
+
"commit": "7cc415b3ef3ce37c9624c7cee51023e66f67cd57",
|
|
9
9
|
"flow_contract": "dd-flow-canonical-2026-08",
|
|
10
|
-
"repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-
|
|
11
|
-
"memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-
|
|
12
|
-
"flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-
|
|
10
|
+
"repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-cp263",
|
|
11
|
+
"memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-cp263/.memory-bank",
|
|
12
|
+
"flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-cp263/.memory-bank/dd-flow",
|
|
13
13
|
"layout": "dot_memory_bank"
|
|
14
14
|
}
|
|
15
15
|
}
|
package/dist/cli/error-output.js
CHANGED
|
@@ -22,8 +22,18 @@ export function writePreparationError(io, args, error) {
|
|
|
22
22
|
io.stderr.write(renderHumanError(payload, args));
|
|
23
23
|
}
|
|
24
24
|
export function renderHumanError(payload, args) {
|
|
25
|
-
const lines = [`dd-flow: ${payload.error.message}`];
|
|
25
|
+
const lines = [`dd-flow: ${payload.error.message}`, `code: ${payload.error.code}`];
|
|
26
26
|
const details = recordObject(payload.error.details);
|
|
27
|
+
for (const key of ["effect", "recoverable", "outcome", "instruction", "retry_instruction", "retry_command", "handoff_required", "work_id", "recovery_instruction"]) {
|
|
28
|
+
if (details?.[key] !== undefined)
|
|
29
|
+
lines.push(`${key}: ${String(details[key])}`);
|
|
30
|
+
}
|
|
31
|
+
const continuation = recordObject(details?.continuation);
|
|
32
|
+
if (continuation)
|
|
33
|
+
for (const key of ["outcome", "instruction", "work_id", "finish_command", "resume_command", "prompt_path"]) {
|
|
34
|
+
if (continuation[key] !== undefined)
|
|
35
|
+
lines.push(`continuation.${key}: ${String(continuation[key])}`);
|
|
36
|
+
}
|
|
27
37
|
const compatibility = recordObject(details?.compatibility);
|
|
28
38
|
const remediation = recordObject(details?.remediation);
|
|
29
39
|
if (compatibility) {
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
5
|
import { AppError, isAppError } from "../shared/errors.js";
|
|
5
6
|
import { validateSchema } from "../services/schema-validation.js";
|
|
7
|
+
// Observe only bytes already consumed by CLI preparation; never reread a file
|
|
8
|
+
// in a hook or while replaying a retained outcome.
|
|
9
|
+
const consumedInputs = new AsyncLocalStorage();
|
|
10
|
+
export function recordConsumedInput(bytes) {
|
|
11
|
+
recordConsumedInputDigest(crypto.createHash("sha256").update(bytes).digest("hex"));
|
|
12
|
+
}
|
|
13
|
+
export function recordConsumedInputDigest(digest) { consumedInputs.getStore()?.push(digest); }
|
|
14
|
+
export async function withPreparedInputBinding(prepare) {
|
|
15
|
+
return consumedInputs.run([], async () => {
|
|
16
|
+
try {
|
|
17
|
+
return await prepare();
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
const inputs = consumedInputs.getStore();
|
|
21
|
+
if (isAppError(error) && inputs.length)
|
|
22
|
+
error.details.consumed_input_sha256 = crypto.createHash("sha256").update(JSON.stringify(inputs)).digest("hex");
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
6
27
|
export function prepareError(error, input = {}) {
|
|
7
28
|
if (isAppError(error)) {
|
|
8
29
|
// A malformed user input is safely correctable. A broken schema registry,
|
|
@@ -61,7 +82,9 @@ export async function readBoundedUtf8Stream(stream, maxBytes) {
|
|
|
61
82
|
}
|
|
62
83
|
}
|
|
63
84
|
try {
|
|
64
|
-
|
|
85
|
+
const text = data + decoder.decode();
|
|
86
|
+
recordConsumedInput(text);
|
|
87
|
+
return text;
|
|
65
88
|
}
|
|
66
89
|
catch {
|
|
67
90
|
throw new AppError("validation", "Standard input must contain valid UTF-8", 2);
|
|
@@ -76,8 +99,11 @@ export function prepareFileBytes(file, label = "input file", maxBytes) {
|
|
|
76
99
|
if (!stat.isFile()) {
|
|
77
100
|
throw new AppError("input_file_not_regular", `${label} must be a readable regular file: ${absolute}`, 2, { file: absolute, label });
|
|
78
101
|
}
|
|
79
|
-
if (maxBytes === undefined)
|
|
80
|
-
|
|
102
|
+
if (maxBytes === undefined) {
|
|
103
|
+
const bytes = fs.readFileSync(descriptor);
|
|
104
|
+
recordConsumedInput(bytes);
|
|
105
|
+
return { file: absolute, bytes };
|
|
106
|
+
}
|
|
81
107
|
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
82
108
|
throw new AppError("usage", "Invalid input byte bound", 2);
|
|
83
109
|
const oversized = () => new AppError("usage", `${label} exceeds its byte bound`, 2);
|
|
@@ -94,7 +120,9 @@ export function prepareFileBytes(file, label = "input file", maxBytes) {
|
|
|
94
120
|
}
|
|
95
121
|
if (length > maxBytes)
|
|
96
122
|
throw oversized();
|
|
97
|
-
|
|
123
|
+
const bytes = buffer.subarray(0, length);
|
|
124
|
+
recordConsumedInput(bytes);
|
|
125
|
+
return { file: absolute, bytes };
|
|
98
126
|
}
|
|
99
127
|
catch (error) {
|
|
100
128
|
if (error?.code === "ENOENT") {
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { requiredOption, optionalOption, optionalNumber, optionalPositiveNumber
|
|
|
5
5
|
import { resolveCommandPaths, resolveCommandProjectRoot } from "../services/command-context.js";
|
|
6
6
|
import { assertAuxiliaryCommandScope, cliManagedInvocationScope, manualRecoveryInvocationScope, assertManualRecoveryInvocationTarget, currentAssignedLifecycle } from "../services/lifecycle-invocations.js";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import { quote } from "shell-quote";
|
|
10
10
|
import { observeLifecycleCommand, observedLifecycleNativeEvent } from "../services/lifecycle-invocations.js";
|
|
11
11
|
import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
|
|
@@ -40,7 +40,7 @@ import { getCanonStatus, registerCanonRoot, resolveCanonRoot, prepareCanonRegist
|
|
|
40
40
|
import { getRuntimeStatus } from "../services/status.js";
|
|
41
41
|
import { getCliVersionReport } from "../services/build-info.js";
|
|
42
42
|
import { parseCommandArgs } from "../services/lifecycle-command.js";
|
|
43
|
-
import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile, prepareFileBytes, readBoundedUtf8Stream } from "./input-preparation.js";
|
|
43
|
+
import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile, prepareFileBytes, readBoundedUtf8Stream, recordConsumedInputDigest, withPreparedInputBinding } from "./input-preparation.js";
|
|
44
44
|
import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, prepareDashboardRefresh, prepareDashboardRender, prepareGlobalDashboardRender, prepareDashboardOptions, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
|
|
45
45
|
import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
|
|
46
46
|
import { getFlowSessionStatus, prepareFlowSessionPayload, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
|
|
@@ -343,6 +343,10 @@ async function runCliRequest(args, io, env) {
|
|
|
343
343
|
? expandLifecycleInvocationArgs(routerContext, boundInvocationArgs, invocationScope, observedOperation)
|
|
344
344
|
: boundInvocationArgs;
|
|
345
345
|
const retainedOutput = observedInvocation ? parseOutputOptions(expandedInvocationArgs) : null;
|
|
346
|
+
if (retainedOutput?.json)
|
|
347
|
+
output.json = true;
|
|
348
|
+
if (retainedOutput?.progressJsonl)
|
|
349
|
+
output.progressJsonl = true;
|
|
346
350
|
if (retainedOutput?.responseFile)
|
|
347
351
|
output.responseFile = retainedOutput.responseFile;
|
|
348
352
|
let invocationArgs = retainedOutput?.args ?? expandedInvocationArgs;
|
|
@@ -408,7 +412,8 @@ async function runCliRequest(args, io, env) {
|
|
|
408
412
|
return;
|
|
409
413
|
nextNativeObservation = Date.now() + 250;
|
|
410
414
|
const { reconcileRetainedNativeHook } = await import("../services/retained-native-hook.js");
|
|
411
|
-
await reconcileRetainedNativeHook(dispatchContext, { scope, invocationId, onProgress
|
|
415
|
+
await reconcileRetainedNativeHook(dispatchContext, { scope, invocationId, onProgress,
|
|
416
|
+
onDiagnostic: value => io.stderr.write(`${JSON.stringify({ kind: "native_hook_diagnostic", ...value })}\n`) });
|
|
412
417
|
} } : {}) });
|
|
413
418
|
if (admission.replay) {
|
|
414
419
|
retainedOutcome = admission.outcome;
|
|
@@ -727,7 +732,7 @@ function attachProgress(result, progress, output) {
|
|
|
727
732
|
}
|
|
728
733
|
return { ok: true, result, progress };
|
|
729
734
|
}
|
|
730
|
-
function renderHumanResult(args, result) {
|
|
735
|
+
export function renderHumanResult(args, result) {
|
|
731
736
|
const [family, command] = args;
|
|
732
737
|
const record = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
733
738
|
const ok = record.ok === false ? "failed" : "ok";
|
|
@@ -795,6 +800,16 @@ function renderHumanResult(args, result) {
|
|
|
795
800
|
if (run.run_index_path)
|
|
796
801
|
lines.push(`run_index: ${String(run.run_index_path)}`);
|
|
797
802
|
}
|
|
803
|
+
for (const key of ["outcome", "instruction", "agent_instruction", "next_action", "resume_command", "resume_command_template", "prompt_path"]) {
|
|
804
|
+
if (record[key] !== undefined && typeof record[key] !== "object")
|
|
805
|
+
lines.push(`${key}: ${String(record[key])}`);
|
|
806
|
+
}
|
|
807
|
+
for (const [name, keys] of [["next", ["finish_command"]], ["repair", ["work_id"]], ["prompt_delivery", ["path", "sha256"]], ["pause", ["user_message", "question_path", "resume_command", "resume_command_template"]]]) {
|
|
808
|
+
const value = recordObject(record[name]);
|
|
809
|
+
for (const key of keys)
|
|
810
|
+
if (value?.[key] !== undefined)
|
|
811
|
+
lines.push(`${name}.${key}: ${String(value[key])}`);
|
|
812
|
+
}
|
|
798
813
|
appendFlowGuidanceHuman(lines, record);
|
|
799
814
|
return `${lines.join("\n")}\n`;
|
|
800
815
|
}
|
|
@@ -932,7 +947,7 @@ function compositionProposalRetry(context, parsed) {
|
|
|
932
947
|
* invocation is allowed to claim execution or a stage claims its hook event. */
|
|
933
948
|
async function prepareCliInput(...args) {
|
|
934
949
|
try {
|
|
935
|
-
return await prepareCliInputValue(...args);
|
|
950
|
+
return await withPreparedInputBinding(() => prepareCliInputValue(...args));
|
|
936
951
|
}
|
|
937
952
|
catch (error) {
|
|
938
953
|
return prepareError(error);
|
|
@@ -3671,11 +3686,14 @@ async function readStdin(stream, maxBytes) {
|
|
|
3671
3686
|
let data = "";
|
|
3672
3687
|
if (maxBytes !== undefined)
|
|
3673
3688
|
return readBoundedUtf8Stream(stream, maxBytes);
|
|
3674
|
-
|
|
3689
|
+
const hash = createHash("sha256"), decoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
3675
3690
|
for await (const chunk of stream) {
|
|
3676
|
-
|
|
3691
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
3692
|
+
hash.update(bytes);
|
|
3693
|
+
data += decoder.decode(bytes, { stream: true });
|
|
3677
3694
|
}
|
|
3678
|
-
|
|
3695
|
+
recordConsumedInputDigest(hash.digest("hex"));
|
|
3696
|
+
return data + decoder.decode();
|
|
3679
3697
|
}
|
|
3680
3698
|
function refreshDashboardsAfterMutation(context, args, result, resolvedScope) {
|
|
3681
3699
|
try {
|
|
@@ -9,7 +9,7 @@ import { assertDaemonOwnership, budgetObservationScope, releaseBudgetObservation
|
|
|
9
9
|
import { processSnapshot } from "./process-snapshot.mjs";
|
|
10
10
|
import { NATIVE_SHUTDOWN_CONTRACT, NATIVE_SHUTDOWN_PHASES } from "./native-children.mjs";
|
|
11
11
|
import { readModelObservations, modelAttribution } from "./model-observations.mjs";
|
|
12
|
-
import { publishOperationProgress, publishOperationWaiting, withOperationProgress } from './operation-progress.mjs';
|
|
12
|
+
import { publishOperationProgress, publishOperationWaiting, withOperationProgress, noteOperationProgress } from './operation-progress.mjs';
|
|
13
13
|
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
14
14
|
|
|
15
15
|
const productive = new Set(["session.create", "session.prompt", "session.start", "session.fork", "session.resume"]);
|
|
@@ -65,30 +65,50 @@ function ownedSnapshot(snapshot, rootPid) {
|
|
|
65
65
|
const mergeProcesses = (...snapshots) => [...new Map(snapshots.flat().map(item => [`${item.pid}:${item.started}`, item])).values()];
|
|
66
66
|
|
|
67
67
|
/** The same incarnation may retry remaining cleanup, never a native Turn. */
|
|
68
|
-
export async function completeDaemonShutdown(root, state, persist, prepare, phases, reconcileResources) {
|
|
68
|
+
export async function completeDaemonShutdown(root, state, persist, prepare, phases, reconcileResources, { cancelTree = false } = {}) {
|
|
69
69
|
const daemonId = state.daemon_id ?? state.config?.daemonId;
|
|
70
70
|
let shutdown = state.shutdown;
|
|
71
71
|
if (shutdown && shutdown.schema_id !== NATIVE_SHUTDOWN_CONTRACT) throw Object.assign(new Error("shutdown contract mismatch; retain historical owner evidence"), { code: "native_contract_mismatch" });
|
|
72
72
|
if (!shutdown || shutdown.daemon_id !== daemonId) shutdown = { schema_id: NATIVE_SHUTDOWN_CONTRACT, daemon_id: daemonId, generation: dispatchGenerations.get(root) ?? 0, phases: {} };
|
|
73
73
|
shutdown = { ...shutdown, required_phases: [...new Set([...NATIVE_SHUTDOWN_PHASES, ...phases.map(([phase]) => phase)])] };
|
|
74
74
|
try {
|
|
75
|
-
if (!shutdown.phases.execution) {
|
|
75
|
+
if (!shutdown.phases.execution && !shutdown.physical_only) {
|
|
76
76
|
// Some native adapters close their provider inside prepare(). Observe
|
|
77
77
|
// ownership first, before that close can reparent a support process.
|
|
78
78
|
const before = ownedSnapshot(await processSnapshot(), state.pid);
|
|
79
79
|
shutdown = { ...shutdown, generation: dispatchGenerations.get(root) ?? shutdown.generation,
|
|
80
80
|
owned_processes: mergeProcesses(shutdown.owned_processes ?? [], before), inventory_complete: state.pid === process.pid };
|
|
81
81
|
await persist({ shutdown });
|
|
82
|
-
|
|
82
|
+
let result;
|
|
83
|
+
try { result = await prepare(); }
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (!cancelTree) throw error;
|
|
86
|
+
// An explicit whole-tree stop owns physical cleanup even when a dead
|
|
87
|
+
// native endpoint cannot acknowledge idle. Never fabricate execution.
|
|
88
|
+
shutdown = { ...shutdown, physical_only: true, preparation_error: errorRecord(error) };
|
|
89
|
+
result = { clean: false, settled: false, native_settled: false,
|
|
90
|
+
native_resources: { entries: [...(shutdown.result?.native_resources?.entries ?? []), ...(state.native_resources_entries ?? [])] } };
|
|
91
|
+
}
|
|
92
|
+
if (cancelTree && (result?.settled !== true || result?.native_settled !== true)) {
|
|
93
|
+
// Some adapters return the failed native observation after cancelling
|
|
94
|
+
// their provider instead of throwing it. The endpoint is no more
|
|
95
|
+
// available in that case; the same physical-only path applies.
|
|
96
|
+
shutdown = { ...shutdown, physical_only: true };
|
|
97
|
+
result = { ...result, clean: false, settled: false, native_settled: false,
|
|
98
|
+
native_resources: { ...result?.native_resources, entries: [...(shutdown.result?.native_resources?.entries ?? []),
|
|
99
|
+
...(result?.native_resources?.entries ?? []), ...(state.native_resources_entries ?? [])] } };
|
|
100
|
+
}
|
|
83
101
|
commitDaemonShutdown(root);
|
|
84
102
|
// Compute ancestry within each snapshot, not across recycled parent PIDs.
|
|
85
103
|
const resources = mergeProcesses(shutdown.owned_processes, ownedSnapshot(await processSnapshot(), state.pid));
|
|
86
|
-
shutdown = { ...shutdown, phases: { execution:
|
|
104
|
+
shutdown = { ...shutdown, phases: { ...shutdown.phases, execution: !shutdown.physical_only }, owned_processes: resources,
|
|
87
105
|
inventory_complete: state.pid === process.pid, tree_observed_at: new Date().toISOString(), preparation_clean: result?.clean !== false, result: result ?? {} };
|
|
88
106
|
await persist({ shutdown, shutdown_state: "stopping" });
|
|
107
|
+
await noteOperationProgress('settlement_started', { cleanup_phase: 'prepare', physical_only: shutdown.physical_only === true });
|
|
89
108
|
}
|
|
90
109
|
commitDaemonShutdown(root);
|
|
91
|
-
if (reconcileResources && !shutdown.phases.owned_resources) {
|
|
110
|
+
if (reconcileResources && !shutdown.physical_only && !shutdown.phases.owned_resources) {
|
|
111
|
+
try {
|
|
92
112
|
const revision = state.native_execution_revision, generation = dispatchGenerations.get(root) ?? shutdown.generation;
|
|
93
113
|
// A fresh attempt cannot reuse a previous inventory while reconciliation
|
|
94
114
|
// is in flight or after the new attempt fails.
|
|
@@ -108,6 +128,15 @@ export async function completeDaemonShutdown(root, state, persist, prepare, phas
|
|
|
108
128
|
native_resource_reconciliation: { daemon_id: daemonId, dispatch_generation: generation, native_revision: revision },
|
|
109
129
|
result: { ...shutdown.result, native_resources: resources } };
|
|
110
130
|
await persist({ shutdown });
|
|
131
|
+
await noteOperationProgress('native_progress', { cleanup_phase: 'native_resources_reconciled' });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (!cancelTree || (state.daemon_id ?? state.config?.daemonId) !== daemonId) throw error;
|
|
134
|
+
shutdown = { ...shutdown, physical_only: true, reconciliation_error: errorRecord(error),
|
|
135
|
+
result: { ...shutdown.result, clean: false, settled: false, native_settled: false,
|
|
136
|
+
native_resources: { ...shutdown.result?.native_resources,
|
|
137
|
+
entries: [...(shutdown.result?.native_resources?.entries ?? []), ...(state.native_resources_entries ?? [])] } } };
|
|
138
|
+
await persist({ shutdown });
|
|
139
|
+
}
|
|
111
140
|
}
|
|
112
141
|
for (const [phase, action] of phases) {
|
|
113
142
|
if (phase === "daemon_resource" && !shutdown.phases.owned_resources) {
|
|
@@ -120,9 +149,14 @@ export async function completeDaemonShutdown(root, state, persist, prepare, phas
|
|
|
120
149
|
const remaining = await processSnapshot();
|
|
121
150
|
if ((shutdown.owned_processes ?? []).some(retained => remaining.some(item => item.pid === retained.pid && item.started === retained.started && !item.zombie))) throw Object.assign(new Error("owned resources remain live"), { code: "native_resources_unproven" });
|
|
122
151
|
const nativeResources = shutdown.result?.native_resources;
|
|
123
|
-
const resourceGap = nativeResources?.entries?.some(resource =>
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
const resourceGap = nativeResources?.entries?.some(resource => {
|
|
153
|
+
if (resource.observation_conflict === true) return true;
|
|
154
|
+
if (resource.pid !== undefined) return !Number.isSafeInteger(resource.pid) || resource.pid <= 0
|
|
155
|
+
|| typeof resource.started !== 'string' || !resource.started
|
|
156
|
+
|| remaining.some(item => item.pid === resource.pid && !item.zombie);
|
|
157
|
+
return resource.state !== "stopped"
|
|
158
|
+
&& !(resource.classification === "required_work" && shutdown.result?.native_settled === true && shutdown.result?.settled === true);
|
|
159
|
+
});
|
|
126
160
|
shutdown = { ...shutdown, phases: { ...shutdown.phases, owned_resources: !resourceGap },
|
|
127
161
|
resource_retirement: { confirmed: !resourceGap, reason: resourceGap ? "native_resource_identity_unproven" : "owned_processes_retired" } };
|
|
128
162
|
await persist({ shutdown });
|
|
@@ -132,14 +166,15 @@ export async function completeDaemonShutdown(root, state, persist, prepare, phas
|
|
|
132
166
|
if (resourceGap) throw Object.assign(new Error("native resource retirement remains unproven"), { code: "native_resources_unproven" });
|
|
133
167
|
}
|
|
134
168
|
if (shutdown.phases[phase]) continue;
|
|
135
|
-
await action();
|
|
169
|
+
await action({ physicalOnly: shutdown.physical_only === true });
|
|
136
170
|
shutdown = { ...shutdown, phases: { ...shutdown.phases, [phase]: true } };
|
|
137
171
|
await persist({ shutdown });
|
|
172
|
+
await noteOperationProgress('native_progress', { cleanup_phase: phase });
|
|
138
173
|
}
|
|
139
174
|
const publishedRevision = state.native_execution_revision;
|
|
140
175
|
const sameRevision = publishedRevision === undefined || publishedRevision === shutdown.result?.native_settlement?.revision;
|
|
141
176
|
const nativeSettled = sameRevision && state.active_tree === false && !state.unclaimed_activity && shutdown.result?.settled === true && shutdown.result?.native_settled === true && shutdown.result?.native_settlement?.confirmed !== false;
|
|
142
|
-
const physicalStopped = shutdown.required_phases.every(phase => shutdown.phases[phase] === true);
|
|
177
|
+
const physicalStopped = shutdown.required_phases.filter(phase => phase !== "execution").every(phase => shutdown.phases[phase] === true);
|
|
143
178
|
// Cleanup retries/joining controls advance the control fence, not the old
|
|
144
179
|
// native Turn. Publish the current control generation with retained phases.
|
|
145
180
|
shutdown = { ...shutdown, generation: dispatchGenerations.get(root) ?? shutdown.generation };
|
|
@@ -25,11 +25,11 @@ import { agyWorkspaceHooks } from "./agy-hooks.mjs";
|
|
|
25
25
|
import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
|
|
26
26
|
import path from "node:path";
|
|
27
27
|
import { observeModel } from "./model-observations.mjs";
|
|
28
|
-
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot, managedAgyEnv } from "./dd-agy.mjs";
|
|
28
|
+
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyErrorDiagnostic, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot, managedAgyEnv } from "./dd-agy.mjs";
|
|
29
29
|
import { agyTerminalEnvelope } from "./agy-terminal-envelope.mjs";
|
|
30
30
|
import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
|
|
31
31
|
import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
32
|
-
import { readNativeLines,
|
|
32
|
+
import { readNativeLines, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
33
33
|
|
|
34
34
|
const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
|
|
35
35
|
class DaemonError extends AgyError {}
|
|
@@ -400,7 +400,12 @@ export class Runtime {
|
|
|
400
400
|
const providerRegistered = new Promise(resolve => { allowProviderFinalization = resolve; });
|
|
401
401
|
const observationFailure = error => { this.drainingError ??= error; readyReject(error); this.active?.reject(error); };
|
|
402
402
|
this.nativeDrain = readNativeLines(this.child.stdout, line => { this.buffer = `${line}\n`; return this.draining = this.drain(readyResolve, readyReject); }, observationFailure);
|
|
403
|
-
|
|
403
|
+
const diagnosticConnection = this.connectionId;
|
|
404
|
+
this.diagnostics = readNativeLines(this.child.stderr, async line => {
|
|
405
|
+
const diagnostic = agyErrorDiagnostic(line);
|
|
406
|
+
if (diagnostic) await this.journal("provider_error_diagnostic", { ...diagnostic, connection_id: diagnosticConnection });
|
|
407
|
+
else if (!/^AGY_ERROR\s*[: ]/.test(line)) await this.journal("stderr", { text: line });
|
|
408
|
+
}, observationFailure);
|
|
404
409
|
this.child.on("error", error => { readyReject(error); if (this.active) { this.active.reject(error); this.active = null; } });
|
|
405
410
|
// A dead leader is not yet a finalized tree: stop must also join the
|
|
406
411
|
// pending drain, process-group check and final persistence/lease writes.
|
|
@@ -491,7 +496,11 @@ export class Runtime {
|
|
|
491
496
|
const reducer = createAgyTranscriptFacts(source, child.provider_session_id);
|
|
492
497
|
return { add: reducer.add, finish: () => {
|
|
493
498
|
const facts = reducer.finish();
|
|
494
|
-
for (const resource of facts.resources)
|
|
499
|
+
for (const resource of facts.resources) {
|
|
500
|
+
const prior = this.nativeResources.get(resource.resource_id);
|
|
501
|
+
this.nativeResources.set(resource.resource_id, { ...mergeAgyResource(prior, resource),
|
|
502
|
+
owner_daemon_id: prior?.owner_daemon_id ?? this.config.daemonId });
|
|
503
|
+
}
|
|
495
504
|
if (facts.gap) { this.state.native_topology_error = facts.gap; this.invalidateSettlement(root); }
|
|
496
505
|
for (const discovered of facts.children) {
|
|
497
506
|
const expected = path.join(this.paths.runtime, 'brain', discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
|
|
@@ -1338,11 +1347,15 @@ async function dispatch(runtime, request) {
|
|
|
1338
1347
|
}
|
|
1339
1348
|
catch (error) { await runtime.recordUnconfirmedHookFailure(error, params.event, params.payload ?? {}, current, request.id); throw error; }
|
|
1340
1349
|
}
|
|
1341
|
-
if (request.operation === "daemon.stop") return await completeDaemonShutdown(runtime.paths.dir, runtime.state, patch => runtime.persist(patch), () => runtime.prepareStop(params.cancelTree === true), [["provider",
|
|
1350
|
+
if (request.operation === "daemon.stop") return await completeDaemonShutdown(runtime.paths.dir, runtime.state, patch => runtime.persist(patch), () => runtime.prepareStop(params.cancelTree === true), [["provider", async ({ physicalOnly }) => {
|
|
1351
|
+
if (!physicalOnly) return await runtime.close(false);
|
|
1352
|
+
await stopProcessGroup(runtime.child);
|
|
1353
|
+
await finishDaemonProcess(runtime.config, runtime.providerProcess);
|
|
1354
|
+
}], ["daemon_resource", ({ physicalOnly }) => physicalOnly ? finishDaemonProcess(runtime.config, runtime.state.resource_process) : runtime.finishResource()]], async () => {
|
|
1342
1355
|
await runtime.retireNativeServices();
|
|
1343
1356
|
await runtime.captureChildTools();
|
|
1344
1357
|
return runtime.receipt().native_resources;
|
|
1345
|
-
});
|
|
1358
|
+
}, { cancelTree: params.cancelTree === true });
|
|
1346
1359
|
throw new DaemonError("unknown_operation", `unknown dd-agy daemon operation: ${request.operation}`);
|
|
1347
1360
|
}
|
|
1348
1361
|
import { authorizeRetainedDaemonResume } from "./driver-recovery.mjs";
|
|
@@ -15,6 +15,23 @@ export class AgyError extends Error {
|
|
|
15
15
|
constructor(code, message, retryable = false, details) { super(message); this.code = code; this.retryable = retryable; this.details = details; }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Stderr has no operation/generation identity. Keep it diagnostic-only; never
|
|
19
|
+
// feed these fields into classification or continuation authority.
|
|
20
|
+
export function agyErrorDiagnostic(line) {
|
|
21
|
+
if (!/^AGY_ERROR\s*[: ]/.test(line)) return null;
|
|
22
|
+
try {
|
|
23
|
+
const envelope = JSON.parse(line.replace(/^AGY_ERROR\s*:?\s*/, ""));
|
|
24
|
+
const native = envelope.error ?? envelope;
|
|
25
|
+
const fields = {};
|
|
26
|
+
for (const key of ["status", "error_code", "code_kind", "retryable", "error_id"]) {
|
|
27
|
+
const value = native?.[key] ?? envelope[key];
|
|
28
|
+
if (typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)
|
|
29
|
+
|| typeof value === "string" && /^[\w.:-]{1,128}$/.test(value)) fields[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return Object.keys(fields).length ? { binding: "unbound", ...fields } : null;
|
|
32
|
+
} catch { return null; }
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
export function agyTerminalFailure(result, metadata = {}) {
|
|
19
36
|
let native = typeof result.error === "object" && result.error ? result.error : { message: result.error ?? "Antigravity returned ERROR" };
|
|
20
37
|
if (typeof result.error === "string" && /^AGY_ERROR\s*[: ]/.test(result.error)) {
|
|
@@ -33,8 +50,10 @@ export function agyTerminalFailure(result, metadata = {}) {
|
|
|
33
50
|
const stream = streamRequestKey(metadata.request_proof);
|
|
34
51
|
const proofMissing = result.status === "ERROR" && ["provider_capacity_transient", "provider_rate_limited", "provider_network_transient"].includes(category) && !turn && !stream;
|
|
35
52
|
if (proofMissing) code = "provider_overload_evidence_unavailable";
|
|
53
|
+
const diagnostic = agyErrorDiagnostic(`AGY_ERROR: ${JSON.stringify(native)}`);
|
|
36
54
|
return new AgyError(code, String(native.message ?? "Antigravity returned ERROR"), ["provider_capacity_transient", "provider_rate_limited", "provider_network_transient"].includes(code), {
|
|
37
55
|
provider_result: result, ...metadata, provider_error: safeProviderError(native), native_source: "agy.result", terminal_status: result.status === "ERROR" ? "failed" : "cancelled",
|
|
56
|
+
...(diagnostic ? { native_error_diagnostic: { ...diagnostic, binding: "native_result" } } : {}),
|
|
38
57
|
...(turn ? { turn_id: turn, native_turn_id: turn } : {}),
|
|
39
58
|
...(proofMissing ? { provider_category: category, reason: "native_turn_identity_unavailable" } : {}),
|
|
40
59
|
will_retry: native.willRetry === true, native_retry_active: native.willRetry === true
|
|
@@ -200,7 +200,7 @@ export async function serveDaemon(stateDir) {
|
|
|
200
200
|
["provider", () => bridge.close()],
|
|
201
201
|
["provider_resource", () => finishDaemonProcess(state.config, state.provider_process)],
|
|
202
202
|
["daemon_resource", () => finishDaemonProcess(state.config, state.resource_process)]
|
|
203
|
-
]);
|
|
203
|
+
], undefined, { cancelTree: params.cancelTree === true });
|
|
204
204
|
}
|
|
205
205
|
if (["session.create", "session.prompt", "session.start", "session.fork", "session.resume"].includes(operation)) assertDaemonDispatch(paths.dir);
|
|
206
206
|
if (active && !["session.inspect", "session.cancel"].includes(operation)) throw Object.assign(new Error(`${active} is in progress`), { code: "operation_busy", retryable: true });
|
|
@@ -98,7 +98,7 @@ export async function serveDaemon(stateDir) {
|
|
|
98
98
|
}],
|
|
99
99
|
["auth", () => removeDroidAuth(state.config)],
|
|
100
100
|
["daemon_resource", () => finishDaemonProcess(state.config, state.resource_process)]
|
|
101
|
-
]);
|
|
101
|
+
], undefined, { cancelTree: params.cancelTree === true });
|
|
102
102
|
}
|
|
103
103
|
catch (error) { if (error.code === "tree_not_settled" && !runtime.closed) stopping = false; throw error; }
|
|
104
104
|
}
|
|
@@ -541,7 +541,7 @@ export class Runtime {
|
|
|
541
541
|
const assertDispatch = captureDispatchGuard(this);
|
|
542
542
|
if (["session.create", "session.prompt", "session.fork", "session.archive"].includes(operation)) assertDaemonDispatch(this.paths.dir);
|
|
543
543
|
if (operation === "daemon.ready") return { ready: this.ready, daemon_id: this.state.daemon_id, pid: process.pid, socket: this.paths.socket, versions: this.state.versions, config_isolation: this.state.config_isolation, shutdown_state: this.state.shutdown_state, recovery_status: this.state.recovery_status, auth_status: this.state.auth_status, active_tree: this.state.active_tree, active_operation: this.active, sessions: [...this.sessions.values()], config: this.state.config };
|
|
544
|
-
if (operation === "daemon.status") { let observationError; if (!this.active && !this.state.shutdown?.phases?.
|
|
544
|
+
if (operation === "daemon.status") { let observationError; if (!this.active && !this.state.shutdown?.phases?.execution && !this.state.shutdown?.physical_only) try { await this.refreshTree(); } catch (error) { observationError = errorPayload(error); } return { ...await this.dispatch("daemon.ready", {}), observation_error: observationError ?? this.state.observation_error ?? null }; }
|
|
545
545
|
if (operation === "hook.resolve") { const sessionId = String(params.sessionId ?? ""); if (!sessionId) throw new DaemonError("hook_identity_missing", "hook has no sessionId"); let session = this.sessions.get(sessionId); if ((!session || session.parent_provider_session_id) && !this.childCreators.has(sessionId) && this.promptCreators.size) { await this.waitForChildCreator(sessionId, params.hookDeadlineAt); session = this.sessions.get(sessionId); } if (!session) { const roots = [...this.sessions.values()].filter((item) => !item.parent_provider_session_id); if (roots.length !== 1) throw new DaemonError("hook_identity_unknown", "unknown hook Session has no unique root"); const observed = await inspectSessionWithBridge(this.bridge, this.options({ sessionId, liveSession: true })); this.track(observed); session = this.sessions.get(sessionId); if (!session || session.root_provider_session_id !== roots[0].provider_session_id) throw new DaemonError("hook_identity_unknown", "hook Session is not a member of the retained native tree"); await this.persist(); } return await this.acceptHook({ daemonId: this.state.daemon_id, rootProviderSessionId: session.root_provider_session_id, parentProviderSessionId: session.parent_provider_session_id ?? null, observedProfile: this.bridge.modelProfiles?.get(sessionId) ?? {} }, sessionId, params.payload); }
|
|
546
546
|
if (operation === "hook.verify") {
|
|
547
547
|
if (params.daemonId !== this.state.daemon_id) throw new DaemonError("native_hook_unproven", "Native hook daemon identity differs");
|
|
@@ -594,7 +594,7 @@ export class Runtime {
|
|
|
594
594
|
["provider_resource", () => finishDaemonProcess(this.state.config, this.state.provider_process)],
|
|
595
595
|
["auth", () => unlink(path.join(this.paths.home, "auth.json")).catch(error => { if (error.code !== "ENOENT") throw error; })],
|
|
596
596
|
["daemon_resource", () => finishDaemonProcess(this.state.config, this.state.resource_process)]
|
|
597
|
-
]);
|
|
597
|
+
], undefined, { cancelTree: params.cancelTree === true });
|
|
598
598
|
}
|
|
599
599
|
throw new DaemonError("unknown_operation", `unknown daemon operation: ${operation}`);
|
|
600
600
|
}
|
|
@@ -69,9 +69,9 @@ async function forwardUsage(config, sessionId, snapshot) {
|
|
|
69
69
|
async function freePort() { return await new Promise((resolve, reject) => { const server = net.createServer(); server.once("error", reject); server.listen(0, "127.0.0.1", () => { const port = server.address().port; server.close(() => resolve(port)); }); }); }
|
|
70
70
|
const errorPayload = errorRecord;
|
|
71
71
|
|
|
72
|
-
async function writePlugin(paths, config) {
|
|
72
|
+
export async function writePlugin(paths, config) {
|
|
73
73
|
const directory = path.join(paths.config, "opencode", "plugins"); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
74
|
-
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ stateDir: paths.dir, daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot, resourceHome: config.resourceHome, nativeWorkStartAdmission: true })});\n`;
|
|
74
|
+
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ stateDir: paths.dir, daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot, resourceHome: config.resourceHome, runtime_owner: config.runtime_owner, noFlow: config.noFlow, nativeWorkStartAdmission: true })});\n`;
|
|
75
75
|
const checksum = createHash("sha256").update(source).digest("hex"); await writeFile(path.join(directory, "dd-flow.js"), source, { mode: 0o600 }); return checksum;
|
|
76
76
|
}
|
|
77
77
|
async function prepare(paths, config, authSource) {
|
|
@@ -668,7 +668,7 @@ export class Runtime {
|
|
|
668
668
|
["provider", () => stopProcessGroup(this.child)],
|
|
669
669
|
["provider_resource", () => finishDaemonProcess(this.state.config, this.providerProcess)],
|
|
670
670
|
["daemon_resource", () => finishDaemonProcess(this.state.config, this.state.resource_process)]
|
|
671
|
-
]);
|
|
671
|
+
], undefined, { cancelTree: params.cancelTree === true });
|
|
672
672
|
}
|
|
673
673
|
throw new DaemonError("unknown_operation", `unknown daemon operation: ${operation}`);
|
|
674
674
|
}
|
|
@@ -693,7 +693,7 @@ export class DaemonRuntime {
|
|
|
693
693
|
["provider", () => this.bridge.close?.()],
|
|
694
694
|
["provider_resource", () => finishDaemonProcess(this.state.config, this.state.provider_process)],
|
|
695
695
|
["daemon_resource", () => finishDaemonProcess(this.state.config, this.state.resource_process)]
|
|
696
|
-
]);
|
|
696
|
+
], undefined, { cancelTree: params.cancelTree === true });
|
|
697
697
|
throw new DaemonError("unknown_operation", `unknown daemon operation: ${operation}`);
|
|
698
698
|
}
|
|
699
699
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export type DelegationWork = { work_id: string; start_command: string; launch_policy?: string };
|
|
2
2
|
export type DelegationInput = { harness: string; stage: string; capacity: number; works?: DelegationWork[]; totalWorks?: number; remainingWorks?: number };
|
|
3
3
|
export function delegationAdapter(harness: string): string | null;
|
|
4
|
-
export function renderNativeChildFollowupInstruction(harness: string, childId: string | { session_id?: unknown; parent_session_id?: unknown; terminal_turn_id?: unknown; continuation_ref?: unknown }): string | null;
|
|
4
|
+
export function renderNativeChildFollowupInstruction(harness: string, childId: string | { session_id?: unknown; parent_session_id?: unknown; terminal_turn_id?: unknown; continuation_ref?: unknown }, options?: { purpose: "work_repair"; context?: string }): string | null;
|
|
5
5
|
export function delegationContract(harness: string): string;
|
|
6
6
|
export const lifecycleRetryInstruction: string;
|
|
7
7
|
export const lifecycleCommandCompletionInstruction: string;
|
|
8
8
|
export const stageLifecycleInstruction: string;
|
|
9
9
|
export const acceptedResultInstruction: string;
|
|
10
|
+
export const workStopConditionInstruction: string;
|
|
10
11
|
export function workerDelegationTask(input: { workId: string; startCommand: string; launchPolicy?: string }): string;
|
|
11
12
|
export function renderDelegationInstructions(input: DelegationInput): string;
|
|
12
13
|
export function renderCapacityInstructions(input: { harness: string; maximum: number }): string;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Shared by PLAN, review, CODE and leaf packets; baseline mismatch is work, not a veto.
|
|
2
|
+
export const workStopConditionInstruction = "Current implementation is the starting state, not an additional accepted constraint: fix mismatches with the accepted goal instead of stopping merely because they exist. Necessary project-local changes in another layer are coordination drift, not new scope; preserve ownership and active-writer fences. stop_conditions may be empty and must protect a specific accepted decision, prohibited action or unavailable resource/evidence, never invent a new user prohibition. If an actual accepted-decision conflict or required proof remains unresolved, retain its exact affected action/criterion and evidence for the coordinator; do not hide it or claim completion. An unsupported browser capability limits only its exact recorded operation/environment, not all UI work; continue independent work without substituting semantic selection for keyboard evidence.";
|
|
3
|
+
|
|
1
4
|
const ADAPTERS = {
|
|
2
5
|
codex: {
|
|
3
6
|
name: "Codex",
|
|
@@ -38,12 +41,15 @@ export function delegationAdapter(harness) {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/** Only documented same-child APIs may continue an existing native worker. */
|
|
41
|
-
export function renderNativeChildFollowupInstruction(harness, childId) {
|
|
44
|
+
export function renderNativeChildFollowupInstruction(harness, childId, options = {}) {
|
|
42
45
|
const child = childId && typeof childId === "object" ? childId : null;
|
|
43
46
|
childId = child?.session_id ?? childId;
|
|
44
47
|
if (typeof childId !== "string" || !childId) return null;
|
|
45
48
|
const adapter = delegationAdapter(harness), target = JSON.stringify(childId);
|
|
46
|
-
|
|
49
|
+
if (options.purpose === "work_repair" && ["zcode", "antigravity"].includes(adapter)) return null;
|
|
50
|
+
const message = JSON.stringify(options.purpose === "work_repair"
|
|
51
|
+
? `Continue the same assigned Work using this retained correction, not provider recovery. Inspect the original check/result diagnostic, correct the evidenced defect, and use only its issued successor. Do not restart the Work, spawn another child, or repeat committed/pending lifecycle commands.\n${options.context ?? ""}`
|
|
52
|
+
: "Continue the same assigned Work after the transient provider failure; do not restart it or repeat committed lifecycle commands.");
|
|
47
53
|
if (adapter === "zcode") {
|
|
48
54
|
const ref = child?.continuation_ref;
|
|
49
55
|
if (ref?.schema_id !== "zcode/child-continuation@1" || ref.capability !== "dd-flow/managed-provider-recovery@1" || ref.child_session_id !== childId
|
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
"documentUpdate": {"type": "object", "additionalProperties": false, "required": ["path", "action", "owner", "reason", "baseline_sha256"], "properties": {"path": {"type": "string", "minLength": 1}, "action": {"enum": ["create", "update"]}, "owner": {"type": "string", "minLength": 1}, "reason": {"type": "string", "minLength": 1}, "baseline_sha256": {"type": ["string", "null"], "pattern": "^[a-f0-9]{64}$"}}},
|
|
21
21
|
"reviewFinding": {"type": "object", "additionalProperties": false, "required": ["finding_ref", "priority", "problem", "impact", "required_outcome", "evidence_refs", "obligation_refs", "decision_reason", "check_refs"], "properties": {"finding_ref": {"type": "string", "minLength": 1}, "priority": {"enum": ["p0", "p1", "p2", "p3"]}, "problem": {"type": "string", "minLength": 1}, "impact": {"type": "string", "minLength": 1}, "required_outcome": {"type": "string", "minLength": 1}, "evidence_refs": {"$ref": "#/$defs/strings"}, "obligation_refs": {"$ref": "#/$defs/strings"}, "decision_reason": {"type": "string", "minLength": 1}, "check_refs": {"$ref": "#/$defs/strings"}}},
|
|
22
22
|
"repair": {"type": "object", "additionalProperties": false, "required": ["origin_work_ids", "verification_check_refs"], "anyOf": [{"required": ["check_receipt_id", "failure_receipt_path"]}, {"required": ["review_findings"]}, {"required": ["semantic_unresolved", "verification_path"]}], "properties": {"origin_work_ids": {"$ref": "#/$defs/strings"}, "check_receipt_id": {"type": "string", "minLength": 1}, "failure_receipt_path": {"type": "string", "minLength": 1}, "review_findings": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/reviewFinding"}}, "semantic_unresolved": {"$ref": "#/$defs/strings"}, "verification_path": {"type": "string", "minLength": 1}, "verification_check_refs": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/check"}}}},
|
|
23
|
-
"work": {"type": "object", "additionalProperties": false, "required": ["schema_id", "key", "launch_policy", "source", "task", "semantic_spine", "requirements", "acceptance", "document_updates", "required_read", "discovery_boundary", "planned_write_areas", "checks", "provides_checks", "stop_conditions", "depends_on", "result_schema"], "properties": {"schema_id": {"const": "dd-flow/code-work-packet@5"}, "key": {"type": "string", "minLength": 1}, "launch_policy": {"const": "fresh_agent_required"}, "source": {"$ref": "#/$defs/source"}, "repair": {"$ref": "#/$defs/repair"}, "task": {"type": "string", "minLength": 1}, "semantic_spine": {"$ref": "#/$defs/spine"}, "requirements": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/obligation"}}, "acceptance": {"type": "array", "items": {"$ref": "#/$defs/acceptance"}}, "document_updates": {"type": "array", "items": {"$ref": "#/$defs/documentUpdate"}}, "required_read": {"$ref": "#/$defs/strings", "description": "Mandatory starting sources, not a read allowlist."}, "discovery_boundary": {"$ref": "#/$defs/strings", "description": "Likely discovery areas; additional project-local reads are allowed."}, "planned_write_areas": {"type": "array", "items": {"type": "string", "minLength": 1}, "description": "Soft coordination hints only. A worker may change any necessary project file inside the RUN workspace."}, "checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "provides_checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "stop_conditions": {"
|
|
23
|
+
"work": {"type": "object", "additionalProperties": false, "required": ["schema_id", "key", "launch_policy", "source", "task", "semantic_spine", "requirements", "acceptance", "document_updates", "required_read", "discovery_boundary", "planned_write_areas", "checks", "provides_checks", "stop_conditions", "depends_on", "result_schema"], "properties": {"schema_id": {"const": "dd-flow/code-work-packet@5"}, "key": {"type": "string", "minLength": 1}, "launch_policy": {"const": "fresh_agent_required"}, "source": {"$ref": "#/$defs/source"}, "repair": {"$ref": "#/$defs/repair"}, "task": {"type": "string", "minLength": 1}, "semantic_spine": {"$ref": "#/$defs/spine"}, "requirements": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/obligation"}}, "acceptance": {"type": "array", "items": {"$ref": "#/$defs/acceptance"}}, "document_updates": {"type": "array", "items": {"$ref": "#/$defs/documentUpdate"}}, "required_read": {"$ref": "#/$defs/strings", "description": "Mandatory starting sources, not a read allowlist."}, "discovery_boundary": {"$ref": "#/$defs/strings", "description": "Likely discovery areas; additional project-local reads are allowed."}, "planned_write_areas": {"type": "array", "items": {"type": "string", "minLength": 1}, "description": "Soft coordination hints only. A worker may change any necessary project file inside the RUN workspace."}, "checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "provides_checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "stop_conditions": {"type": "array", "items": {"type": "string", "minLength": 1}, "description": "Optional action-specific accepted-decision, safety or evidence boundaries. An empty list is valid; a mismatch with current implementation or planned paths alone is not a stop condition."}, "depends_on": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "result_schema": {"const": "dd-flow/code-work-result@3"}}}
|
|
24
24
|
}
|
|
25
25
|
}
|