@sema-agent/core 7.9.2 → 7.11.0

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 (86) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/agents/child-model-seat.d.ts +71 -14
  3. package/dist/agents/child-model-seat.js +23 -7
  4. package/dist/agents/subagent.d.ts +2 -2
  5. package/dist/agents/subagent.js +8 -5
  6. package/dist/agents/teacher.js +2 -2
  7. package/dist/core/ask-unresolvable-notice.d.ts +52 -0
  8. package/dist/core/ask-unresolvable-notice.js +25 -0
  9. package/dist/core/auto-mode-defaults.d.ts +15 -3
  10. package/dist/core/auto-mode-defaults.js +1 -0
  11. package/dist/core/auto-mode.d.ts +86 -23
  12. package/dist/core/auto-mode.js +43 -12
  13. package/dist/core/checkpoint-store.d.ts +14 -0
  14. package/dist/core/checkpoint-store.js +2 -1
  15. package/dist/core/engine-notice.d.ts +28 -7
  16. package/dist/core/gate-fold.js +1 -0
  17. package/dist/core/gate-lanes.d.ts +6 -1
  18. package/dist/core/gate-lanes.js +60 -18
  19. package/dist/core/governance-codes.d.ts +1 -1
  20. package/dist/core/governance-codes.js +4 -0
  21. package/dist/core/hooks.d.ts +37 -1
  22. package/dist/core/hooks.js +2 -0
  23. package/dist/core/permission-rule-model.d.ts +53 -16
  24. package/dist/core/permission-rule-model.js +59 -21
  25. package/dist/core/permission-rules.d.ts +6 -4
  26. package/dist/core/permission-rules.js +14 -14
  27. package/dist/core/persisted-rule-arms.js +4 -3
  28. package/dist/core/read-only-shell-table.d.ts +87 -0
  29. package/dist/core/read-only-shell-table.js +485 -0
  30. package/dist/core/read-only-shell.d.ts +42 -0
  31. package/dist/core/read-only-shell.js +316 -0
  32. package/dist/core/roles.d.ts +3 -2
  33. package/dist/core/runner/contracts.d.ts +26 -2
  34. package/dist/core/runner/denial-limit-arms.d.ts +14 -3
  35. package/dist/core/runner/denial-limit-arms.js +15 -5
  36. package/dist/core/runner/gate-exit.d.ts +5 -0
  37. package/dist/core/runner/permission-rule-lanes.d.ts +7 -1
  38. package/dist/core/runner/permission-rule-lanes.js +9 -3
  39. package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
  40. package/dist/core/runner/prepare-caps-and-workflow.js +35 -14
  41. package/dist/core/runner/prepare-gate-stations.d.ts +3 -2
  42. package/dist/core/runner/prepare-gate-stations.js +12 -0
  43. package/dist/core/runner/prepare-policy-chain.js +7 -6
  44. package/dist/core/runner/prepare-task.js +1 -1
  45. package/dist/core/runner/prepare-turn-wiring.js +1 -1
  46. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
  47. package/dist/core/runner/prepare-wiring-manifest.js +8 -1
  48. package/dist/core/runner/runtask.d.ts +33 -32
  49. package/dist/core/runner/runtask.js +63 -33
  50. package/dist/core/runner-deps.d.ts +9 -2
  51. package/dist/core/shell-lexer.d.ts +18 -0
  52. package/dist/core/shell-lexer.js +17 -10
  53. package/dist/core/shell-wrapper-table.js +8 -5
  54. package/dist/core/swappable-deps.d.ts +90 -0
  55. package/dist/core/swappable-deps.js +55 -0
  56. package/dist/core/tool-policy.d.ts +30 -1
  57. package/dist/core/tool-policy.js +6 -2
  58. package/dist/core/tools.d.ts +28 -7
  59. package/dist/core/tools.js +44 -4
  60. package/dist/core/trace.d.ts +15 -0
  61. package/dist/core/wiring-manifest.d.ts +15 -1
  62. package/dist/core/wiring-manifest.js +10 -2
  63. package/dist/core/workflow-journal-store.d.ts +21 -2
  64. package/dist/core/workflow-journal-store.js +1 -1
  65. package/dist/engine/execution-env/node-execution-env.d.ts +2 -0
  66. package/dist/engine/execution-env/node-execution-env.js +2 -1
  67. package/dist/engine/harness/agent-harness.d.ts +3 -1
  68. package/dist/engine/harness/agent-harness.js +1 -1
  69. package/dist/engine/harness/types.d.ts +15 -2
  70. package/dist/index.d.ts +8 -4
  71. package/dist/index.js +7 -3
  72. package/dist/orchestration/run-workflow-tool.d.ts +22 -2
  73. package/dist/orchestration/run-workflow-tool.js +14 -1
  74. package/dist/orchestration/workflow-governance.d.ts +3 -2
  75. package/dist/orchestration/workflow-observe.d.ts +1 -1
  76. package/dist/orchestration/workflow-observe.js +2 -0
  77. package/dist/orchestration/workflow-primitives.d.ts +4 -1
  78. package/dist/orchestration/workflow-primitives.js +1 -6
  79. package/dist/orchestration/workflow-types.d.ts +37 -2
  80. package/dist/orchestration/workflow-types.js +16 -0
  81. package/dist/orchestration/workflow.d.ts +53 -6
  82. package/dist/orchestration/workflow.js +319 -51
  83. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
  84. package/dist/stores/file/workflow-journal-store.js +10 -3
  85. package/package.json +1 -1
  86. package/test/export-surface.snapshot.json +85 -5
@@ -7,18 +7,19 @@ import { TERMINAL_CAUSE_IS_REPLAYABLE, isTerminalCauseKind } from "../core/termi
7
7
  import { terminalProjection } from "../core/runner/terminal-projection.js";
8
8
  import { builtinAgentDefinitions } from "../agents/builtin-agents.js";
9
9
  import { GENERAL_PURPOSE_SUBAGENT_TYPE, markerFragment } from "../agents/subagent.js";
10
+ import { deploymentSubagentSystemPrompt, runnerRoleMap } from "../agents/child-model-seat.js";
10
11
  import { combinePolicies, createAllowDenyPolicy } from "../core/tool-policy.js";
11
12
  import { createSafeNotifier } from "../core/safe-notify.js";
12
13
  import { callKeyOrdinal, oversizeJournalResult, journalOversizeTombstone, JOURNAL_OVERSIZE_ERROR_CODE, MAX_JOURNAL_RESULT_BYTES } from "../core/workflow-journal-store.js";
13
14
  import { isWorkflowRunActive, closeWorkflowChannel, markWorkflowActive, publishWorkflowEvent } from "./workflow-observe.js";
14
- import { isDurablePause, mapNestedSuspend } from "../agents/suspend-guard.js";
15
+ import { isDurablePause } from "../agents/suspend-guard.js";
15
16
  import { RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
16
17
  import { boundInputHashOf } from "../core/canonical-json.js";
17
18
  import { boundedRedactedSummary } from "../core/untrusted-egress.js";
18
19
  import { delimitUntrusted } from "../core/untrusted-text.js";
19
20
  import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
20
21
  import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
21
- import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
22
+ import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentParkedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
22
23
  import { WorkflowScriptError } from "./workflow-meta.js";
23
24
  export * from "./workflow-types.js";
24
25
  const MAX_TRANSCRIPT_CHARS = 4000;
@@ -132,6 +133,16 @@ function withWorkflowChildPersona(spec, schema) {
132
133
  const note = schema ? WORKFLOW_SUBAGENT_APPEND_SCHEMA : WORKFLOW_SUBAGENT_APPEND;
133
134
  return { ...spec, appendSystemPrompt: spec.appendSystemPrompt ? `${spec.appendSystemPrompt}\n\n${note}` : note };
134
135
  }
136
+ function withWorkflowChildSeats(typed, inheritedModelSnap, inheritedThinkingSnap, roles) {
137
+ const statedPersona = deploymentSubagentSystemPrompt(roles);
138
+ return {
139
+ ...typed,
140
+ modelRole: typed.modelRole ?? "subagent",
141
+ ...(typed.model === undefined && inheritedModelSnap !== undefined ? { model: inheritedModelSnap } : {}),
142
+ ...(typed.thinking === undefined && inheritedThinkingSnap !== undefined ? { thinking: inheritedThinkingSnap } : {}),
143
+ ...(typed.systemPrompt === undefined && statedPersona !== undefined ? { systemPrompt: statedPersona } : {}),
144
+ };
145
+ }
135
146
  export function workflowAgentCallKey(ordinal, spec, opts) {
136
147
  const identity = {
137
148
  ...spec,
@@ -206,10 +217,9 @@ export class WorkflowJournalIncompatibleError extends Error {
206
217
  fromRunId;
207
218
  ordinal;
208
219
  code = "workflow.journal_incompatible";
209
- constructor(fromRunId, ordinal) {
210
- super(`startWorkflow: resume from "${fromRunId}" was REFUSED — journal entry #${ordinal} carries no terminal cause ` +
211
- "(a journal written before TaskResult.terminal, or a malformed entry). Completed work must not run again; " +
212
- "start a fresh run instead of resuming this journal.");
220
+ constructor(fromRunId, ordinal, reason) {
221
+ super(`startWorkflow: resume from "${fromRunId}" was REFUSED — ${reason ?? `journal entry #${ordinal} carries no terminal cause (a journal written before TaskResult.terminal, or a malformed entry)`}. ` +
222
+ "Completed work must not run again; start a fresh run instead of resuming this journal.");
213
223
  this.fromRunId = fromRunId;
214
224
  this.ordinal = ordinal;
215
225
  this.name = "WorkflowJournalIncompatibleError";
@@ -448,6 +458,12 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
448
458
  if (opts.resumeFromRunId !== undefined && opts.journalStore === undefined) {
449
459
  throw new Error("runWorkflow: resumeFromRunId requires a journalStore to load the prior run's journal");
450
460
  }
461
+ if (opts.defaultDurableApproval !== undefined && opts.store === undefined) {
462
+ throw new Error("runWorkflow: defaultDurableApproval requires a WorkflowRunStore — a workflow child's park is recorded on its wa* row, and without a store there is no durable row for a host to route the parked approval by");
463
+ }
464
+ if (opts.parkedResume !== undefined && opts.resumeFromRunId === undefined) {
465
+ throw new Error("runWorkflow: parkedResume drives a PARKED ordinal of a prior run and requires resumeFromRunId");
466
+ }
451
467
  const now = opts.now ?? (() => Date.now());
452
468
  const scope = opts.scope ?? "default";
453
469
  const runId = opts.runId ?? randomUUID();
@@ -769,6 +785,60 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
769
785
  let resumeClaim;
770
786
  const replayByOrdinal = [];
771
787
  let diverged = false;
788
+ let parkedLeg;
789
+ const parkedError = () => {
790
+ const p = parkedLeg;
791
+ return new WorkflowAgentParkedError(p.label, p.paused.terminal.gate.kind, p.paused.terminal.checkpointId);
792
+ };
793
+ const parkedResumeFor = (token) => opts.parkedResume?.find((d) => d.token === token);
794
+ const parkedCallChangedError = (ordinal) => Object.assign(new Error(`workflow resume: ordinal ${ordinal} is PARKED on an approval checkpoint, but the call at that ordinal changed (its call key differs from the journaled one) — ` +
795
+ "the script or its args were edited at a parked call, and a fresh spawn there would run beside the pinned child. Restore the call and resume again, or decide the checkpoint " +
796
+ "through the deployment's approval channel and start a fresh run."), { code: "workflow.parked_call_changed" });
797
+ let inFlightLegs = 0;
798
+ let legsIdleResolve;
799
+ const legStart = () => {
800
+ inFlightLegs += 1;
801
+ };
802
+ const legEnd = () => {
803
+ inFlightLegs -= 1;
804
+ if (inFlightLegs <= 0) {
805
+ inFlightLegs = 0;
806
+ const r = legsIdleResolve;
807
+ legsIdleResolve = undefined;
808
+ r?.();
809
+ }
810
+ };
811
+ const awaitLegsIdle = async (signal) => {
812
+ while (inFlightLegs > 0 && signal?.aborted !== true) {
813
+ await new Promise((resolve) => {
814
+ legsIdleResolve = resolve;
815
+ signal?.addEventListener("abort", () => resolve(), { once: true });
816
+ });
817
+ }
818
+ };
819
+ const writtenOrdinals = new Set();
820
+ const carryUnresolvedParks = async () => {
821
+ if (opts.resumeFromRunId === undefined || !journalStore)
822
+ return;
823
+ for (const e of replayByOrdinal) {
824
+ if (e?.parked === undefined)
825
+ continue;
826
+ const ord = callKeyOrdinal(e.callKey);
827
+ if (writtenOrdinals.has(ord))
828
+ continue;
829
+ if (workflowSignal.aborted) {
830
+ emitLogLine(`resume-journal: the prior run's PARKED leg at ordinal ${ord} was NOT carried into this run's journal (the run was cancelled or timed out first) — resume from the prior run, not from this one.`);
831
+ continue;
832
+ }
833
+ const outcome = await finalizeWithin(timers, journalAppendParked(e.callKey, e.parked).then(() => "ok", (err) => ({ err })), "timeout");
834
+ if (outcome !== "ok") {
835
+ const why = outcome === "timeout" ? `the append did not settle within ${WORKFLOW_RESUME_CLAIM_FINALIZE_TIMEOUT_MS}ms` : outcome.err instanceof Error ? outcome.err.message : String(outcome.err);
836
+ emitLogLine(`resume-journal: the prior run's PARKED leg at ordinal ${ord} could not be carried into this run's journal (${why}) — resume from the prior run, not from this one.`);
837
+ }
838
+ }
839
+ };
840
+ const driveNeverStarted = (r) => r.terminal.kind === "failed" && (r.terminal.code === "resume.reopened_unstarted" || r.terminal.code === "resume.reopen_failed_unstarted");
841
+ const isPausedResult = (r) => isDurablePause(r.terminal);
772
842
  let isolationAdvised = false;
773
843
  const adviseIsolationIfConcurrent = (opts) => {
774
844
  if (isolationAdvised || opts.isolation || sem.active <= 1)
@@ -783,6 +853,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
783
853
  };
784
854
  let journalTail = Promise.resolve();
785
855
  const JOURNAL_DRAIN_MAX_MS = 5_000;
856
+ const durableApprovalInherit = (s) => s.durableApproval === undefined && opts.defaultDurableApproval !== undefined ? { durableApproval: { ...opts.defaultDurableApproval } } : {};
786
857
  const journalDurableResult = (result) => {
787
858
  const t = result.terminal;
788
859
  if (t === undefined || t.kind !== "failed" || typeof t.message !== "string" || t.message === "")
@@ -821,8 +892,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
821
892
  if (dbg)
822
893
  console.error(`[sema:wf-journal] runId=${runId} callKey=${callKey} TOMBSTONE-ERROR ${err instanceof Error ? err.message : String(err)}`);
823
894
  }
895
+ writtenOrdinals.add(callKeyOrdinal(callKey));
824
896
  return;
825
897
  }
898
+ writtenOrdinals.add(callKeyOrdinal(callKey));
826
899
  const p = journalStore.append(runId, scope, { callKey, result: stored });
827
900
  journalTail = journalTail.then(() => p).catch(() => undefined);
828
901
  try {
@@ -836,6 +909,56 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
836
909
  throw err;
837
910
  }
838
911
  };
912
+ const journalAppendParked = async (callKey, paused) => {
913
+ if (!journalStore)
914
+ return;
915
+ writtenOrdinals.add(callKeyOrdinal(callKey));
916
+ let lastErr;
917
+ for (let attempt = 0; attempt < 3; attempt++) {
918
+ const p = journalStore.append(runId, scope, { callKey, parked: paused });
919
+ journalTail = journalTail.then(() => p).catch(() => undefined);
920
+ try {
921
+ await p;
922
+ return;
923
+ }
924
+ catch (err) {
925
+ lastErr = err;
926
+ if (attempt < 2)
927
+ await new Promise((r) => timers.setTimeout(r, 25 * (attempt + 1)));
928
+ }
929
+ }
930
+ throw lastErr;
931
+ };
932
+ const parkAgentLeg = async (rec, paused, activityTail, chargeLive) => {
933
+ const s = paused.stats;
934
+ accumulateStats(paused, chargeLive);
935
+ rec.status = "parked";
936
+ rec.parkedCheckpointToken = paused.terminal.token;
937
+ rec.taskStatus = terminalProjection(paused.terminal).status;
938
+ if (paused.sessionId)
939
+ rec.sessionId = paused.sessionId;
940
+ setLegRunId(rec, paused);
941
+ rec.endedAt = now();
942
+ rec.stats = { tokens: s.tokens ?? 0, turns: s.turns ?? 0, costMicroUsd: s.costMicroUsd };
943
+ if (s.toolCalls !== undefined)
944
+ rec.toolCalls = s.toolCalls;
945
+ if (activityTail.length > 0)
946
+ rec.activity = activityTail.slice();
947
+ foldLegApprovalReport(rec, paused);
948
+ emit({ type: "agent_end", runId, label: rec.label, phase: rec.phase, ...(rec.groupId !== undefined ? { groupId: rec.groupId } : {}), status: "parked", ...(paused.model !== undefined ? { modelResolved: paused.model } : {}), ts: rec.endedAt });
949
+ void persist("update");
950
+ bceLive.delete(waIdOf(rec.callKey));
951
+ if (parkedLeg === undefined)
952
+ parkedLeg = { label: rec.label, callKey: rec.callKey, paused };
953
+ try {
954
+ await journalAppendParked(rec.callKey, paused);
955
+ }
956
+ catch (err) {
957
+ emitRunLog(`resume-journal: the PARKED leg at ordinal ${callKeyOrdinal(rec.callKey)} ("${rec.label.slice(0, 80)}") could not be recorded (${err instanceof Error ? err.message : String(err)}) — ` +
958
+ "a resume of this run will NOT find the park at its ordinal; decide the checkpoint through the deployment's approval channel and start a fresh run instead of resuming this one.");
959
+ }
960
+ throw parkedError();
961
+ };
839
962
  const createActivityCapture = (callKey, label, groupId, rec) => {
840
963
  const tail = [];
841
964
  let beatCount = 0;
@@ -1027,7 +1150,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1027
1150
  const phase = agentOpts.phase ?? currentPhase?.title;
1028
1151
  const phaseInstance = resolveAgentPhase(agentOpts.phase);
1029
1152
  const groupId = currentGroup;
1030
- const typeDefModel = agentOpts.agentType !== undefined ? agentRegistry.find((d) => d.name === agentOpts.agentType)?.model : undefined;
1153
+ const typeDef = agentOpts.agentType !== undefined ? agentRegistry.find((d) => d.name === agentOpts.agentType) : undefined;
1154
+ const typeDefModel = typeDef?.model;
1031
1155
  let inheritedModelSnap;
1032
1156
  let inheritedModelError;
1033
1157
  if (spec.model === undefined && typeDefModel === undefined) {
@@ -1038,12 +1162,17 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1038
1162
  inheritedModelError = err instanceof Error ? err : new Error(String(err));
1039
1163
  }
1040
1164
  }
1041
- const specForIdentity = inheritedModelSnap !== undefined ? { ...spec, model: inheritedModelSnap } : spec;
1165
+ const inheritedThinkingSnap = spec.thinking === undefined ? opts.defaultThinking?.() : undefined;
1166
+ const specForIdentity = {
1167
+ ...spec,
1168
+ ...(inheritedModelSnap !== undefined ? { model: inheritedModelSnap } : {}),
1169
+ ...(inheritedThinkingSnap !== undefined ? { thinking: inheritedThinkingSnap } : {}),
1170
+ };
1042
1171
  const callKey = workflowAgentCallKey(run.agents.length, specForIdentity, agentOpts);
1043
1172
  const prompt = boundedRedactedSummary(spec.systemPrompt ? `${spec.systemPrompt}\n\n${spec.objective}` : spec.objective, MAX_TRANSCRIPT_CHARS);
1044
1173
  const specForLabel = spec.model === undefined && typeDefModel !== undefined ? { ...spec, model: typeDefModel } : specForIdentity;
1045
1174
  const model = workflowModelLabel(specForLabel, modelCatalog);
1046
- return { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model };
1175
+ return { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model };
1047
1176
  };
1048
1177
  const reviewSpawnBeforeLaunch = async (lane, label, callKey, runSpec, effectiveSignal) => {
1049
1178
  const review = opts.autoModeReview;
@@ -1190,10 +1319,35 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1190
1319
  },
1191
1320
  async agent(spec, agentOpts = {}) {
1192
1321
  const effectiveSignal = assertAgentSpawnAllowed("ctx.agent", spec, agentOpts);
1193
- const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
1322
+ const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
1323
+ let drive;
1324
+ const cached = opts.resumeFromRunId !== undefined ? replayByOrdinal[run.agents.length] : undefined;
1325
+ if (cached?.parked !== undefined) {
1326
+ if (cached.callKey !== callKey)
1327
+ throw parkedCallChangedError(run.agents.length);
1328
+ {
1329
+ const decision = parkedResumeFor(cached.parked.terminal.token);
1330
+ if (decision === undefined) {
1331
+ if (finalized)
1332
+ return cached.parked;
1333
+ const at = now();
1334
+ const reparkRec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(cached.parked.model !== undefined ? { model: cached.parked.model } : {}), replayed: true, status: "running", queuedAt: at, startedAt: at };
1335
+ if (run.resume)
1336
+ run.resume.replayed += 1;
1337
+ run.agents.push(reparkRec);
1338
+ if (phaseInstance)
1339
+ agentPhaseOf.set(reparkRec, phaseInstance);
1340
+ emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(cached.parked.model !== undefined ? { model: cached.parked.model } : {}), replayed: true, ts: at });
1341
+ bceSpawn(callKey, label, agentOpts.agentType, true, cached.parked.sessionId || undefined, cached.parked.model);
1342
+ return await parkAgentLeg(reparkRec, cached.parked, [], false);
1343
+ }
1344
+ drive = { token: decision.token, outcome: decision.outcome, ...(decision.inheritedGate !== undefined ? { inheritedGate: decision.inheritedGate } : {}), sessionId: cached.parked.sessionId || undefined, paused: cached.parked };
1345
+ }
1346
+ }
1347
+ if (parkedLeg !== undefined && drive === undefined)
1348
+ throw parkedError();
1194
1349
  if (opts.resumeFromRunId !== undefined && !diverged) {
1195
- const cached = replayByOrdinal[run.agents.length];
1196
- if (cached && cached.callKey === callKey && TERMINAL_CAUSE_IS_REPLAYABLE[cached.result.terminal.kind]) {
1350
+ if (drive === undefined && cached && cached.callKey === callKey && cached.result !== undefined && TERMINAL_CAUSE_IS_REPLAYABLE[cached.result.terminal.kind]) {
1197
1351
  if (finalized)
1198
1352
  return cached.result;
1199
1353
  const r = cached.result;
@@ -1237,18 +1391,20 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1237
1391
  }
1238
1392
  return r;
1239
1393
  }
1240
- noteDivergence(run.agents.length, cached === undefined
1241
- ? run.resume?.journalEntries === 0
1242
- ? "the journal loaded no entries"
1243
- : run.agents.length >= replayByOrdinal.length
1244
- ? "a new call past the journaled prefix — the prior run ended before this point (the intended extend-the-script resume shape)"
1245
- : "no journal entry at this ordinal (never recorded, e.g. an oversize result under a pre-tombstone engine, or dropped)"
1246
- : cached.callKey !== callKey
1247
- ? "the call key changed — the script or its args differ here"
1248
- : cached.result.terminal?.kind === "failed" && cached.result.terminal.code === JOURNAL_OVERSIZE_ERROR_CODE
1249
- ? "the prior result exceeded the journal size cap and was never cached"
1250
- : `the journaled result was ${cached.result.terminal?.kind ?? "(no terminal record)"}, not completed`);
1251
- diverged = true;
1394
+ if (drive === undefined) {
1395
+ noteDivergence(run.agents.length, cached === undefined
1396
+ ? run.resume?.journalEntries === 0
1397
+ ? "the journal loaded no entries"
1398
+ : run.agents.length >= replayByOrdinal.length
1399
+ ? "a new call past the journaled prefix the prior run ended before this point (the intended extend-the-script resume shape)"
1400
+ : "no journal entry at this ordinal (never recorded, e.g. an oversize result under a pre-tombstone engine, or dropped)"
1401
+ : cached.callKey !== callKey
1402
+ ? "the call key changed the script or its args differ here"
1403
+ : cached.result?.terminal?.kind === "failed" && cached.result.terminal.code === JOURNAL_OVERSIZE_ERROR_CODE
1404
+ ? "the prior result exceeded the journal size cap and was never cached"
1405
+ : `the journaled result was ${cached.result?.terminal?.kind ?? "(no terminal record)"}, not completed`);
1406
+ diverged = true;
1407
+ }
1252
1408
  }
1253
1409
  if (budgetTotal !== null && spent() >= budgetTotal) {
1254
1410
  throw new WorkflowBudgetExceededError(spent(), budgetTotal);
@@ -1260,6 +1416,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1260
1416
  agentPhaseOf.set(rec, phaseInstance);
1261
1417
  emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), queuedAt: rec.queuedAt, ts: rec.queuedAt });
1262
1418
  void persist("update");
1419
+ legStart();
1263
1420
  const release = await sem.acquire();
1264
1421
  adviseIsolationIfConcurrent(agentOpts);
1265
1422
  let result;
@@ -1268,21 +1425,24 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1268
1425
  throw new Error("workflow aborted");
1269
1426
  if (finalized)
1270
1427
  throw new Error("workflow run already finalized — ctx.agent cannot spawn after the run ended");
1428
+ if (parkedLeg !== undefined && drive === undefined)
1429
+ throw parkedError();
1271
1430
  if (budgetTotal !== null && spent() >= budgetTotal) {
1272
1431
  throw new WorkflowBudgetExceededError(spent(), budgetTotal);
1273
1432
  }
1274
1433
  rec.startedAt = now();
1275
- const bornChildSessionId = resolveChildSessionIdAtSpawn(spec);
1434
+ const bornChildSessionId = drive !== undefined ? drive.sessionId : resolveChildSessionIdAtSpawn(spec);
1276
1435
  bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId, model);
1277
1436
  if (inheritedModelError !== undefined)
1278
1437
  throw inheritedModelError;
1279
1438
  const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
1280
- const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
1439
+ const typedSpec = withWorkflowChildSeats(typedSpec0, inheritedModelSnap, inheritedThinkingSnap, runnerRoleMap(runner));
1281
1440
  const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
1282
1441
  const authInherit = framedSpec.getApiKeyAndHeaders === undefined && opts.defaultGetApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.defaultGetApiKeyAndHeaders } : {};
1442
+ const parkInherit = durableApprovalInherit(framedSpec);
1283
1443
  const preReviewSpec = agentOpts.schema
1284
- ? { ...framedSpec, ...authInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
1285
- : { ...framedSpec, ...authInherit, signal: effectiveSignal };
1444
+ ? { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
1445
+ : { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal };
1286
1446
  const runSpec = await reviewSpawnBeforeLaunch("ctx.agent", label, callKey, preReviewSpec, effectiveSignal);
1287
1447
  const enrichedForward = opts.onForwardEvent !== undefined
1288
1448
  ? (e) => {
@@ -1319,7 +1479,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1319
1479
  const onCallerAbort = () => attemptCtl.abort(new Error("workflow aborted"));
1320
1480
  effectiveSignal?.addEventListener("abort", onCallerAbort, { once: true });
1321
1481
  armWatchdog();
1322
- const attemptSessionId = attempts === 1 ? bornChildSessionId : resolveChildSessionIdAtSpawn(runSpec);
1482
+ const attemptSessionId = drive !== undefined ? drive.sessionId : attempts === 1 ? bornChildSessionId : resolveChildSessionIdAtSpawn(runSpec);
1323
1483
  const attemptSpec = attemptSessionId !== undefined ? { ...runSpec, sessionId: attemptSessionId, signal: attemptCtl.signal } : { ...runSpec, signal: attemptCtl.signal };
1324
1484
  if (attemptSessionId !== undefined && !finalized) {
1325
1485
  rec.sessionId = attemptSessionId;
@@ -1373,10 +1533,15 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1373
1533
  activeUsageBeats.add(activeBeat);
1374
1534
  try {
1375
1535
  attemptResult = await workflowDepthStore.run({ depth: depth + 1 }, async () => {
1376
- if (typeof runner.runTaskStream !== "function") {
1536
+ if (drive === undefined && typeof runner.runTaskStream !== "function") {
1377
1537
  return runner.runTask(attemptSpec, attemptInternals);
1378
1538
  }
1379
- const stream = runner.runTaskStream(attemptSpec, undefined, attemptInternals);
1539
+ const stream = drive === undefined
1540
+ ? runner.runTaskStream(attemptSpec, undefined, attemptInternals)
1541
+ : await (() => {
1542
+ const { objective: _o, sessionId: _s, ...resumeConfig } = attemptSpec;
1543
+ return runner.resumeStream(drive.token, drive.outcome, resumeConfig, { ...attemptInternals, ...(drive.inheritedGate !== undefined ? { inheritedGate: drive.inheritedGate } : {}) });
1544
+ })();
1380
1545
  for await (const ev of stream) {
1381
1546
  if (ev.type === "text_delta" || ev.type === "reasoning_delta")
1382
1547
  onStreamProgress();
@@ -1409,7 +1574,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1409
1574
  }
1410
1575
  throw new Error("workflow aborted");
1411
1576
  }
1412
- if (stallFired && attemptResult?.terminal.kind !== "completed") {
1577
+ if (drive === undefined && stallFired && attemptResult?.terminal.kind !== "completed") {
1413
1578
  if (attemptResult !== undefined && !finalized)
1414
1579
  accumulateStats(attemptResult, true);
1415
1580
  if (attemptResult !== undefined && !finalized)
@@ -1438,7 +1603,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1438
1603
  (result.stats.outputTokens ?? Number.POSITIVE_INFINITY) < 50 &&
1439
1604
  lastStopReason === undefined &&
1440
1605
  durationMs > stallMs * 0.5;
1441
- if (degraded && attempts === 1 && !throttleRetried) {
1606
+ if (degraded && attempts === 1 && !throttleRetried && drive === undefined) {
1442
1607
  throttleRetried = true;
1443
1608
  lastAttemptReason = "throttled";
1444
1609
  emitRunLog(`[${label}] throttled response (${(result.result ?? "").trim().length} chars in ${Math.round(durationMs / 1000)}s) — sleeping ${Math.round(throttleBackoffMs / 1000)}s before retry`);
@@ -1474,6 +1639,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1474
1639
  }
1475
1640
  }
1476
1641
  catch (err) {
1642
+ if (drive !== undefined && !finalized && !(err instanceof WorkflowAgentParkedError)) {
1643
+ emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${err instanceof Error ? err.message : String(err)}); the park stands.`);
1644
+ return await parkAgentLeg(rec, drive.paused, activityTail, false);
1645
+ }
1477
1646
  if (!finalized) {
1478
1647
  rec.status = "failed";
1479
1648
  rec.endedAt = now();
@@ -1520,7 +1689,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1520
1689
  },
1521
1690
  stats: { turns: 0, tokens: 0, costMicroUsd: 0 },
1522
1691
  }, label).catch(() => undefined);
1523
- if (!(err instanceof WorkflowBudgetExceededError && rec.startedAt === undefined))
1692
+ if (!((err instanceof WorkflowBudgetExceededError || err instanceof WorkflowAgentParkedError) && rec.startedAt === undefined))
1524
1693
  await journaled;
1525
1694
  bceTerminal(callKey, "failed", rec.output ?? (err instanceof Error ? err.message : String(err)), rec.sessionId, rec.stats);
1526
1695
  }
@@ -1528,11 +1697,16 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1528
1697
  }
1529
1698
  finally {
1530
1699
  release();
1700
+ legEnd();
1531
1701
  }
1532
1702
  if (finalized)
1533
1703
  return result;
1534
- if (isDurablePause(result.terminal)) {
1535
- result = mapNestedSuspend(result);
1704
+ if (isPausedResult(result)) {
1705
+ return await parkAgentLeg(rec, result, activityTail, true);
1706
+ }
1707
+ if (drive !== undefined && driveNeverStarted(result)) {
1708
+ emitRunLog(`[park] the parked leg "${label}" was driven but the approved action never started (${result.terminal.kind === "failed" ? result.terminal.code : ""}); the park stands.`);
1709
+ return await parkAgentLeg(rec, drive.paused, activityTail, false);
1536
1710
  }
1537
1711
  await settleAgentResult(rec, result, activityTail, agentOpts, "awaited");
1538
1712
  return result;
@@ -1542,9 +1716,31 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1542
1716
  if (budgetTotal !== null && spent() >= budgetTotal) {
1543
1717
  throw new WorkflowBudgetExceededError(spent(), budgetTotal);
1544
1718
  }
1545
- const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
1546
- noteDivergence(run.agents.length, "ctx.agentStream results are never replayed");
1547
- diverged = true;
1719
+ const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
1720
+ let drive;
1721
+ const parkedHere = opts.resumeFromRunId !== undefined ? replayByOrdinal[run.agents.length] : undefined;
1722
+ if (parkedHere?.parked !== undefined) {
1723
+ if (parkedHere.callKey !== callKey)
1724
+ throw parkedCallChangedError(run.agents.length);
1725
+ const decision = parkedResumeFor(parkedHere.parked.terminal.token);
1726
+ if (decision === undefined) {
1727
+ const at = now();
1728
+ const reparkRec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(parkedHere.parked.model !== undefined ? { model: parkedHere.parked.model } : {}), replayed: true, status: "running", queuedAt: at, startedAt: at };
1729
+ if (run.resume)
1730
+ run.resume.replayed += 1;
1731
+ run.agents.push(reparkRec);
1732
+ if (phaseInstance)
1733
+ agentPhaseOf.set(reparkRec, phaseInstance);
1734
+ emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(parkedHere.parked.model !== undefined ? { model: parkedHere.parked.model } : {}), replayed: true, ts: at });
1735
+ bceSpawn(callKey, label, agentOpts.agentType, true, parkedHere.parked.sessionId || undefined, parkedHere.parked.model);
1736
+ return await parkAgentLeg(reparkRec, parkedHere.parked, [], false);
1737
+ }
1738
+ drive = { token: decision.token, outcome: decision.outcome, ...(decision.inheritedGate !== undefined ? { inheritedGate: decision.inheritedGate } : {}), sessionId: parkedHere.parked.sessionId || undefined, paused: parkedHere.parked };
1739
+ }
1740
+ if (drive === undefined) {
1741
+ noteDivergence(run.agents.length, "ctx.agentStream results are never replayed");
1742
+ diverged = true;
1743
+ }
1548
1744
  const rec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(model !== undefined ? { model } : {}), status: "running", queuedAt: now() };
1549
1745
  const { tail: activityTail, onActivity } = createActivityCapture(callKey, label, groupId, rec);
1550
1746
  run.agents.push(rec);
@@ -1552,6 +1748,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1552
1748
  agentPhaseOf.set(rec, phaseInstance);
1553
1749
  emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), queuedAt: rec.queuedAt, ts: rec.queuedAt });
1554
1750
  void persist("update");
1751
+ legStart();
1555
1752
  const release = await sem.acquire();
1556
1753
  adviseIsolationIfConcurrent(agentOpts);
1557
1754
  let released = false;
@@ -1569,17 +1766,34 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1569
1766
  void persist("update");
1570
1767
  }
1571
1768
  };
1769
+ const releaseLeg = () => {
1770
+ if (!released)
1771
+ legEnd();
1772
+ releaseOnce();
1773
+ };
1572
1774
  if (effectiveSignal?.aborted || finalized) {
1573
1775
  recordFailed();
1574
- releaseOnce();
1776
+ releaseLeg();
1575
1777
  throw new Error(finalized ? "workflow run already finalized — ctx.agentStream cannot spawn after the run ended" : "workflow aborted");
1576
1778
  }
1779
+ if (parkedLeg !== undefined && drive === undefined) {
1780
+ const parked = parkedError();
1781
+ rec.errorMessage = boundedRedactedSummary(parked.message, MAX_TRANSCRIPT_CHARS);
1782
+ recordFailed();
1783
+ releaseLeg();
1784
+ throw parked;
1785
+ }
1577
1786
  if (budgetTotal !== null && spent() >= budgetTotal) {
1787
+ if (drive !== undefined) {
1788
+ emitRunLog(`[park] the parked leg "${label}" could not be driven — the run's token ceiling was reached before its turn; the park stands.`);
1789
+ releaseLeg();
1790
+ return await parkAgentLeg(rec, drive.paused, [], false);
1791
+ }
1578
1792
  const refusal = new WorkflowBudgetExceededError(spent(), budgetTotal);
1579
1793
  rec.errorCode = refusal.code;
1580
1794
  rec.errorMessage = boundedRedactedSummary(refusal.message, MAX_TRANSCRIPT_CHARS);
1581
1795
  recordFailed();
1582
- releaseOnce();
1796
+ releaseLeg();
1583
1797
  void journalAppend(callKey, {
1584
1798
  taskId: callKey,
1585
1799
  sessionId: "",
@@ -1590,19 +1804,20 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1590
1804
  throw refusal;
1591
1805
  }
1592
1806
  rec.startedAt = now();
1593
- const childSessionId = resolveChildSessionIdAtSpawn(spec);
1807
+ const childSessionId = drive !== undefined ? drive.sessionId : resolveChildSessionIdAtSpawn(spec);
1594
1808
  bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId, model);
1595
1809
  let stream;
1596
1810
  try {
1597
1811
  if (inheritedModelError !== undefined)
1598
1812
  throw inheritedModelError;
1599
1813
  const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
1600
- const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
1814
+ const typedSpec = withWorkflowChildSeats(typedSpec0, inheritedModelSnap, inheritedThinkingSnap, runnerRoleMap(runner));
1601
1815
  const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
1602
1816
  const authInherit = framedSpec.getApiKeyAndHeaders === undefined && opts.defaultGetApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.defaultGetApiKeyAndHeaders } : {};
1817
+ const parkInherit = durableApprovalInherit(framedSpec);
1603
1818
  const baseRunSpec = agentOpts.schema
1604
- ? { ...framedSpec, ...authInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
1605
- : { ...framedSpec, ...authInherit, signal: effectiveSignal };
1819
+ ? { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
1820
+ : { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal };
1606
1821
  const preReviewSpec = childSessionId !== undefined ? { ...baseRunSpec, sessionId: childSessionId } : baseRunSpec;
1607
1822
  const runSpec = await reviewSpawnBeforeLaunch("ctx.agentStream", label, callKey, preReviewSpec, effectiveSignal);
1608
1823
  const enrichedForwardS = opts.onForwardEvent !== undefined
@@ -1634,9 +1849,20 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1634
1849
  }
1635
1850
  : {}),
1636
1851
  };
1637
- stream = workflowDepthStore.run({ depth: depth + 1 }, () => runner.runTaskStream(runSpec, undefined, runInternals));
1852
+ stream = drive === undefined
1853
+ ? workflowDepthStore.run({ depth: depth + 1 }, () => runner.runTaskStream(runSpec, undefined, runInternals))
1854
+ : await workflowDepthStore.run({ depth: depth + 1 }, () => {
1855
+ const d = drive;
1856
+ const { objective: _o, sessionId: _s, ...resumeConfig } = runSpec;
1857
+ return runner.resumeStream(d.token, d.outcome, resumeConfig, { ...runInternals, ...(d.inheritedGate !== undefined ? { inheritedGate: d.inheritedGate } : {}) });
1858
+ });
1638
1859
  }
1639
1860
  catch (err) {
1861
+ if (drive !== undefined && !finalized) {
1862
+ emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${err instanceof Error ? err.message : String(err)}); the park stands.`);
1863
+ releaseLeg();
1864
+ return await parkAgentLeg(rec, drive.paused, [], false);
1865
+ }
1640
1866
  if (!finalized && rec.errorMessage === undefined && err instanceof Error && err.message !== "")
1641
1867
  rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
1642
1868
  if (!finalized && err instanceof WorkflowAgentBlockedError) {
@@ -1651,7 +1877,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1651
1877
  }).catch(() => undefined);
1652
1878
  }
1653
1879
  recordFailed();
1654
- releaseOnce();
1880
+ releaseLeg();
1655
1881
  bceTerminal(callKey, "failed", err instanceof Error ? err.message : String(err), undefined, undefined);
1656
1882
  throw err;
1657
1883
  }
@@ -1691,7 +1917,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1691
1917
  accumulateStats({ stats: partialSpend }, true);
1692
1918
  }
1693
1919
  recordFailed();
1694
- releaseOnce();
1920
+ releaseLeg();
1695
1921
  void journalAppend(callKey, {
1696
1922
  taskId: callKey,
1697
1923
  sessionId: "",
@@ -1704,11 +1930,15 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1704
1930
  }
1705
1931
  rollbackUsageBeat();
1706
1932
  activeUsageBeats.delete(activeBeat);
1707
- releaseOnce();
1933
+ releaseLeg();
1708
1934
  if (finalized)
1709
1935
  return result;
1710
- if (isDurablePause(result.terminal)) {
1711
- result = mapNestedSuspend(result);
1936
+ if (isPausedResult(result)) {
1937
+ return await parkAgentLeg(rec, result, activityTail, true);
1938
+ }
1939
+ if (drive !== undefined && driveNeverStarted(result)) {
1940
+ emitRunLog(`[park] the parked leg "${label}" was driven but the approved action never started (${result.terminal.kind === "failed" ? result.terminal.code : ""}); the park stands.`);
1941
+ return await parkAgentLeg(rec, drive.paused, activityTail, false);
1712
1942
  }
1713
1943
  await settleAgentResult(rec, result, activityTail, agentOpts, "best-effort");
1714
1944
  return result;
@@ -1866,12 +2096,40 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1866
2096
  }
1867
2097
  const entries = await journalStore.load(opts.resumeFromRunId, scope);
1868
2098
  for (const e of entries) {
2099
+ const parked = e.parked;
2100
+ if (parked !== undefined) {
2101
+ const pt = parked?.terminal;
2102
+ if (pt === null || typeof pt !== "object" || pt.kind !== "paused" || typeof pt.token !== "string" || pt.token.length === 0) {
2103
+ throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), callKeyOrdinal(e.callKey));
2104
+ }
2105
+ replayByOrdinal[callKeyOrdinal(e.callKey)] = e;
2106
+ continue;
2107
+ }
1869
2108
  const t = e.result?.terminal;
1870
2109
  if (t === null || typeof t !== "object" || !isTerminalCauseKind(t.kind)) {
1871
2110
  throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), callKeyOrdinal(e.callKey));
1872
2111
  }
1873
2112
  replayByOrdinal[callKeyOrdinal(e.callKey)] = e;
1874
2113
  }
2114
+ if (opts.store !== undefined) {
2115
+ let prior;
2116
+ try {
2117
+ prior = await opts.store.get(opts.resumeFromRunId);
2118
+ }
2119
+ catch (err) {
2120
+ throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), -1, `the prior run's record could not be read from the run store (${err instanceof Error ? err.message : String(err)}), so a lost park cannot be ruled out`);
2121
+ }
2122
+ if (prior !== null && prior.scope === scope) {
2123
+ for (const row of prior.agents) {
2124
+ if (row.status !== "parked")
2125
+ continue;
2126
+ const ord = callKeyOrdinal(row.callKey);
2127
+ if (replayByOrdinal[ord]?.parked === undefined) {
2128
+ throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), ord, `the prior run's agent #${ord} ("${row.label.slice(0, 80)}") is PARKED on an approval checkpoint but its journal entry is missing (the park was not recorded) — a resume would run that call live beside the pinned child`);
2129
+ }
2130
+ }
2131
+ }
2132
+ }
1875
2133
  run.resume = {
1876
2134
  fromRunId: clampRunIdText(opts.resumeFromRunId),
1877
2135
  journalEntries: entries.length,
@@ -1912,6 +2170,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1912
2170
  else {
1913
2171
  result = await bodyPromise;
1914
2172
  }
2173
+ if (parkedLeg !== undefined) {
2174
+ await awaitLegsIdle(workflowSignal);
2175
+ throw parkedError();
2176
+ }
2177
+ await carryUnresolvedParks();
1915
2178
  if (maxResultChars !== undefined) {
1916
2179
  let size = 0;
1917
2180
  try {
@@ -1944,6 +2207,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1944
2207
  return { result, runId, run };
1945
2208
  }
1946
2209
  catch (err) {
2210
+ if (parkedLeg !== undefined)
2211
+ await awaitLegsIdle(workflowSignal);
2212
+ await carryUnresolvedParks();
1947
2213
  finalized = true;
1948
2214
  const unsettledOnFailure = settleActiveUsageBeats();
1949
2215
  stampBudgetOvershoot(unsettledOnFailure);
@@ -1952,6 +2218,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1952
2218
  run.status = "failed";
1953
2219
  run.completionId ??= uuidv7();
1954
2220
  run.endedAt = now();
2221
+ if (parkedLeg !== undefined && !(err instanceof WorkflowAgentParkedError))
2222
+ err = parkedError();
1955
2223
  run.error = err instanceof Error ? err.message : String(err);
1956
2224
  closeAbandonedAgents(run.endedAt);
1957
2225
  restampPhaseFailures();