@deksden-com/dd-flow-cli 0.9.0-beta.143 → 0.9.0-beta.145

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 (197) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +9 -3
  5. package/dist/cli/command-metadata.js +33 -14
  6. package/dist/cli/help.js +32 -1
  7. package/dist/cli/hook-ingress.js +27 -3
  8. package/dist/cli/memory-flow-admission.js +7 -3
  9. package/dist/cli/memory-init-invocation.js +10 -3
  10. package/dist/cli/memory-init.js +74 -68
  11. package/dist/cli/memory-storage-admission.js +3 -2
  12. package/dist/cli/memory-storage-qualification.js +105 -0
  13. package/dist/cli/memory-storage-transition.js +123 -0
  14. package/dist/cli/memory-upgrade.js +189 -0
  15. package/dist/cli/native-hook-ingress.js +14 -4
  16. package/dist/cli/native-hook-preparation.js +55 -0
  17. package/dist/cli/response-file.js +2 -1
  18. package/dist/cli/run-cli.js +70 -32
  19. package/dist/cli/setup.js +2 -1
  20. package/dist/cli.js +12 -0
  21. package/dist/domain/stage-handler-identities.js +8 -0
  22. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  23. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  24. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  25. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  26. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
  27. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  28. package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
  29. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
  30. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  31. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  32. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  33. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  34. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  35. package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
  36. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  37. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  38. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  39. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
  40. package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
  41. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  42. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  43. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  44. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  45. package/dist/schemas/release-impact.schema.json +3 -3
  46. package/dist/schemas/stage-start-response.schema.json +4 -0
  47. package/dist/services/accepted-intent.js +13 -1
  48. package/dist/services/canon.js +43 -7
  49. package/dist/services/cli-operation-classifier.js +1 -1
  50. package/dist/services/cli-upgrade-allowlist.js +1 -1
  51. package/dist/services/code-checks.js +11 -4
  52. package/dist/services/compatibility-preflight.js +5 -2
  53. package/dist/services/controller-fanout.js +21 -4
  54. package/dist/services/controller-memory-task.js +45 -0
  55. package/dist/services/controller-memory-worker.js +48 -0
  56. package/dist/services/dashboard.js +2 -2
  57. package/dist/services/descriptor-run-start.js +26 -19
  58. package/dist/services/descriptor-stage-coordinator.js +30 -10
  59. package/dist/services/descriptor-stage-start.js +40 -4
  60. package/dist/services/engines.js +64 -14
  61. package/dist/services/eval-snapshots.js +104 -93
  62. package/dist/services/external-work-launch.js +5 -3
  63. package/dist/services/flow-catalog.js +8 -4
  64. package/dist/services/frozen-engine.js +113 -11
  65. package/dist/services/hook-memory-routing.js +188 -0
  66. package/dist/services/hook-recovery-guard.js +14 -0
  67. package/dist/services/hook-session-observation.js +24 -0
  68. package/dist/services/hooks.js +27 -8
  69. package/dist/services/hybrid-contracts.js +2 -13
  70. package/dist/services/hybrid-digest.js +14 -0
  71. package/dist/services/installed-flow-source.js +25 -4
  72. package/dist/services/lifecycle-invocations.js +33 -5
  73. package/dist/services/memory-accepted-composition.js +5 -4
  74. package/dist/services/memory-completed-correction-wave.js +23 -15
  75. package/dist/services/memory-compose-handler.js +163 -21
  76. package/dist/services/memory-composition-acceptance.js +186 -10
  77. package/dist/services/memory-composition-graph.js +42 -11
  78. package/dist/services/memory-composition-registration.js +23 -8
  79. package/dist/services/memory-composition-work-custody.js +14 -9
  80. package/dist/services/memory-composition-work-results.js +15 -3
  81. package/dist/services/memory-corrected-review-batch.js +10 -3
  82. package/dist/services/memory-corrected-review-findings.js +4 -2
  83. package/dist/services/memory-corrected-review-inputs.js +10 -5
  84. package/dist/services/memory-corrected-review-receipt.js +17 -9
  85. package/dist/services/memory-corrected-review-registration.js +3 -2
  86. package/dist/services/memory-correction-check-receipt.js +20 -4
  87. package/dist/services/memory-custody-snapshot.js +36 -0
  88. package/dist/services/memory-finish-evidence.js +44 -22
  89. package/dist/services/memory-finish-handler.js +37 -23
  90. package/dist/services/memory-finish-integration.js +218 -48
  91. package/dist/services/memory-finish-readiness.js +66 -19
  92. package/dist/services/memory-git-integration.js +87 -18
  93. package/dist/services/memory-historical-corrected-review.js +21 -15
  94. package/dist/services/memory-historical-original-review.js +22 -9
  95. package/dist/services/memory-historical-repair-registration.js +14 -8
  96. package/dist/services/memory-historical-second-review.js +30 -11
  97. package/dist/services/memory-init-contexts.js +28 -2
  98. package/dist/services/memory-init-controller-start.js +19 -8
  99. package/dist/services/memory-init-git-policy.js +50 -21
  100. package/dist/services/memory-init-lint-capability.js +21 -3
  101. package/dist/services/memory-init-native-capacity.js +13 -0
  102. package/dist/services/memory-init-preflight.js +11 -1
  103. package/dist/services/memory-init-request-candidate.js +73 -17
  104. package/dist/services/memory-init-request-input.js +23 -7
  105. package/dist/services/memory-init-run-bundle.js +49 -12
  106. package/dist/services/memory-init-storage.js +17 -7
  107. package/dist/services/memory-layer.js +68 -4
  108. package/dist/services/memory-lint-config.js +111 -2
  109. package/dist/services/memory-native-review-copy.js +5 -67
  110. package/dist/services/memory-normal-flow-entry.js +12 -0
  111. package/dist/services/memory-ordinary-defaults.js +22 -5
  112. package/dist/services/memory-preflight-handler.js +15 -0
  113. package/dist/services/memory-repair-current-view.js +1 -1
  114. package/dist/services/memory-repair-document-delta.js +70 -33
  115. package/dist/services/memory-repair-history.js +12 -7
  116. package/dist/services/memory-repair-physical-binding.js +1 -119
  117. package/dist/services/memory-repair-publication-plan.js +5 -3
  118. package/dist/services/memory-repair-publication-source.js +1 -1
  119. package/dist/services/memory-repair-publication.js +3 -2
  120. package/dist/services/memory-repair-registration.js +2 -1
  121. package/dist/services/memory-repair-work-baseline.js +4 -2
  122. package/dist/services/memory-repair-work-completion.js +32 -12
  123. package/dist/services/memory-repair-work-results.js +21 -2
  124. package/dist/services/memory-repair-workspace.js +11 -2
  125. package/dist/services/memory-repair-writer-copy.js +2 -1
  126. package/dist/services/memory-result-binding.js +78 -12
  127. package/dist/services/memory-retained-review-authority.js +17 -8
  128. package/dist/services/memory-retained-second-baseline.js +1 -1
  129. package/dist/services/memory-retained-second-repairs.js +12 -6
  130. package/dist/services/memory-review-correction-plan.js +47 -12
  131. package/dist/services/memory-review-handler.js +17 -17
  132. package/dist/services/memory-review-inputs.js +55 -9
  133. package/dist/services/memory-review-limits.js +10 -0
  134. package/dist/services/memory-review-receipt.js +2 -2
  135. package/dist/services/memory-review-registration.js +13 -4
  136. package/dist/services/memory-review-work-custody.js +3 -1
  137. package/dist/services/memory-review-work-input.js +26 -7
  138. package/dist/services/memory-review-work-results.js +26 -4
  139. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  140. package/dist/services/memory-second-correction-plan.js +2 -1
  141. package/dist/services/memory-second-repair-launch.js +4 -4
  142. package/dist/services/memory-second-repair-source.js +1 -1
  143. package/dist/services/memory-stage-dispatch.js +38 -1
  144. package/dist/services/memory-stage-results.js +113 -22
  145. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  146. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  147. package/dist/services/memory-upgrade-evidence.js +100 -0
  148. package/dist/services/memory-upgrade-inventory.js +442 -0
  149. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  150. package/dist/services/memory-upgrade-publication.js +413 -0
  151. package/dist/services/memory-upgrade-selection.js +430 -0
  152. package/dist/services/memory-upgrade-start.js +60 -0
  153. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  154. package/dist/services/memory-verification-acceptance.js +38 -17
  155. package/dist/services/memory-verification-checks.js +77 -23
  156. package/dist/services/memory-verification-receipt.js +6 -5
  157. package/dist/services/memory-verify-handler.js +15 -13
  158. package/dist/services/memory-work-result-json.js +12 -0
  159. package/dist/services/memory-work-result-preparation.js +5 -4
  160. package/dist/services/merge-server.js +4 -2
  161. package/dist/services/migrations.js +127 -98
  162. package/dist/services/physical-startup.js +45 -0
  163. package/dist/services/project-registry.js +97 -0
  164. package/dist/services/project-summary.js +1 -1
  165. package/dist/services/projects.js +3 -93
  166. package/dist/services/prompt-delivery.js +71 -0
  167. package/dist/services/provider-capacity.js +74 -9
  168. package/dist/services/recovery-snapshot-database.js +32 -4
  169. package/dist/services/repair-intents.js +1 -12
  170. package/dist/services/repair-receipt.js +13 -0
  171. package/dist/services/run-control-worker.js +35 -18
  172. package/dist/services/run-controller-process.js +2 -6
  173. package/dist/services/run-controller-recovery.js +6 -1
  174. package/dist/services/run-controller.js +131 -80
  175. package/dist/services/run-recovery-guard.js +30 -0
  176. package/dist/services/run-recovery.js +11 -44
  177. package/dist/services/run-start-custody.js +17 -5
  178. package/dist/services/runtime-scope-worker.js +46 -19
  179. package/dist/services/sessions.js +3 -24
  180. package/dist/services/setup.js +63 -13
  181. package/dist/services/stage-handlers.js +9 -7
  182. package/dist/services/stage-pause.js +108 -44
  183. package/dist/services/version-status.js +1 -1
  184. package/dist/services/vnext-fanout.js +21 -9
  185. package/dist/services/vnext-plan-review.js +3 -5
  186. package/dist/services/work-registry.js +80 -33
  187. package/dist/services/workspace-files.js +1 -1
  188. package/dist/shared/entity-references.js +1 -8
  189. package/dist/storage/data-preservation.js +65 -0
  190. package/dist/storage/database.js +53 -19
  191. package/dist/storage/import-guard.js +84 -27
  192. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  193. package/dist/storage/paired-writer-migration.js +424 -0
  194. package/dist/storage/writer-contract.js +11 -4
  195. package/dist/storage/writer-migration.js +313 -52
  196. package/dist/storage/writer-restore.js +217 -0
  197. package/package.json +8 -4
@@ -1,6 +1,7 @@
1
1
  import { AppError, errorRecord } from "../shared/errors.js";
2
2
  import { parseCommandArgs } from "../services/lifecycle-command.js";
3
3
  import { channel } from "node:diagnostics_channel";
4
+ import { nativeHookNonParticipation } from "./native-hook-preparation.js";
4
5
  /** These ingress commands consume a prepared store; they never prepare it. */
5
6
  export function isNativeHookIngress(args) {
6
7
  return args[2] === "handle" && ((args[1] === "event" && ["agy", "grok", "droid", "opencode", "zcode"].includes(args[0] ?? ""))
@@ -8,7 +9,8 @@ export function isNativeHookIngress(args) {
8
9
  }
9
10
  export async function dispatchNativeHook(args, stdin, env) {
10
11
  const started = performance.now();
11
- const deadline = env.DD_FLOW_HOOK_DEADLINE_AT ? Number(env.DD_FLOW_HOOK_DEADLINE_AT) : undefined;
12
+ const deadline = env.DD_FLOW_HOOK_DEADLINE_AT ? Number(env.DD_FLOW_HOOK_DEADLINE_AT) : Date.now() + 25_000;
13
+ env = { ...env, DD_FLOW_HOOK_DEADLINE_AT: String(deadline) };
12
14
  if (deadline !== undefined && !Number.isSafeInteger(deadline))
13
15
  throw new AppError("hook_deadline_invalid", "Native hook deadline must be an absolute millisecond timestamp", 1, { effect: "unknown" });
14
16
  const phase = (name) => {
@@ -24,6 +26,11 @@ export async function dispatchNativeHook(args, stdin, env) {
24
26
  for (const [name, values] of parsed.options)
25
27
  if (!["project-root", "json"].includes(name) || values.length !== 1 || name === "json" && values[0] !== "__dd_flow_flag__")
26
28
  throw new AppError("usage", `Invalid native hook option: ${name}`, 2);
29
+ const unrelated = nativeHookNonParticipation(args, stdin);
30
+ if (unrelated) {
31
+ phase("ack");
32
+ return unrelated;
33
+ }
27
34
  const [hooks, { createContext }, { resolveDdFlowHome }] = await Promise.all([
28
35
  import("../services/hooks.js"), import("../runtime/context.js"), import("../storage/paths.js")
29
36
  ]);
@@ -58,7 +65,9 @@ export async function dispatchNativeHook(args, stdin, env) {
58
65
  timing.subscribe(log);
59
66
  try {
60
67
  phase("receipt");
61
- return await handler(context, { projectRoot, stdin });
68
+ const result = await handler(context, { projectRoot, stdin });
69
+ phase("ack");
70
+ return result;
62
71
  }
63
72
  finally {
64
73
  timing.unsubscribe(log);
@@ -68,8 +77,9 @@ export async function dispatchNativeHook(args, stdin, env) {
68
77
  export async function runNativeHookIngress(args) {
69
78
  try {
70
79
  const { readNativeHookInput, hookBudget } = await import("../harness-runtime/lib/native-hook-command.mjs");
71
- const stdin = await readNativeHookInput(process.stdin, hookBudget(25_000, process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null));
72
- process.stdout.write(`${JSON.stringify(await dispatchNativeHook(args, stdin, process.env))}\n`);
80
+ const budget = hookBudget(25_000, process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null);
81
+ const stdin = await readNativeHookInput(process.stdin, budget);
82
+ process.stdout.write(`${JSON.stringify(await dispatchNativeHook(args, stdin, { ...process.env, DD_FLOW_HOOK_DEADLINE_AT: String(budget.deadlineAt) }))}\n`);
73
83
  return 0;
74
84
  }
75
85
  catch (error) {
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ import { parseJsonObject } from "../shared/json.js";
3
+ import { continuationCommand, parseLifecycleCommand } from "../services/lifecycle-command.js";
4
+ const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
5
+ const text = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
6
+ /** Only a validated, ordinary before-tool command can skip the stateful handler.
7
+ * In particular after-tool, Stop, malformed and ambiguous events remain owned
8
+ * by their existing handler; this gate does not invent an admission policy. */
9
+ export function nativeHookNonParticipation(args, stdin) {
10
+ const payload = parseJsonObject(stdin || "{}", "native hook stdin");
11
+ let command;
12
+ if (args[0] === "zcode" && args[1] === "event") {
13
+ const update = object(object(payload.params).update);
14
+ if (payload.method !== "session/update" || update.sessionUpdate !== "tool_call")
15
+ return null;
16
+ const tool = text(object(object(update._meta).claudeCode).toolName);
17
+ if (!tool || !["Bash", "bash", "execute"].includes(tool))
18
+ return null;
19
+ const input = object(update.rawInput);
20
+ command = text(input.command) ?? text(input.cmd);
21
+ }
22
+ else if (args[0] === "grok" || args[0] === "zcode") {
23
+ const event = text(payload.hook_event_name) ?? text(payload.hookEventName);
24
+ if (event && !["PreToolUse", "pre_tool_use"].includes(event))
25
+ return null;
26
+ const tool = text(payload.tool_name) ?? text(payload.toolName);
27
+ if (tool && !(args[0] === "grok" ? ["Bash", "bash", "run_terminal_command", "RunTerminalCommand"] : ["Bash", "bash", "execute"]).includes(tool))
28
+ return null;
29
+ const input = object(payload.tool_input ?? payload.toolInput);
30
+ command = text(input.command) ?? text(input.cmd) ?? text(payload.command);
31
+ }
32
+ else {
33
+ const family = args[0];
34
+ if (payload.schema_id !== `dd-flow/${family}-tool-event@1` || payload.phase !== "before")
35
+ return null;
36
+ const session = object(payload.session), tool = text(payload.tool);
37
+ const directory = family === "agy" ? text(payload.launch_cwd) : text(session.directory);
38
+ const id = family === "agy" ? text(payload.conversation_id) : text(session.provider_session_id);
39
+ if (!directory || !path.isAbsolute(directory) || !id || !text(payload.daemon_id) || !text(payload.event_id)
40
+ || family === "opencode" && !text(payload.tool_call_id) || !tool
41
+ || !(family === "droid" ? ["Execute"] : ["bash", "Bash", "shell", "run_command", "run_terminal_command", "RunTerminalCommand"]).includes(tool)
42
+ || family === "droid" && session.parent_provider_session_id === id)
43
+ return null;
44
+ if (family === "agy" && Array.isArray(payload.workspace_paths)
45
+ && payload.workspace_paths.length && !payload.workspace_paths.some(value => typeof value === "string" && path.resolve(value) === path.resolve(directory)))
46
+ return null;
47
+ const input = object(payload.input);
48
+ command = text(input.command) ?? text(input.cmd) ?? text(input.CommandLine);
49
+ }
50
+ if (!command)
51
+ return null;
52
+ const parsed = parseLifecycleCommand(command);
53
+ return parsed.participation === "none" && !continuationCommand(command)
54
+ ? { ok: true, observed: false, reason: args[0] === "zcode" && args[1] === "event" ? "acp_evidence_only" : "event_not_participating" } : null;
55
+ }
@@ -40,7 +40,8 @@ export function writeResponseFile(payload, output) {
40
40
  if (!output.responseFile)
41
41
  return payload;
42
42
  const file = path.resolve(output.responseFile), temporary = `${file}.tmp-${randomUUID()}`;
43
- const visible = payload && typeof payload === "object" && !Array.isArray(payload) ? { ...payload } : { ok: true, result: payload };
43
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
44
+ const visible = record ? { ...(record.prompt_delivery ? { prompt_delivery: record.prompt_delivery } : {}), ...record } : { ok: true, result: payload };
44
45
  let ownsTemporary = false;
45
46
  try {
46
47
  prepareResponseDestination(file);
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
3
+ import { preparePromptDelivery, publishPromptDelivery } from "../services/prompt-delivery.js";
3
4
  import { requiredOption, optionalOption, optionalNumber, optionalPositiveNumber } from "./argument-values.js";
4
5
  import { resolveCommandPaths, resolveCommandProjectRoot } from "../services/command-context.js";
5
6
  import { assertAuxiliaryCommandScope, cliManagedInvocationScope, manualRecoveryInvocationScope, assertManualRecoveryInvocationTarget } from "../services/lifecycle-invocations.js";
@@ -49,6 +50,7 @@ import { validateSchema } from "../services/schema-validation.js";
49
50
  import { preflightMemoryPermissions } from "../services/memory-permissions.js";
50
51
  import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
51
52
  import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, prepareMigrationApply, verifyMigrationReport } from "../services/migrations.js";
53
+ import { PreparedUpgradeStorageReadiness } from "../services/memory-upgrade-storage-readiness.js";
52
54
  import { publishProjectSummary } from "../services/project-summary.js";
53
55
  import { requireProjectByRoot } from "../services/projects.js";
54
56
  import { resolveProjectRoot, resolveDdFlowHome } from "../storage/paths.js";
@@ -62,9 +64,9 @@ import { addVnextCodeRepair, prepareVnextCodeRepair, prepareVnextCodeStart } fro
62
64
  import { isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReviewRepair, prepareVnextCodeReviewStart } from "../services/vnext-code-review.js";
63
65
  import { applyVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest } from "../services/vnext-merge.js";
64
66
  import { sdlcStageHandlers } from "../services/stage-handlers.js";
65
- import { startMemoryStage, finishMemoryStage } from "../services/memory-stage-dispatch.js";
67
+ import { startMemoryStage, finishMemoryStage, startMemoryUpgradeStage, finishMemoryUpgradeStage } from "../services/memory-stage-dispatch.js";
66
68
  import { bindStageCoordinatorWork } from "../services/work-registry.js";
67
- import { registerMemoryCompositionFromStage } from "../services/memory-compose-handler.js";
69
+ import { registerMemoryCompositionFromStage, publishMemoryUpgradeFromStage } from "../services/memory-compose-handler.js";
68
70
  import { runMemoryVerificationChecks } from "../services/memory-verify-handler.js";
69
71
  import { registerMemoryReviewFromStage, collectMemoryInitialReviewFromStage } from "../services/memory-review-handler.js";
70
72
  import { PreparedRetainedMemoryReviewAuthority } from "../services/memory-retained-review-authority.js";
@@ -81,7 +83,7 @@ import { blockStageForRuntime, unblockStageAfterRuntimeRepair, prepareStageRunti
81
83
  import { classifyCliOperation, requiresExplicitUpgradeAuthorization } from "../services/cli-operation-classifier.js";
82
84
  import { prepareWorkerPrompt, renderWorkerPrompt } from "../services/prompts.js";
83
85
  import { finishStage, prepareStageFinish, startStage, prepareLegacyStageStart } from "../services/stage-lifecycle.js";
84
- import { addWorkBatch, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, workResultByteLimit, retryWork, showWork, startWork } from "../services/work-registry.js";
86
+ import { addWorkBatch, assertGenericWorkBatchAllowed, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, workResultByteLimit, retryWork, showWork, startWork } from "../services/work-registry.js";
85
87
  import { launchExternalWork } from "../services/external-work-launch.js";
86
88
  import { createEvalBootstrapSnapshot, prepareEvalBootstrapSnapshot, createEvalRunSnapshot, prepareEvalRunSnapshotCreation, prepareVnextSpecifyRun, prepareEvalRunRestore, prepareEvalBootstrapRestore, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
87
89
  import { forkRunFromSnapshot, prepareRunFork } from "../services/run-fork.js";
@@ -328,7 +330,7 @@ async function runCliRequest(args, io, env) {
328
330
  if (!invocationId) {
329
331
  const preparationContext = createContext({ ...env }, "read_existing", undefined, maintenanceOptions);
330
332
  try {
331
- preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile);
333
+ preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile, undefined, commandProgress(io, output));
332
334
  }
333
335
  finally {
334
336
  preparationContext.db.close?.();
@@ -357,8 +359,8 @@ async function runCliRequest(args, io, env) {
357
359
  throw error;
358
360
  }
359
361
  },
360
- prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute); },
361
- assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command) });
362
+ prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute, commandProgress(io, output)); },
363
+ assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command), progress: commandProgress(io, output) });
362
364
  if (admission.replay) {
363
365
  retainedOutcome = admission.outcome;
364
366
  settleHook = undefined;
@@ -386,7 +388,7 @@ async function runCliRequest(args, io, env) {
386
388
  throw new AppError(error.code, error.message, error.exitCode, error.details);
387
389
  }
388
390
  if (!retainedOutcome && !preparedInput)
389
- preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile);
391
+ preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, undefined, commandProgress(io, output));
390
392
  if (!retainedOutcome)
391
393
  await preflightCliCompatibility(dispatchContext, invocationArgs, env, scopeProjectRoot);
392
394
  if (!retainedOutcome) {
@@ -394,11 +396,10 @@ async function runCliRequest(args, io, env) {
394
396
  const event = !invocationId ? nativeEvent : null;
395
397
  settleHook = observeLifecycleCommand(dispatchContext, command, event ?? undefined);
396
398
  }
397
- const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput);
398
- if (!retainedOutcome && invocationArgs[0] === "stage" && invocationArgs[1] === "start"
399
- && result && typeof result === "object" && "prompt_path" in result && typeof result.prompt_path === "string") {
399
+ const result = preparePromptDelivery(retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput));
400
+ if (!retainedOutcome && result && typeof result === "object" && "worker_prompt_markdown" in result && typeof result.worker_prompt_markdown === "string") {
400
401
  try {
401
- bindLifecyclePublishedPacket(dispatchContext, fs.readFileSync(result.prompt_path, "utf8"));
402
+ bindLifecyclePublishedPacket(dispatchContext, result.worker_prompt_markdown);
402
403
  }
403
404
  catch (cause) {
404
405
  throw new AppError("invocation_packet_binding_failed", "Stage started but its published lifecycle assignments could not be retained", 1, { effect: "committed", cause: String(cause) });
@@ -422,6 +423,7 @@ async function runCliRequest(args, io, env) {
422
423
  writeProgressJsonl(io, "done", "Command finished");
423
424
  emitHumanProgress(io, output, progress, "done");
424
425
  const payload = attachProgress(resultWithWarnings, progress, output);
426
+ publishPromptDelivery(payload);
425
427
  const visible = writeResponseFile(payload, output);
426
428
  const maintenanceReceipt = payload;
427
429
  const maintenanceExpiry = maintenanceReceipt?.lease_expires_at ?? maintenanceReceipt?.process?.lease_expires_at;
@@ -436,7 +438,7 @@ async function runCliRequest(args, io, env) {
436
438
  const record = errorRecord(caught);
437
439
  maintenanceDiagnostics?.failure(caught);
438
440
  const error = isAppError(caught) ? caught : Object.assign(new AppError(record.code, record.message, record.exitCode, { effect: "unknown", ...record.details, ...(caught?.errcode !== undefined ? { sqlite_extended_code: caught.errcode } : {}) }), { ...(record.cause ? { cause: record.cause } : {}), ...(record.cleanup_error ? { cleanup_error: record.cleanup_error } : {}), ...(record.retryable !== undefined ? { retryable: record.retryable } : {}) });
439
- if (!settleHook && !invocationSettlement) {
441
+ if (!settleHook && !invocationSettlement && error.code !== "prompt_delivery_pending") {
440
442
  let earlyContext;
441
443
  try {
442
444
  const command = quote(["dd-flow", ...args]);
@@ -558,6 +560,8 @@ function databaseModeForCommand(args, classification) {
558
560
  if (isRuntimeMaintenance(args))
559
561
  return "read_existing";
560
562
  const [family, command] = args;
563
+ if (family === "migration" && command === "readiness")
564
+ return "prepared_existing";
561
565
  // Managed attempts are issued only from a prepared store. Opening that
562
566
  // existing writer must not run migrations before their input is admitted.
563
567
  if (parseArgs(args.slice(2)).options.has("invocation-id"))
@@ -635,7 +639,12 @@ function writeProgressJsonl(io, phase, message, detail) {
635
639
  io.stderr.write(`${JSON.stringify({ schema_id: "dd-flow/progress@1", phase, message, at: new Date().toISOString(), ...(detail ? { detail } : {}) })}\n`);
636
640
  }
637
641
  function commandProgress(io, output) {
638
- return (message) => output.progressJsonl ? writeProgressJsonl(io, "running", message) : io.stderr.write(`[running] ${message}\n`);
642
+ return (message) => {
643
+ if (output.progressJsonl)
644
+ writeProgressJsonl(io, "running", message);
645
+ else if (!output.json && !output.hookJsonOnly)
646
+ io.stderr.write(`[running] ${message}\n`);
647
+ };
639
648
  }
640
649
  function attachProgress(result, progress, output) {
641
650
  if (progress.length === 0 || output.hookJsonOnly)
@@ -842,6 +851,14 @@ function stringValue(value) {
842
851
  function shortSha(value) {
843
852
  return value ? value.slice(0, 12) : "unknown";
844
853
  }
854
+ function compositionProposalRetry(context, parsed) {
855
+ return {
856
+ retry_command: runtimeCommand(context, ["memory", "compose", "register", "--run", requiredOption(parsed, "run"),
857
+ "--project-root", requiredOption(parsed, "project-root"), "--request-key", requiredOption(parsed, "request-key"),
858
+ "--result-file", requiredOption(parsed, "result-file"), "--json", "--progress-jsonl"]),
859
+ retry_instruction: "Correct only the rejected composition proposal, then execute retry_command in this same Session. No Work graph was registered by this rejected call; do not dispatch children or finish the Stage until registration succeeds."
860
+ };
861
+ }
845
862
  /** All caller supplied file/stdin data is consumed here, before a managed
846
863
  * invocation is allowed to claim execution or a stage claims its hook event. */
847
864
  async function prepareCliInput(...args) {
@@ -852,7 +869,7 @@ async function prepareCliInput(...args) {
852
869
  return prepareError(error);
853
870
  }
854
871
  }
855
- async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args)) {
872
+ async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args), progress) {
856
873
  const { family, command, parsed } = route;
857
874
  const prepared = { route };
858
875
  if (optionalOption(parsed, "reason-file")) {
@@ -941,6 +958,10 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
941
958
  prepared.codexHooks = prepareCodexHooks(context, { projectRoot: requiredOption(parsed, "project-root"), target: optionalOption(parsed, "target"), profile: optionalOption(parsed, "profile"), yes: hasOption(parsed, "yes") });
942
959
  if (route.key === "migration apply")
943
960
  prepared.migrationApply = prepareMigrationApply(context, migrationApplyInput(parsed, classifyCliOperation(args, context.env)));
961
+ if (route.key === "migration readiness")
962
+ prepared.upgradeStorageScope = {
963
+ projectRoot: requiredOption(parsed, "project-root"), workspaceRoot: requiredOption(parsed, "workspace-root"), requestId: requiredOption(parsed, "request-id")
964
+ };
944
965
  if (route.key === "migration verify")
945
966
  prepared.migrationVerification = verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
946
967
  if (responseFile)
@@ -1115,6 +1136,7 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1115
1136
  if (route.key === "work add-batch") {
1116
1137
  const file = requiredOption(parsed, "file");
1117
1138
  try {
1139
+ assertGenericWorkBatchAllowed(context, requiredOption(parsed, "parent"));
1118
1140
  prepared.workBatch = prepareWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file,
1119
1141
  ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}),
1120
1142
  ...(optionalOption(parsed, "request-key") ? { requestKey: optionalOption(parsed, "request-key") } : {}) });
@@ -1210,14 +1232,15 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1210
1232
  const bundle = readFlowRunExecutionBundle(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") });
1211
1233
  if (bundle)
1212
1234
  prepared.stageBindingBundle = { stage, bundle, ...(contextSha256 ? { contextSha256 } : {}) };
1213
- if (bundle?.descriptor.descriptor.id === "mb-init") {
1235
+ if (bundle && ["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id)) {
1214
1236
  bundle.descriptor.stage(stage);
1215
1237
  if (hasOption(parsed, "dir"))
1216
1238
  throw new AppError("usage", "Memory stage layout belongs to its frozen descriptor", 2);
1217
1239
  const loaded = contextFile && contextSha256 ? loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) : undefined;
1218
1240
  if (loaded)
1219
1241
  prepared.stageStart = { externalContext: loaded };
1220
- prepared.memoryStage = { operation: "start", bytes: loaded ? Buffer.from(loaded.bytes) : Buffer.alloc(0) };
1242
+ prepared.memoryStage = { operation: "start", bytes: loaded ? Buffer.from(loaded.bytes) : Buffer.alloc(0),
1243
+ flowId: bundle.descriptor.descriptor.id === "mb-upgrade" ? "mb-upgrade" : "mb-init" };
1221
1244
  return prepared;
1222
1245
  }
1223
1246
  }
@@ -1313,7 +1336,7 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1313
1336
  throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
1314
1337
  const maxBytes = workResultByteLimit(context, requiredPosition(parsed, 0, "work-id"));
1315
1338
  prepared.workFinish = { result: resultFile ? prepareTextFile(resultFile, "Work result", maxBytes).text : await readStdin(io.stdin, maxBytes) };
1316
- prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result);
1339
+ prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result, progress);
1317
1340
  }
1318
1341
  if (family === "stage" && command === "fanout" && requiredPosition(parsed, 0, "fanout action") === "reconcile") {
1319
1342
  prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
@@ -1323,10 +1346,18 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1323
1346
  }
1324
1347
  if (family === "memory" && (["compose", "corrections"].includes(command ?? "") && parsed.positional[0] === "register"
1325
1348
  || command === "repair" && parsed.positional[0] === "publication-reserve")) {
1326
- const bytes = prepareFileBytes(requiredOption(parsed, "result-file"), `Memory ${command} result`, 1024 * 1024).bytes;
1327
- if (!bytes.length || bytes.length > 1024 * 1024)
1328
- throw new AppError("usage", "Memory operation result exceeds its byte bound", 2);
1329
- prepared.memoryOperationBytes = bytes;
1349
+ try {
1350
+ const bytes = prepareFileBytes(requiredOption(parsed, "result-file"), `Memory ${command} result`, 1024 * 1024).bytes;
1351
+ if (!bytes.length || bytes.length > 1024 * 1024)
1352
+ throw new AppError("usage", "Memory operation result exceeds its byte bound", 2, { phase: "prepare", effect: "no_effect", recoverable: true });
1353
+ prepared.memoryOperationBytes = bytes;
1354
+ }
1355
+ catch (error) {
1356
+ if (command === "compose" && isAppError(error) && error.details.phase === "prepare"
1357
+ && error.details.effect === "no_effect" && error.details.recoverable === true)
1358
+ Object.assign(error.details, compositionProposalRetry(context, parsed));
1359
+ throw error;
1360
+ }
1330
1361
  }
1331
1362
  if (family !== "stage" || command !== "finish")
1332
1363
  return prepared;
@@ -1343,9 +1374,9 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1343
1374
  const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
1344
1375
  const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
1345
1376
  const runtime = run(projectRoot, runId);
1346
- if (JSON.parse(runtime.indexJson).execution_bundle?.flow_id === "mb-init") {
1377
+ if (["mb-init", "mb-upgrade"].includes(JSON.parse(runtime.indexJson).execution_bundle?.flow_id)) {
1347
1378
  const bundle = readFlowRunExecutionBundle(context, { projectRoot, runId });
1348
- if (!bundle || bundle.descriptor.descriptor.id !== "mb-init")
1379
+ if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
1349
1380
  throw new AppError("cli_input_unprepared", "Memory RUN bundle is unavailable", 1);
1350
1381
  bundle.descriptor.stage(stage);
1351
1382
  if (["dir", "outcome", "decision-file", "verification-file", "retry-check", "reason", "reason-file", "request", "work"].some(option => hasOption(parsed, option)))
@@ -1355,7 +1386,7 @@ async function prepareCliInputValue(args, context, io, scopeProjectRoot, respons
1355
1386
  const bytes = resultStdin ? Buffer.from(await readStdin(io.stdin, 1024 * 1024)) : prepareFileBytes(resultFile, "Memory stage result", 1024 * 1024).bytes;
1356
1387
  if (!bytes.length || bytes.length > 1024 * 1024)
1357
1388
  throw new AppError("usage", "Memory stage result exceeds its byte bound", 2);
1358
- prepared.memoryStage = { operation: "finish", bytes };
1389
+ prepared.memoryStage = { operation: "finish", bytes, flowId: bundle.descriptor.descriptor.id === "mb-upgrade" ? "mb-upgrade" : "mb-init" };
1359
1390
  return prepared;
1360
1391
  }
1361
1392
  const terminalReplay = (name) => {
@@ -1668,6 +1699,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1668
1699
  });
1669
1700
  }
1670
1701
  if (family === "work" && command === "add-batch") {
1702
+ assertGenericWorkBatchAllowed(context, requiredOption(parsed, "parent"));
1671
1703
  return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file"), prepared: prepared.workBatch,
1672
1704
  ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}),
1673
1705
  ...(optionalOption(parsed, "request-key") ? { requestKey: optionalOption(parsed, "request-key") } : {}) });
@@ -1694,7 +1726,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1694
1726
  const hookEventId = optionalOption(parsed, "hook-event-id");
1695
1727
  const recoveryId = optionalOption(parsed, "recovery-id");
1696
1728
  const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
1697
- return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot });
1729
+ return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot, progress: commandProgress(io, output) });
1698
1730
  }
1699
1731
  if (family === "work" && command === "finish") {
1700
1732
  const resultFile = optionalOption(parsed, "result-file");
@@ -1748,7 +1780,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1748
1780
  matchKey: entityReferenceVariants(runId).map(reference => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
1749
1781
  errorCode: "trusted_session_binding_required", operation: "Memory stage start"
1750
1782
  }).eventKey;
1751
- const work = startMemoryStage(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes });
1783
+ const work = (prepared.memoryStage.flowId === "mb-upgrade" ? startMemoryUpgradeStage : startMemoryStage)(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes });
1752
1784
  try {
1753
1785
  const binding = bindStageCoordinatorWork(context, { workId: work.work_id, hookEventId: event, stage,
1754
1786
  promptPath: work.prompt_path, resultPath: work.result_path, ...(contextSha256 ? { contextSha256 } : {}) });
@@ -1867,7 +1899,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1867
1899
  // Descriptor settlement validates exact coordinator custody and claims
1868
1900
  // the fresh hook atomically with result acceptance. Do not consume it
1869
1901
  // through the SDLC lifecycle owner before that settlement.
1870
- return controlledReply(finishMemoryStage(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes,
1902
+ return controlledReply((prepared.memoryStage.flowId === "mb-upgrade" ? finishMemoryUpgradeStage : finishMemoryStage)(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes,
1871
1903
  hookEventId: hookEventId ?? findRecentMatchingHookEvent(context, {
1872
1904
  projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
1873
1905
  matchKey: entityReferenceVariants(runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, stage, projectRoot)),
@@ -2179,6 +2211,13 @@ function dispatchEngine(context, command, parsed, classification, prepared) {
2179
2211
  function dispatchMigration(context, command, parsed, classification, prepared) {
2180
2212
  if (command === "impact")
2181
2213
  return assessMigrationImpact(context, { projectRoot: requiredOption(parsed, "project-root"), ...(optionalOption(parsed, "target-version") ? { targetVersion: optionalOption(parsed, "target-version") } : {}), ...(optionalOption(parsed, "mode") ? { mode: optionalOption(parsed, "mode") } : {}) });
2214
+ if (command === "readiness") {
2215
+ if (!prepared.upgradeStorageScope)
2216
+ throw new AppError("validation", "Upgrade storage scope must be prepared before publication", 2);
2217
+ // Unlike the initial readonly syntax pass, this connection is admitted
2218
+ // prepared-existing. Physical writer checks still precede receipt writes.
2219
+ return { ok: true, ...PreparedUpgradeStorageReadiness.prepare(context, prepared.upgradeStorageScope).apply(context) };
2220
+ }
2182
2221
  if (command === "plan" || command === "report") {
2183
2222
  return planMigration(context, {
2184
2223
  projectRoot: requiredOption(parsed, "project-root"),
@@ -2425,7 +2464,7 @@ function dispatchMemory(context, command, parsed, prepared) {
2425
2464
  const workspaceRoot = requiredOption(parsed, "workspace-root");
2426
2465
  if (wave === "2") {
2427
2466
  const bundle = readFlowRunExecutionBundle(context, work);
2428
- if (!bundle || bundle.descriptor.descriptor.id !== "mb-init" || bundle.execution.snapshot().scope.workspace !== workspaceRoot)
2467
+ if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id) || bundle.execution.snapshot().scope.workspace !== workspaceRoot)
2429
2468
  throw new AppError("memory_repair_scope_invalid", "Repair workspace does not match its frozen memory RUN", 2, { phase: "prepare", effect: "no_effect" });
2430
2469
  }
2431
2470
  if (subcommand === "current-view")
@@ -2455,14 +2494,13 @@ function dispatchMemory(context, command, parsed, prepared) {
2455
2494
  if (error instanceof AppError && (error.code === "memory_stage_result_invalid"
2456
2495
  || error.code === "memory_preflight_handler_invalid" && ["unselected_preflight_source", "proposed_source_sha256_mismatch"].includes(String(error.details.reason))) && error.details.phase === "prepare"
2457
2496
  && error.details.effect === "no_effect" && error.details.recoverable === true) {
2458
- error.details.retry_command = runtimeCommand(context, ["memory", "compose", "register", "--run", requiredOption(parsed, "run"),
2459
- "--project-root", requiredOption(parsed, "project-root"), "--request-key", requiredOption(parsed, "request-key"),
2460
- "--result-file", requiredOption(parsed, "result-file"), "--json", "--progress-jsonl"]);
2461
- error.details.retry_instruction = "Correct only the rejected composition proposal, then execute retry_command in this same Session. No Work graph was registered by this rejected call; do not dispatch children or finish the Stage until registration succeeds.";
2497
+ Object.assign(error.details, compositionProposalRetry(context, parsed));
2462
2498
  }
2463
2499
  throw error;
2464
2500
  }
2465
2501
  }
2502
+ if (command === "upgrade" && subcommand === "publish")
2503
+ return publishMemoryUpgradeFromStage(context, scope());
2466
2504
  if (command === "verify" && subcommand === "checks")
2467
2505
  return runMemoryVerificationChecks(context, scope());
2468
2506
  if (command === "review" && subcommand === "register")
package/dist/cli/setup.js CHANGED
@@ -129,7 +129,8 @@ export async function runSetupCli(args, env, io = { stdout: process.stdout, stde
129
129
  throw error;
130
130
  }
131
131
  }
132
- result = { ...installed, action: route.key === "setup update" ? "setup_update" : "setup_install", observed_cli: observedCli,
132
+ result = { ...installed, request_installation: installed.installation,
133
+ action: route.key === "setup update" ? "setup_update" : "setup_install", observed_cli: observedCli,
133
134
  executor: installed.executor ? { profile_id: installed.executor.id, revision: hybridDigest(installed.executor), readiness: "unknown_requires_qualification" } : { profile_id: null, readiness: "not_configured" },
134
135
  ...readSetupStatus(home) };
135
136
  }
package/dist/cli.js CHANGED
@@ -49,10 +49,22 @@ if (args[0] === "init" || args[0] === "flow" && args[1] === "mb-init") {
49
49
  publishSetup('imports_ready');
50
50
  process.exit(await runMemoryInitCli(args, process.env));
51
51
  }
52
+ if (args[0] === "upgrade" || args[0] === "flow" && args[1] === "mb-upgrade") {
53
+ const { runMemoryUpgradeCli } = await import("./cli/memory-upgrade.js");
54
+ process.exit(await runMemoryUpgradeCli(args, process.env));
55
+ }
52
56
  if (args.slice(0, 3).join(" ") === "runtime storage admit") {
53
57
  const { runMemoryStorageAdmissionCli } = await import("./cli/memory-storage-admission.js");
54
58
  process.exit(runMemoryStorageAdmissionCli(args));
55
59
  }
60
+ if (args.slice(0, 3).join(" ") === "runtime storage qualify") {
61
+ const { runMemoryStorageQualificationCli } = await import("./cli/memory-storage-qualification.js");
62
+ process.exit(runMemoryStorageQualificationCli(args));
63
+ }
64
+ if (args.slice(0, 3).join(" ") === "runtime storage transition") {
65
+ const { runMemoryStorageTransitionCli } = await import("./cli/memory-storage-transition.js");
66
+ process.exit(runMemoryStorageTransitionCli(args));
67
+ }
56
68
  if (args.slice(0, 3).join(" ") === "runtime flow admit") {
57
69
  const { runMemoryFlowAdmissionCli } = await import("./cli/memory-flow-admission.js");
58
70
  process.exit(await runMemoryFlowAdmissionCli(args));
@@ -18,3 +18,11 @@ export const memoryInitHandlerIdentities = Object.freeze({
18
18
  review: Object.freeze({ stage: "review", entryId: "memory-stage-entry", finishId: "memory-stage-finish" }),
19
19
  finish: Object.freeze({ stage: "finish", entryId: "memory-stage-entry", finishId: "memory-stage-finish" })
20
20
  });
21
+ export const memoryUpgradeHandlerIdentities = Object.freeze({
22
+ preflight: Object.freeze({ stage: "preflight", entryId: "memory-upgrade-stage-entry", finishId: "memory-upgrade-stage-finish" }),
23
+ "diff-analysis": Object.freeze({ stage: "diff-analysis", entryId: "memory-upgrade-stage-entry", finishId: "memory-upgrade-stage-finish" }),
24
+ upgrade: Object.freeze({ stage: "upgrade", entryId: "memory-upgrade-stage-entry", finishId: "memory-upgrade-stage-finish" }),
25
+ verify: Object.freeze({ stage: "verify", entryId: "memory-upgrade-stage-entry", finishId: "memory-upgrade-stage-finish" }),
26
+ review: Object.freeze({ stage: "review", entryId: "memory-upgrade-stage-entry", finishId: "memory-upgrade-stage-finish" }),
27
+ finish: Object.freeze({ stage: "finish", entryId: "memory-upgrade-stage-entry", finishId: "memory-upgrade-stage-finish" })
28
+ });
@@ -23,7 +23,7 @@ async function hook(options) {
23
23
  const normalized = { schema_id: "dd-flow/agy-tool-event@1", event_id: identity.event_id, phase, daemon_id: identity.daemon_id, launch_cwd: identity.launch_cwd, conversation_id: payload.conversationId, parent_conversation_id: identity.parent_conversation_id, execution_num: payload.executionNum, step_index: payload.stepIdx, tool: payload.toolCall?.name, input: args, workspace_paths: payload.workspacePaths, transcript_path: payload.transcriptPath, model: payload.modelName, profile: identity.profile, ...(phase === "after" ? { error: payload.error ?? "" } : {}) };
24
24
  await invokeNativeHook({ bin: options["dd-flow-bin"] ?? identity.dd_flow_bin,
25
25
  args: ["agy", "event", "handle", "--project-root", options["project-root"] ?? identity.project_root, "--json"],
26
- payload: normalized, home: options["dd-flow-home"] ?? identity.dd_flow_home, timeoutMs: budget.remaining("admission", 15_000) });
26
+ payload: normalized, home: options["dd-flow-home"] ?? identity.dd_flow_home, timeoutMs: budget.remaining("admission") });
27
27
  }
28
28
  return event === "PreToolUse" ? { decision: "allow" } : {};
29
29
  } catch (error) {
@@ -31,8 +31,8 @@ async function hook(options) {
31
31
  // reads only receipts matching request IDs it actually accepted this turn.
32
32
  const correctable = event === "PreToolUse" && error.code === "lifecycle_shell_syntax_invalid" && error.details?.effect === "no_effect"
33
33
  && error.details?.recoverable === true && error.details?.disposition === "pre_cli_shell_refusal";
34
- try { await writeJsonAtomic(path.join(options["state-dir"], "hook-failures", `${requestId}.json`), { request_id: requestId, observed_at: new Date().toISOString(), code: error.code ?? "agy_hook_rejected", message: error.message,
35
- details: { ...error.details, effect: correctable ? "no_effect" : "unknown", ...(correctable ? { native_pre_cli_refusal: true } : {}) } }); }
34
+ try { await budget.run(() => writeJsonAtomic(path.join(options["state-dir"], "hook-failures", `${requestId}.json`), { request_id: requestId, observed_at: new Date().toISOString(), code: error.code ?? "agy_hook_rejected", message: error.message,
35
+ details: { ...error.details, effect: correctable ? "no_effect" : "unknown", ...(correctable ? { native_pre_cli_refusal: true } : {}) } }, () => budget.remaining("failure_receipt_commit")), "failure_receipt"); }
36
36
  catch (storage) { process.stderr.write(`${JSON.stringify({ code: error.code, receipt_error: storage.message })}\n`); }
37
37
  throw error;
38
38
  }
@@ -12,12 +12,12 @@ async function hook(options) {
12
12
  const budget = hookBudget();
13
13
  const stdin = await readNativeHookInput(process.stdin, budget); const payload = JSON.parse(stdin || "{}"); const identity = await callDaemon(options["state-dir"], "hook.resolve", { sessionId: payload.sessionId ?? payload.session_id, payload }, budget.remaining("identity"));
14
14
  const enriched = { ...payload, _meta: { ...(payload._meta ?? {}), ddGrok: identity } }; const args = ["grok", "event", "handle", "--project-root", options["project-root"], "--json"]; const command = options["dd-flow-bin"];
15
- try { return await invokeNativeHook({ bin: command, args, home: options["dd-flow-home"], payload: enriched, timeoutMs: budget.remaining("admission", 15_000) }); }
15
+ try { return await invokeNativeHook({ bin: command, args, home: options["dd-flow-home"], payload: enriched, timeoutMs: budget.remaining("admission") }); }
16
16
  catch (error) {
17
17
  if (identity.nativeHookBinding) error.details = { ...error.details, native_hook_binding: identity.nativeHookBinding };
18
18
  try {
19
- await writeNativeHookFailure(options["state-dir"], identity.nativeHookBinding, error, hookBudget(1_000, null));
20
- if (identity.nativeHookBinding) await callDaemon(options["state-dir"], "hook.failed", { requestId: identity.nativeHookBinding.request_id }, 1_000);
19
+ await writeNativeHookFailure(options["state-dir"], identity.nativeHookBinding, error, budget);
20
+ if (identity.nativeHookBinding) await callDaemon(options["state-dir"], "hook.failed", { requestId: identity.nativeHookBinding.request_id }, budget.remaining("failure_notification"));
21
21
  } catch (storage) { process.stderr.write(`${JSON.stringify({ code: error.code, receipt_error: storage.message })}\n`); }
22
22
  throw error;
23
23
  }
@@ -37,14 +37,14 @@ async function hook(options) {
37
37
  const identity = await callDaemon(options["state-dir"], "hook.resolve", { sessionId: payload.session_id ?? payload.sessionId, payload }, budget.remaining("identity"));
38
38
  const command = options["dd-flow-bin"]; const args = ["zcode", "hook", "handle", "--project-root", options["project-root"], "--json"];
39
39
 
40
- try { return await invokeNativeHook({ bin: command, args, home: options["dd-flow-home"], timeoutMs: budget.remaining("admission", 15_000), payload: { ...payload, _meta: { ...(payload._meta ?? {}), ddZcode: identity } } }); }
40
+ try { return await invokeNativeHook({ bin: command, args, home: options["dd-flow-home"], timeoutMs: budget.remaining("admission"), payload: { ...payload, _meta: { ...(payload._meta ?? {}), ddZcode: identity } } }); }
41
41
  catch (error) {
42
42
  if (identity.nativeHookBinding) error.details = { ...error.details, native_hook_binding: identity.nativeHookBinding };
43
43
  try {
44
- // The 25s work budget leaves this bounded 2s evidence tail inside the
45
- // qualified 30s native hook limit, even after the work deadline expired.
46
- await writeNativeHookFailure(options["state-dir"], identity.nativeHookBinding, error, hookBudget(1_000, null));
47
- if (identity.nativeHookBinding) await callDaemon(options["state-dir"], "hook.failed", { requestId: identity.nativeHookBinding.request_id }, 1_000);
44
+ // Evidence shares the original deadline; an expired admission cannot
45
+ // start a new independent failure-report window.
46
+ await writeNativeHookFailure(options["state-dir"], identity.nativeHookBinding, error, budget);
47
+ if (identity.nativeHookBinding) await callDaemon(options["state-dir"], "hook.failed", { requestId: identity.nativeHookBinding.request_id }, budget.remaining("failure_notification"));
48
48
  } catch (storage) { process.stderr.write(`${JSON.stringify({ code: error.code, receipt_error: storage.message })}\n`); }
49
49
  throw error;
50
50
  }
@@ -0,0 +1,58 @@
1
+ import { parseLifecycleCommand, commandOption, commandPosition } from "./lifecycle-command.mjs";
2
+ import { entityReferenceVariants } from "./entity-references.mjs";
3
+
4
+ /** Ordered native transcript evidence, never assistant text as a success signal. */
5
+ export function recoveredAgyErrorBoundary(text, error, floor = -1) {
6
+ let rows;
7
+ 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
+ for (const row of rows) {
10
+ if (!Number.isInteger(row.step_index) || row.step_index <= previous) return null;
11
+ previous = row.step_index;
12
+ if (row.step_index <= (floor ?? -1)) continue;
13
+ if (row.type === "USER_INPUT") { lastError = null; boundary = null; invocation = null; continue; }
14
+ if (row.type === "ERROR_MESSAGE") { lastError = row; boundary = null; invocation = null; continue; }
15
+ if (Array.isArray(row.tool_calls)) {
16
+ invocation = null;
17
+ if (row.tool_calls.length === 1 && row.tool_calls[0]?.name === "run_command") {
18
+ let command = row.tool_calls[0]?.args?.CommandLine;
19
+ if (typeof command === "string") {
20
+ try { const unquoted = JSON.parse(command); if (typeof unquoted === "string") command = unquoted; } catch { /* ordinary shell bytes */ }
21
+ const parsed = parseLifecycleCommand(command);
22
+ if ((parsed.kind === "standalone" || parsed.kind === "compound" && parsed.supported === true && parsed.invocations?.length === 1) && ["stage_finish", "work_finish", "stage_pause", "recovery_accept"].includes(parsed.invocation.operation)) invocation = parsed.invocation;
23
+ }
24
+ }
25
+ }
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", "");
28
+ if (!output) continue;
29
+ for (const match of output.matchAll(/^\s*\{/gm)) {
30
+ let receipt;
31
+ const candidate = output.slice(match.index).trim();
32
+ try { receipt = JSON.parse(candidate); } catch {
33
+ // AGY's transcript renderer can unescape control characters inside
34
+ // pause.user_message. Parse only the intact machine header, never the
35
+ // rendered question; the controller must still verify the DB receipt.
36
+ const end = candidate.indexOf(',\n "user_message":');
37
+ if (end < 0) continue;
38
+ try { receipt = JSON.parse(`${candidate.slice(0, end)}}}`); } catch { continue; }
39
+ }
40
+ if (receipt.ok === true && invocation && invocation.operation !== "stage_pause") {
41
+ if (invocation.operation === "recovery_accept" && receipt.accepted === true && typeof receipt.recovery_id === "string") boundary = { receipt: { ok: true, outcome: "completed", lifecycle_operation: "recovery_accept", recovery_id: receipt.recovery_id }, error_step: lastError.step_index, boundary_step: row.step_index };
42
+ const stage = receipt.stage ?? commandOption(invocation, "stage");
43
+ const runId = receipt.run_id;
44
+ const workId = receipt.work_id ?? receipt.work?.work_id ?? commandOption(invocation, "work") ?? (invocation.operation === "work_finish" ? commandPosition(invocation, 0) : null);
45
+ if (invocation.operation === "work_finish" && receipt.status === "completed" && typeof workId === "string") boundary = { receipt: { ok: true, outcome: "completed", work_id: workId, lifecycle_operation: "work_finish" }, error_step: lastError.step_index, boundary_step: row.step_index };
46
+ if (typeof stage === "string" && typeof runId === "string" && (invocation.operation === "work_finish" || entityReferenceVariants(runId).includes(commandPosition(invocation, 0)))) {
47
+ boundary = { receipt: { ok: true, outcome: "completed", run_id: runId, stage, ...(workId ? { work_id: workId } : {}), lifecycle_operation: invocation.operation }, error_step: lastError.step_index, boundary_step: row.step_index };
48
+ }
49
+ }
50
+ if (invocation?.operation === "stage_pause" && commandOption(invocation, "stage") === receipt.stage && receipt.ok === true && receipt.outcome === "paused"
51
+ && [receipt.run_id, receipt.stage, receipt.work_id].every(value => typeof value === "string" && value)
52
+ && (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
+ lifecycle_operation: "stage_pause", pause: { id: receipt.pause.id } }, error_step: lastError.step_index, boundary_step: row.step_index };
54
+ }
55
+ }
56
+ return boundary && lastError?.error === error ? boundary : null;
57
+ }
58
+