@deksden-com/dd-flow-cli 0.9.0-beta.124 → 0.9.0-beta.127

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 (113) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +10 -1
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +4 -3
  6. package/dist/cli/help.js +1 -0
  7. package/dist/cli/hook-ingress.js +1 -1
  8. package/dist/cli/run-cli.js +126 -193
  9. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  10. package/dist/cli/runtime-maintenance-ingress.js +51 -0
  11. package/dist/cli/runtime-process.js +128 -0
  12. package/dist/cli.js +8 -5
  13. package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
  14. package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
  15. package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
  16. package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
  17. package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
  18. package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
  19. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  20. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  21. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  22. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  23. package/dist/harness-runtime/lib/daemon-operations.mjs +60 -13
  24. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +151 -72
  25. package/dist/harness-runtime/lib/dd-agy.mjs +11 -23
  26. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +9 -14
  27. package/dist/harness-runtime/lib/dd-codex.mjs +252 -80
  28. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +14 -19
  29. package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
  30. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +56 -30
  31. package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
  32. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +18 -27
  33. package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
  34. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +56 -53
  35. package/dist/harness-runtime/lib/dd-zcode.mjs +200 -111
  36. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  37. package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
  38. package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
  39. package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
  40. package/dist/harness-runtime/lib/managed-daemon.d.mts +10 -1
  41. package/dist/harness-runtime/lib/managed-daemon.mjs +228 -40
  42. package/dist/harness-runtime/lib/native-children.d.mts +13 -0
  43. package/dist/harness-runtime/lib/native-children.mjs +78 -0
  44. package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
  45. package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
  46. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  47. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  48. package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
  49. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  50. package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
  51. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  52. package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
  53. package/dist/harness-runtime/lib/process-json.mjs +136 -38
  54. package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
  55. package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
  56. package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
  57. package/dist/runtime/context.js +8 -4
  58. package/dist/schemas/run-control-receipt.schema.json +19 -0
  59. package/dist/services/code-checks.js +79 -9
  60. package/dist/services/codex-capacity.js +117 -41
  61. package/dist/services/codex-hook-delivery.js +2 -2
  62. package/dist/services/compatibility-preflight.js +4 -1
  63. package/dist/services/controller-fanout.js +9 -76
  64. package/dist/services/engines.js +70 -20
  65. package/dist/services/eval-snapshots.js +179 -40
  66. package/dist/services/external-work-launch.js +13 -8
  67. package/dist/services/harness-adapter.js +175 -11
  68. package/dist/services/hooks.js +65 -21
  69. package/dist/services/lifecycle-invocations.js +404 -32
  70. package/dist/services/managed-lease-monitor.js +128 -0
  71. package/dist/services/managed-processes.js +103 -47
  72. package/dist/services/merge-server.js +58 -21
  73. package/dist/services/recovery-observation-budget.js +67 -7
  74. package/dist/services/recovery-snapshot-database.js +7 -4
  75. package/dist/services/run-control-receipt.js +1 -1
  76. package/dist/services/run-control-worker.js +177 -118
  77. package/dist/services/run-control.js +99 -8
  78. package/dist/services/run-controller-adapter.js +169 -46
  79. package/dist/services/run-controller-capture.js +14 -2
  80. package/dist/services/run-controller-process.js +11 -4
  81. package/dist/services/run-controller-state.js +68 -7
  82. package/dist/services/run-controller.js +166 -94
  83. package/dist/services/run-fork.js +8 -3
  84. package/dist/services/run-recovery-guard.js +75 -0
  85. package/dist/services/run-recovery-runtime.js +1 -56
  86. package/dist/services/run-recovery.js +17 -13
  87. package/dist/services/runs.js +39 -6
  88. package/dist/services/runtime-budget.js +32 -15
  89. package/dist/services/runtime-scope-control.js +8 -0
  90. package/dist/services/runtime-scope-resume.js +30 -8
  91. package/dist/services/runtime-scope-worker.js +118 -73
  92. package/dist/services/runtime-service.js +6 -7
  93. package/dist/services/scope-writer-cleanup.js +3 -2
  94. package/dist/services/stage-context.js +135 -0
  95. package/dist/services/stage-lifecycle.js +3 -15
  96. package/dist/services/stage-pause.js +3 -2
  97. package/dist/services/usage.js +14 -3
  98. package/dist/services/vnext-code-review.js +10 -9
  99. package/dist/services/vnext-code.js +10 -8
  100. package/dist/services/vnext-contracts.js +10 -1
  101. package/dist/services/vnext-execution-profile.js +75 -2
  102. package/dist/services/vnext-fanout.js +5 -0
  103. package/dist/services/vnext-merge.js +10 -9
  104. package/dist/services/vnext-plan-review.js +21 -17
  105. package/dist/services/vnext-plan.js +9 -7
  106. package/dist/services/vnext-protocolize.js +15 -9
  107. package/dist/services/vnext-specify.js +6 -6
  108. package/dist/services/work-registry.js +34 -4
  109. package/dist/services/workspace-bootstrap.js +14 -3
  110. package/dist/shared/errors.js +11 -6
  111. package/dist/storage/agent-output.js +26 -0
  112. package/dist/storage/database.js +80 -21
  113. package/package.json +7 -4
@@ -8,10 +8,11 @@ import { beginSnapshotImport, completeSnapshotImport } from "../storage/import-g
8
8
  import { foreignKeyTransaction } from "../storage/foreign-key-transaction.js";
9
9
  import { createContext } from "../runtime/context.js";
10
10
  import { AppError } from "../shared/errors.js";
11
- import { resolveProjectRoot, runtimeProtocolDir, runtimeRunDir } from "../storage/paths.js";
11
+ import { canonicalPath, resolveProjectRoot, runtimeProtocolDir, runtimeRunDir } from "../storage/paths.js";
12
12
  import { registerProject, requireProjectByRoot } from "./projects.js";
13
13
  import { freezeRunExecutionRouting, getFlowRunStatus, importFlowRunExecutionProfile, startFlowRun } from "./runs.js";
14
14
  import { loadExecutionRoutingInput, resolveExecutionPolicy } from "./execution-policy.js";
15
+ import { validateRunOperationalDecision } from "./vnext-execution-profile.js";
15
16
  import { readVnextFlowDefinition } from "./vnext-specify.js";
16
17
  import { captureRecoveryDatabase, readRecoveryResourceEvidence } from "./recovery-snapshot-database.js";
17
18
  import { resourceHome } from "./managed-processes.js";
@@ -19,6 +20,7 @@ import { codexHomeSharedEntries } from "./hooks.js";
19
20
  import { prepareOutputFile } from "../cli/input-preparation.js";
20
21
  import { localEnvironmentAllowed, projectServicePathFilter } from "./workspace-files.js";
21
22
  import { cleanupScopeWriters } from "./scope-writer-cleanup.js";
23
+ import { isObservationOperation, nativeInventoryVersion, retainedDaemonOperations } from "./run-control.js";
22
24
  /** Capture the only flow boundary which exists before `stage start --bootstrap` creates a RUN. */
23
25
  export function prepareEvalBootstrapSnapshot(context, input) {
24
26
  const projectRoot = resolveProjectRoot(input.projectRoot);
@@ -75,7 +77,7 @@ function bootstrapSourceVersion(projectRoot) {
75
77
  treeHash(projectRoot, true),
76
78
  treeModesHash(projectRoot, true),
77
79
  git(projectRoot, ["symbolic-ref", "--quiet", "HEAD"]),
78
- gitBytes(projectRoot, ["show-ref", "--head"]).toString("base64"),
80
+ git(projectRoot, ["rev-parse", "HEAD"]),
79
81
  indexEntries(projectRoot).toString("base64")
80
82
  ]);
81
83
  }
@@ -108,7 +110,7 @@ export function createEvalRunSnapshot(context, input) {
108
110
  try {
109
111
  input.boundary?.verify();
110
112
  sourceVersion = input.boundary ? snapshotSourceVersion(context, input) : null;
111
- const result = captureEvalRunSnapshot(context, { ...input, output: temporary });
113
+ const result = captureEvalRunSnapshot(context, { ...input, ...(sourceVersion ? { journalCuts: sourceVersion.journals, observationCuts: sourceVersion.observations } : {}), output: temporary });
112
114
  if (input.boundary && sourceVersion) {
113
115
  Object.assign(result, { consistency: "controller_boundary_writer_barrier@1", boundary_capture: { controller_id: input.boundary.controllerId, operation_id: input.boundary.operationId, boundary_key: input.boundary.boundaryKey, source_sha256: sourceVersion.sha256, observation: input.boundary.observation } });
114
116
  writeJson(path.join(temporary, "snapshot.json"), Object.fromEntries(Object.entries(result).filter(([key]) => key !== "ok" && key !== "snapshot")));
@@ -126,7 +128,7 @@ export function createEvalRunSnapshot(context, input) {
126
128
  if (sourceVersion) {
127
129
  let after;
128
130
  try {
129
- after = snapshotSourceVersion(context, input, sourceVersion.databases);
131
+ after = snapshotSourceVersion(context, input, sourceVersion.databases, sourceVersion.journals);
130
132
  }
131
133
  catch (error) {
132
134
  if (error.code !== "ENOENT")
@@ -145,6 +147,13 @@ export function createEvalRunSnapshot(context, input) {
145
147
  }
146
148
  catch (error) {
147
149
  fs.rmSync(temporary, { recursive: true, force: true });
150
+ // Native journal publication creates its directory before requested.json.
151
+ // Retry a gap during any capture phase, without classifying an incomplete
152
+ // request as read-only. Persistent missing inputs still exhaust the budget.
153
+ if (input.boundary && error.code === "ENOENT") {
154
+ const missing = error.path;
155
+ throw new AppError("snapshot_source_changed", "Snapshot source was not fully published during boundary capture", 1, { components: [missing && path.isAbsolute(missing) && isInside(context.ddFlowHome, missing) ? `runtime/${path.relative(context.ddFlowHome, missing)}` : "source_path_disappeared"] });
156
+ }
148
157
  throw error;
149
158
  }
150
159
  finally {
@@ -204,17 +213,22 @@ function captureEvalRunSnapshot(context, input) {
204
213
  fs.mkdirSync(output, { recursive: true, mode: 0o700 });
205
214
  const resourceDatabase = path.join(context.ddFlowHome, "runtime.sqlite");
206
215
  const databaseFiles = [context.db.path, resourceDatabase].flatMap(file => [file, `${file}-wal`, `${file}-shm`]);
207
- const codexExclusions = codexHomeSnapshotExclusions(context, project.id, input.runId);
216
+ const codexExclusions = [...codexHomeSnapshotExclusions(context, project.id, input.runId), ...input.journalCuts?.keys() ?? [], ...input.observationCuts?.keys() ?? []];
208
217
  if (recovery) {
209
218
  if (typeof status.run.run_root !== "string" || !isInside(physicalPath(context.ddFlowHome), physicalPath(status.run.run_root)) || samePath(physicalPath(context.ddFlowHome), physicalPath(status.run.run_root)))
210
219
  throw new AppError("snapshot_run_scope_invalid", "Recovery requires a RUN artifact root within its runtime", 1);
211
220
  for (const source of new Set([status.run.run_root, runtimeRunDir(context.ddFlowHome, project.id, input.runId)])) {
212
221
  if (fs.existsSync(source))
213
- copyRuntimeTree(source, path.join(output, "runtime", path.relative(context.ddFlowHome, source)), true, codexExclusions);
222
+ copyRuntimeTree(source, path.join(output, "runtime", path.relative(context.ddFlowHome, source)), true, codexExclusions, [], input.observationCuts);
214
223
  }
215
224
  }
216
225
  else
217
- copyRuntimeTree(context.ddFlowHome, path.join(output, "runtime"), false, [...databaseFiles, ...codexExclusions], grokProviderHomes(context.ddFlowHome));
226
+ copyRuntimeTree(context.ddFlowHome, path.join(output, "runtime"), false, [...databaseFiles, ...codexExclusions], grokProviderHomes(context.ddFlowHome), input.observationCuts);
227
+ for (const [file, cut] of input.journalCuts ?? []) {
228
+ const target = path.join(output, "runtime", path.relative(context.ddFlowHome, file));
229
+ fs.mkdirSync(path.dirname(target), { recursive: true });
230
+ fs.writeFileSync(target, cut.payload, { mode: cut.mode });
231
+ }
218
232
  // VACUUM INTO is a consistent SQLite read snapshot, including committed WAL
219
233
  // pages. Copying the live database and its WAL separately is not atomic.
220
234
  const databaseCopy = path.join(output, "runtime", path.relative(context.ddFlowHome, context.db.path));
@@ -275,6 +289,8 @@ function captureEvalRunSnapshot(context, input) {
275
289
  project_sha256: treeHash(path.join(output, "project")),
276
290
  project_payload_policy: "service-files@1",
277
291
  ...(!recovery ? { runtime_payload_policy: "flow-evidence@1" } : {}),
292
+ ...(input.journalCuts ? { journal_cuts: [...input.journalCuts].map(([file, cut]) => ({ path: path.relative(context.ddFlowHome, file), bytes: cut.payload.length, sha256: cut.sha256 })) } : {}),
293
+ ...(input.observationCuts ? { observation_cuts: [...input.observationCuts].map(([file, receipt]) => ({ path: path.relative(context.ddFlowHome, file), receipt })) } : {}),
278
294
  project_git: projectGit,
279
295
  workspace,
280
296
  payload_modes: { project: treeModesHash(path.join(output, "project")), runtime: treeModesHash(path.join(output, "runtime")), ...(workspace.kind === "git_worktree" ? { workspace: treeModesHash(path.join(output, "workspace")) } : {}) },
@@ -325,6 +341,8 @@ export function prepareEvalRunRestore(context, input, preparedManifest) {
325
341
  const recovery = manifest.purpose === "recovery";
326
342
  if (recovery && input.executionRoutingFile !== undefined)
327
343
  throw new AppError("snapshot_recovery_policy_frozen", "Recovery must retain its frozen execution policy", 2);
344
+ if (recovery && input.operationalDecisionFile !== undefined)
345
+ throw new AppError("snapshot_recovery_policy_frozen", "Recovery cannot replace its accepted operational decision", 2);
328
346
  if (recovery && input.recoveryId !== manifest.recovery_id)
329
347
  throw new AppError("snapshot_recovery_requires_explicit_restore", "Recovery snapshots require their explicit --recovery-id", 1, { snapshot });
330
348
  if (!recovery && input.recoveryId)
@@ -340,7 +358,9 @@ export function prepareEvalRunRestore(context, input, preparedManifest) {
340
358
  const sourceRuntime = path.join(snapshot, "runtime");
341
359
  const sourceProject = path.join(snapshot, "project");
342
360
  const routingInput = input.executionRoutingFile !== undefined ? loadExecutionRoutingInput(input.executionRoutingFile) : undefined;
343
- let selectedProfile;
361
+ const acceptedOperational = recovery ? undefined : prepareSnapshotOperationalDecision({ snapshot, runId: manifest.run_id, projectRoot, now: context.now(), ...(input.operationalDecisionFile ? { file: input.operationalDecisionFile } : {}) });
362
+ const operationalDecisionBytes = acceptedOperational?.bytes;
363
+ let selectedProfile = acceptedOperational?.profile;
344
364
  if (routingInput) {
345
365
  // Resolve selected profiles before replacing the caller's fresh runtime.
346
366
  const sourceDb = new DatabaseSync(path.join(sourceRuntime, "db.sqlite"), { readOnly: true });
@@ -350,7 +370,7 @@ export function prepareEvalRunRestore(context, input, preparedManifest) {
350
370
  if (!index?.execution_profile || !["vnext_specify", "vnext_protocolize"].includes(index.flow_kind))
351
371
  throw new AppError("execution_profile_not_frozen", "Stage-entry routing requires a vNext execution snapshot", 1);
352
372
  assertStageEntry(index.stage_runs, manifest.stage_entry);
353
- selectedProfile = freezeRunExecutionRouting(context.ddFlowHome, index.execution_profile, routingInput);
373
+ selectedProfile = freezeRunExecutionRouting(context.ddFlowHome, selectedProfile ?? index.execution_profile, routingInput);
354
374
  for (const row of sourceDb.prepare("SELECT payload_json FROM works WHERE run_id = ? AND status = 'created'").all(manifest.run_id)) {
355
375
  const payload = row.payload_json ? JSON.parse(row.payload_json) : null;
356
376
  if (payload?.execution_override !== undefined)
@@ -368,7 +388,34 @@ export function prepareEvalRunRestore(context, input, preparedManifest) {
368
388
  sourceDb.close();
369
389
  }
370
390
  }
371
- return { snapshot, manifest, recovery, projectRoot, sourceRuntime, sourceProject, routingInput, selectedProfile };
391
+ return { snapshot, manifest, recovery, projectRoot, sourceRuntime, sourceProject, routingInput, selectedProfile, operationalDecisionBytes };
392
+ }
393
+ /** Shared no-effect scope preparation for direct restore and run fork. */
394
+ export function prepareSnapshotOperationalDecision(input) {
395
+ const sourceDb = new DatabaseSync(path.join(input.snapshot, "runtime", "db.sqlite"), { readOnly: true });
396
+ try {
397
+ const row = sourceDb.prepare("SELECT index_json FROM runs WHERE id = ?").get(input.runId);
398
+ const profile = row ? JSON.parse(row.index_json).execution_profile : undefined;
399
+ if (profile?.operational_decision && !input.file)
400
+ throw new AppError("snapshot_operational_acceptance_required", "A new stage-entry execution needs its own accepted operational decision", 2);
401
+ if (!input.file)
402
+ return undefined;
403
+ if (!profile)
404
+ throw new AppError("execution_profile_not_frozen", "Operational stage-entry decision needs a frozen vNext profile", 2);
405
+ const bytes = fs.readFileSync(input.file);
406
+ const decision = validateRunOperationalDecision(JSON.parse(bytes.toString("utf8")), profile.settings);
407
+ if (canonicalPath(decision.scope.project_root, false) !== canonicalPath(input.projectRoot, false))
408
+ throw new AppError("operational_decision_scope_mismatch", "Imported decision must name the new project root", 2);
409
+ const sourceProject = path.join(input.snapshot, "project"), policyFile = path.resolve(sourceProject, decision.policy.path);
410
+ if (!isInside(sourceProject, policyFile) || !fs.existsSync(policyFile) || !isInside(fs.realpathSync(sourceProject), fs.realpathSync(policyFile)) || fileHash(policyFile) !== decision.policy.sha256)
411
+ throw new AppError("operational_decision_policy_changed", "Imported decision policy must match the owned frozen project source", 2);
412
+ if (decision.exception?.expires_at && Date.parse(decision.exception.expires_at) <= Date.parse(input.now))
413
+ throw new AppError("operational_decision_expired", "Imported operational decision has explicitly expired", 2);
414
+ return { profile: { ...profile, operational_decision: decision }, bytes };
415
+ }
416
+ finally {
417
+ sourceDb.close();
418
+ }
372
419
  }
373
420
  function assertRunRestoreTargets(context, input, snapshot, projectRoot, manifest) {
374
421
  assertRestoreTargetSeparation(snapshot, [projectRoot, context.ddFlowHome]);
@@ -392,6 +439,8 @@ function assertRunRestoreTargets(context, input, snapshot, projectRoot, manifest
392
439
  }
393
440
  export function restoreEvalRunSnapshot(context, input, prepared = prepareEvalRunRestore(context, input), prearmedImportId) {
394
441
  const { snapshot, manifest, recovery, projectRoot, sourceRuntime, sourceProject, routingInput, selectedProfile } = prepared;
442
+ if (input.operationalDecisionFile && (!prepared.operationalDecisionBytes || !fs.readFileSync(input.operationalDecisionFile).equals(prepared.operationalDecisionBytes)))
443
+ throw new AppError("operational_decision_changed", "Accepted operational decision changed after import preparation", 2);
395
444
  assertRunRestoreTargets(context, input, snapshot, projectRoot, manifest);
396
445
  const sourceManifestSha256 = fileHash(path.join(snapshot, "snapshot.json"));
397
446
  const importId = prearmedImportId ?? beginSnapshotImport({ home: context.ddFlowHome, projectRoot,
@@ -414,18 +463,36 @@ export function restoreEvalRunSnapshot(context, input, prepared = prepareEvalRun
414
463
  const publicationRun = restored.db.get("SELECT project_id, run_root FROM runs WHERE id = ?", [manifest.run_id]);
415
464
  if (!originalRunRoot || !publicationRun?.run_root || !isInside(manifest.dd_flow_home, originalRunRoot) || !isInside(context.ddFlowHome, publicationRun.run_root))
416
465
  throw new AppError("snapshot_invalid", "Restored publication roots are not bound to their runtime", 1);
417
- restored.db.run("INSERT INTO audit_events (project_id, event_type, forced, payload_json, created_at) VALUES (?, 'flow_run.publication_relocation', 0, ?, ?)", [publicationRun.project_id, JSON.stringify({ run_id: manifest.run_id, from: originalRunRoot, to: publicationRun.run_root, source_manifest_sha256: sourceManifestSha256, import_id: importId }), restored.now()]);
466
+ const pathMapping = [{ from: manifest.dd_flow_home, to: context.ddFlowHome }, { from: sourceWorkspaceRoot(manifest), to: workspaceRoot }, { from: manifest.project_root, to: projectRoot }];
467
+ restored.db.run("INSERT INTO audit_events (project_id, event_type, forced, payload_json, created_at) VALUES (?, 'flow_run.publication_relocation', 0, ?, ?)", [publicationRun.project_id, JSON.stringify({ run_id: manifest.run_id, from: originalRunRoot, to: publicationRun.run_root, source_manifest_sha256: sourceManifestSha256, import_id: importId, path_mapping: pathMapping }), restored.now()]);
418
468
  registerProject(restored, { root: projectRoot });
419
469
  requireProjectByRoot(restored, projectRoot);
420
- if (selectedProfile && routingInput && runtimeLineage) {
470
+ if (selectedProfile && runtimeLineage) {
421
471
  const run = restored.db.get("SELECT run_root FROM runs WHERE id = ?", [manifest.run_id]);
422
- const artifact = path.join(run.run_root, selectedProfile.routing_input.artifact);
423
- if (fs.existsSync(artifact)) {
424
- if (!fs.readFileSync(artifact).equals(routingInput.bytes))
425
- throw new AppError("snapshot_checksum_mismatch", "Existing routing input does not match its checksum", 1);
472
+ if (routingInput) {
473
+ const artifact = path.join(run.run_root, selectedProfile.routing_input.artifact);
474
+ if (fs.existsSync(artifact)) {
475
+ if (!fs.readFileSync(artifact).equals(routingInput.bytes))
476
+ throw new AppError("snapshot_checksum_mismatch", "Existing routing input does not match its checksum", 1);
477
+ }
478
+ else
479
+ fs.writeFileSync(artifact, routingInput.bytes, { flag: "wx", mode: 0o600 });
480
+ }
481
+ if (input.operationalDecisionFile) {
482
+ if (!prepared.operationalDecisionBytes || !fs.readFileSync(input.operationalDecisionFile).equals(prepared.operationalDecisionBytes))
483
+ throw new AppError("operational_decision_changed", "Accepted operational decision changed before import publication", 2);
484
+ const binding = { project_id: publicationRun.project_id, run_id: manifest.run_id, ...selectedProfile.operational_decision.scope };
485
+ const policyBytes = fs.readFileSync(path.resolve(projectRoot, selectedProfile.operational_decision.policy.path));
486
+ const policySource = { path: `operational-policy.${selectedProfile.operational_decision.policy.sha256}.source.md`, sha256: crypto.createHash("sha256").update(policyBytes).digest("hex") };
487
+ if (policySource.sha256 !== selectedProfile.operational_decision.policy.sha256)
488
+ throw new AppError("operational_decision_policy_changed", "Imported policy changed before binding", 2);
489
+ if (!fs.existsSync(path.join(run.run_root, policySource.path)))
490
+ fs.writeFileSync(path.join(run.run_root, policySource.path), policyBytes, { flag: "wx" });
491
+ const bytes = `${JSON.stringify({ schema_id: "dd-flow/run-operational-contract@1", binding, settings: { merge_delivery: selectedProfile.settings.merge_delivery, merge_cleanup: selectedProfile.settings.merge_cleanup }, decision: selectedProfile.operational_decision, policy_source: policySource }, null, 2)}\n`;
492
+ const artifact = `operational-contract.${crypto.createHash("sha256").update(bytes).digest("hex")}.json`;
493
+ fs.writeFileSync(path.join(run.run_root, artifact), bytes, { flag: "wx" });
494
+ selectedProfile.operational_contract = { path: artifact, sha256: crypto.createHash("sha256").update(bytes).digest("hex"), policy_source: policySource, binding };
426
495
  }
427
- else
428
- fs.writeFileSync(artifact, routingInput.bytes, { flag: "wx", mode: 0o600 });
429
496
  importFlowRunExecutionProfile(restored, { projectRoot, runId: manifest.run_id, stage: manifest.stage_entry, lineageFile: runtimeLineage, profile: selectedProfile });
430
497
  }
431
498
  const relocation = recovery ? {
@@ -637,13 +704,29 @@ function rewriteOperationalRouteReceipts(root, oldRoot, newRoot, oldWorkspace, n
637
704
  };
638
705
  visit(root);
639
706
  }
640
- function rewriteDatabaseColumn(context, table, column, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome) { for (const row of context.db.all(`SELECT rowid AS rid, ${column} AS value FROM ${table} WHERE ${column} IS NOT NULL`)) {
641
- if (!row.value)
642
- continue;
643
- const value = column.endsWith("_json") || column === "index_json" ? JSON.stringify(rebaseStructuredValue(JSON.parse(row.value), oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome)) : rebasePathString(row.value, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome);
644
- if (value !== row.value)
645
- context.db.run(`UPDATE ${table} SET ${column} = ? WHERE rowid = ?`, [value, row.rid]);
646
- } }
707
+ function rewriteDatabaseColumn(context, table, column, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome) {
708
+ for (const row of context.db.all(`SELECT rowid AS rid, ${column} AS value FROM ${table} WHERE ${column} IS NOT NULL`)) {
709
+ if (!row.value)
710
+ continue;
711
+ let value;
712
+ if (column.endsWith("_json") || column === "index_json") {
713
+ const original = JSON.parse(row.value);
714
+ const mapped = rebaseStructuredValue(original, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome);
715
+ if (table === "runs" && column === "index_json" && original.execution_profile) {
716
+ // Accepted source/binding are logical provenance, not mutable physical locators.
717
+ if (original.execution_profile.operational_decision)
718
+ mapped.execution_profile.operational_decision = original.execution_profile.operational_decision;
719
+ if (original.execution_profile.operational_contract)
720
+ mapped.execution_profile.operational_contract = original.execution_profile.operational_contract;
721
+ }
722
+ value = JSON.stringify(mapped);
723
+ }
724
+ else
725
+ value = rebasePathString(row.value, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome);
726
+ if (value !== row.value)
727
+ context.db.run(`UPDATE ${table} SET ${column} = ? WHERE rowid = ?`, [value, row.rid]);
728
+ }
729
+ }
647
730
  function rebaseStructuredValue(value, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome) { if (typeof value === "string")
648
731
  return rebasePathString(value, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome); if (Array.isArray(value))
649
732
  return value.map((item) => rebaseStructuredValue(item, oldRoot, newRoot, oldWorkspace, newWorkspace, oldHome, newHome)); if (value && typeof value === "object")
@@ -720,7 +803,7 @@ function prepareSnapshotProjectGit(projectRoot) {
720
803
  function snapshotProjectGit(projectRoot, output) {
721
804
  const { branch, head } = prepareSnapshotProjectGit(projectRoot);
722
805
  const bundle = path.join(output, "project.bundle");
723
- runGit(projectRoot, ["bundle", "create", bundle, "--all"]);
806
+ runGit(projectRoot, ["bundle", "create", bundle, `refs/heads/${branch}`]);
724
807
  return { branch, head, bundle_sha256: fileHash(bundle), ...snapshotIndex(projectRoot, output, "project") };
725
808
  }
726
809
  function restoreProjectGit(snapshot, source, projectRoot) {
@@ -910,6 +993,17 @@ function grokProviderHomes(runtimeRoot) {
910
993
  visit(runtimeRoot);
911
994
  return homes;
912
995
  }
996
+ function nativeDaemonFile(file) {
997
+ if (!fs.lstatSync(file, { throwIfNoEntry: false })?.isFile())
998
+ return false;
999
+ try {
1000
+ const state = JSON.parse(fs.readFileSync(file, "utf8"));
1001
+ return typeof state.daemon_id === "string" && /^dd-(codex|zcode|grok|agy|opencode|droid)\/daemon-state@1$/.test(String(state.schema_id));
1002
+ }
1003
+ catch {
1004
+ return false;
1005
+ } // Unrecognized files retain full-byte protection.
1006
+ }
913
1007
  function sqliteFile(file) {
914
1008
  if (!fs.existsSync(file) || !fs.statSync(file).isFile())
915
1009
  return false;
@@ -943,7 +1037,7 @@ function sqliteValue(value) {
943
1037
  * This intentionally applies to the small set of managed SQLite files, not
944
1038
  * to arbitrary provider databases.
945
1039
  */
946
- function sqliteSemanticFingerprint(db, resourceRegistry = false) {
1040
+ function sqliteSemanticFingerprint(db) {
947
1041
  db.exec("PRAGMA busy_timeout = 4000");
948
1042
  db.exec("BEGIN");
949
1043
  try {
@@ -953,9 +1047,8 @@ function sqliteSemanticFingerprint(db, resourceRegistry = false) {
953
1047
  .sort((left, right) => Number(left.cid ?? 0) - Number(right.cid ?? 0))
954
1048
  .map(column => ({ name: String(column.name ?? ""), type: String(column.type ?? ""), notnull: Number(column.notnull ?? 0), default: sqliteValue(column.dflt_value ?? null), pk: Number(column.pk ?? 0) }));
955
1049
  const names = columns.map(column => column.name);
956
- const stableNames = resourceRegistry && row.name === "managed_processes" ? names.filter(name => name !== "lease_expires_at" && name !== "updated_at") : names;
957
1050
  const rows = db.prepare(`SELECT * FROM ${sqliteIdentifier(row.name)}`).all()
958
- .map(record => stableNames.map(name => [name, sqliteValue(record[name])]))
1051
+ .map(record => names.map(name => [name, sqliteValue(record[name])]))
959
1052
  .sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
960
1053
  return { name: row.name, columns, rows };
961
1054
  });
@@ -983,8 +1076,9 @@ function runtimeSnapshotKind(file, excluded, providerHomes = []) {
983
1076
  /** Productive writers are fenced separately. Compare exactly the copied
984
1077
  * source set; SQLite serialization includes committed WAL pages without
985
1078
  * mistaking a physical checkpoint for new logical content. */
986
- function snapshotSourceVersion(context, input, retainedDatabases) {
1079
+ function snapshotSourceVersion(context, input, retainedDatabases, retainedJournals) {
987
1080
  const databases = retainedDatabases ?? new Map();
1081
+ const journals = retainedJournals ?? new Map();
988
1082
  const projectRoot = resolveProjectRoot(input.projectRoot);
989
1083
  const project = requireProjectByRoot(context, projectRoot);
990
1084
  const run = context.db.get("SELECT workspace_root FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
@@ -993,34 +1087,62 @@ function snapshotSourceVersion(context, input, retainedDatabases) {
993
1087
  const entries = new Map();
994
1088
  const excluded = codexHomeSnapshotExclusions(context, project.id, input.runId);
995
1089
  const providerHomes = input.recoveryId === undefined ? grokProviderHomes(context.ddFlowHome) : [];
1090
+ const observations = new Map();
1091
+ const operationRoots = new Set();
996
1092
  const visit = (directory) => {
1093
+ const daemon = path.join(directory, "daemon.json");
1094
+ const native = nativeDaemonFile(daemon);
1095
+ if (native) {
1096
+ operationRoots.add(path.join(directory, "operations"));
1097
+ for (const operation of retainedDaemonOperations(directory))
1098
+ if (isObservationOperation(operation.operation))
1099
+ observations.set(path.join(directory, "operations", crypto.createHash("sha256").update(String(operation.operation_id)).digest("hex")), operation);
1100
+ }
997
1101
  for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
998
1102
  const file = path.join(directory, entry.name);
1103
+ // These diagnostic cuts are retained, but their creation cannot
1104
+ // invalidate a productive boundary. Unknown operations remain protected.
1105
+ if (observations.has(file))
1106
+ continue;
999
1107
  const kind = runtimeSnapshotKind(file, excluded, providerHomes);
1000
1108
  if (kind === "excluded" || kind === "sidecar")
1001
1109
  continue;
1002
1110
  const key = `runtime/${path.relative(context.ddFlowHome, file)}`;
1003
1111
  const stat = fs.lstatSync(file);
1004
1112
  if (stat.isDirectory()) {
1005
- entries.set(key, "directory");
1113
+ if (!operationRoots.has(file))
1114
+ entries.set(key, "directory");
1006
1115
  visit(file);
1007
1116
  }
1008
1117
  else if (stat.isSymbolicLink())
1009
1118
  entries.set(key, `link:${fs.readlinkSync(file)}`);
1119
+ else if (native && file === daemon)
1120
+ entries.set(key, `daemon:${nativeInventoryVersion(directory)}:${stat.mode & 0o111}`);
1010
1121
  else if (kind === "sqlite") {
1122
+ // Their productive identities are checked by boundary.verify(). A
1123
+ // consistent backup is evidence, not a demand for global DB silence.
1124
+ if (file === context.db.path || file === path.join(resourceHome(context), "runtime.sqlite")) {
1125
+ entries.set(key, `managed-sqlite:${stat.mode & 0o111}`);
1126
+ continue;
1127
+ }
1011
1128
  let db = databases.get(file);
1012
1129
  if (!db) {
1013
1130
  db = new DatabaseSync(file, { readOnly: true });
1014
1131
  databases.set(file, db);
1015
1132
  }
1016
- // Resource heartbeats are copied consistently but are not productive
1017
- // RUN writers; owner identity is fenced by boundary.verify().
1018
- if (file === path.join(resourceHome(context), "runtime.sqlite")) {
1019
- entries.set(key, `resource-sqlite:${sqliteSemanticFingerprint(db, true)}:${stat.mode & 0o111}`);
1020
- continue;
1021
- }
1022
1133
  entries.set(key, `sqlite:${sqliteSemanticFingerprint(db)}:${stat.mode & 0o111}`);
1023
1134
  }
1135
+ else if (stat.isFile() && ["adapter.events.jsonl", "events.jsonl", "model-observations.jsonl", "timeline.jsonl", "daemon.log", "controller.log"].includes(path.basename(file))) {
1136
+ const bytes = fs.readFileSync(file);
1137
+ let cut = journals.get(file);
1138
+ if (!cut && !retainedJournals) {
1139
+ const payload = bytes.subarray(0, bytes.lastIndexOf(10) + 1);
1140
+ cut = { payload, dev: stat.dev, ino: stat.ino, mode: stat.mode, sha256: crypto.createHash("sha256").update(payload).digest("hex") };
1141
+ journals.set(file, cut);
1142
+ }
1143
+ const valid = cut && cut.dev === stat.dev && cut.ino === stat.ino && bytes.length >= cut.payload.length && bytes.subarray(0, cut.payload.length).equals(cut.payload);
1144
+ entries.set(key, valid && cut ? `journal:${cut.sha256}:${cut.payload.length}:${stat.mode & 0o111}` : "journal:changed");
1145
+ }
1024
1146
  else if (stat.isFile())
1025
1147
  entries.set(key, `file:${crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex")}:${stat.mode & 0o111}`);
1026
1148
  }
@@ -1029,9 +1151,9 @@ function snapshotSourceVersion(context, input, retainedDatabases) {
1029
1151
  visit(context.ddFlowHome);
1030
1152
  for (const root of new Set([projectRoot, run.workspace_root]))
1031
1153
  entries.set(`source/${root}`, JSON.stringify([
1032
- treeHash(root, true), treeModesHash(root, true), git(root, ["symbolic-ref", "--quiet", "HEAD"]), gitBytes(root, ["show-ref", "--head"]).toString("base64"), indexEntries(root).toString("base64")
1154
+ treeHash(root, true), treeModesHash(root, true), git(root, ["symbolic-ref", "--quiet", "HEAD"]), git(root, ["rev-parse", "HEAD"]), indexEntries(root).toString("base64")
1033
1155
  ]));
1034
- return { entries, databases, sha256: crypto.createHash("sha256").update(JSON.stringify([...entries].sort(([a], [b]) => a.localeCompare(b)))).digest("hex") };
1156
+ return { entries, databases, journals, observations, sha256: crypto.createHash("sha256").update(JSON.stringify([...entries].sort(([a], [b]) => a.localeCompare(b)))).digest("hex") };
1035
1157
  }
1036
1158
  catch (error) {
1037
1159
  if (!retainedDatabases)
@@ -1083,7 +1205,7 @@ function codexHomeSnapshotExclusions(context, projectId, runId) {
1083
1205
  return excluded;
1084
1206
  }
1085
1207
  /** Copy a dedicated home or scoped recovery tree, preserving SQLite WAL commits. */
1086
- function copyRuntimeTree(source, destination, scoped = true, excluded = [], providerHomes = []) {
1208
+ function copyRuntimeTree(source, destination, scoped = true, excluded = [], providerHomes = [], observationCuts) {
1087
1209
  if (fs.lstatSync(source).isSymbolicLink())
1088
1210
  throw new AppError("snapshot_runtime_symlink_outside_scope", "Recovery artifact roots must not be symbolic links", 1, { path: source });
1089
1211
  const root = physicalPath(source);
@@ -1094,6 +1216,23 @@ function copyRuntimeTree(source, destination, scoped = true, excluded = [], prov
1094
1216
  fs.cpSync(source, destination, {
1095
1217
  recursive: true, verbatimSymlinks: true,
1096
1218
  filter: file => {
1219
+ if (observationCuts && path.basename(path.dirname(file)) === "operations" && fs.lstatSync(file).isDirectory() && nativeDaemonFile(path.join(path.dirname(path.dirname(file)), "daemon.json"))) {
1220
+ let operations;
1221
+ try {
1222
+ operations = retainedDaemonOperations(path.dirname(path.dirname(file)));
1223
+ }
1224
+ catch (error) {
1225
+ if (error.code !== "ENOENT")
1226
+ throw error;
1227
+ throw new AppError("snapshot_source_changed", "Native operation was not fully published during capture", 1, { components: ["native_operation_publication"] });
1228
+ }
1229
+ const operation = operations.find(row => crypto.createHash("sha256").update(String(row.operation_id)).digest("hex") === path.basename(file));
1230
+ if (operation && isObservationOperation(operation.operation)) {
1231
+ if (!observationCuts.has(file))
1232
+ observationCuts.set(file, operation);
1233
+ return false;
1234
+ }
1235
+ }
1097
1236
  const kind = runtimeSnapshotKind(file, excluded, providerHomes);
1098
1237
  if (kind === "excluded" || kind === "sidecar")
1099
1238
  return false;
@@ -93,10 +93,16 @@ export async function launchExternalWork(context, input) {
93
93
  const common = ["--dd-flow-bin", flowExecutable, "--state-dir", root, "--journal", journal, "--cwd", isolatedWorkspace ?? run.workspace_root, "--provider", resolved.worker.provider, "--model", resolved.worker.model, "--reasoning", resolved.worker.reasoning, "--mode", resolved.worker.mode, "--permission", resolved.worker.permission, "--project-root", run.project_root, "--dd-flow-home", context.ddFlowHome, ...harnessRuntimeArguments(context.ddFlowHome, runtimeHarness(resolved.worker)), "--json"];
94
94
  const evidence = (value) => fs.appendFileSync(journal, `${JSON.stringify({ type: "adapter_call", at: context.now(), ...value })}\n`);
95
95
  try {
96
- const call = (args, timeoutMs, id = `${operationId}:${args[0]}.${args[1]}`) => runHarnessAdapter({ executable, args, env: runtimeContext.env, operationId: id, runtimeOwner, ...(timeoutMs ? { timeoutMs } : {}), ...(input.signal ? { signal: input.signal } : {}), ...(input.progress ? { progress: input.progress } : {}), onEvidence: evidence });
96
+ const call = (args, timeoutMs, id = `${operationId}:${args[0]}.${args[1]}`) => runHarnessAdapter({ executable, args, harness: runtimeHarness(resolved.worker), env: runtimeContext.env, operationId: id, runtimeOwner, ...(timeoutMs !== undefined ? { timeoutMs } : {}), ...(input.signal ? { signal: input.signal } : {}), ...(input.progress ? { progress: input.progress } : {}), onEvidence: evidence, onNativeOutcome: value => {
97
+ if (args[0] !== "session" || args[1] !== "create")
98
+ return;
99
+ const nativeId = adapterSessionId(value);
100
+ if (nativeId)
101
+ context.db.run("UPDATE work_launches SET provider_session_id = ?, adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ?", [nativeId, JSON.stringify({ create: value, settlement_pending: true }), context.now(), operationId]);
102
+ } });
97
103
  await call(["daemon", "start", ...common]);
98
104
  assertRunMutationAllowed(context, work.project_id, work.run_id, generation);
99
- const created = await call(["session", "create", ...common], runtimeHarness(resolved.worker) === "zcode-acp" ? 180_000 : undefined);
105
+ const created = await call(["session", "create", ...common]);
100
106
  const sessionId = adapterSessionId(created);
101
107
  if (!sessionId)
102
108
  throw new AppError("harness_adapter_invalid", "Harness adapter did not return a typed Session id", 1, { work_id: work.work_id, harness: runtimeHarness(resolved.worker) });
@@ -111,11 +117,10 @@ export async function launchExternalWork(context, input) {
111
117
  const promptFile = path.join(root, "launch.md");
112
118
  fs.writeFileSync(promptFile, `Start the assigned Work now. Your first tool call must be this exact standalone command:\n\n${workStartCommand(runtimeContext, work)}\n\nUse the packet returned by that command. Do not claim another Work or run this command more than once.\n\n${lifecycleRetryInstruction}${isolatedWorkspace ? `\n\nThe review copy has no .git. Read the captured status/diff or gitless note in ${path.join(isolatedWorkspace, ".dd-flow", "review-evidence")}; do not run Git inside the copy.\n` : "\n"}`);
113
119
  const promptOperationId = `${operationId}:session.prompt`;
114
- const promptDeadline = Date.now() + 45 * 60_000;
115
- context.db.run("UPDATE work_launches SET adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'launching'", [JSON.stringify({ create: created, prompt_deadline: promptDeadline }), context.now(), operationId]);
120
+ context.db.run("UPDATE work_launches SET adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'launching'", [JSON.stringify({ create: created, progress_policy: "native-inactivity@1" }), context.now(), operationId]);
116
121
  let prompted;
117
122
  try {
118
- prompted = await call(["session", "prompt", "--session-id", sessionId, "--prompt-file", promptFile, ...common], 45 * 60_000);
123
+ prompted = await call(["session", "prompt", "--session-id", sessionId, "--prompt-file", promptFile, ...common], null);
119
124
  }
120
125
  catch (error) {
121
126
  if (runtimeHarness(resolved.worker) !== "codex-desktop")
@@ -124,12 +129,12 @@ export async function launchExternalWork(context, input) {
124
129
  const capacityIntents = retained?.adapter_receipt_json ? JSON.parse(retained.adapter_receipt_json).capacity_intents ?? [] : [];
125
130
  const capacityFailures = retained?.adapter_receipt_json ? JSON.parse(retained.adapter_receipt_json).capacity_failures ?? [] : [];
126
131
  const retainCapacity = () => {
127
- const changed = context.db.run("UPDATE work_launches SET adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'launching'", [JSON.stringify({ create: created, prompt_deadline: promptDeadline, capacity_intents: capacityIntents, capacity_failures: capacityFailures }), context.now(), operationId]);
132
+ const changed = context.db.run("UPDATE work_launches SET adapter_receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'launching'", [JSON.stringify({ create: created, progress_policy: "native-inactivity@1", capacity_intents: capacityIntents, capacity_failures: capacityFailures }), context.now(), operationId]);
128
133
  if (changed.changes !== 1)
129
134
  throw new AppError("capacity_continuation_blocked", "External Work launch owner changed", 1);
130
135
  };
131
136
  const resumed = await continueCodexCapacity({ error, sessionId, operationId: promptOperationId, allowSettledDescendants: true,
132
- ...(input.signal ? { signal: input.signal } : {}), deadline: promptDeadline,
137
+ ...(input.signal ? { signal: input.signal } : {}),
133
138
  continuationText: `Continue only Work ${work.work_id} in RUN ${work.run_id}, Stage ${stage ?? "current"}, in the same Session. Inspect its current durable state and the retained Work packet. Never repeat Work start or a completed tool, accepted answer, check or lifecycle command. Use only the current exact issued successor and finish at the original Work boundary.`,
134
139
  retainedIntents: () => capacityIntents,
135
140
  retainedFailures: () => capacityFailures,
@@ -160,7 +165,7 @@ export async function launchExternalWork(context, input) {
160
165
  }
161
166
  else
162
167
  fs.writeFileSync(file, text, { flag: "wx", mode: 0o600 });
163
- return call(["session", "prompt", "--session-id", sessionId, "--prompt-file", file, ...common], Math.max(1, promptDeadline - Date.now()), id);
168
+ return call(["session", "prompt", "--session-id", sessionId, "--prompt-file", file, ...common], null, id);
164
169
  } });
165
170
  if (!resumed)
166
171
  throw error;