@bastani/atomic 0.9.19-alpha.9 → 0.9.20-alpha.1

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 (109) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +30 -0
  3. package/dist/builtin/intercom/index.bundle.mjs +15 -1
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/mcp/CHANGELOG.md +8 -0
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/subagents/CHANGELOG.md +30 -0
  8. package/dist/builtin/subagents/package.json +1 -1
  9. package/dist/builtin/web-access/CHANGELOG.md +14 -0
  10. package/dist/builtin/web-access/package.json +1 -1
  11. package/dist/builtin/workflows/CHANGELOG.md +67 -0
  12. package/dist/builtin/workflows/builtin/{chunk-nkrafh9s.js → chunk-ewkkkbak.js} +1 -1
  13. package/dist/builtin/workflows/builtin/{chunk-1jth021m.js → chunk-zqtm1d2c.js} +1 -1
  14. package/dist/builtin/workflows/builtin/goal.js +2 -2
  15. package/dist/builtin/workflows/builtin/index.js +3 -3
  16. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  17. package/dist/builtin/workflows/package.json +1 -1
  18. package/dist/builtin/workflows/src/extension/index.bundle.mjs +546 -133
  19. package/dist/builtin/workflows/src/index.js +326 -72
  20. package/dist/core/extensions/api-types.d.ts +2 -0
  21. package/dist/core/extensions/api-types.d.ts.map +1 -1
  22. package/dist/core/extensions/api-types.js.map +1 -1
  23. package/dist/core/extensions/index.d.ts +1 -1
  24. package/dist/core/extensions/index.d.ts.map +1 -1
  25. package/dist/core/extensions/index.js.map +1 -1
  26. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  27. package/dist/core/extensions/loader-api.js.map +1 -1
  28. package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
  29. package/dist/core/extensions/loader-host-modules.js +3 -1
  30. package/dist/core/extensions/loader-host-modules.js.map +1 -1
  31. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  32. package/dist/core/extensions/loader-virtual-modules.js +2 -0
  33. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  34. package/dist/core/extensions/reactive-widget.d.ts +6 -0
  35. package/dist/core/extensions/reactive-widget.d.ts.map +1 -1
  36. package/dist/core/extensions/reactive-widget.js +7 -1
  37. package/dist/core/extensions/reactive-widget.js.map +1 -1
  38. package/dist/core/extensions/runner-shortcuts.d.ts.map +1 -1
  39. package/dist/core/extensions/runner-shortcuts.js +32 -3
  40. package/dist/core/extensions/runner-shortcuts.js.map +1 -1
  41. package/dist/core/extensions/runtime-types.d.ts +4 -0
  42. package/dist/core/extensions/runtime-types.d.ts.map +1 -1
  43. package/dist/core/extensions/runtime-types.js.map +1 -1
  44. package/dist/core/extensions/ui-types.d.ts +20 -0
  45. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  46. package/dist/core/extensions/ui-types.js.map +1 -1
  47. package/dist/core/keybinding-identity.d.ts +4 -0
  48. package/dist/core/keybinding-identity.d.ts.map +1 -0
  49. package/dist/core/keybinding-identity.js +10 -0
  50. package/dist/core/keybinding-identity.js.map +1 -0
  51. package/dist/core/keybindings.d.ts +10 -0
  52. package/dist/core/keybindings.d.ts.map +1 -1
  53. package/dist/core/keybindings.js +2 -0
  54. package/dist/core/keybindings.js.map +1 -1
  55. package/dist/core/tools/bash.d.ts.map +1 -1
  56. package/dist/core/tools/bash.js +7 -1
  57. package/dist/core/tools/bash.js.map +1 -1
  58. package/dist/index-extensions.d.ts +1 -1
  59. package/dist/index-extensions.d.ts.map +1 -1
  60. package/dist/index-extensions.js.map +1 -1
  61. package/dist/index.d.ts +1 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +1 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/modes/interactive/components/scroll-widget.d.ts +23 -0
  66. package/dist/modes/interactive/components/scroll-widget.d.ts.map +1 -0
  67. package/dist/modes/interactive/components/scroll-widget.js +61 -0
  68. package/dist/modes/interactive/components/scroll-widget.js.map +1 -0
  69. package/dist/modes/interactive/interactive-extension-runtime.js +10 -3
  70. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  71. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-mode-base.js +3 -2
  73. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  74. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +4 -1
  75. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  76. package/dist/modes/interactive-engine/engine-custom-ui.js +15 -7
  77. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  78. package/dist/modes/interactive-engine/engine-dialog-host.d.ts.map +1 -1
  79. package/dist/modes/interactive-engine/engine-dialog-host.js +4 -1
  80. package/dist/modes/interactive-engine/engine-dialog-host.js.map +1 -1
  81. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
  82. package/dist/modes/interactive-engine/extension-ui-bridge.js +1 -1
  83. package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
  84. package/dist/modes/interactive-engine/protocol.d.ts +12 -1
  85. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  86. package/dist/modes/interactive-engine/protocol.js +45 -2
  87. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  88. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  89. package/dist/modes/interactive-engine/remote-component.js +18 -8
  90. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  91. package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
  92. package/dist/modes/rpc/rpc-extension-ui.js +2 -1
  93. package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
  94. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  95. package/dist/modes/rpc/rpc-mode.js +1 -0
  96. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  97. package/dist/modes/rpc/rpc-types.d.ts +4 -0
  98. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  99. package/dist/modes/rpc/rpc-types.js.map +1 -1
  100. package/docs/background-tasks.md +2 -0
  101. package/docs/extensions/api-reference.md +2 -0
  102. package/docs/extensions/authoring.md +2 -0
  103. package/docs/keybindings.md +17 -0
  104. package/docs/tui.md +34 -0
  105. package/docs/workflows/api-reference.md +2 -0
  106. package/docs/workflows/operations.md +13 -1
  107. package/npm-shrinkwrap.json +32 -32
  108. package/package.json +3 -3
  109. /package/dist/builtin/workflows/builtin/{chunk-ngkqkzej.js → chunk-m0xfsef8.js} +0 -0
@@ -2304,6 +2304,13 @@ function isTopLevelWorkflowRun(run) {
2304
2304
  }
2305
2305
 
2306
2306
  // dist/builtin/workflows/src/shared/timing.ts
2307
+ function stageTimingFields(stage = {}) {
2308
+ return {
2309
+ ...stage.startedAt !== undefined ? { startedAt: stage.startedAt } : {},
2310
+ ...stage.endedAt !== undefined ? { endedAt: stage.endedAt } : {},
2311
+ ...stage.durationMs !== undefined ? { durationMs: stage.durationMs } : {}
2312
+ };
2313
+ }
2307
2314
  function nonNegative(ms) {
2308
2315
  return Math.max(0, ms);
2309
2316
  }
@@ -2329,6 +2336,8 @@ function elapsedStageMs(stage, now = Date.now()) {
2329
2336
  return nonNegative(stage.durationMs);
2330
2337
  if (stage.startedAt === undefined)
2331
2338
  return;
2339
+ if (stage.replayed && stage.endedAt === undefined)
2340
+ return;
2332
2341
  const effectiveNow = stage.endedAt ?? now;
2333
2342
  return elapsedFromStart(stage.startedAt, effectiveNow, stage.pausedDurationMs, stage.pausedAt);
2334
2343
  }
@@ -5994,6 +6003,7 @@ function cachedStageId(runId, replayKey) {
5994
6003
  function stageMetadataCheckpointId(replayKey, stage) {
5995
6004
  return `${stableCheckpointId("stage-meta", replayKey)}:${durableHash({
5996
6005
  stageId: stage.id,
6006
+ ...stage.replayed === true ? { replayed: true } : {},
5997
6007
  status: stage.status,
5998
6008
  endedAt: stage.endedAt ?? 0,
5999
6009
  durationMs: stage.durationMs ?? 0,
@@ -6001,11 +6011,9 @@ function stageMetadataCheckpointId(replayKey, stage) {
6001
6011
  })}`;
6002
6012
  }
6003
6013
  function recordCachedStageIntoStore(store, runId, name, replayKey, output, completedStageReplayKeys, parentIds, checkpoint) {
6004
- const now = Date.now();
6005
6014
  const sourceStageId = checkpoint?.topology?.run?.runId === runId ? checkpoint.topology.stageId : undefined;
6006
6015
  const stageId = sourceStageId ?? cachedStageId(runId, replayKey);
6007
6016
  const result = checkpoint?.result ?? (typeof output === "string" ? output : JSON.stringify(output));
6008
- const endedAt = checkpoint?.endedAt ?? checkpoint?.completedAt ?? now;
6009
6017
  const hasCurrentIdentity = checkpoint?.topology?.sourceOrder !== undefined || checkpoint?.topology?.status !== undefined || checkpoint?.topology?.occurrenceKey !== undefined || checkpoint?.topology?.boundary !== undefined;
6010
6018
  const childResult = parseWorkflowChildResult(output) ?? (hasCurrentIdentity ? undefined : parseLegacyWorkflowChildResult(output));
6011
6019
  const workflowChild = childResult === undefined ? undefined : workflowChildSnapshotFromResult(childResult);
@@ -6015,9 +6023,7 @@ function recordCachedStageIntoStore(store, runId, name, replayKey, output, compl
6015
6023
  name,
6016
6024
  status: "completed",
6017
6025
  parentIds: parentIds !== undefined ? Object.freeze([...parentIds]) : [],
6018
- startedAt: checkpoint?.startedAt ?? endedAt,
6019
- endedAt,
6020
- durationMs: checkpoint?.durationMs ?? 0,
6026
+ ...stageTimingFields(checkpoint),
6021
6027
  result,
6022
6028
  replayKey,
6023
6029
  replayed: true,
@@ -8908,7 +8914,7 @@ function appendStageStart(api, payload) {
8908
8914
  ...payload.replayKey !== undefined ? { replayKey: payload.replayKey } : {},
8909
8915
  ...payload.replayedFromStageId !== undefined ? { replayedFromStageId: payload.replayedFromStageId } : {},
8910
8916
  ...payload.replayed !== undefined ? { replayed: payload.replayed } : {},
8911
- ts: payload.ts
8917
+ ...payload.ts !== undefined ? { ts: payload.ts } : {}
8912
8918
  });
8913
8919
  }
8914
8920
  function appendStageEnd(api, payload, opts) {
@@ -8919,6 +8925,7 @@ function appendStageEnd(api, payload, opts) {
8919
8925
  stageId: payload.stageId,
8920
8926
  status: payload.status,
8921
8927
  ...payload.durationMs !== undefined ? { durationMs: payload.durationMs } : {},
8928
+ ...payload.endedAt !== undefined ? { endedAt: payload.endedAt } : {},
8922
8929
  ...payload.summary !== undefined ? { summary: payload.summary } : {},
8923
8930
  ...payload.error !== undefined ? { error: payload.error } : {},
8924
8931
  ...payload.failureKind !== undefined ? { failureKind: payload.failureKind } : {},
@@ -9622,6 +9629,26 @@ function createContinuationReplayIndex(continuation, sourceToContinuationNodeIds
9622
9629
  }
9623
9630
 
9624
9631
  // dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts
9632
+ function continuationPromptAnswer(store, sourceRun, sourceStage) {
9633
+ let run = sourceRun;
9634
+ let stage = sourceStage;
9635
+ const visited = new Set;
9636
+ while (!visited.has(stage.id)) {
9637
+ visited.add(stage.id);
9638
+ const answer = store.getStagePromptAnswer(run.id, stage.id);
9639
+ if (answer !== undefined)
9640
+ return answer;
9641
+ if (!stage.replayed || stage.replayedFromStageId === undefined || run.resumedFromRunId === undefined)
9642
+ break;
9643
+ const parentRun = store.runs().find((candidate) => candidate.id === run.resumedFromRunId);
9644
+ const parentStage = parentRun?.stages.find((candidate) => candidate.id === stage.replayedFromStageId);
9645
+ if (parentRun === undefined || parentStage === undefined)
9646
+ break;
9647
+ run = parentRun;
9648
+ stage = parentStage;
9649
+ }
9650
+ return;
9651
+ }
9625
9652
  function buildPromptNodeUiAdapter(input) {
9626
9653
  const ask = async (descriptor, durableReplay) => {
9627
9654
  input.throwIfWorkflowExitSelected();
@@ -9636,6 +9663,9 @@ function buildPromptNodeUiAdapter(input) {
9636
9663
  const prompt = makePrompt(descriptor);
9637
9664
  const replayKey = promptReplayKey(descriptor);
9638
9665
  const durableTopology = input.durableTopologyForReplayKey?.(replayKey);
9666
+ if (durableTopology?.status === "completed" && durableReplay === undefined) {
9667
+ throw new Error(`insufficient_state: missing durable UI answer for completed prompt ${durableTopology.stageId}`);
9668
+ }
9639
9669
  const stageId = durableTopology?.stageId ?? crypto.randomUUID();
9640
9670
  const provisionalParentIds = input.tracker.onSpawn(stageId, descriptor.kind);
9641
9671
  const replayDecision = input.replayIndex.decide({
@@ -9649,7 +9679,7 @@ function buildPromptNodeUiAdapter(input) {
9649
9679
  if (!sameStringSet(parentIds, provisionalParentIds))
9650
9680
  input.tracker.replaceParents(stageId, parentIds);
9651
9681
  const replaySource = replayDecision.source;
9652
- const continuationAnswer = replayDecision.kind === "replay" ? input.activeStore.getStagePromptAnswer(input.opts.continuation.source.id, replayDecision.source.id) : undefined;
9682
+ const continuationAnswer = replayDecision.kind === "replay" ? continuationPromptAnswer(input.activeStore, input.opts.continuation.source, replayDecision.source) : undefined;
9653
9683
  const replayAnswer = durableReplay === undefined ? continuationAnswer : { value: durableReplay.response };
9654
9684
  const shouldReplay = replayAnswer !== undefined;
9655
9685
  if (shouldReplay && durableReplay === undefined)
@@ -9662,13 +9692,11 @@ function buildPromptNodeUiAdapter(input) {
9662
9692
  replayKey,
9663
9693
  status: shouldReplay ? "completed" : "running",
9664
9694
  parentIds: Object.freeze(parentIds),
9665
- startedAt: prompt.createdAt,
9695
+ ...shouldReplay ? stageTimingFields(durableReplay === undefined ? replaySource : input.durableTimingForStageId?.(stageId)) : { startedAt: prompt.createdAt },
9666
9696
  promptFootprint: { ...prompt },
9667
9697
  toolEvents: [],
9668
9698
  attachable: !shouldReplay,
9669
9699
  ...shouldReplay ? {
9670
- endedAt: prompt.createdAt,
9671
- durationMs: 0,
9672
9700
  promptAnswerState: promptAnswerStatus,
9673
9701
  replayedFromStageId: replaySourceId,
9674
9702
  replayed: true
@@ -9698,8 +9726,10 @@ function buildPromptNodeUiAdapter(input) {
9698
9726
  pauseGate = undefined;
9699
9727
  currentPauseGate?.resolve();
9700
9728
  stageSnapshot.status = status;
9701
- stageSnapshot.endedAt = Date.now();
9702
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
9729
+ if (!shouldReplay) {
9730
+ stageSnapshot.endedAt = Date.now();
9731
+ stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
9732
+ }
9703
9733
  input.activeStore.recordStageAttachable(input.runId, stageId, false);
9704
9734
  input.activeStore.recordStageEnd(input.runId, stageSnapshot);
9705
9735
  await input.opts.onStageEnd?.(input.runId, stageSnapshot);
@@ -9709,6 +9739,7 @@ function buildPromptNodeUiAdapter(input) {
9709
9739
  stageId,
9710
9740
  status: stageSnapshot.status,
9711
9741
  durationMs: stageSnapshot.durationMs,
9742
+ endedAt: stageSnapshot.endedAt,
9712
9743
  ...stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {},
9713
9744
  ...stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {},
9714
9745
  ...stageSnapshot.failureCode !== undefined ? { failureCode: stageSnapshot.failureCode } : {},
@@ -9775,7 +9806,7 @@ function buildPromptNodeUiAdapter(input) {
9775
9806
  name: stageSnapshot.name,
9776
9807
  parentIds: stageSnapshot.parentIds,
9777
9808
  ...stageReplayFields(stageSnapshot),
9778
- ts: prompt.createdAt
9809
+ ts: stageSnapshot.startedAt
9779
9810
  });
9780
9811
  }
9781
9812
  if (shouldReplay) {
@@ -11312,7 +11343,7 @@ function wrapUiWithDurable(base, deps) {
11312
11343
  // dist/builtin/workflows/src/engine/primitives/ui.ts
11313
11344
  function buildExitGatedUiContext(input) {
11314
11345
  const base = input.opts.usePromptNodesForUi === true ? input.baseFromPromptNodes() : input.opts.executionMode === "non_interactive" && input.opts.ui === undefined ? makeHeadlessUnavailableUIContext() : normalizeUIContext(input.opts.ui);
11315
- const promptNodeReplay = input.opts.usePromptNodesForUi === true && input.opts.continuation !== undefined;
11346
+ const promptNodeReplay = input.opts.usePromptNodesForUi === true && input.opts.continuation !== undefined && input.opts.continuation.source.id !== input.durableUi?.workflowId;
11316
11347
  const durableBase = input.durableUi !== undefined && !promptNodeReplay ? wrapUiWithDurable(base, input.durableUi) : base;
11317
11348
  const invoke = (call) => {
11318
11349
  input.throwIfWorkflowExitSelected();
@@ -11935,7 +11966,7 @@ function mergeStageDraft(existing, checkpoint, sequence) {
11935
11966
  ...valueOrExisting("thinkingLevel", checkpoint, existing),
11936
11967
  ...valueOrExisting("attemptedModels", checkpoint, existing),
11937
11968
  ...valueOrExisting("modelAttempts", checkpoint, existing),
11938
- ...checkpoint.topology !== undefined ? { topology: checkpoint.topology } : existing?.topology !== undefined ? { topology: existing.topology } : {}
11969
+ ...existing?.topology?.run !== undefined && checkpoint.topology?.run === undefined ? { topology: existing.topology } : checkpoint.topology !== undefined ? { topology: checkpoint.topology } : existing?.topology !== undefined ? { topology: existing.topology } : {}
11939
11970
  };
11940
11971
  }
11941
11972
  function valueOrExisting(key, checkpoint, existing) {
@@ -13226,13 +13257,11 @@ function createWorkflowBoundaryFactory(input) {
13226
13257
  replayKey,
13227
13258
  status: replayedChild !== undefined ? "completed" : "running",
13228
13259
  parentIds: Object.freeze([...parentIds]),
13229
- startedAt,
13260
+ ...replayedChild === undefined ? { startedAt } : stageTimingFields(replaySource),
13230
13261
  toolEvents: [],
13231
13262
  attachable: false,
13232
13263
  ...replaySource !== undefined ? { replayedFromStageId: replaySource.id, replayed: replayedChild !== undefined } : {},
13233
13264
  ...replayedChild !== undefined && replayChildSnapshot !== undefined ? {
13234
- endedAt: startedAt,
13235
- durationMs: 0,
13236
13265
  ...replayDecision.kind === "replay" && replayDecision.source.result !== undefined ? { result: replayDecision.source.result } : {},
13237
13266
  workflowChild: cloneWorkflowChildReplaySnapshot(replayChildSnapshot)
13238
13267
  } : {}
@@ -13249,7 +13278,7 @@ function createWorkflowBoundaryFactory(input) {
13249
13278
  name,
13250
13279
  parentIds: stageSnapshot.parentIds,
13251
13280
  ...stageReplayFields(stageSnapshot),
13252
- ts: startedAt
13281
+ ts: stageSnapshot.startedAt
13253
13282
  });
13254
13283
  };
13255
13284
  const appendStageEndForSnapshot = () => {
@@ -13260,6 +13289,7 @@ function createWorkflowBoundaryFactory(input) {
13260
13289
  stageId,
13261
13290
  status: stageSnapshot.status,
13262
13291
  durationMs: stageSnapshot.durationMs,
13292
+ endedAt: stageSnapshot.endedAt,
13263
13293
  ...stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {},
13264
13294
  ...stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {},
13265
13295
  ...stageSnapshot.failureCode !== undefined ? { failureCode: stageSnapshot.failureCode } : {},
@@ -13296,8 +13326,10 @@ function createWorkflowBoundaryFactory(input) {
13296
13326
  clearBoundaryChildMetadata();
13297
13327
  applyFailureToStage(stageSnapshot, input.classifyExecutorFailure(failureError));
13298
13328
  }
13299
- stageSnapshot.endedAt = Date.now();
13300
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
13329
+ if (replayedChild === undefined) {
13330
+ stageSnapshot.endedAt = Date.now();
13331
+ stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
13332
+ }
13301
13333
  input.activeStore.recordStageEnd(input.runId, stageSnapshot);
13302
13334
  input.opts.onStageEnd?.(input.runId, stageSnapshot);
13303
13335
  appendStageEndForSnapshot();
@@ -13365,6 +13397,12 @@ function createWorkflowBoundaryFactory(input) {
13365
13397
  // dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts
13366
13398
  import { runCallback as runCallback3, runSynchronousCallback } from "@bastani/atomic";
13367
13399
 
13400
+ // dist/builtin/workflows/src/shared/pending-stage-route-readiness.ts
13401
+ var owners = new WeakMap;
13402
+ function workflowPendingStageRouteReady(store, runId) {
13403
+ return owners.get(store)?.ready(runId);
13404
+ }
13405
+
13368
13406
  // dist/builtin/workflows/src/runs/foreground/executor-queued-user-message.ts
13369
13407
  function removedMessages(before, after) {
13370
13408
  const remaining = [...after];
@@ -14259,7 +14297,7 @@ function createReplayStageContext(input) {
14259
14297
  name,
14260
14298
  parentIds: stageSnapshot.parentIds,
14261
14299
  ...stageReplayFields(stageSnapshot),
14262
- ts: stageSnapshot.startedAt ?? Date.now()
14300
+ ts: stageSnapshot.startedAt
14263
14301
  });
14264
14302
  };
14265
14303
  const appendReplayStageEnd = () => {
@@ -14269,7 +14307,8 @@ function createReplayStageContext(input) {
14269
14307
  runId,
14270
14308
  stageId,
14271
14309
  status: stageSnapshot.status,
14272
- durationMs: stageSnapshot.durationMs ?? 0,
14310
+ durationMs: stageSnapshot.durationMs,
14311
+ endedAt: stageSnapshot.endedAt,
14273
14312
  ...stageSnapshot.status === "completed" && stageSnapshot.result !== undefined ? { summary: stageSnapshot.result } : {},
14274
14313
  ...stageSnapshot.skippedReason !== undefined ? { skippedReason: stageSnapshot.skippedReason } : {},
14275
14314
  ...stageSnapshot.sessionId !== undefined ? { sessionId: stageSnapshot.sessionId } : {},
@@ -14287,8 +14326,6 @@ function createReplayStageContext(input) {
14287
14326
  delete stageSnapshot.result;
14288
14327
  stageSnapshot.skippedReason = input.workflowExitSkippedReason(reason);
14289
14328
  }
14290
- stageSnapshot.endedAt = Date.now();
14291
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
14292
14329
  input.activeStore.recordStageEnd(runId, stageSnapshot);
14293
14330
  input.opts.onStageEnd?.(runId, stageSnapshot);
14294
14331
  appendReplayStageEnd();
@@ -15649,16 +15686,52 @@ function stageSessionExtensionRunner(current) {
15649
15686
  }
15650
15687
  return;
15651
15688
  }
15689
+ var shutdowns = new WeakMap;
15690
+ function shutdownStageSession(current) {
15691
+ if (!current)
15692
+ return Promise.resolve();
15693
+ const existing = shutdowns.get(current);
15694
+ if (existing)
15695
+ return existing;
15696
+ const shutdown = Promise.resolve().then(async () => {
15697
+ const runner = stageSessionExtensionRunner(current);
15698
+ if (runner?.hasHandlers("session_shutdown"))
15699
+ await runner.emit({ type: "session_shutdown", reason: "quit" });
15700
+ });
15701
+ shutdowns.set(current, shutdown);
15702
+ return shutdown;
15703
+ }
15704
+
15705
+ class StageSessionBindingCleanupFailure extends AggregateError {
15706
+ constructor(bindingError, cleanupErrors) {
15707
+ super([bindingError, ...cleanupErrors], "atomic-workflows: failed binding cleanup did not release session ownership", {
15708
+ cause: bindingError
15709
+ });
15710
+ this.name = "StageSessionBindingCleanupFailure";
15711
+ }
15712
+ }
15713
+ async function cleanupFailedStageSessionBinding(current, bindingError) {
15714
+ const cleanupErrors = [];
15715
+ try {
15716
+ await shutdownStageSession(current);
15717
+ } catch (error) {
15718
+ cleanupErrors.push(error);
15719
+ }
15720
+ try {
15721
+ await current.dispose();
15722
+ } catch (error) {
15723
+ cleanupErrors.push(error);
15724
+ }
15725
+ if (cleanupErrors.length > 0)
15726
+ throw new StageSessionBindingCleanupFailure(bindingError, cleanupErrors);
15727
+ }
15652
15728
  async function disposeStageSession(current) {
15653
15729
  if (!current)
15654
15730
  return;
15655
- const runner = stageSessionExtensionRunner(current);
15656
- if (runner?.hasHandlers("session_shutdown")) {
15657
- try {
15658
- await runner.emit({ type: "session_shutdown", reason: "quit" });
15659
- } catch (error) {
15660
- console.error("atomic-workflows: stage session_shutdown handler failed", error);
15661
- }
15731
+ try {
15732
+ await shutdownStageSession(current);
15733
+ } catch (error) {
15734
+ console.error("atomic-workflows: stage session_shutdown handler failed", error);
15662
15735
  }
15663
15736
  await current.dispose();
15664
15737
  }
@@ -15972,6 +16045,27 @@ function terminatingToolCallId(event) {
15972
16045
  return typeof callId === "string" && callId.length > 0 ? callId : undefined;
15973
16046
  }
15974
16047
 
16048
+ // dist/builtin/workflows/src/runs/foreground/stage-startup-wait.ts
16049
+ function waitForStageStartup(operation, signal) {
16050
+ return new Promise((resolve, reject) => {
16051
+ const onAbort = () => {
16052
+ signal.removeEventListener("abort", onAbort);
16053
+ reject(signal.reason ?? new DOMException("Stage startup cancelled", "AbortError"));
16054
+ };
16055
+ operation.then((value) => {
16056
+ signal.removeEventListener("abort", onAbort);
16057
+ resolve(value);
16058
+ }, (error) => {
16059
+ signal.removeEventListener("abort", onAbort);
16060
+ reject(error);
16061
+ });
16062
+ if (signal.aborted)
16063
+ onAbort();
16064
+ else
16065
+ signal.addEventListener("abort", onAbort, { once: true });
16066
+ });
16067
+ }
16068
+
15975
16069
  // dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts
15976
16070
  function hasMeaningfulUsage(usage) {
15977
16071
  if (usage === undefined)
@@ -16052,9 +16146,14 @@ class StageSessionController {
16052
16146
  activeCreation;
16053
16147
  ownedCreationPromise;
16054
16148
  abortGeneration = 0;
16149
+ routeAuthorityWait;
16150
+ startupWait = new AbortController;
16151
+ startup;
16055
16152
  abortReason;
16056
16153
  abortReasonGeneration = 0;
16057
16154
  sessionPromise;
16155
+ sessionShutdownPromise;
16156
+ bindingCleanupFailure;
16058
16157
  reattachSessionFile;
16059
16158
  lastPromptStartIndex;
16060
16159
  attemptUsageStartIndex;
@@ -16171,8 +16270,7 @@ class StageSessionController {
16171
16270
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${error}. Retrying with ${candidateLabel(nextCandidate)}.`);
16172
16271
  this.modelWarnings.push(...this.pendingFallbackWarnings);
16173
16272
  this.pendingFallbackWarnings.length = 0;
16174
- await this.disposeCurrentSession();
16175
- this.activeCandidateIndex = index + 1;
16273
+ await this.disposeCurrentSession(index + 1);
16176
16274
  this.notifyModelFallbackMetaChange();
16177
16275
  return true;
16178
16276
  }
@@ -16200,28 +16298,41 @@ class StageSessionController {
16200
16298
  this.session?.setThinkingLevel(level);
16201
16299
  }
16202
16300
  async ensureSession(consumer = "prompt") {
16301
+ if (this.bindingCleanupFailure !== undefined)
16302
+ throw this.bindingCleanupFailure;
16303
+ if (this.sessionShutdownPromise !== undefined) {
16304
+ const generation = this.abortGeneration;
16305
+ await this.sessionShutdownPromise;
16306
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== generation)
16307
+ throw this.staleCreationReason(generation);
16308
+ }
16203
16309
  if (this.disposed)
16204
16310
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
16205
- if (this.session !== undefined)
16311
+ this.opts.signal?.throwIfAborted();
16312
+ if (this.session !== undefined && this.activeCreation === undefined)
16206
16313
  return this.session;
16207
16314
  if (!this.sessionPromise) {
16315
+ this.beginStartup();
16208
16316
  const pending = this.createInitialSession(consumer);
16209
16317
  this.sessionPromise = pending;
16210
16318
  this.ownedCreationPromise = pending;
16211
- const release = () => {
16319
+ const release = (failed = false) => {
16212
16320
  if (this.ownedCreationPromise === pending)
16213
16321
  this.ownedCreationPromise = undefined;
16322
+ this.settleStartup(failed);
16214
16323
  };
16215
- pending.then(release, () => {
16216
- release();
16217
- if (this.sessionPromise === pending)
16324
+ pending.then(() => release(), () => {
16325
+ release(true);
16326
+ if (this.sessionPromise === pending) {
16218
16327
  this.sessionPromise = undefined;
16328
+ this.activeCandidateIndex = undefined;
16329
+ }
16219
16330
  });
16220
16331
  }
16221
- return this.sessionPromise;
16332
+ return waitForStageStartup(this.sessionPromise, this.startupWait.signal);
16222
16333
  }
16223
16334
  async ensureSessionFromFile(sessionFile, consumer = "prompt") {
16224
- if (!this.sessionPromise && !this.session)
16335
+ if (!this.sessionShutdownPromise && !this.sessionPromise && !this.session)
16225
16336
  this.reattachSessionFile = sessionFile;
16226
16337
  const session = await this.ensureSession(consumer);
16227
16338
  await session.closeWorkflowStageGeneration?.();
@@ -16266,6 +16377,10 @@ class StageSessionController {
16266
16377
  this.artifactCapture.close();
16267
16378
  }
16268
16379
  async promptWithFallback(text, sdkOptions, consumer = "prompt") {
16380
+ if (this.session !== undefined && this.activeCreation === undefined && this.ownedCreationPromise === undefined && this.startupWait.signal.aborted) {
16381
+ this.opts.signal?.throwIfAborted();
16382
+ this.startupWait = new AbortController;
16383
+ }
16269
16384
  if (!this.hasExplicitModelFallbackConfig) {
16270
16385
  try {
16271
16386
  const activeSession = await this.ensureSession(consumer);
@@ -16288,7 +16403,13 @@ class StageSessionController {
16288
16403
  }
16289
16404
  return;
16290
16405
  }
16291
- const candidates = await this.modelCandidates();
16406
+ const readinessOwner = this.ownedCreationPromise ?? this.activeCreation;
16407
+ if (readinessOwner !== undefined)
16408
+ await waitForStageStartup(readinessOwner, this.startupWait.signal);
16409
+ if (this.session === undefined)
16410
+ this.beginStartup();
16411
+ const startupSignal = this.startupWait.signal;
16412
+ const candidates = await waitForStageStartup(this.modelCandidates(), startupSignal);
16292
16413
  if (candidates.length === 0) {
16293
16414
  try {
16294
16415
  const activeSession = await this.ensureSession(consumer);
@@ -16304,14 +16425,9 @@ class StageSessionController {
16304
16425
  }
16305
16426
  return;
16306
16427
  }
16307
- if (this.session === undefined && this.sessionPromise !== undefined) {
16308
- try {
16309
- await this.sessionPromise;
16310
- } catch (error) {
16311
- if (error instanceof StageSessionCreationCancelled)
16312
- return;
16313
- }
16314
- }
16428
+ const creationOwner = this.ownedCreationPromise ?? this.activeCreation;
16429
+ if (creationOwner !== undefined)
16430
+ await waitForStageStartup(creationOwner, startupSignal);
16315
16431
  const resumedText = this.pendingCreationResumeMessage;
16316
16432
  this.pendingCreationResumeMessage = undefined;
16317
16433
  let promptText = resumedText ?? text;
@@ -16319,9 +16435,21 @@ class StageSessionController {
16319
16435
  return;
16320
16436
  let index = this.activeCandidateIndex ?? 0;
16321
16437
  while (index < candidates.length) {
16438
+ if (this.ownedCreationPromise !== undefined || this.activeCreation !== undefined) {
16439
+ try {
16440
+ await waitForStageStartup(this.ownedCreationPromise ?? this.activeCreation, startupSignal);
16441
+ } catch (error) {
16442
+ if (error instanceof StageSessionCreationCancelled)
16443
+ return;
16444
+ throw error;
16445
+ }
16446
+ index = this.activeCandidateIndex ?? index;
16447
+ promptText = this.pendingCreationResumeMessage ?? promptText;
16448
+ this.pendingCreationResumeMessage = undefined;
16449
+ }
16322
16450
  const candidate = candidates[index];
16323
16451
  try {
16324
- const created = this.session && this.activeCandidateIndex === index ? this.session : await this.createSessionWithThrownErrorRetry(candidate, consumer);
16452
+ const created = this.session && this.activeCandidateIndex === index ? this.session : await waitForStageStartup(this.createSessionWithThrownErrorRetry(candidate, consumer), startupSignal);
16325
16453
  if (isSessionCreationPauseResult(created)) {
16326
16454
  if (created.resumeMessage === undefined)
16327
16455
  return;
@@ -16348,8 +16476,10 @@ class StageSessionController {
16348
16476
  const failure = await this.handleCandidateFailure(err, candidate, candidates, index);
16349
16477
  if (failure === "handled")
16350
16478
  return;
16351
- if (failure === "throw")
16479
+ if (failure === "throw") {
16480
+ this.settleStartup(true);
16352
16481
  throw err;
16482
+ }
16353
16483
  index += 1;
16354
16484
  }
16355
16485
  }
@@ -16392,7 +16522,8 @@ class StageSessionController {
16392
16522
  this.messageAdmission.dispose();
16393
16523
  this.deliveryActivity.dispose();
16394
16524
  await this.replacement.dispose();
16395
- await disposeStageSession(this.session);
16525
+ if (this.activeCreation === undefined)
16526
+ await disposeStageSession(this.session);
16396
16527
  }
16397
16528
  async drainPendingDisposal() {
16398
16529
  if (!this.pendingDisposal)
@@ -16466,6 +16597,12 @@ class StageSessionController {
16466
16597
  this.abortGeneration += 1;
16467
16598
  this.abortReason = reason;
16468
16599
  this.abortReasonGeneration = this.abortGeneration;
16600
+ this.routeAuthorityWait?.abort(reason);
16601
+ this.startupWait.abort(reason);
16602
+ if (this.startup?.state === "active") {
16603
+ this.startup = { ...this.startup, state: "cancelled" };
16604
+ this.opts.onStartupChange?.(this.startup);
16605
+ }
16469
16606
  this.abortThrownErrorRetries(reason);
16470
16607
  }
16471
16608
  pauseThrownErrorRetries(resume) {
@@ -16706,11 +16843,15 @@ class StageSessionController {
16706
16843
  return this.candidatesPromise;
16707
16844
  }
16708
16845
  async createInitialSession(consumer) {
16846
+ const generation = this.abortGeneration;
16709
16847
  if (!this.hasExplicitModelFallbackConfig) {
16710
16848
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
16711
16849
  }
16712
16850
  const candidates = await this.modelCandidates();
16713
- const first = candidates[0];
16851
+ if (this.abortGeneration !== generation || this.opts.signal?.aborted || this.disposed)
16852
+ throw this.staleCreationReason(generation);
16853
+ const initialIndex = this.activeCandidateIndex ?? 0;
16854
+ const first = candidates[initialIndex];
16714
16855
  if (first === undefined) {
16715
16856
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
16716
16857
  }
@@ -16723,9 +16864,9 @@ class StageSessionController {
16723
16864
  this.resumeCurrentSession = true;
16724
16865
  return resumed;
16725
16866
  }
16726
- this.activeCandidateIndex = 0;
16867
+ this.activeCandidateIndex = initialIndex;
16727
16868
  this.selectedModel = first.id;
16728
- return this.createSessionObservingPause(first, consumer).catch((error) => this.createInitialSessionCandidateWalk(candidates, consumer, 0, { error }));
16869
+ return this.createSessionObservingPause(first, consumer).catch((error) => this.createInitialSessionCandidateWalk(candidates, consumer, initialIndex, { error }));
16729
16870
  }
16730
16871
  async createInitialSessionCandidateWalk(candidates, consumer, startIndex, initialFailure) {
16731
16872
  let index = startIndex;
@@ -16807,11 +16948,13 @@ class StageSessionController {
16807
16948
  }
16808
16949
  return await this.createSessionObservingPause(candidate, consumer);
16809
16950
  } catch (error) {
16951
+ if (error instanceof StageSessionBindingCleanupFailure)
16952
+ throw error;
16810
16953
  const errorSettingsManager = retrySettingsManagerFromError(error);
16811
16954
  if (errorSettingsManager !== undefined)
16812
16955
  this.sessionSettingsManager = errorSettingsManager;
16813
16956
  const decision = isWorkflowPendingStageDeliveryFailure(error) ? undefined : nextRetryDecision(this.retrySettings(), retryAttempt, isRetryableSameModelFailure(error));
16814
- if (decision === undefined || this.disposed || this.opts.signal?.aborted === true || this.capturedStructuredOutputForAttempt()) {
16957
+ if (decision === undefined || this.disposed || this.opts.signal?.aborted === true || this.startupWait.signal.aborted || this.capturedStructuredOutputForAttempt()) {
16815
16958
  throw error;
16816
16959
  }
16817
16960
  retryAttempt = decision.attempt;
@@ -16840,16 +16983,86 @@ class StageSessionController {
16840
16983
  }
16841
16984
  }
16842
16985
  createSession(candidate, consumer, resumeOptions) {
16986
+ if (this.bindingCleanupFailure !== undefined)
16987
+ return Promise.reject(this.bindingCleanupFailure);
16988
+ if (this.sessionShutdownPromise !== undefined) {
16989
+ const generation = this.abortGeneration;
16990
+ return this.sessionShutdownPromise.then(() => {
16991
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== generation)
16992
+ throw this.staleCreationReason(generation);
16993
+ return this.createSession(candidate, consumer, resumeOptions);
16994
+ });
16995
+ }
16996
+ if (this.activeCreation !== undefined)
16997
+ return this.activeCreation;
16998
+ if (this.session !== undefined)
16999
+ return Promise.resolve(this.session);
17000
+ this.startupWait.signal.throwIfAborted();
16843
17001
  const creation = this.createSessionAttempt(candidate, consumer, resumeOptions);
16844
17002
  this.activeCreation = creation;
16845
17003
  creation.finally(() => {
16846
17004
  if (this.activeCreation === creation)
16847
17005
  this.activeCreation = undefined;
17006
+ this.settleStartup();
16848
17007
  }).catch(() => {});
16849
17008
  return creation;
16850
17009
  }
17010
+ settleStartup(failed = false) {
17011
+ if (this.activeCreation !== undefined || this.ownedCreationPromise !== undefined || this.startup === undefined)
17012
+ return;
17013
+ if (this.startup.state === "dispatched" || this.startup.phase === "ready")
17014
+ return;
17015
+ if (this.startup.state === "active" && !failed && this.bindingCleanupFailure === undefined)
17016
+ return;
17017
+ this.startup = {
17018
+ ...this.startup,
17019
+ state: this.startup.state === "active" ? "failed" : this.startup.state,
17020
+ ownershipPending: this.bindingCleanupFailure !== undefined,
17021
+ ...this.bindingCleanupFailure === undefined ? { settledAt: Date.now() } : {}
17022
+ };
17023
+ this.opts.onStartupChange?.(this.startup);
17024
+ }
17025
+ beginStartup() {
17026
+ if (this.activeCreation !== undefined || this.ownedCreationPromise !== undefined || this.sessionPromise !== undefined)
17027
+ return;
17028
+ this.opts.signal?.throwIfAborted();
17029
+ if (this.startupWait.signal.aborted)
17030
+ this.startupWait = new AbortController;
17031
+ this.reportStartupPhase("model-resolution", true);
17032
+ }
17033
+ reportStartupPhase(phase, reset = false) {
17034
+ if (!reset && this.startup !== undefined && this.startup.state !== "active")
17035
+ return;
17036
+ const now = Date.now();
17037
+ this.startup = {
17038
+ phase,
17039
+ startedAt: reset ? now : this.startup?.startedAt ?? now,
17040
+ phaseStartedAt: now,
17041
+ state: phase === "first-dispatch" ? "dispatched" : "active",
17042
+ ownershipPending: phase !== "first-dispatch" && phase !== "ready"
17043
+ };
17044
+ this.opts.onStartupChange?.(this.startup);
17045
+ }
16851
17046
  async createSessionAttempt(candidate, consumer, resumeOptions) {
16852
17047
  const startGeneration = this.abortGeneration;
17048
+ if (this.disposed || this.opts.signal?.aborted)
17049
+ throw this.staleCreationReason(startGeneration);
17050
+ this.reportStartupPhase("route-authority");
17051
+ const authority = this.opts.routeAuthorityReady?.();
17052
+ if (authority !== undefined) {
17053
+ const wait = new AbortController;
17054
+ this.routeAuthorityWait = wait;
17055
+ try {
17056
+ await raceAbort2(authority.completion, wait.signal);
17057
+ authority.assertCurrent();
17058
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== startGeneration)
17059
+ throw this.staleCreationReason(startGeneration);
17060
+ } finally {
17061
+ if (this.routeAuthorityWait === wait)
17062
+ this.routeAuthorityWait = undefined;
17063
+ }
17064
+ }
17065
+ this.reportStartupPhase("resource-preparation");
16853
17066
  this.applyCandidateThinking(candidate);
16854
17067
  const stageOptions = buildStageSessionOptions({
16855
17068
  effectiveStageOptions: this.effectiveStageOptions,
@@ -16862,10 +17075,19 @@ class StageSessionController {
16862
17075
  try {
16863
17076
  created = this.opts.adapters.agentSession ? await this.opts.adapters.agentSession.create(stripWorkflowOnlyOptions(stageOptions, this.opts.defaultSessionDir, this.meta, this.opts.pendingStageDelivery), {
16864
17077
  ...this.meta,
17078
+ startupSignal: this.startupWait.signal,
17079
+ onStartupPhase: (phase) => {
17080
+ if (this.abortGeneration === startGeneration)
17081
+ this.reportStartupPhase(phase);
17082
+ },
16865
17083
  stageOptions,
16866
17084
  ...this.sharedOrchestrationContext !== undefined ? { orchestrationContext: this.sharedOrchestrationContext } : {}
16867
17085
  }) : missingAdapter(consumer);
16868
17086
  } catch (error) {
17087
+ if (error instanceof StageSessionBindingCleanupFailure) {
17088
+ this.bindingCleanupFailure = error;
17089
+ throw error;
17090
+ }
16869
17091
  if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
16870
17092
  throw this.disposed ? new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`) : this.staleCreationReason(startGeneration);
16871
17093
  throw error;
@@ -16876,20 +17098,28 @@ class StageSessionController {
16876
17098
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
16877
17099
  throw this.staleCreationReason(startGeneration);
16878
17100
  }
17101
+ this.reportStartupPhase("session-attachment");
16879
17102
  const session = attachCreatedStageSession(created, this.disposed, this.opts.stageName, (result) => this.attachSession(result));
16880
17103
  const attachedSession = session instanceof Promise ? await session : session;
16881
- const pendingStageDeliveryReady = this.sharedOrchestrationContext?.pendingStageDelivery?.ready();
16882
- if (pendingStageDeliveryReady !== undefined) {
17104
+ this.reportStartupPhase("delivery-readiness");
17105
+ try {
17106
+ await this.sharedOrchestrationContext?.pendingStageDelivery?.ready();
17107
+ await this.opts.onSessionReady?.();
17108
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== startGeneration)
17109
+ throw this.staleCreationReason(startGeneration);
17110
+ } catch (reason) {
17111
+ if (this.session === attachedSession)
17112
+ this.session = undefined;
16883
17113
  try {
16884
- await pendingStageDeliveryReady;
17114
+ await cleanupFailedStageSessionBinding(attachedSession, reason);
16885
17115
  } catch (error) {
16886
- if (this.session === attachedSession)
16887
- this.session = undefined;
16888
- await disposeStageSession(attachedSession).catch(() => {});
17116
+ if (error instanceof StageSessionBindingCleanupFailure)
17117
+ this.bindingCleanupFailure = error;
16889
17118
  throw error;
16890
17119
  }
17120
+ throw reason;
16891
17121
  }
16892
- await this.opts.onSessionReady?.();
17122
+ this.reportStartupPhase("ready");
16893
17123
  return attachedSession;
16894
17124
  }
16895
17125
  attachSession(created) {
@@ -16929,12 +17159,16 @@ class StageSessionController {
16929
17159
  });
16930
17160
  return result.session;
16931
17161
  }
16932
- async disposeCurrentSession() {
17162
+ async disposeCurrentSession(nextCandidateIndex) {
17163
+ if (this.sessionShutdownPromise !== undefined)
17164
+ await this.sessionShutdownPromise;
17165
+ const startGeneration = this.abortGeneration;
16933
17166
  this.abortThrownErrorRetries(new Error(`atomic-workflows: stage "${this.opts.stageName}" session was replaced`));
16934
17167
  const current = this.session;
16935
17168
  this.messageAdmission.reset();
16936
17169
  this.replacement.retire(current);
16937
17170
  this.session = undefined;
17171
+ this.activeCandidateIndex = nextCandidateIndex;
16938
17172
  if (this.sessionPromise !== this.ownedCreationPromise)
16939
17173
  this.sessionPromise = undefined;
16940
17174
  this.sessionSettingsManager = undefined;
@@ -16945,6 +17179,15 @@ class StageSessionController {
16945
17179
  this.unsubscribeTerminateWatcher?.();
16946
17180
  this.unsubscribeTerminateWatcher = undefined;
16947
17181
  this.terminatingToolCallIds.clear();
17182
+ const shutdown = Promise.resolve().then(() => shutdownStageSession(current));
17183
+ this.sessionShutdownPromise = shutdown;
17184
+ shutdown.then(() => {
17185
+ if (this.sessionShutdownPromise === shutdown)
17186
+ this.sessionShutdownPromise = undefined;
17187
+ }, () => {});
17188
+ await shutdown;
17189
+ if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
17190
+ throw this.staleCreationReason(startGeneration);
16948
17191
  }
16949
17192
  async promptWithPauseResume(activeSession, initialText, sdkOptions) {
16950
17193
  let nextText = initialText;
@@ -16962,6 +17205,7 @@ class StageSessionController {
16962
17205
  this.lastPromptStartIndex = promptStartIndex;
16963
17206
  this.unresolvedContextOverflowMessage = undefined;
16964
17207
  try {
17208
+ this.reportStartupPhase("first-dispatch");
16965
17209
  await activeSession.prompt(nextText, sdkOptions);
16966
17210
  const pendingPauseAfterPrompt = this.pauseControl.currentResume();
16967
17211
  if (pendingPauseAfterPrompt) {
@@ -17054,12 +17298,13 @@ class StageSessionController {
17054
17298
  throw err;
17055
17299
  }
17056
17300
  this.pendingFallbackWarnings.push(resumedOverflowNextIndex === undefined ? `[fallback] resume on ${resumedLabel} failed: ${message}. Restarting fallback from ${candidateLabel(candidates[0])}.` : `[fallback] resume on ${resumedLabel} failed: ${message}. Retrying with ${candidateLabel(candidates[resumedOverflowNextIndex])}.`);
17057
- await this.disposeCurrentSession();
17058
- this.activeCandidateIndex = resumedOverflowNextIndex;
17301
+ await this.disposeCurrentSession(resumedOverflowNextIndex);
17059
17302
  return false;
17060
17303
  }
17061
17304
  }
17062
17305
  async handleCandidateFailure(err, candidate, candidates, index) {
17306
+ if (err instanceof StageSessionBindingCleanupFailure)
17307
+ throw err;
17063
17308
  const message = errorMessage2(err);
17064
17309
  const terminalStageDelivery = isWorkflowPendingStageDeliveryFailure(err);
17065
17310
  if (!terminalStageDelivery && this.capturedStructuredOutputForAttempt() && isRetryableModelFailure(err)) {
@@ -17075,7 +17320,7 @@ class StageSessionController {
17075
17320
  ...usage === undefined ? {} : { usage },
17076
17321
  error: message
17077
17322
  });
17078
- if (this.opts.signal?.aborted || terminalStageDelivery || !isRetryableModelFailure(err) || index === candidates.length - 1) {
17323
+ if (this.opts.signal?.aborted || this.startupWait.signal.aborted || terminalStageDelivery || !isRetryableModelFailure(err) || index === candidates.length - 1) {
17079
17324
  this.modelWarnings.push(...this.pendingFallbackWarnings);
17080
17325
  this.pendingFallbackWarnings.length = 0;
17081
17326
  this.notifyModelFallbackMetaChange();
@@ -17083,7 +17328,7 @@ class StageSessionController {
17083
17328
  }
17084
17329
  const nextCandidate = candidates[index + 1];
17085
17330
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${message}. Retrying with ${candidateLabel(nextCandidate)}.`);
17086
- await this.disposeCurrentSession();
17331
+ await this.disposeCurrentSession(index + 1);
17087
17332
  return "retry";
17088
17333
  }
17089
17334
  capturedStructuredOutputForAttempt() {
@@ -17724,9 +17969,7 @@ function createWorkflowStageFactory(input) {
17724
17969
  toolEvents: [],
17725
17970
  pendingStageDeliveryAvailable,
17726
17971
  ...shouldReplay ? {
17727
- startedAt: Date.now(),
17728
- endedAt: Date.now(),
17729
- durationMs: 0,
17972
+ ...stageTimingFields(replaySource),
17730
17973
  ...replaySource.result !== undefined ? { result: replaySource.result } : {},
17731
17974
  ...replaySource.sessionId !== undefined ? { sessionId: replaySource.sessionId } : {},
17732
17975
  ...replaySource.sessionFile !== undefined ? { sessionFile: replaySource.sessionFile } : {},
@@ -17789,11 +18032,19 @@ function createWorkflowStageFactory(input) {
17789
18032
  signal: input.signal,
17790
18033
  stageOptions: stageOptionsForContext,
17791
18034
  ...pendingStageDeliveryAvailable ? {
18035
+ routeAuthorityReady: () => workflowPendingStageRouteReady(input.activeStore, input.runId),
17792
18036
  pendingStageDelivery: createWorkflowPendingStageDelivery(input.activeStore, input.runId, stageId, name)
17793
18037
  } : {},
17794
18038
  models: input.opts.models,
17795
18039
  executionMode: input.opts.executionMode,
17796
18040
  defaultSessionDir: input.opts.defaultSessionDir,
18041
+ onStartupChange(startup) {
18042
+ const current = input.activeStore.runs().find((run) => run.id === input.runId);
18043
+ if (!current?.stages.includes(stageSnapshot))
18044
+ return;
18045
+ stageSnapshot.startup = startup;
18046
+ input.activeStore.recordStageStart(input.runId, stageSnapshot);
18047
+ },
17797
18048
  onModelFallbackMetaChange(meta) {
17798
18049
  applyModelFallbackMeta(meta);
17799
18050
  if (stageSnapshot.status === "running")
@@ -17956,6 +18207,7 @@ function createWorkflowStageFactory(input) {
17956
18207
  stageId,
17957
18208
  status: stageSnapshot.status,
17958
18209
  durationMs: stageSnapshot.durationMs,
18210
+ endedAt: stageSnapshot.endedAt,
17959
18211
  ...stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {},
17960
18212
  ...stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {},
17961
18213
  ...stageSnapshot.failureCode !== undefined ? { failureCode: stageSnapshot.failureCode } : {},
@@ -18561,6 +18813,7 @@ async function run(def, inputs, opts = {}) {
18561
18813
  };
18562
18814
  };
18563
18815
  const resolvePromptNodeTopology = createDurableStageTopologyResolver(durableBackend, runId);
18816
+ const priorPromptStageCheckpoints = durableBackend.listCheckpoints(runId).filter((checkpoint) => checkpoint.kind === "stage");
18564
18817
  let promptNodeUi;
18565
18818
  const getPromptNodeUi = () => {
18566
18819
  promptNodeUi ??= buildPromptNodeUiAdapter({
@@ -18577,6 +18830,7 @@ async function run(def, inputs, opts = {}) {
18577
18830
  workflowExitSkippedReason: exit.workflowExitSkippedReason,
18578
18831
  preserveWorkflowExitSkippedReason: exit.preserveWorkflowExitSkippedReason,
18579
18832
  durableTopologyForReplayKey: resolvePromptNodeTopology,
18833
+ durableTimingForStageId: (stageId) => priorPromptStageCheckpoints.find((checkpoint) => checkpoint.topology?.stageId === stageId && checkpoint.topology.status === "completed" && (checkpoint.topology.run === undefined || checkpoint.topology.run.runId === runId)),
18580
18834
  onPendingStage: async (pendingRunId, snapshot) => pendingRunId === runId ? void await recordDurableActiveStage(durableStageDeps, snapshot) : undefined
18581
18835
  });
18582
18836
  return promptNodeUi;