@deksden-com/dd-flow-cli 0.9.0-beta.150 → 0.9.0-beta.151

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +17 -22
  3. package/dist/build-info.json +6 -6
  4. package/dist/harness-runtime/lib/agy-error-boundary.mjs +26 -5
  5. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +67 -0
  6. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +10 -14
  7. package/dist/harness-runtime/lib/dd-agy.mjs +3 -3
  8. package/dist/harness-runtime/lib/dd-droid.mjs +2 -2
  9. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +8 -3
  10. package/dist/harness-runtime/lib/dd-opencode.mjs +2 -2
  11. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +1 -1
  12. package/dist/harness-runtime/lib/dd-zcode.mjs +37 -12
  13. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -1
  14. package/dist/harness-runtime/lib/delegation-instructions.mjs +13 -4
  15. package/dist/harness-runtime/lib/model-observations.mjs +19 -7
  16. package/dist/harness-runtime/lib/native-children.d.mts +12 -1
  17. package/dist/harness-runtime/lib/native-children.mjs +47 -1
  18. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +9 -1
  19. package/dist/harness-runtime/lib/provider-overload-policy.mjs +97 -6
  20. package/dist/services/check-runner-evidence.js +17 -302
  21. package/dist/services/check-runner-proof.js +305 -0
  22. package/dist/services/code-checks.js +4 -1
  23. package/dist/services/controller-child-capacity.js +58 -0
  24. package/dist/services/controller-child-observer.js +9 -3
  25. package/dist/services/memory-init-native-capacity.js +1 -1
  26. package/dist/services/provider-capacity.js +15 -7
  27. package/dist/services/run-controller-adapter.js +1 -1
  28. package/dist/services/run-controller.js +87 -18
  29. package/dist/services/run-recovery-guard.js +1 -1
  30. package/dist/services/run-recovery-runtime.js +1 -1
  31. package/dist/services/stage-context.js +56 -8
  32. package/dist/services/vnext-fanout.js +1 -1
  33. package/dist/verification-components.json +299 -0
  34. package/package.json +7 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.151
4
+
5
+ ### Patch Changes
6
+
7
+ - Preserve causal AGY completion and model/role evidence, share bounded recursive
8
+ runner proof, and continue eligible provider failures with effect-fenced native
9
+ child admission. Admit the reviewed CP253 ZCode bridge and bind browser facts
10
+ to stage context without turning an unavailable probe into a baseline gate.
11
+ - Replace blanket release checks with scoped contract receipts and package
12
+ delivery verification; keep provider, Memory Bank and CLI ownership separate
13
+ so harness fixes do not launch unrelated acceptance suites.
14
+
3
15
  ## 0.9.0-beta.150
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -510,28 +510,23 @@ remain invalid and receive a standalone retry.
510
510
 
511
511
  ## Verification
512
512
 
513
- For a code merge, start with the cheap common contracts, then select affected
514
- storage/process/flow checks explicitly and record the selection reason:
515
-
516
- ```bash
517
- pnpm gate:merge
518
- ```
519
-
520
- For release acceptance, freeze source/canon, build once, then use
521
- `DD_FLOW_GATE_REPORT_ROOT=/absolute/external/report pnpm gate:release`.
522
- Node family selection lives in `package.json`, Vitest membership in
523
- `vitest.config.ts`, and required release receipts in `scripts/release-candidate.mjs`.
524
- The local and CI profiles cover the same required guarantees; qualification-only
525
- cases report their prerequisites/skips separately. Tests invoking compiled CLI
526
- must use the current frozen build, even for a partial merge check.
527
- For development use `pnpm test:fast`, `pnpm test:core` or `pnpm test:flow` plus
528
- the affected native contracts; these subsets are not full acceptance.
529
- New files default to serial core execution. A forced serial fallback is
530
- `pnpm test:integration --no-file-parallelism --maxWorkers=1`.
531
- The [test-suite verification runbook](https://github.com/deksden-com/dd-eval/blob/main/runbooks/flow-test-suite-verification.md)
532
- owns group qualification, complete reports and cross-repository commands.
533
-
534
- Manual smoke checks should use a temporary `DD_FLOW_HOME` when they do not intentionally inspect the operator's real local state.
513
+ Checks follow changed contracts, not a merge/release/EVAL event. Inspect
514
+ `pnpm verify:plan`, then run `pnpm test` (or `pnpm gate:merge`) for only missing
515
+ affected contracts. `scripts/verification-policy.mjs` owns impact selection;
516
+ existing Node/Vitest runners retain complete selected-file/case evidence.
517
+ Unknown impact requires an explicit policy mapping, never a full-suite fallback.
518
+
519
+ There is no mandatory full release gate. Freeze source/canon, build and pack the
520
+ exact artifact, then accept with `DD_FLOW_VERIFICATION_RECEIPT` pointing to the
521
+ scoped verification receipt. Preserve receipts outside the checkout. Unchanged
522
+ contract inputs reuse intact reports; commit/version/build time alone do not
523
+ invalidate behavior evidence. Failed/unknown attempts require investigation,
524
+ not automatic retries. Package identity and registry readback remain mandatory.
525
+ GitHub CI is disabled. Publication does not repeat the accepted package's smoke
526
+ after identical registry tar readback. EVAL preparation is resource readiness,
527
+ not another engine or product test suite.
528
+ The [verification runbook](https://github.com/deksden-com/dd-eval/blob/main/runbooks/flow-test-suite-verification.md)
529
+ owns the local procedure. A scoped PASS is not a claim that every test ran.
535
530
 
536
531
  ### RUN status during recovery
537
532
 
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.150",
4
- "cli_commit": "ad05c70447949f295e588546fe27f6abc4640c3f",
5
- "built_at": "2026-10-24T17:47:48.856Z",
3
+ "cli_version": "0.9.0-beta.151",
4
+ "cli_commit": "89cad0d654601de59b12929240eee4e958b74207",
5
+ "built_at": "2026-10-25T10:59:05.638Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.2",
8
8
  "commit": "925ef10ecc0b683f7feba6f74913e15560842500",
9
9
  "flow_contract": "dd-flow-canonical-2026-08",
10
- "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release146",
11
- "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release146/.memory-bank",
12
- "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-canon-release146/.memory-bank/dd-flow",
10
+ "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-cp253",
11
+ "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-cp253/.memory-bank",
12
+ "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-cp253/.memory-bank/dd-flow",
13
13
  "layout": "dot_memory_bank"
14
14
  }
15
15
  }
@@ -1,19 +1,22 @@
1
1
  import { parseLifecycleCommand, commandOption, commandPosition } from "./lifecycle-command.mjs";
2
2
  import { entityReferenceVariants } from "./entity-references.mjs";
3
+ import { agyTerminalEnvelope, ownedAgyOutput } from "./agy-terminal-envelope.mjs";
3
4
 
4
5
  /** Ordered native transcript evidence, never assistant text as a success signal. */
5
- export function recoveredAgyErrorBoundary(text, error, floor = -1) {
6
+ export function recoveredAgyErrorBoundary(text, error, floor = -1, source = null, sessionId = null) {
6
7
  let rows;
7
8
  try { rows = text.trim().split("\n").filter(Boolean).map(JSON.parse); } catch { return null; }
8
- let previous = -1, lastError = null, boundary = null, invocation = null;
9
+ let previous = -1, lastError = null, boundary = null, invocation = null, call = null;
10
+ const launches = new Map();
9
11
  for (const row of rows) {
10
12
  if (!Number.isInteger(row.step_index) || row.step_index <= previous) return null;
11
13
  previous = row.step_index;
12
14
  if (row.step_index <= (floor ?? -1)) continue;
13
- if (row.type === "USER_INPUT") { lastError = null; boundary = null; invocation = null; continue; }
15
+ if (row.type === "USER_INPUT") { lastError = null; boundary = null; invocation = null; call = null; launches.clear(); continue; }
14
16
  if (row.type === "ERROR_MESSAGE") { lastError = row; boundary = null; invocation = null; continue; }
15
17
  if (Array.isArray(row.tool_calls)) {
16
18
  invocation = null;
19
+ call = row.tool_calls.length === 1 ? row.tool_calls[0] : null;
17
20
  if (row.tool_calls.length === 1 && row.tool_calls[0]?.name === "run_command") {
18
21
  let command = row.tool_calls[0]?.args?.CommandLine;
19
22
  if (typeof command === "string") {
@@ -23,8 +26,22 @@ export function recoveredAgyErrorBoundary(text, error, floor = -1) {
23
26
  }
24
27
  }
25
28
  }
26
- if (!lastError || row.type !== "GENERIC" || row.status !== "DONE" || typeof row.content !== "string" || !row.content.includes("Command exited with code 0.")) continue;
27
- const output = row.content.split("Command output:\n")[1]?.replaceAll("\r", "");
29
+ if (row.type === 'GENERIC' && row.status === 'RUNNING') {
30
+ const task = row.content?.match(/^Created At: [^\n]+\nTool is running as a background task with task id: ([\w-]+\/task-\d+)\n/);
31
+ if (task && invocation && (!sessionId || task[1].startsWith(`${sessionId}/`))) launches.set(task[1], launches.has(task[1]) ? null : { invocation, step: row.step_index });
32
+ call = null; invocation = null; continue;
33
+ }
34
+ const envelope = agyTerminalEnvelope(row, call);
35
+ let launchStep = null;
36
+ if (envelope?.task_id) {
37
+ const launch = launches.get(envelope.task_id);
38
+ invocation = launch && launch.step < row.step_index ? launch.invocation : null;
39
+ launchStep = launch?.step ?? null;
40
+ }
41
+ if (!lastError || envelope?.exit_code !== 0) continue;
42
+ const retained = envelope.output_reference && source && sessionId ? ownedAgyOutput(envelope.output_reference, source, sessionId, envelope.task_id) : null;
43
+ const full = retained ? agyTerminalEnvelope({ type: 'GENERIC', status: 'DONE', content: retained.text }) : null;
44
+ const output = (retained ? full?.exit_code === 0 ? full.output : null : envelope.output)?.replaceAll("\r", "");
28
45
  if (!output) continue;
29
46
  for (const match of output.matchAll(/^\s*\{/gm)) {
30
47
  let receipt;
@@ -51,6 +68,10 @@ export function recoveredAgyErrorBoundary(text, error, floor = -1) {
51
68
  && [receipt.run_id, receipt.stage, receipt.work_id].every(value => typeof value === "string" && value)
52
69
  && (receipt.outcome !== "paused" || typeof receipt.pause?.id === "string")) boundary = { receipt: { ok: true, outcome: receipt.outcome, run_id: receipt.run_id, stage: receipt.stage, work_id: receipt.work_id,
53
70
  lifecycle_operation: "stage_pause", pause: { id: receipt.pause.id } }, error_step: lastError.step_index, boundary_step: row.step_index };
71
+ if (boundary?.boundary_step === row.step_index) {
72
+ if (launchStep !== null) boundary.invocation_step = launchStep;
73
+ if (retained) boundary.output_source = { locator: retained.locator, sha256: retained.sha256 };
74
+ }
54
75
  }
55
76
  }
56
77
  return boundary && lastError?.error === error ? boundary : null;
@@ -0,0 +1,67 @@
1
+ import { constants, openSync, closeSync, fstatSync, lstatSync, readSync, realpathSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ const taskId = '[\\w-]+/task-\\d+';
7
+
8
+ export function ownedAgyOutput(reference, source, sessionId, ownerTask = null) {
9
+ let fd;
10
+ try {
11
+ const transcript = fileURLToPath(source.locator), file = fileURLToPath(reference);
12
+ const system = path.dirname(path.dirname(transcript));
13
+ if (path.basename(path.dirname(system)) !== sessionId || path.basename(system) !== '.system_generated') return null;
14
+ const relative = path.relative(system, file);
15
+ if (!/^(?:steps\/\d+\/output\.txt|tasks\/task-\d+\.log)$/.test(relative) || realpathSync(file) !== file || realpathSync(system) !== system) return null;
16
+ if (ownerTask) {
17
+ const number = ownerTask.match(new RegExp(`^${sessionId}/task-(\\d+)$`))?.[1];
18
+ if (!number || ![`steps/${number}/output.txt`, `tasks/task-${number}.log`].includes(relative)) return null;
19
+ }
20
+ const before = lstatSync(file);
21
+ if (!before.isFile() || before.size > 2 * 1024 * 1024) return null;
22
+ fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
23
+ const stat = fstatSync(fd);
24
+ if (!stat.isFile() || stat.ino !== before.ino || stat.dev !== before.dev) return null;
25
+ const bytes = Buffer.alloc(stat.size);
26
+ let offset = 0;
27
+ while (offset < bytes.length) { const count = readSync(fd, bytes, offset, bytes.length - offset, offset); if (!count) return null; offset += count; }
28
+ const after = fstatSync(fd);
29
+ if (stat.size !== after.size || stat.mtimeMs !== after.mtimeMs) return null;
30
+ return { text: bytes.toString('utf8'), sha256: createHash('sha256').update(bytes).digest('hex'), locator: reference };
31
+ } catch { return null; }
32
+ finally { if (fd !== undefined) closeSync(fd); }
33
+ }
34
+
35
+ /** Only a native envelope is decoded here; stdout cannot introduce terminal authority. */
36
+ export function agyTerminalEnvelope(row, call = null) {
37
+ if (row?.status !== 'DONE' || typeof row.content !== 'string') return null;
38
+ let body = row.content.replaceAll('\r\n', '\n'), id = null, cancelled = false;
39
+ if (row.source === 'SYSTEM' && row.type === 'SYSTEM_MESSAGE') {
40
+ const match = body.match(new RegExp(`(?:^|\\n)\\[Message\\] timestamp=[^\\n]+ sender=(${taskId}) priority=MESSAGE_PRIORITY_(?:HIGH|LOW) content=Task id "(${taskId})" (finished with result:|was canceled with result:)\\n`));
41
+ if (!match || match[1] !== match[2]) return null;
42
+ id = match[1]; cancelled = match[3].startsWith('was canceled');
43
+ body = body.slice(match.index + match[0].length).replace(/\n<\/SYSTEM_MESSAGE>\s*$/, '');
44
+ } else if (row.type !== 'GENERIC' || row.source && row.source !== 'MODEL') return null;
45
+ else {
46
+ body = body.replace(/^Created At: [^\n]+\nCompleted At: [^\n]+\n/, '');
47
+ if (call?.name === 'manage_task') {
48
+ const decode = value => { try { return JSON.parse(value); } catch { return value; } };
49
+ id = decode(call.args?.TaskId);
50
+ const action = decode(call.args?.Action);
51
+ const status = body.match(new RegExp(`^Task: (${taskId})\\nStatus: (DONE|COMPLETED|FAILED|CANCELLED)(?:\\n|$)`));
52
+ if (action === 'status' && status?.[1] === id) return { task_id: id, task_terminal: true, exit_code: null, command_outcome: status[2] === 'DONE' ? null : status[2].toLowerCase(), output: null };
53
+ if (action === 'kill' && body.match(new RegExp(`^Task "(${taskId})" cancelled\\.(?:\\n|$)`))?.[1] === id) return { task_id: id, task_terminal: true, exit_code: null, command_outcome: 'cancelled', output: null };
54
+ return null;
55
+ }
56
+ }
57
+ // This exact wrapper preserves the beginning of the native result, including its exit header.
58
+ const wrapper = body.match(/^The tool's output was truncated due to being too long\. You can find the full output in a file at (file:\/\/[^\n]+)\.\n\nHere is a preview with the middle of the output truncated:\n/);
59
+ if (wrapper) body = body.slice(wrapper[0].length);
60
+ if (cancelled && /^Tool execution was canceled(?:\n|$)/.test(body)) return { task_id: id, task_terminal: true, exit_code: null, command_outcome: 'cancelled', output: null };
61
+ const header = body.match(/^Command exited with code (-?\d+)\.(?:\n|$)/);
62
+ if (!header) return null;
63
+ const exit = Number(header[1]);
64
+ if (!Number.isSafeInteger(exit)) return null;
65
+ return { task_id: id, task_terminal: true, exit_code: exit, command_outcome: exit === 0 ? 'completed' : 'failed', output: wrapper ? null : body.split('Command output:\n')[1] ?? null,
66
+ ...(wrapper ? { output_reference: wrapper[1] } : {}) };
67
+ }
@@ -24,6 +24,7 @@ import path from "node:path";
24
24
  import { observeModel } from "./model-observations.mjs";
25
25
  import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot, managedAgyEnv } from "./dd-agy.mjs";
26
26
  import { recoveredAgyErrorBoundary } from "./agy-error-boundary.mjs";
27
+ import { agyTerminalEnvelope } from "./agy-terminal-envelope.mjs";
27
28
  import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
28
29
  import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
29
30
  import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
@@ -51,10 +52,10 @@ export function agyTranscriptFacts(text, source, sessionId) {
51
52
  const content = typeof event.content === 'string' ? event.content : '';
52
53
  const native_source = { ...source, step_index: event.step_index };
53
54
  if (event.source === 'SYSTEM' && event.type === 'SYSTEM_MESSAGE' && event.status === 'DONE') {
54
- const terminal = content.match(/(?:^|\n)\[Message\] timestamp=[^\n]+ sender=([\w-]+\/task-\d+) priority=MESSAGE_PRIORITY_(?:HIGH|LOW) content=Task id "([\w-]+\/task-\d+)" (finished with result:\nCommand exited with code (-?\d+)\.|was canceled with result:\nTool execution was canceled)(?:\n|$)/);
55
- if (terminal && terminal[1] === terminal[2]) {
56
- const prior = resources.get(terminal[1]);
57
- if (prior) resources.set(prior.resource_id, mergeAgyResource(prior, { ...prior, state: 'stopped', native_outcome: terminal[4] === undefined ? 'cancelled' : Number(terminal[4]) === 0 ? 'completed' : 'failed', terminal_source: native_source }));
55
+ const terminal = agyTerminalEnvelope(event);
56
+ if (terminal) {
57
+ const prior = resources.get(terminal.task_id);
58
+ if (prior) resources.set(prior.resource_id, mergeAgyResource(prior, { ...prior, state: 'stopped', ...(terminal.command_outcome ? { native_outcome: terminal.command_outcome } : {}), terminal_source: native_source }));
58
59
  }
59
60
  continue;
60
61
  }
@@ -63,13 +64,8 @@ export function agyTranscriptFacts(text, source, sessionId) {
63
64
  if (event.type !== 'GENERIC') continue;
64
65
  const call = calls.shift();
65
66
  if (call?.name === 'manage_task' && event.status === 'DONE') {
66
- const decode = value => { try { return JSON.parse(value); } catch { return value; } };
67
- const id = decode(call.args?.TaskId), action = decode(call.args?.Action);
68
- const prior = resources.get(id);
69
- if (prior && action === 'kill' && content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nTask "([\w-]+\/task-\d+)" cancelled\.(?:\n|$)/)?.[1] === id)
70
- resources.set(id, mergeAgyResource(prior, { ...prior, state: 'stopped', native_outcome: 'cancelled', terminal_source: native_source }));
71
- const status = action === 'status' && content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nTask: ([\w-]+\/task-\d+)\nStatus: (COMPLETED|FAILED|CANCELLED)\n/);
72
- if (prior && status?.[1] === id) resources.set(id, mergeAgyResource(prior, { ...prior, state: 'stopped', native_outcome: status[2].toLowerCase(), terminal_source: native_source }));
67
+ const terminal = agyTerminalEnvelope(event, call), prior = resources.get(terminal?.task_id);
68
+ if (prior) resources.set(prior.resource_id, mergeAgyResource(prior, { ...prior, state: 'stopped', ...(terminal.command_outcome ? { native_outcome: terminal.command_outcome } : {}), terminal_source: native_source }));
73
69
  }
74
70
  if (call?.name === 'invoke_subagent' && event.status === 'DONE') {
75
71
  const match = content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nCreated the following subagent:\n(\{[\s\S]*?\})\nThe subagent will send you a message/);
@@ -98,7 +94,7 @@ export function mergeAgyResource(prior, next) {
98
94
  return { ...prior, state: 'unknown', observation_conflict: true };
99
95
  if (prior.observation_conflict || next.observation_conflict || prior.native_outcome && next.native_outcome && prior.native_outcome !== next.native_outcome)
100
96
  return { ...prior, state: 'unknown', observation_conflict: true };
101
- if (next.native_outcome && (!Number.isSafeInteger(next.native_source?.step_index) || !Number.isSafeInteger(next.terminal_source?.step_index) || next.terminal_source.step_index <= next.native_source.step_index))
97
+ if (next.terminal_source && (!Number.isSafeInteger(next.native_source?.step_index) || !Number.isSafeInteger(next.terminal_source?.step_index) || next.terminal_source.step_index <= next.native_source.step_index))
102
98
  return { ...prior, state: 'unknown', observation_conflict: true };
103
99
  if (prior.state === 'stopped' && next.state !== 'stopped') {
104
100
  if (next.native_source?.step_index > (prior.terminal_source?.step_index ?? prior.native_source?.step_index))
@@ -578,8 +574,8 @@ export class Runtime {
578
574
  if (result.status === "ERROR" && settled && rootStop?.fullyIdle === true && !this.state.last_hook_rejection && current.operationId && this.paths.runtime) {
579
575
  const root = this.init.conversation_id, transcript = path.join(this.paths.runtime, "brain", root, ".system_generated", "logs", "transcript.jsonl");
580
576
  await captureAgyTranscript(this.paths.runtime, { provider_session_id: root, parent_provider_session_id: root, log_uri: new URL(`file://${transcript}`).href }, root, [root], (text, source) => {
581
- const boundary = recoveredAgyErrorBoundary(text, result.error, current.stepFloor);
582
- const hook = current.nativeHookEvents?.get(boundary?.boundary_step);
577
+ const boundary = recoveredAgyErrorBoundary(text, result.error, current.stepFloor, source, root);
578
+ const hook = current.nativeHookEvents?.get(boundary?.invocation_step ?? boundary?.boundary_step);
583
579
  if (boundary && hook) terminalMetadata.recovered_error_boundary = { ...boundary, receipt: { ...boundary.receipt, native_hook_event_key: hook, daemon_instance: this.config.daemonId }, source };
584
580
  });
585
581
  }
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { SETUP_STARTUP_MS } from './setup-progress.mjs';
5
5
 
6
6
  import { checkObservedProfile } from "./model-observations.mjs";
7
- import { classifyProviderError, safeProviderError, streamRequestKey } from "./provider-overload-policy.mjs";
7
+ import { classifyRecoveryError as classifyProviderError, safeProviderError, streamRequestKey } from "./provider-overload-policy.mjs";
8
8
 
9
9
  const DEFAULT_MODEL = "gemini-3.1-pro-high";
10
10
  export const AGY_VERSION_TIMEOUT_MS = 30_000;
@@ -31,9 +31,9 @@ export function agyTerminalFailure(result, metadata = {}) {
31
31
  const identity = result.turn_id ?? result.request_id;
32
32
  const turn = typeof identity === "string" && identity ? identity : null;
33
33
  const stream = streamRequestKey(metadata.request_proof);
34
- const proofMissing = result.status === "ERROR" && ["provider_capacity_transient", "provider_rate_limited"].includes(category) && !turn && !stream;
34
+ const proofMissing = result.status === "ERROR" && ["provider_capacity_transient", "provider_rate_limited", "provider_network_transient"].includes(category) && !turn && !stream;
35
35
  if (proofMissing) code = "provider_overload_evidence_unavailable";
36
- return new AgyError(code, String(native.message ?? "Antigravity returned ERROR"), code === "provider_capacity_transient" || code === "provider_rate_limited", {
36
+ return new AgyError(code, String(native.message ?? "Antigravity returned ERROR"), ["provider_capacity_transient", "provider_rate_limited", "provider_network_transient"].includes(code), {
37
37
  provider_result: result, ...metadata, provider_error: safeProviderError(native), native_source: "agy.result", terminal_status: result.status === "ERROR" ? "failed" : "cancelled",
38
38
  ...(turn ? { turn_id: turn, native_turn_id: turn } : {}),
39
39
  ...(proofMissing ? { provider_category: category, reason: "native_turn_identity_unavailable" } : {}),
@@ -21,7 +21,7 @@ const metadataReaders = new Map();
21
21
  function metadataReader(factory) { if (!metadataReaders.has(factory)) metadataReaders.set(factory, new DroidMetadataReader(factory)); return metadataReaders.get(factory); }
22
22
  import { confirmDaemonProcess, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
23
23
  import { renderAdapterPolicy, lifecycleRetryInstruction } from "./delegation-instructions.mjs";
24
- import { classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
24
+ import { classifyRecoveryError as classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
25
25
  import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
26
26
 
27
27
  export const DROID_CONTRACT = "dd-droid-harness@1";
@@ -108,7 +108,7 @@ export function droidTerminalFailure(outcome, sessionId, settled) {
108
108
  terminal_status: "failed", native_source: "droid.agent_turn_outcome", observed_at: new Date().toISOString(),
109
109
  provider_error: safeProviderError(outcome.error), will_retry: outcome.error.willRetry === true, native_retry_active: outcome.error.willRetry === true,
110
110
  native_turn_items: { observed: settled === true, possible_effects: true, pending: settled !== true }
111
- }), { retryable: code === "provider_capacity_transient" || code === "provider_rate_limited" });
111
+ }), { retryable: ["provider_capacity_transient", "provider_rate_limited", "provider_network_transient"].includes(code) });
112
112
  }
113
113
 
114
114
 
@@ -91,9 +91,12 @@ async function prepare(paths, config, authSource) {
91
91
  }
92
92
  }
93
93
  catch (error) { if (error.code !== "ENOENT") throw new DaemonError("opencode_config_invalid", "Portable OpenCode provider config must be valid JSON"); }
94
- await writeJson(path.join(paths.config, "opencode", "opencode.json"), { ...portable, ...agentRestrictions, permissions,
95
- agents: { ...agentRestrictions.agents, "dd-flow-worker": { mode: "subagent", system: "Execute the assigned dd-flow Work. Follow the issued lifecycle instructions. Do not spawn descendants.", model: `${config.provider}/${config.model}${config.variant !== "default" ? `#${config.variant}` : ""}` } },
96
- experimental: { subagent_depth: 1, ...(policies ? { policies } : {}) } });
94
+ const generatedConfig = { ...portable, ...agentRestrictions, permissions,
95
+ agents: { ...agentRestrictions.agents, "dd-flow-worker": { ...agentRestrictions.agent?.[config.agent], ...agentRestrictions.agents?.[config.agent], mode: "subagent", system: "Execute the assigned dd-flow Work. Follow the issued lifecycle instructions. Do not spawn descendants.", model: `${config.provider}/${config.model}${config.variant !== "default" ? `#${config.variant}` : ""}` } },
96
+ experimental: { subagent_depth: 1, ...(policies ? { policies } : {}) } };
97
+ const configPath = path.join(paths.config, "opencode", "opencode.json");
98
+ await writeJson(configPath, generatedConfig);
99
+ config.workerLaunchConfig = { locator: configPath, sha256: createHash('sha256').update(await readFile(configPath)).digest('hex'), primary_agent: config.agent, subagent: 'dd-flow-worker', max_depth: 1 };
97
100
  config.pluginSha256 = await writePlugin(paths, config);
98
101
  }
99
102
  async function bootstrapCredentials(config, client) {
@@ -303,6 +306,8 @@ export class Runtime {
303
306
  if (info.sessionID && info.sessionID !== id) throw new DaemonError("session_identity_mismatch", "native message belongs to another Session");
304
307
  await observeModel({ journal: this.state.config.journal ?? this.paths.journal, harness: "opencode-server", sessionId: id, parentSessionId: parent,
305
308
  requested: { ...this.state.config, reasoning: this.state.config.variant, mode: parent ? "dd-flow-worker" : this.state.config.agent },
309
+ nativeAgentRole: this.state.config.workerLaunchConfig ? { name: info.mode ?? null, kind: parent ? 'subagent' : 'primary', parent_session_id: parent,
310
+ launch: { ...this.state.config.workerLaunchConfig, root_session_id: this.sessions.get(id)?.root_provider_session_id ?? null } } : null,
306
311
  observed: { provider: info.providerID, model: info.modelID, reasoning: info.variant, mode: info.mode }, source: { method: "session/message", message_id: info.id, event_sha256: createHash("sha256").update(`${id}:${info.id}:${info.modelID}`).digest("hex") }, evidence: "response" });
307
312
  this.modelMessages.add(info.id);
308
313
  }
@@ -4,7 +4,7 @@ import { ToolObservations, openCodeToolObservation } from "./tool-observations.m
4
4
  import { ObservationClock, observedTimeout, validateObservationDuration } from "./observation-clock.mjs";
5
5
  import { assertNativeLine } from "./native-streams.mjs";
6
6
  import { operationProgressReporter } from "./operation-progress.mjs";
7
- import { classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
7
+ import { classifyRecoveryError as classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
8
8
 
9
9
  export const OPENCODE_PROTOCOL = 2;
10
10
  export class OpenCodeError extends Error {
@@ -59,7 +59,7 @@ export function providerFailure(native, sessionId, turnId) {
59
59
  const category = classifyProviderError(native), code = category === "provider_failed" ? "opencode_provider_failed" : category;
60
60
  // Never forward response.body or provider credentials into controller/Judge.
61
61
  return new OpenCodeError(code, `OpenCode native execution failed (${type}${status ? `, HTTP ${status}` : ""})`,
62
- ["provider_capacity_transient", "provider_rate_limited"].includes(code), {
62
+ ["provider_capacity_transient", "provider_rate_limited", "provider_network_transient"].includes(code), {
63
63
  provider_session_id: sessionId, session_id: sessionId, turn_id: turnId, native_error_type: type,
64
64
  native_turn_id: turnId, terminal_status: "failed",
65
65
  provider_error: safeProviderError(native),
@@ -459,7 +459,7 @@ export class DaemonRuntime {
459
459
  if (active && active.session_id !== params.sessionId) throw new DaemonError("session_identity_mismatch", "Retained observation has a foreign active root");
460
460
  const cacheScope = `${params.sessionId}:${generation}`;
461
461
  if (this.childObservationScope !== cacheScope) { this.childObservationScope = cacheScope; this.childTerminalCache = new Map(); this.childObservationKeys = new Set(); }
462
- const subagents = await readZcodeSubagents(this.bridge, root.adapter_session_id, 30_000, true, this.childTerminalCache);
462
+ const subagents = await readZcodeSubagents(this.bridge, root.adapter_session_id, 30_000, true, this.childTerminalCache, root.provider_session_id);
463
463
  if (generation !== (this.controlGeneration ?? 0)) throw new DaemonError("operation_cancelled", "Observation generation was retired");
464
464
  if (activeIdentity(this.state.active_request) !== capturedIdentity) throw new DaemonError("operation_cancelled", "Observation belongs to a retired active Turn");
465
465
  assertDaemonDispatch(this.paths.dir);
@@ -15,7 +15,7 @@ import { commandText } from "./process-json.mjs";
15
15
  import { readNativeLines, streamNativeDiagnostics, nativeObservationError } from "./native-streams.mjs";
16
16
  import { operationProgressReporter } from "./operation-progress.mjs";
17
17
  import { safeNativeFailure } from "./native-children.mjs";
18
- import { classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
18
+ import { classifyRecoveryError as classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
19
19
 
20
20
  import { readZcodeUsage } from "./zcode-usage.mjs";
21
21
  const ZCODE_HARNESS_CONTRACT = "dd-zcode-harness@1";
@@ -200,6 +200,7 @@ export class AcpBridge {
200
200
  clientInfo: this.options.clientInfo ?? { name: "dd-zcode", version: "0.1.0" },
201
201
  }, this.options.initializeTimeoutMs ?? SETUP_STARTUP_MS);
202
202
  publishSetup('native_initialized', 'native-initialize');
203
+ this.recoveryCapability = initialized?._meta?.zcode?.ddFlowRecovery;
203
204
  return initialized;
204
205
  }
205
206
 
@@ -306,7 +307,7 @@ export class AcpBridge {
306
307
  code: [provider?.code, data?.code, data?.native_code].filter(value => typeof value === "string").join(" "), message: messageText };
307
308
  const category = pending.method === "session/prompt" ? classifyProviderError(nativeFailure) : "provider_failed";
308
309
  error.code = unknown ? nativeCode : category === "provider_failed" ? "acp_request_failed" : category;
309
- error.retryable = ["provider_rate_limited", "provider_capacity_transient"].includes(error.code);
310
+ error.retryable = ["provider_rate_limited", "provider_capacity_transient", "provider_network_transient"].includes(error.code);
310
311
  error.details = { acp_code: message.error.code ?? null,
311
312
  observed_at: provider?.observed_at ?? now(), provider_session_id: pending.sessionId ?? null, source: "acp.error_response", request_id: message.id,
312
313
  terminal_response: { method: pending.method, session_id: pending.sessionId, request_id: message.id },
@@ -581,13 +582,13 @@ async function resume(bridge, sessionId, cwd) {
581
582
 
582
583
  /** Ended children are paginated by the native API (20 by default). A partial
583
584
  * directory is never settlement evidence, including during teardown. */
584
- export async function readZcodeSubagents(bridge, sessionId, timeoutMs = 30_000, retained = false, terminalCache = new Map()) {
585
+ export async function readZcodeSubagents(bridge, sessionId, timeoutMs = 30_000, retained = false, terminalCache = new Map(), parentSessionId = sessionId) {
585
586
  const clock = new ObservationClock({ timeoutMs });
586
587
  const cursors = new Set(), ended = new Map();
587
588
  let first, cursor;
588
589
  for (;;) {
589
590
  if (clock.sample()) fail("ZCode child observation is unavailable", "operation_observation_lost");
590
- const response = await bridge.request(retained ? "zcode/session/retainedSubagents" : "zcode/session/subagents", { sessionId, ...(cursor ? { endedCursor: cursor } : {}) }, timeoutMs);
591
+ const response = await bridge.request(retained ? "zcode/session/retainedSubagents" : "zcode/session/subagents", { sessionId, proofContract: "zcode/retained-child-effects@1", ...(cursor ? { endedCursor: cursor } : {}) }, timeoutMs);
591
592
  if (retained && response?.sessionId !== sessionId) fail("Retained ZCode child directory has a foreign Session identity", "native_topology_incomplete");
592
593
  const page = retained ? response.topology : response;
593
594
  if (!page || !Array.isArray(page.running)) fail("ZCode child directory is unavailable", "native_topology_incomplete");
@@ -615,7 +616,7 @@ export async function readZcodeSubagents(bridge, sessionId, timeoutMs = 30_000,
615
616
  const cacheKey = JSON.stringify([child.childSessionId, child.startedAt, child.endedAt]);
616
617
  if (terminalCache.has(cacheKey)) { items.push(terminalCache.get(cacheKey)); continue; }
617
618
  let terminal;
618
- try { terminal = await bridge.request("zcode/session/retainedTerminal", { sessionId: child.childSessionId, startedAt: child.startedAt, endedAt: child.endedAt }, timeoutMs); }
619
+ try { terminal = await bridge.request("zcode/session/retainedTerminal", { sessionId: child.childSessionId, parentSessionId, startedAt: child.startedAt, endedAt: child.endedAt, proofContract: "zcode/retained-child-effects@1" }, timeoutMs); }
619
620
  catch (error) { items.push({ ...child, terminal_metadata_unavailable: error.code ?? "terminal_metadata_unavailable" }); continue; }
620
621
  if (terminal?.available !== true) { items.push({ ...child, terminal_metadata_unavailable: "terminal_metadata_unavailable" }); continue; }
621
622
  const observedAt = Date.parse(terminal.terminal_observed_at);
@@ -623,9 +624,23 @@ export async function readZcodeSubagents(bridge, sessionId, timeoutMs = 30_000,
623
624
  || terminal.startedAt !== child.startedAt || terminal.endedAt !== child.endedAt
624
625
  || !Number.isFinite(observedAt) || observedAt < child.startedAt || observedAt > child.endedAt) fail("ZCode terminal failure metadata has foreign identity", "native_child_turn_conflict");
625
626
  const providerError = safeNativeFailure(terminal.provider_error);
626
- const item = { ...child, terminal_turn_id: terminal.terminal_turn_id, terminal_observed_at: terminal.terminal_observed_at,
627
+ const proof = terminal.effects_proof;
628
+ const ref = terminal.continuation_ref;
629
+ const effectsBound = proof?.schema_id === "zcode/retained-child-effects@1" && proof.session_id === child.childSessionId
630
+ && proof.started_at === child.startedAt && proof.ended_at === child.endedAt && proof.terminal_turn_id === terminal.terminal_turn_id
631
+ && proof.settled === true && proof.native_turn_items?.observed === true && proof.native_turn_items?.pending === false;
632
+ const refBound = effectsBound && ref?.schema_id === "zcode/child-continuation@1" && ref.child_session_id === child.childSessionId
633
+ && ref.parent_session_id === parentSessionId && ref.started_at === child.startedAt && ref.ended_at === child.endedAt && ref.terminal_turn_id === terminal.terminal_turn_id
634
+ && typeof ref.agent_id === "string" && /^agent_[\w-]+$/.test(ref.agent_id) && typeof ref.parent_tool_call_id === "string" && ref.parent_tool_call_id
635
+ && ref.capability === "dd-flow/managed-provider-recovery@1";
636
+ const item = { ...child, terminal_turn_id: terminal.terminal_turn_id, terminal_observed_at: terminal.terminal_observed_at, native_source: "zcode.retained-terminal",
637
+ ...(effectsBound ? { native_turn_items: proof.native_turn_items, effects_proof: proof } : {}),
638
+ ...(refBound ? { continuation_ref: ref } : {}),
639
+ ...(!effectsBound || !refBound ? { recovery_unavailable_reason: typeof (terminal.continuation_unavailable_reason ?? proof?.reason) === "string" && /^[a-z][a-z0-9_]{0,127}$/.test(terminal.continuation_unavailable_reason ?? proof?.reason) ? terminal.continuation_unavailable_reason ?? proof.reason : !effectsBound ? "native_effects_unproven" : "native_continuation_unproven" } : {}),
627
640
  ...(providerError ? { provider_error: providerError } : {}) };
628
- terminalCache.set(cacheKey, item);
641
+ // Effects/topology proof is admission-time authority: never reuse it
642
+ // across a later inspection merely because the old terminal is cached.
643
+ if (!proof && !ref) terminalCache.set(cacheKey, item);
629
644
  items.push(item);
630
645
  clock.sample(child.childSessionId);
631
646
  }
@@ -638,10 +653,10 @@ export async function readZcodeSubagents(bridge, sessionId, timeoutMs = 30_000,
638
653
  }
639
654
  }
640
655
 
641
- async function inspect(bridge, sessionId) {
656
+ async function inspect(bridge, sessionId, parentSessionId = sessionId) {
642
657
  const [read, subagents] = await Promise.all([
643
658
  bridge.request("zcode/session/read", { sessionId }),
644
- readZcodeSubagents(bridge, sessionId),
659
+ readZcodeSubagents(bridge, sessionId, 30_000, false, new Map(), parentSessionId),
645
660
  ]);
646
661
  let usage = null, usage_observation_error = null;
647
662
  try { usage = await readZcodeUsage(bridge, sessionId); }
@@ -984,6 +999,8 @@ export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContrac
984
999
  ["636b14181d5c9a95476055079836fd6b58169f58", "dd-zcode-harness@2"],
985
1000
  ["4621fabd3f517ecdd349dfebebae9077c01d2f2b", "dd-zcode-harness@2"],
986
1001
  ["9c5ca6cc81c4eb5065e43f8b7ab6b7cc5ab7ae47", "dd-zcode-harness@2"],
1002
+ // CP253 installed artifact: exact same-child Read continuation and retained proof.
1003
+ ["b18dfdbedaa9330e765315bce0f1a609792d6c91", "dd-zcode-harness@2"],
987
1004
  ]);
988
1005
  const qualified = qualifiedBridgeCommits.get(bridgeCommit) === harnessContract;
989
1006
  return { status: qualified ? "qualified" : "unqualified", mechanism: "cli-invocation@1", sha256,
@@ -1082,14 +1099,22 @@ export async function promptSessionWithBridge(bridge, options) {
1082
1099
  const textCursor = bridge.assistantTextCursor(identity.adapterSessionId);
1083
1100
  options.assertDispatch?.();
1084
1101
  bridge.dispatchOperationId = options.operationId;
1085
- const turn = await bridge.request("session/prompt", { sessionId: identity.adapterSessionId, prompt: [{ type: "text", text: prompt }] }, timeoutMs, {
1102
+ const managed = options.operationId ? { schema_id: "dd-flow/managed-provider-recovery@1", owner: "controller", operation_id: options.operationId, root_operation_id: options.operationId.replace(/:capacity:\d+:session\.prompt$/, "") } : null;
1103
+ const acknowledged = value => managed && value && Object.keys(managed).every(key => value[key] === managed[key]);
1104
+ if (managed && (bridge.recoveryCapability?.schema_id !== managed.schema_id || bridge.recoveryCapability?.owner !== "controller")) fail("ZCode bridge does not acknowledge controller-owned provider recovery", "provider_recovery_unavailable");
1105
+ const turn = await bridge.request("session/prompt", { sessionId: identity.adapterSessionId, prompt: [{ type: "text", text: prompt }], ...(managed ? { _meta: { zcode: { ddFlowRecovery: managed } } } : {}) }, timeoutMs, {
1086
1106
  idleSessionId: identity.adapterSessionId,
1087
1107
  idleTimeoutMs: options.livenessTimeoutMs ?? 600_000,
1088
1108
  inactivityMode: "diagnostic",
1089
1109
  }).catch(error => {
1090
1110
  if (error.details?.terminal_response?.session_id === identity.adapterSessionId) error.details.provider_session_id = identity.providerSessionId;
1111
+ if (managed && !acknowledged(error.details?.native?._meta?.zcode?.ddFlowRecovery)) {
1112
+ const unknown = new Error("ZCode provider failure omitted managed recovery acknowledgement");
1113
+ unknown.code = "native_outcome_unknown"; unknown.details = { primary_error: errorRecord(error) }; throw unknown;
1114
+ }
1091
1115
  throw error;
1092
1116
  });
1117
+ if (managed && !acknowledged(turn?._meta?.zcode?.ddFlowRecovery)) fail("ZCode bridge omitted the managed recovery acknowledgement", "native_outcome_unknown");
1093
1118
  const stopReason = typeof turn?.stopReason === "string" ? turn.stopReason : null;
1094
1119
  const completionEvidence = turn?._meta?.zcodeCompletionEvidence;
1095
1120
  if (completionEvidence === "inferred" || completionEvidence === "failed") {
@@ -1113,7 +1138,7 @@ export async function promptSessionWithBridge(bridge, options) {
1113
1138
  throw error;
1114
1139
  }
1115
1140
  let evidence;
1116
- try { await bridge.flush(); evidence = await inspect(bridge, identity.adapterSessionId); }
1141
+ try { await bridge.flush(); evidence = await inspect(bridge, identity.adapterSessionId, identity.providerSessionId); }
1117
1142
  catch (cause) {
1118
1143
  const error = new Error("ZCode completed the native turn, but post-turn observation failed");
1119
1144
  error.code = "native_outcome_observation_failed";
@@ -1141,7 +1166,7 @@ export async function inspectSession(options) {
1141
1166
  export async function inspectSessionWithBridge(bridge, options) {
1142
1167
  requireJournal(options);
1143
1168
  const identity = await controlledIdentity(bridge, options);
1144
- return receipt(options, { harness: "zcode-acp", provider_session_id: identity.providerSessionId, adapter_session_id: identity.adapterSessionId, ...publicEvidence(await inspect(bridge, identity.adapterSessionId)) });
1169
+ return receipt(options, { harness: "zcode-acp", provider_session_id: identity.providerSessionId, adapter_session_id: identity.adapterSessionId, ...publicEvidence(await inspect(bridge, identity.adapterSessionId, identity.providerSessionId)) });
1145
1170
  }
1146
1171
 
1147
1172
  export async function cancelSession(options) {
@@ -1,7 +1,7 @@
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): 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;
5
5
  export function delegationContract(harness: string): string;
6
6
  export const lifecycleRetryInstruction: string;
7
7
  export const lifecycleCommandCompletionInstruction: string;
@@ -39,12 +39,21 @@ export function delegationAdapter(harness) {
39
39
 
40
40
  /** Only documented same-child APIs may continue an existing native worker. */
41
41
  export function renderNativeChildFollowupInstruction(harness, childId) {
42
+ const child = childId && typeof childId === "object" ? childId : null;
43
+ childId = child?.session_id ?? childId;
42
44
  if (typeof childId !== "string" || !childId) return null;
43
45
  const adapter = delegationAdapter(harness), target = JSON.stringify(childId);
44
- if (adapter === "codex") return `Use collaboration.followup_task with target: ${target} and message: "Continue the same assigned Work after the transient provider overload; do not restart it or repeat committed lifecycle commands." Do not spawn a replacement.`;
45
- if (adapter === "opencode") return `Use the native subagent tool with sessionID: ${target} and prompt: "Continue the same assigned Work after the transient provider overload; do not restart it or repeat committed lifecycle commands." Keep the same model and reasoning. Do not spawn a replacement.`;
46
- if (adapter === "droid") return `Use the native Task tool with resume: ${target}, subagent_type: "dd-flow-worker", and prompt: "Continue the same assigned Work after the transient provider overload; do not restart it or repeat committed lifecycle commands." Omit model and complexity overrides. Do not spawn a replacement.`;
47
- if (adapter === "grok") return `Use the native send_subagent_message tool with subagent_id: ${target}, delivery: "queue", and text: "Continue the same assigned Work after the transient provider overload; do not restart it or repeat committed lifecycle commands." Retain its accepted message_id and wait on the same subagent. If admission is uncertain, do not resend. Do not spawn a replacement.`;
46
+ const message = JSON.stringify("Continue the same assigned Work after the transient provider failure; do not restart it or repeat committed lifecycle commands.");
47
+ if (adapter === "zcode") {
48
+ const ref = child?.continuation_ref;
49
+ if (ref?.schema_id !== "zcode/child-continuation@1" || ref.capability !== "dd-flow/managed-provider-recovery@1" || ref.child_session_id !== childId
50
+ || ref.parent_session_id !== child.parent_session_id || ref.terminal_turn_id !== child.terminal_turn_id || !/^agent_[\w-]{1,200}$/.test(ref.agent_id ?? "")) return null;
51
+ return `Use the native SendMessage tool exactly once with to: ${JSON.stringify(ref.agent_id)}, summary: "Continue existing Work", and message: ${message}. Retain messageId and delivery only when the native result supplies them; never invent missing IDs. The adapter verifies typed native completion and successor evidence. Wait for this same agent with TaskOutput. If admission is uncertain, do not resend. Do not spawn a replacement or override model/reasoning.`;
52
+ }
53
+ if (adapter === "codex") return `Use collaboration.followup_task with target: ${target} and message: ${message}. Do not spawn a replacement.`;
54
+ if (adapter === "opencode") return `Use the native subagent tool with sessionID: ${target} and prompt: ${message}. Keep the same model and reasoning. Do not spawn a replacement.`;
55
+ if (adapter === "droid") return `Use the native Task tool with resume: ${target}, subagent_type: "dd-flow-worker", and prompt: ${message}. Omit model and complexity overrides. Do not spawn a replacement.`;
56
+ if (adapter === "grok") return `Use the native send_subagent_message tool with subagent_id: ${target}, delivery: "queue", and text: ${message}. Retain its accepted message_id and wait on the same subagent. If admission is uncertain, do not resend. Do not spawn a replacement.`;
48
57
  return null;
49
58
  }
50
59