@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
@@ -67164,6 +67164,13 @@ function topLevelWorkflowRuns(runs) {
67164
67164
  }
67165
67165
 
67166
67166
  // dist/builtin/workflows/src/shared/timing.ts
67167
+ function stageTimingFields(stage = {}) {
67168
+ return {
67169
+ ...stage.startedAt !== undefined ? { startedAt: stage.startedAt } : {},
67170
+ ...stage.endedAt !== undefined ? { endedAt: stage.endedAt } : {},
67171
+ ...stage.durationMs !== undefined ? { durationMs: stage.durationMs } : {}
67172
+ };
67173
+ }
67167
67174
  function nonNegative(ms) {
67168
67175
  return Math.max(0, ms);
67169
67176
  }
@@ -67189,6 +67196,8 @@ function elapsedStageMs(stage, now = Date.now()) {
67189
67196
  return nonNegative(stage.durationMs);
67190
67197
  if (stage.startedAt === undefined)
67191
67198
  return;
67199
+ if (stage.replayed && stage.endedAt === undefined)
67200
+ return;
67192
67201
  const effectiveNow = stage.endedAt ?? now;
67193
67202
  return elapsedFromStart(stage.startedAt, effectiveNow, stage.pausedDurationMs, stage.pausedAt);
67194
67203
  }
@@ -68525,19 +68534,57 @@ function formatWorkflowStageTarget(rootRunId, ...segments) {
68525
68534
 
68526
68535
  // dist/builtin/workflows/src/shared/pending-stage-status.ts
68527
68536
  function workflowBoundarySegments(runs, runId) {
68537
+ return createWorkflowBoundarySegmentsResolver(runs)(runId);
68538
+ }
68539
+ function createWorkflowBoundarySegmentsResolver(runs) {
68528
68540
  const runById = new Map(runs.map((run) => [run.id, run]));
68529
- const segments = [];
68530
- let current = runById.get(runId);
68531
- while (current !== undefined && current.parentRunId !== undefined) {
68532
- const parent = runById.get(current.parentRunId);
68533
- const boundary = parent?.stages.find((stage) => stage.id === current?.parentStageId);
68534
- if (parent === undefined || boundary === undefined)
68535
- return;
68536
- const boundaryName = boundary.name;
68537
- segments.unshift(boundaryName.length > 0 && !boundaryName.includes("/") && !boundaryName.includes("*") ? boundaryName : current.id);
68538
- current = parent;
68541
+ const stagesByRun = new Map;
68542
+ const childrenByParent = new Map;
68543
+ for (const run of runs) {
68544
+ const byId = new Map;
68545
+ const byName = new Map;
68546
+ for (const stage of run.stages) {
68547
+ const ids = byId.get(stage.id) ?? [];
68548
+ ids.push(stage);
68549
+ byId.set(stage.id, ids);
68550
+ const names = byName.get(stage.name) ?? [];
68551
+ names.push(stage);
68552
+ byName.set(stage.name, names);
68553
+ }
68554
+ stagesByRun.set(run.id, { byId, byName });
68555
+ if (run.parentRunId === undefined || run.parentStageId === undefined)
68556
+ continue;
68557
+ const byStage = childrenByParent.get(run.parentRunId) ?? new Map;
68558
+ const children = byStage.get(run.parentStageId) ?? [];
68559
+ children.push(run);
68560
+ byStage.set(run.parentStageId, children);
68561
+ childrenByParent.set(run.parentRunId, byStage);
68539
68562
  }
68540
- return current === undefined ? undefined : segments;
68563
+ const cachedSegments = new Map;
68564
+ return (runId) => {
68565
+ if (cachedSegments.has(runId))
68566
+ return cachedSegments.get(runId);
68567
+ const segments = [];
68568
+ let current = runById.get(runId);
68569
+ while (current !== undefined && current.parentRunId !== undefined) {
68570
+ const parent = runById.get(current.parentRunId);
68571
+ const stages = stagesByRun.get(current.parentRunId);
68572
+ const boundary = current.parentStageId === undefined ? undefined : stages?.byId.get(current.parentStageId)?.[0];
68573
+ if (parent === undefined || stages === undefined || boundary === undefined) {
68574
+ cachedSegments.set(runId, undefined);
68575
+ return;
68576
+ }
68577
+ const boundaryName = boundary.name;
68578
+ const matchingBoundaries = stages.byId.get(boundaryName) ?? stages.byName.get(boundaryName);
68579
+ const matchingBoundary = matchingBoundaries?.length === 1 ? matchingBoundaries[0] : undefined;
68580
+ const children = matchingBoundary === undefined ? undefined : childrenByParent.get(parent.id)?.get(matchingBoundary.id);
68581
+ segments.unshift(boundaryName.length > 0 && !boundaryName.includes("/") && !boundaryName.includes("*") && children?.length === 1 && children[0]?.id === current.id ? boundaryName : current.id);
68582
+ current = parent;
68583
+ }
68584
+ const result = current === undefined ? undefined : segments;
68585
+ cachedSegments.set(runId, result);
68586
+ return result;
68587
+ };
68541
68588
  }
68542
68589
  function workflowBoundaryHops(runs, runId) {
68543
68590
  const runById = new Map(runs.map((run) => [run.id, run]));
@@ -68593,6 +68640,15 @@ function pendingWorkflowStageStatuses(run, resolveOwningRunStatus, resolveBounda
68593
68640
  });
68594
68641
  }
68595
68642
 
68643
+ // dist/builtin/workflows/src/shared/stage-startup.ts
68644
+ function formatStageStartup(startup, now = Date.now()) {
68645
+ const observedAt = startup.state === "dispatched" ? startup.phaseStartedAt : startup.settledAt ?? now;
68646
+ const age = Math.max(0, Math.floor((observedAt - startup.phaseStartedAt) / 1000));
68647
+ const total = Math.max(0, Math.floor((observedAt - startup.startedAt) / 1000));
68648
+ const recovery = startup.state === "cancelled" && startup.ownershipPending ? "; cleanup pending — do not retry in-process; stop the owning Atomic process before fresh execution" : "";
68649
+ return `startup ${startup.phase} (${total}s total, ${age}s on current step; ${startup.state})${recovery}`;
68650
+ }
68651
+
68596
68652
  // dist/builtin/workflows/src/tui/status-helpers.ts
68597
68653
  function statusColor(status, theme) {
68598
68654
  switch (status) {
@@ -68848,9 +68904,15 @@ function pendingStageRows(runId, rootRunId, runStatus, stage, width, theme, reso
68848
68904
  const text = hexToAnsi(theme.text);
68849
68905
  return wrapped.map(({ prefix, chunk }, index) => index === 0 ? ` ${muted}${pad(label, 16)}${RESET}${text}${chunk}${RESET} ` : `${prefix}${text}${chunk}${RESET} `);
68850
68906
  }
68907
+ function startupRows(stage, now, width, theme) {
68908
+ if (stage.startup === undefined)
68909
+ return [];
68910
+ return wrapIdentifierLines(formatStageStartup(stage.startup, now), Math.max(1, width - 2), " ", " ").map(({ prefix, chunk }) => theme === undefined ? `${prefix}${chunk}` : `${prefix}${hexToAnsi(theme.textMuted)}${chunk}${RESET}`);
68911
+ }
68851
68912
  function renderStageRowsPlain(runId, rootRunId, runStatus, stage, now, width, resolveOwningRunStatus, resolveBoundarySegments) {
68852
68913
  const rows = [
68853
68914
  ` ${stageLinePlain(stage, now, Math.max(1, width - 2))} `,
68915
+ ...startupRows(stage, now, width),
68854
68916
  ...pendingStageRows(runId, rootRunId, runStatus, stage, width, undefined, resolveOwningRunStatus, resolveBoundarySegments)
68855
68917
  ];
68856
68918
  if (stage.error) {
@@ -68862,6 +68924,7 @@ function renderStageRowsPlain(runId, rootRunId, runStatus, stage, now, width, re
68862
68924
  function renderStageRowsThemed(runId, rootRunId, runStatus, stage, now, theme, width, resolveOwningRunStatus, resolveBoundarySegments) {
68863
68925
  const rows = [
68864
68926
  ` ${stageLineThemed(stage, now, theme, Math.max(1, width - 2))} `,
68927
+ ...startupRows(stage, now, width, theme),
68865
68928
  ...pendingStageRows(runId, rootRunId, runStatus, stage, width, theme, resolveOwningRunStatus, resolveBoundarySegments)
68866
68929
  ];
68867
68930
  if (stage.error) {
@@ -79145,8 +79208,26 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now(), la
79145
79208
  });
79146
79209
  }
79147
79210
 
79211
+ // dist/builtin/workflows/src/tui/widget-scroll-hint.ts
79212
+ import { keybindingIdentity } from "@bastani/atomic";
79213
+ import { getKeybindings, matchesKey as matchesKey2 } from "@earendil-works/pi-tui";
79214
+ var legacyInputs = Array.from({ length: 128 }, (_, code) => String.fromCharCode(code)).flatMap((data) => [
79215
+ data,
79216
+ `\x1B${data}`
79217
+ ]);
79218
+ function workflowScrollHint(bindings = getKeybindings().getResolvedBindings(), platform = process.platform) {
79219
+ const keys = ["app.workflows.scrollUp", "app.workflows.scrollDown"].map((action) => {
79220
+ const configured = bindings[action] ?? [];
79221
+ return (Array.isArray(configured) ? configured : [configured]).find((key) => {
79222
+ const inputs = legacyInputs.filter((data) => matchesKey2(data, key));
79223
+ return !Object.entries(bindings).some(([other, values]) => other !== action && (Array.isArray(values) ? values : [values]).some((value2) => value2 !== undefined && (keybindingIdentity(value2) === keybindingIdentity(key) || inputs.some((data) => matchesKey2(data, value2)))));
79224
+ });
79225
+ }).filter((key) => key !== undefined);
79226
+ const label = keys.map((key) => key.replace(/^alt\+/i, platform === "darwin" ? "Option+" : "Alt+")).join("/");
79227
+ return ` ↑↓ scroll${label ? ` · ${label}` : ""}`;
79228
+ }
79229
+
79148
79230
  // dist/builtin/workflows/src/tui/widget-viewport.ts
79149
- import { ScrollableComponentViewport } from "@bastani/atomic";
79150
79231
  var WORKFLOW_WIDGET_MAX_ROWS = 10;
79151
79232
 
79152
79233
  class WorkflowWidgetViewport {
@@ -79154,53 +79235,60 @@ class WorkflowWidgetViewport {
79154
79235
  terminalRows;
79155
79236
  requestRender;
79156
79237
  getRunRows;
79157
- viewport = new ScrollableComponentViewport;
79238
+ getHint;
79239
+ position = 0;
79240
+ version = 0;
79241
+ collapsed = false;
79242
+ state;
79158
79243
  lines = [];
79159
- scrollable = false;
79160
79244
  runRows = [];
79161
- constructor(content, terminalRows, requestRender, getRunRows) {
79245
+ constructor(content, terminalRows, requestRender, getRunRows, getHint = () => "") {
79162
79246
  this.content = content;
79163
79247
  this.terminalRows = terminalRows;
79164
79248
  this.requestRender = requestRender;
79165
79249
  this.getRunRows = getRunRows;
79166
- this.viewport.setComponents([{ render: () => this.lines, invalidate() {} }]);
79167
- this.viewport.scrollTo(0);
79250
+ this.getHint = getHint;
79251
+ }
79252
+ getScrollRequest() {
79253
+ return { version: this.version, scrollTop: this.collapsed ? 0 : this.position };
79254
+ }
79255
+ onScroll(state) {
79256
+ this.state = state;
79257
+ if (!this.collapsed)
79258
+ this.position = state.scrollTop;
79168
79259
  }
79169
79260
  scroll(direction) {
79170
- if (!this.scrollable)
79261
+ if (this.collapsed)
79171
79262
  return;
79172
- this.viewport.scrollBy(direction);
79263
+ const height = this.state?.viewportHeight ?? Math.max(1, Math.min(WORKFLOW_WIDGET_MAX_ROWS, Math.floor(this.terminalRows() / 3)));
79264
+ const next = Math.max(0, Math.min(Math.max(0, this.lines.length - height), this.position + direction));
79265
+ if (next === this.position)
79266
+ return;
79267
+ this.position = next;
79268
+ this.version++;
79173
79269
  this.requestRender();
79174
79270
  }
79175
79271
  render(width) {
79176
- const cap = Math.max(1, Math.min(WORKFLOW_WIDGET_MAX_ROWS, Math.floor(this.terminalRows() / 3)));
79177
- const nextLines = this.content.render(width);
79178
- const nextRunRows = this.getRunRows?.() ?? [];
79179
- if (this.getRunRows && nextLines.length === 1 && nextRunRows.length === 0) {
79180
- this.scrollable = false;
79272
+ const content = this.content.render(width);
79273
+ const hint = content.length > 1 ? this.getHint() : "";
79274
+ const nextLines = hint ? [...content, truncateToWidth2(hint, width, "…")] : content;
79275
+ const nextRunRows = (this.getRunRows?.() ?? []).map((run, index, rows) => hint && index === rows.length - 1 ? { ...run, end: run.end + 1 } : run);
79276
+ const collapsed = Boolean(this.getRunRows && nextLines.length === 1 && nextRunRows.length === 0);
79277
+ if (collapsed) {
79278
+ if (!this.collapsed)
79279
+ this.version++;
79280
+ this.collapsed = true;
79181
79281
  return nextLines;
79182
79282
  }
79183
- const previousLineCount = this.lines.length;
79184
- const offset = this.viewport.getMaxScroll() - this.viewport.getScrollFromBottom();
79185
- const anchoredOffset = this.getRunRows ? this.resolveAnchor(offset, nextRunRows) : undefined;
79283
+ const anchoredOffset = this.getRunRows ? this.resolveAnchor(this.position, nextRunRows) : this.position;
79284
+ if (this.collapsed || anchoredOffset !== this.position) {
79285
+ this.position = anchoredOffset;
79286
+ this.version++;
79287
+ }
79288
+ this.collapsed = false;
79186
79289
  this.lines = nextLines;
79187
79290
  this.runRows = nextRunRows;
79188
- if (anchoredOffset !== undefined) {
79189
- this.viewport.scrollTo(anchoredOffset);
79190
- } else if (previousLineCount <= 1 || this.lines.length < previousLineCount && this.lines.length <= cap) {
79191
- this.viewport.scrollTo(0);
79192
- }
79193
- this.scrollable = this.lines.length > 1;
79194
- this.viewport.setVisibleRows(1);
79195
- this.viewport.render(width);
79196
- if (!this.scrollable)
79197
- return this.lines;
79198
- const first = this.viewport.getMaxScroll() - this.viewport.getScrollFromBottom() + 1;
79199
- const visible = this.lines.slice(first - 1, first - 1 + Math.max(1, cap - 1));
79200
- if (cap === 1)
79201
- return visible;
79202
- const hint = ` ${first}–${first + visible.length - 1}/${this.lines.length} · Alt+PgUp/PgDn scroll workflows`;
79203
- return [...visible, truncateToWidth2(hint, width, "…")];
79291
+ return nextLines;
79204
79292
  }
79205
79293
  resolveAnchor(offset, next) {
79206
79294
  if (offset === 0 || next.length === 0)
@@ -79272,7 +79360,7 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
79272
79360
  widgetViewports.delete(storeInstance);
79273
79361
  setWidget.call(ui, key, factory ? (tui, theme) => {
79274
79362
  const host = tui;
79275
- const viewport = new WorkflowWidgetViewport(factory(tui, theme), () => host?.terminal?.rows ?? 30, () => requestRender ? requestRender.call(ui) : host?.requestRender?.(), () => layout.runs);
79363
+ const viewport = new WorkflowWidgetViewport(factory(tui, theme), () => host?.terminal?.rows ?? 30, () => requestRender ? requestRender.call(ui) : host?.requestRender?.(), () => layout.runs, workflowScrollHint);
79276
79364
  widgetViewports.set(storeInstance, viewport);
79277
79365
  return viewport;
79278
79366
  } : undefined, opts);
@@ -79282,6 +79370,7 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
79282
79370
  },
79283
79371
  key: WIDGET_KEY,
79284
79372
  placement: "belowEditor",
79373
+ scroll: { maxHeight: 10, maxHeightFraction: 1 / 3 },
79285
79374
  timers,
79286
79375
  getSnapshot: () => liveWidgetSnapshot(storeInstance),
79287
79376
  subscribe: (listener) => subscribeStoreInvalidation(storeInstance, listener),
@@ -87809,6 +87898,7 @@ function cachedStageId(runId, replayKey) {
87809
87898
  function stageMetadataCheckpointId(replayKey, stage) {
87810
87899
  return `${stableCheckpointId("stage-meta", replayKey)}:${durableHash({
87811
87900
  stageId: stage.id,
87901
+ ...stage.replayed === true ? { replayed: true } : {},
87812
87902
  status: stage.status,
87813
87903
  endedAt: stage.endedAt ?? 0,
87814
87904
  durationMs: stage.durationMs ?? 0,
@@ -87816,11 +87906,9 @@ function stageMetadataCheckpointId(replayKey, stage) {
87816
87906
  })}`;
87817
87907
  }
87818
87908
  function recordCachedStageIntoStore(store, runId, name, replayKey, output, completedStageReplayKeys, parentIds, checkpoint) {
87819
- const now = Date.now();
87820
87909
  const sourceStageId = checkpoint?.topology?.run?.runId === runId ? checkpoint.topology.stageId : undefined;
87821
87910
  const stageId = sourceStageId ?? cachedStageId(runId, replayKey);
87822
87911
  const result = checkpoint?.result ?? (typeof output === "string" ? output : JSON.stringify(output));
87823
- const endedAt = checkpoint?.endedAt ?? checkpoint?.completedAt ?? now;
87824
87912
  const hasCurrentIdentity = checkpoint?.topology?.sourceOrder !== undefined || checkpoint?.topology?.status !== undefined || checkpoint?.topology?.occurrenceKey !== undefined || checkpoint?.topology?.boundary !== undefined;
87825
87913
  const childResult = parseWorkflowChildResult(output) ?? (hasCurrentIdentity ? undefined : parseLegacyWorkflowChildResult(output));
87826
87914
  const workflowChild = childResult === undefined ? undefined : workflowChildSnapshotFromResult(childResult);
@@ -87830,9 +87918,7 @@ function recordCachedStageIntoStore(store, runId, name, replayKey, output, compl
87830
87918
  name,
87831
87919
  status: "completed",
87832
87920
  parentIds: parentIds !== undefined ? Object.freeze([...parentIds]) : [],
87833
- startedAt: checkpoint?.startedAt ?? endedAt,
87834
- endedAt,
87835
- durationMs: checkpoint?.durationMs ?? 0,
87921
+ ...stageTimingFields(checkpoint),
87836
87922
  result,
87837
87923
  replayKey,
87838
87924
  replayed: true,
@@ -88196,7 +88282,7 @@ function appendStageStart(api, payload) {
88196
88282
  ...payload.replayKey !== undefined ? { replayKey: payload.replayKey } : {},
88197
88283
  ...payload.replayedFromStageId !== undefined ? { replayedFromStageId: payload.replayedFromStageId } : {},
88198
88284
  ...payload.replayed !== undefined ? { replayed: payload.replayed } : {},
88199
- ts: payload.ts
88285
+ ...payload.ts !== undefined ? { ts: payload.ts } : {}
88200
88286
  });
88201
88287
  }
88202
88288
  function appendStageEnd(api, payload, opts) {
@@ -88207,6 +88293,7 @@ function appendStageEnd(api, payload, opts) {
88207
88293
  stageId: payload.stageId,
88208
88294
  status: payload.status,
88209
88295
  ...payload.durationMs !== undefined ? { durationMs: payload.durationMs } : {},
88296
+ ...payload.endedAt !== undefined ? { endedAt: payload.endedAt } : {},
88210
88297
  ...payload.summary !== undefined ? { summary: payload.summary } : {},
88211
88298
  ...payload.error !== undefined ? { error: payload.error } : {},
88212
88299
  ...payload.failureKind !== undefined ? { failureKind: payload.failureKind } : {},
@@ -100857,6 +100944,26 @@ function createContinuationReplayIndex(continuation, sourceToContinuationNodeIds
100857
100944
  }
100858
100945
 
100859
100946
  // dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts
100947
+ function continuationPromptAnswer(store, sourceRun, sourceStage) {
100948
+ let run = sourceRun;
100949
+ let stage = sourceStage;
100950
+ const visited = new Set;
100951
+ while (!visited.has(stage.id)) {
100952
+ visited.add(stage.id);
100953
+ const answer = store.getStagePromptAnswer(run.id, stage.id);
100954
+ if (answer !== undefined)
100955
+ return answer;
100956
+ if (!stage.replayed || stage.replayedFromStageId === undefined || run.resumedFromRunId === undefined)
100957
+ break;
100958
+ const parentRun = store.runs().find((candidate) => candidate.id === run.resumedFromRunId);
100959
+ const parentStage = parentRun?.stages.find((candidate) => candidate.id === stage.replayedFromStageId);
100960
+ if (parentRun === undefined || parentStage === undefined)
100961
+ break;
100962
+ run = parentRun;
100963
+ stage = parentStage;
100964
+ }
100965
+ return;
100966
+ }
100860
100967
  function buildPromptNodeUiAdapter(input) {
100861
100968
  const ask = async (descriptor, durableReplay) => {
100862
100969
  input.throwIfWorkflowExitSelected();
@@ -100871,6 +100978,9 @@ function buildPromptNodeUiAdapter(input) {
100871
100978
  const prompt = makePrompt(descriptor);
100872
100979
  const replayKey = promptReplayKey(descriptor);
100873
100980
  const durableTopology = input.durableTopologyForReplayKey?.(replayKey);
100981
+ if (durableTopology?.status === "completed" && durableReplay === undefined) {
100982
+ throw new Error(`insufficient_state: missing durable UI answer for completed prompt ${durableTopology.stageId}`);
100983
+ }
100874
100984
  const stageId = durableTopology?.stageId ?? crypto.randomUUID();
100875
100985
  const provisionalParentIds = input.tracker.onSpawn(stageId, descriptor.kind);
100876
100986
  const replayDecision = input.replayIndex.decide({
@@ -100884,7 +100994,7 @@ function buildPromptNodeUiAdapter(input) {
100884
100994
  if (!sameStringSet(parentIds, provisionalParentIds))
100885
100995
  input.tracker.replaceParents(stageId, parentIds);
100886
100996
  const replaySource = replayDecision.source;
100887
- const continuationAnswer = replayDecision.kind === "replay" ? input.activeStore.getStagePromptAnswer(input.opts.continuation.source.id, replayDecision.source.id) : undefined;
100997
+ const continuationAnswer = replayDecision.kind === "replay" ? continuationPromptAnswer(input.activeStore, input.opts.continuation.source, replayDecision.source) : undefined;
100888
100998
  const replayAnswer = durableReplay === undefined ? continuationAnswer : { value: durableReplay.response };
100889
100999
  const shouldReplay = replayAnswer !== undefined;
100890
101000
  if (shouldReplay && durableReplay === undefined)
@@ -100897,13 +101007,11 @@ function buildPromptNodeUiAdapter(input) {
100897
101007
  replayKey,
100898
101008
  status: shouldReplay ? "completed" : "running",
100899
101009
  parentIds: Object.freeze(parentIds),
100900
- startedAt: prompt.createdAt,
101010
+ ...shouldReplay ? stageTimingFields(durableReplay === undefined ? replaySource : input.durableTimingForStageId?.(stageId)) : { startedAt: prompt.createdAt },
100901
101011
  promptFootprint: { ...prompt },
100902
101012
  toolEvents: [],
100903
101013
  attachable: !shouldReplay,
100904
101014
  ...shouldReplay ? {
100905
- endedAt: prompt.createdAt,
100906
- durationMs: 0,
100907
101015
  promptAnswerState: promptAnswerStatus,
100908
101016
  replayedFromStageId: replaySourceId,
100909
101017
  replayed: true
@@ -100933,8 +101041,10 @@ function buildPromptNodeUiAdapter(input) {
100933
101041
  pauseGate = undefined;
100934
101042
  currentPauseGate?.resolve();
100935
101043
  stageSnapshot.status = status;
100936
- stageSnapshot.endedAt = Date.now();
100937
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
101044
+ if (!shouldReplay) {
101045
+ stageSnapshot.endedAt = Date.now();
101046
+ stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
101047
+ }
100938
101048
  input.activeStore.recordStageAttachable(input.runId, stageId, false);
100939
101049
  input.activeStore.recordStageEnd(input.runId, stageSnapshot);
100940
101050
  await input.opts.onStageEnd?.(input.runId, stageSnapshot);
@@ -100944,6 +101054,7 @@ function buildPromptNodeUiAdapter(input) {
100944
101054
  stageId,
100945
101055
  status: stageSnapshot.status,
100946
101056
  durationMs: stageSnapshot.durationMs,
101057
+ endedAt: stageSnapshot.endedAt,
100947
101058
  ...stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {},
100948
101059
  ...stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {},
100949
101060
  ...stageSnapshot.failureCode !== undefined ? { failureCode: stageSnapshot.failureCode } : {},
@@ -101010,7 +101121,7 @@ function buildPromptNodeUiAdapter(input) {
101010
101121
  name: stageSnapshot.name,
101011
101122
  parentIds: stageSnapshot.parentIds,
101012
101123
  ...stageReplayFields(stageSnapshot),
101013
- ts: prompt.createdAt
101124
+ ts: stageSnapshot.startedAt
101014
101125
  });
101015
101126
  }
101016
101127
  if (shouldReplay) {
@@ -102281,7 +102392,7 @@ function wrapUiWithDurable(base, deps) {
102281
102392
  // dist/builtin/workflows/src/engine/primitives/ui.ts
102282
102393
  function buildExitGatedUiContext(input) {
102283
102394
  const base = input.opts.usePromptNodesForUi === true ? input.baseFromPromptNodes() : input.opts.executionMode === "non_interactive" && input.opts.ui === undefined ? makeHeadlessUnavailableUIContext() : normalizeUIContext(input.opts.ui);
102284
- const promptNodeReplay = input.opts.usePromptNodesForUi === true && input.opts.continuation !== undefined;
102395
+ const promptNodeReplay = input.opts.usePromptNodesForUi === true && input.opts.continuation !== undefined && input.opts.continuation.source.id !== input.durableUi?.workflowId;
102285
102396
  const durableBase = input.durableUi !== undefined && !promptNodeReplay ? wrapUiWithDurable(base, input.durableUi) : base;
102286
102397
  const invoke = (call) => {
102287
102398
  input.throwIfWorkflowExitSelected();
@@ -102805,7 +102916,7 @@ function mergeStageDraft(existing, checkpoint, sequence) {
102805
102916
  ...valueOrExisting("thinkingLevel", checkpoint, existing),
102806
102917
  ...valueOrExisting("attemptedModels", checkpoint, existing),
102807
102918
  ...valueOrExisting("modelAttempts", checkpoint, existing),
102808
- ...checkpoint.topology !== undefined ? { topology: checkpoint.topology } : existing?.topology !== undefined ? { topology: existing.topology } : {}
102919
+ ...existing?.topology?.run !== undefined && checkpoint.topology?.run === undefined ? { topology: existing.topology } : checkpoint.topology !== undefined ? { topology: checkpoint.topology } : existing?.topology !== undefined ? { topology: existing.topology } : {}
102809
102920
  };
102810
102921
  }
102811
102922
  function valueOrExisting(key, checkpoint, existing) {
@@ -103026,7 +103137,7 @@ function resolveToolResumeFrontier(source, backend) {
103026
103137
  if (source.failedToolNodeId === undefined && !checkpoints.some((checkpoint) => checkpoint.kind === "tool" && checkpoint.throwingFailureError === source.error && checkpoint.argsHash === frontier.argsHash && checkpoint.topology?.nodeId === frontier.id && checkpoint.topology.ordinal === frontier.ordinal))
103027
103138
  return fail("missing typed tool failure checkpoint for legacy frontier");
103028
103139
  for (const stage of source.stages) {
103029
- if (stage.status !== "completed" || !checkpoints.some((checkpoint) => checkpoint.kind === "stage" && checkpoint.replayKey === stage.replayKey && checkpoint.output !== undefined))
103140
+ if (stage.status !== "completed" || !checkpoints.some((checkpoint) => checkpoint.kind === "stage" && checkpoint.replayKey === stage.replayKey && (checkpoint.output !== undefined || ["input", "confirm", "select", "editor", "custom"].includes(stage.name) && stage.replayKey?.startsWith(`prompt:${stage.name}:`) === true && checkpoint.name === stage.name && checkpoint.topology?.stageId === stage.id && checkpoint.topology.status === "completed" && checkpoint.topology.run?.runId === source.id)))
103030
103141
  return fail(`unfinished or missing completed stage checkpoint ${stage.id}`);
103031
103142
  }
103032
103143
  for (const tool of tools) {
@@ -104446,13 +104557,11 @@ function createWorkflowBoundaryFactory(input) {
104446
104557
  replayKey,
104447
104558
  status: replayedChild !== undefined ? "completed" : "running",
104448
104559
  parentIds: Object.freeze([...parentIds]),
104449
- startedAt,
104560
+ ...replayedChild === undefined ? { startedAt } : stageTimingFields(replaySource),
104450
104561
  toolEvents: [],
104451
104562
  attachable: false,
104452
104563
  ...replaySource !== undefined ? { replayedFromStageId: replaySource.id, replayed: replayedChild !== undefined } : {},
104453
104564
  ...replayedChild !== undefined && replayChildSnapshot !== undefined ? {
104454
- endedAt: startedAt,
104455
- durationMs: 0,
104456
104565
  ...replayDecision.kind === "replay" && replayDecision.source.result !== undefined ? { result: replayDecision.source.result } : {},
104457
104566
  workflowChild: cloneWorkflowChildReplaySnapshot(replayChildSnapshot)
104458
104567
  } : {}
@@ -104469,7 +104578,7 @@ function createWorkflowBoundaryFactory(input) {
104469
104578
  name,
104470
104579
  parentIds: stageSnapshot.parentIds,
104471
104580
  ...stageReplayFields(stageSnapshot),
104472
- ts: startedAt
104581
+ ts: stageSnapshot.startedAt
104473
104582
  });
104474
104583
  };
104475
104584
  const appendStageEndForSnapshot = () => {
@@ -104480,6 +104589,7 @@ function createWorkflowBoundaryFactory(input) {
104480
104589
  stageId,
104481
104590
  status: stageSnapshot.status,
104482
104591
  durationMs: stageSnapshot.durationMs,
104592
+ endedAt: stageSnapshot.endedAt,
104483
104593
  ...stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {},
104484
104594
  ...stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {},
104485
104595
  ...stageSnapshot.failureCode !== undefined ? { failureCode: stageSnapshot.failureCode } : {},
@@ -104516,8 +104626,10 @@ function createWorkflowBoundaryFactory(input) {
104516
104626
  clearBoundaryChildMetadata();
104517
104627
  applyFailureToStage(stageSnapshot, input.classifyExecutorFailure(failureError));
104518
104628
  }
104519
- stageSnapshot.endedAt = Date.now();
104520
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
104629
+ if (replayedChild === undefined) {
104630
+ stageSnapshot.endedAt = Date.now();
104631
+ stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
104632
+ }
104521
104633
  input.activeStore.recordStageEnd(input.runId, stageSnapshot);
104522
104634
  input.opts.onStageEnd?.(input.runId, stageSnapshot);
104523
104635
  appendStageEndForSnapshot();
@@ -104585,6 +104697,34 @@ function createWorkflowBoundaryFactory(input) {
104585
104697
  // dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts
104586
104698
  import { runCallback as runCallback3, runSynchronousCallback } from "@bastani/atomic";
104587
104699
 
104700
+ // dist/builtin/workflows/src/shared/pending-stage-route-readiness.ts
104701
+ var owners = new WeakMap;
104702
+ function registerWorkflowPendingStageRouteReadiness(store, ready) {
104703
+ owners.get(store)?.retire();
104704
+ const controller = new AbortController;
104705
+ const owner = {
104706
+ ready(runId) {
104707
+ const completion = ready(runId);
104708
+ return completion === undefined ? undefined : {
104709
+ completion: raceAbort2(completion, controller.signal),
104710
+ assertCurrent: () => controller.signal.throwIfAborted()
104711
+ };
104712
+ },
104713
+ retire() {
104714
+ controller.abort(new Error("atomic-workflows: Intercom route authority owner retired before stage startup"));
104715
+ }
104716
+ };
104717
+ owners.set(store, owner);
104718
+ return () => {
104719
+ owner.retire();
104720
+ if (owners.get(store) === owner)
104721
+ owners.delete(store);
104722
+ };
104723
+ }
104724
+ function workflowPendingStageRouteReady(store, runId) {
104725
+ return owners.get(store)?.ready(runId);
104726
+ }
104727
+
104588
104728
  // dist/builtin/workflows/src/runs/foreground/executor-queued-user-message.ts
104589
104729
  function removedMessages(before, after) {
104590
104730
  const remaining = [...after];
@@ -105439,7 +105579,7 @@ function createReplayStageContext(input) {
105439
105579
  name,
105440
105580
  parentIds: stageSnapshot.parentIds,
105441
105581
  ...stageReplayFields(stageSnapshot),
105442
- ts: stageSnapshot.startedAt ?? Date.now()
105582
+ ts: stageSnapshot.startedAt
105443
105583
  });
105444
105584
  };
105445
105585
  const appendReplayStageEnd = () => {
@@ -105449,7 +105589,8 @@ function createReplayStageContext(input) {
105449
105589
  runId,
105450
105590
  stageId,
105451
105591
  status: stageSnapshot.status,
105452
- durationMs: stageSnapshot.durationMs ?? 0,
105592
+ durationMs: stageSnapshot.durationMs,
105593
+ endedAt: stageSnapshot.endedAt,
105453
105594
  ...stageSnapshot.status === "completed" && stageSnapshot.result !== undefined ? { summary: stageSnapshot.result } : {},
105454
105595
  ...stageSnapshot.skippedReason !== undefined ? { skippedReason: stageSnapshot.skippedReason } : {},
105455
105596
  ...stageSnapshot.sessionId !== undefined ? { sessionId: stageSnapshot.sessionId } : {},
@@ -105467,8 +105608,6 @@ function createReplayStageContext(input) {
105467
105608
  delete stageSnapshot.result;
105468
105609
  stageSnapshot.skippedReason = input.workflowExitSkippedReason(reason);
105469
105610
  }
105470
- stageSnapshot.endedAt = Date.now();
105471
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
105472
105611
  input.activeStore.recordStageEnd(runId, stageSnapshot);
105473
105612
  input.opts.onStageEnd?.(runId, stageSnapshot);
105474
105613
  appendReplayStageEnd();
@@ -106724,16 +106863,52 @@ function stageSessionExtensionRunner(current) {
106724
106863
  }
106725
106864
  return;
106726
106865
  }
106866
+ var shutdowns = new WeakMap;
106867
+ function shutdownStageSession(current) {
106868
+ if (!current)
106869
+ return Promise.resolve();
106870
+ const existing = shutdowns.get(current);
106871
+ if (existing)
106872
+ return existing;
106873
+ const shutdown = Promise.resolve().then(async () => {
106874
+ const runner = stageSessionExtensionRunner(current);
106875
+ if (runner?.hasHandlers("session_shutdown"))
106876
+ await runner.emit({ type: "session_shutdown", reason: "quit" });
106877
+ });
106878
+ shutdowns.set(current, shutdown);
106879
+ return shutdown;
106880
+ }
106881
+
106882
+ class StageSessionBindingCleanupFailure extends AggregateError {
106883
+ constructor(bindingError, cleanupErrors) {
106884
+ super([bindingError, ...cleanupErrors], "atomic-workflows: failed binding cleanup did not release session ownership", {
106885
+ cause: bindingError
106886
+ });
106887
+ this.name = "StageSessionBindingCleanupFailure";
106888
+ }
106889
+ }
106890
+ async function cleanupFailedStageSessionBinding(current, bindingError) {
106891
+ const cleanupErrors = [];
106892
+ try {
106893
+ await shutdownStageSession(current);
106894
+ } catch (error) {
106895
+ cleanupErrors.push(error);
106896
+ }
106897
+ try {
106898
+ await current.dispose();
106899
+ } catch (error) {
106900
+ cleanupErrors.push(error);
106901
+ }
106902
+ if (cleanupErrors.length > 0)
106903
+ throw new StageSessionBindingCleanupFailure(bindingError, cleanupErrors);
106904
+ }
106727
106905
  async function disposeStageSession(current) {
106728
106906
  if (!current)
106729
106907
  return;
106730
- const runner = stageSessionExtensionRunner(current);
106731
- if (runner?.hasHandlers("session_shutdown")) {
106732
- try {
106733
- await runner.emit({ type: "session_shutdown", reason: "quit" });
106734
- } catch (error) {
106735
- console.error("atomic-workflows: stage session_shutdown handler failed", error);
106736
- }
106908
+ try {
106909
+ await shutdownStageSession(current);
106910
+ } catch (error) {
106911
+ console.error("atomic-workflows: stage session_shutdown handler failed", error);
106737
106912
  }
106738
106913
  await current.dispose();
106739
106914
  }
@@ -107047,6 +107222,27 @@ function terminatingToolCallId(event) {
107047
107222
  return typeof callId === "string" && callId.length > 0 ? callId : undefined;
107048
107223
  }
107049
107224
 
107225
+ // dist/builtin/workflows/src/runs/foreground/stage-startup-wait.ts
107226
+ function waitForStageStartup(operation, signal) {
107227
+ return new Promise((resolve, reject) => {
107228
+ const onAbort = () => {
107229
+ signal.removeEventListener("abort", onAbort);
107230
+ reject(signal.reason ?? new DOMException("Stage startup cancelled", "AbortError"));
107231
+ };
107232
+ operation.then((value2) => {
107233
+ signal.removeEventListener("abort", onAbort);
107234
+ resolve(value2);
107235
+ }, (error) => {
107236
+ signal.removeEventListener("abort", onAbort);
107237
+ reject(error);
107238
+ });
107239
+ if (signal.aborted)
107240
+ onAbort();
107241
+ else
107242
+ signal.addEventListener("abort", onAbort, { once: true });
107243
+ });
107244
+ }
107245
+
107050
107246
  // dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts
107051
107247
  function hasMeaningfulUsage(usage) {
107052
107248
  if (usage === undefined)
@@ -107127,9 +107323,14 @@ class StageSessionController {
107127
107323
  activeCreation;
107128
107324
  ownedCreationPromise;
107129
107325
  abortGeneration = 0;
107326
+ routeAuthorityWait;
107327
+ startupWait = new AbortController;
107328
+ startup;
107130
107329
  abortReason;
107131
107330
  abortReasonGeneration = 0;
107132
107331
  sessionPromise;
107332
+ sessionShutdownPromise;
107333
+ bindingCleanupFailure;
107133
107334
  reattachSessionFile;
107134
107335
  lastPromptStartIndex;
107135
107336
  attemptUsageStartIndex;
@@ -107246,8 +107447,7 @@ class StageSessionController {
107246
107447
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${error}. Retrying with ${candidateLabel(nextCandidate)}.`);
107247
107448
  this.modelWarnings.push(...this.pendingFallbackWarnings);
107248
107449
  this.pendingFallbackWarnings.length = 0;
107249
- await this.disposeCurrentSession();
107250
- this.activeCandidateIndex = index + 1;
107450
+ await this.disposeCurrentSession(index + 1);
107251
107451
  this.notifyModelFallbackMetaChange();
107252
107452
  return true;
107253
107453
  }
@@ -107275,28 +107475,41 @@ class StageSessionController {
107275
107475
  this.session?.setThinkingLevel(level);
107276
107476
  }
107277
107477
  async ensureSession(consumer = "prompt") {
107478
+ if (this.bindingCleanupFailure !== undefined)
107479
+ throw this.bindingCleanupFailure;
107480
+ if (this.sessionShutdownPromise !== undefined) {
107481
+ const generation = this.abortGeneration;
107482
+ await this.sessionShutdownPromise;
107483
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== generation)
107484
+ throw this.staleCreationReason(generation);
107485
+ }
107278
107486
  if (this.disposed)
107279
107487
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
107280
- if (this.session !== undefined)
107488
+ this.opts.signal?.throwIfAborted();
107489
+ if (this.session !== undefined && this.activeCreation === undefined)
107281
107490
  return this.session;
107282
107491
  if (!this.sessionPromise) {
107492
+ this.beginStartup();
107283
107493
  const pending = this.createInitialSession(consumer);
107284
107494
  this.sessionPromise = pending;
107285
107495
  this.ownedCreationPromise = pending;
107286
- const release = () => {
107496
+ const release = (failed = false) => {
107287
107497
  if (this.ownedCreationPromise === pending)
107288
107498
  this.ownedCreationPromise = undefined;
107499
+ this.settleStartup(failed);
107289
107500
  };
107290
- pending.then(release, () => {
107291
- release();
107292
- if (this.sessionPromise === pending)
107501
+ pending.then(() => release(), () => {
107502
+ release(true);
107503
+ if (this.sessionPromise === pending) {
107293
107504
  this.sessionPromise = undefined;
107505
+ this.activeCandidateIndex = undefined;
107506
+ }
107294
107507
  });
107295
107508
  }
107296
- return this.sessionPromise;
107509
+ return waitForStageStartup(this.sessionPromise, this.startupWait.signal);
107297
107510
  }
107298
107511
  async ensureSessionFromFile(sessionFile, consumer = "prompt") {
107299
- if (!this.sessionPromise && !this.session)
107512
+ if (!this.sessionShutdownPromise && !this.sessionPromise && !this.session)
107300
107513
  this.reattachSessionFile = sessionFile;
107301
107514
  const session = await this.ensureSession(consumer);
107302
107515
  await session.closeWorkflowStageGeneration?.();
@@ -107341,6 +107554,10 @@ class StageSessionController {
107341
107554
  this.artifactCapture.close();
107342
107555
  }
107343
107556
  async promptWithFallback(text, sdkOptions, consumer = "prompt") {
107557
+ if (this.session !== undefined && this.activeCreation === undefined && this.ownedCreationPromise === undefined && this.startupWait.signal.aborted) {
107558
+ this.opts.signal?.throwIfAborted();
107559
+ this.startupWait = new AbortController;
107560
+ }
107344
107561
  if (!this.hasExplicitModelFallbackConfig) {
107345
107562
  try {
107346
107563
  const activeSession = await this.ensureSession(consumer);
@@ -107363,7 +107580,13 @@ class StageSessionController {
107363
107580
  }
107364
107581
  return;
107365
107582
  }
107366
- const candidates = await this.modelCandidates();
107583
+ const readinessOwner = this.ownedCreationPromise ?? this.activeCreation;
107584
+ if (readinessOwner !== undefined)
107585
+ await waitForStageStartup(readinessOwner, this.startupWait.signal);
107586
+ if (this.session === undefined)
107587
+ this.beginStartup();
107588
+ const startupSignal = this.startupWait.signal;
107589
+ const candidates = await waitForStageStartup(this.modelCandidates(), startupSignal);
107367
107590
  if (candidates.length === 0) {
107368
107591
  try {
107369
107592
  const activeSession = await this.ensureSession(consumer);
@@ -107379,14 +107602,9 @@ class StageSessionController {
107379
107602
  }
107380
107603
  return;
107381
107604
  }
107382
- if (this.session === undefined && this.sessionPromise !== undefined) {
107383
- try {
107384
- await this.sessionPromise;
107385
- } catch (error) {
107386
- if (error instanceof StageSessionCreationCancelled)
107387
- return;
107388
- }
107389
- }
107605
+ const creationOwner = this.ownedCreationPromise ?? this.activeCreation;
107606
+ if (creationOwner !== undefined)
107607
+ await waitForStageStartup(creationOwner, startupSignal);
107390
107608
  const resumedText = this.pendingCreationResumeMessage;
107391
107609
  this.pendingCreationResumeMessage = undefined;
107392
107610
  let promptText = resumedText ?? text;
@@ -107394,9 +107612,21 @@ class StageSessionController {
107394
107612
  return;
107395
107613
  let index = this.activeCandidateIndex ?? 0;
107396
107614
  while (index < candidates.length) {
107615
+ if (this.ownedCreationPromise !== undefined || this.activeCreation !== undefined) {
107616
+ try {
107617
+ await waitForStageStartup(this.ownedCreationPromise ?? this.activeCreation, startupSignal);
107618
+ } catch (error) {
107619
+ if (error instanceof StageSessionCreationCancelled)
107620
+ return;
107621
+ throw error;
107622
+ }
107623
+ index = this.activeCandidateIndex ?? index;
107624
+ promptText = this.pendingCreationResumeMessage ?? promptText;
107625
+ this.pendingCreationResumeMessage = undefined;
107626
+ }
107397
107627
  const candidate = candidates[index];
107398
107628
  try {
107399
- const created = this.session && this.activeCandidateIndex === index ? this.session : await this.createSessionWithThrownErrorRetry(candidate, consumer);
107629
+ const created = this.session && this.activeCandidateIndex === index ? this.session : await waitForStageStartup(this.createSessionWithThrownErrorRetry(candidate, consumer), startupSignal);
107400
107630
  if (isSessionCreationPauseResult(created)) {
107401
107631
  if (created.resumeMessage === undefined)
107402
107632
  return;
@@ -107423,8 +107653,10 @@ class StageSessionController {
107423
107653
  const failure = await this.handleCandidateFailure(err, candidate, candidates, index);
107424
107654
  if (failure === "handled")
107425
107655
  return;
107426
- if (failure === "throw")
107656
+ if (failure === "throw") {
107657
+ this.settleStartup(true);
107427
107658
  throw err;
107659
+ }
107428
107660
  index += 1;
107429
107661
  }
107430
107662
  }
@@ -107467,7 +107699,8 @@ class StageSessionController {
107467
107699
  this.messageAdmission.dispose();
107468
107700
  this.deliveryActivity.dispose();
107469
107701
  await this.replacement.dispose();
107470
- await disposeStageSession(this.session);
107702
+ if (this.activeCreation === undefined)
107703
+ await disposeStageSession(this.session);
107471
107704
  }
107472
107705
  async drainPendingDisposal() {
107473
107706
  if (!this.pendingDisposal)
@@ -107541,6 +107774,12 @@ class StageSessionController {
107541
107774
  this.abortGeneration += 1;
107542
107775
  this.abortReason = reason;
107543
107776
  this.abortReasonGeneration = this.abortGeneration;
107777
+ this.routeAuthorityWait?.abort(reason);
107778
+ this.startupWait.abort(reason);
107779
+ if (this.startup?.state === "active") {
107780
+ this.startup = { ...this.startup, state: "cancelled" };
107781
+ this.opts.onStartupChange?.(this.startup);
107782
+ }
107544
107783
  this.abortThrownErrorRetries(reason);
107545
107784
  }
107546
107785
  pauseThrownErrorRetries(resume) {
@@ -107781,11 +108020,15 @@ class StageSessionController {
107781
108020
  return this.candidatesPromise;
107782
108021
  }
107783
108022
  async createInitialSession(consumer) {
108023
+ const generation = this.abortGeneration;
107784
108024
  if (!this.hasExplicitModelFallbackConfig) {
107785
108025
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
107786
108026
  }
107787
108027
  const candidates = await this.modelCandidates();
107788
- const first = candidates[0];
108028
+ if (this.abortGeneration !== generation || this.opts.signal?.aborted || this.disposed)
108029
+ throw this.staleCreationReason(generation);
108030
+ const initialIndex = this.activeCandidateIndex ?? 0;
108031
+ const first = candidates[initialIndex];
107789
108032
  if (first === undefined) {
107790
108033
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
107791
108034
  }
@@ -107798,9 +108041,9 @@ class StageSessionController {
107798
108041
  this.resumeCurrentSession = true;
107799
108042
  return resumed;
107800
108043
  }
107801
- this.activeCandidateIndex = 0;
108044
+ this.activeCandidateIndex = initialIndex;
107802
108045
  this.selectedModel = first.id;
107803
- return this.createSessionObservingPause(first, consumer).catch((error) => this.createInitialSessionCandidateWalk(candidates, consumer, 0, { error }));
108046
+ return this.createSessionObservingPause(first, consumer).catch((error) => this.createInitialSessionCandidateWalk(candidates, consumer, initialIndex, { error }));
107804
108047
  }
107805
108048
  async createInitialSessionCandidateWalk(candidates, consumer, startIndex, initialFailure) {
107806
108049
  let index = startIndex;
@@ -107882,11 +108125,13 @@ class StageSessionController {
107882
108125
  }
107883
108126
  return await this.createSessionObservingPause(candidate, consumer);
107884
108127
  } catch (error) {
108128
+ if (error instanceof StageSessionBindingCleanupFailure)
108129
+ throw error;
107885
108130
  const errorSettingsManager = retrySettingsManagerFromError(error);
107886
108131
  if (errorSettingsManager !== undefined)
107887
108132
  this.sessionSettingsManager = errorSettingsManager;
107888
108133
  const decision = isWorkflowPendingStageDeliveryFailure(error) ? undefined : nextRetryDecision(this.retrySettings(), retryAttempt, isRetryableSameModelFailure(error));
107889
- if (decision === undefined || this.disposed || this.opts.signal?.aborted === true || this.capturedStructuredOutputForAttempt()) {
108134
+ if (decision === undefined || this.disposed || this.opts.signal?.aborted === true || this.startupWait.signal.aborted || this.capturedStructuredOutputForAttempt()) {
107890
108135
  throw error;
107891
108136
  }
107892
108137
  retryAttempt = decision.attempt;
@@ -107915,16 +108160,86 @@ class StageSessionController {
107915
108160
  }
107916
108161
  }
107917
108162
  createSession(candidate, consumer, resumeOptions) {
108163
+ if (this.bindingCleanupFailure !== undefined)
108164
+ return Promise.reject(this.bindingCleanupFailure);
108165
+ if (this.sessionShutdownPromise !== undefined) {
108166
+ const generation = this.abortGeneration;
108167
+ return this.sessionShutdownPromise.then(() => {
108168
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== generation)
108169
+ throw this.staleCreationReason(generation);
108170
+ return this.createSession(candidate, consumer, resumeOptions);
108171
+ });
108172
+ }
108173
+ if (this.activeCreation !== undefined)
108174
+ return this.activeCreation;
108175
+ if (this.session !== undefined)
108176
+ return Promise.resolve(this.session);
108177
+ this.startupWait.signal.throwIfAborted();
107918
108178
  const creation = this.createSessionAttempt(candidate, consumer, resumeOptions);
107919
108179
  this.activeCreation = creation;
107920
108180
  creation.finally(() => {
107921
108181
  if (this.activeCreation === creation)
107922
108182
  this.activeCreation = undefined;
108183
+ this.settleStartup();
107923
108184
  }).catch(() => {});
107924
108185
  return creation;
107925
108186
  }
108187
+ settleStartup(failed = false) {
108188
+ if (this.activeCreation !== undefined || this.ownedCreationPromise !== undefined || this.startup === undefined)
108189
+ return;
108190
+ if (this.startup.state === "dispatched" || this.startup.phase === "ready")
108191
+ return;
108192
+ if (this.startup.state === "active" && !failed && this.bindingCleanupFailure === undefined)
108193
+ return;
108194
+ this.startup = {
108195
+ ...this.startup,
108196
+ state: this.startup.state === "active" ? "failed" : this.startup.state,
108197
+ ownershipPending: this.bindingCleanupFailure !== undefined,
108198
+ ...this.bindingCleanupFailure === undefined ? { settledAt: Date.now() } : {}
108199
+ };
108200
+ this.opts.onStartupChange?.(this.startup);
108201
+ }
108202
+ beginStartup() {
108203
+ if (this.activeCreation !== undefined || this.ownedCreationPromise !== undefined || this.sessionPromise !== undefined)
108204
+ return;
108205
+ this.opts.signal?.throwIfAborted();
108206
+ if (this.startupWait.signal.aborted)
108207
+ this.startupWait = new AbortController;
108208
+ this.reportStartupPhase("model-resolution", true);
108209
+ }
108210
+ reportStartupPhase(phase, reset = false) {
108211
+ if (!reset && this.startup !== undefined && this.startup.state !== "active")
108212
+ return;
108213
+ const now = Date.now();
108214
+ this.startup = {
108215
+ phase,
108216
+ startedAt: reset ? now : this.startup?.startedAt ?? now,
108217
+ phaseStartedAt: now,
108218
+ state: phase === "first-dispatch" ? "dispatched" : "active",
108219
+ ownershipPending: phase !== "first-dispatch" && phase !== "ready"
108220
+ };
108221
+ this.opts.onStartupChange?.(this.startup);
108222
+ }
107926
108223
  async createSessionAttempt(candidate, consumer, resumeOptions) {
107927
108224
  const startGeneration = this.abortGeneration;
108225
+ if (this.disposed || this.opts.signal?.aborted)
108226
+ throw this.staleCreationReason(startGeneration);
108227
+ this.reportStartupPhase("route-authority");
108228
+ const authority = this.opts.routeAuthorityReady?.();
108229
+ if (authority !== undefined) {
108230
+ const wait = new AbortController;
108231
+ this.routeAuthorityWait = wait;
108232
+ try {
108233
+ await raceAbort2(authority.completion, wait.signal);
108234
+ authority.assertCurrent();
108235
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== startGeneration)
108236
+ throw this.staleCreationReason(startGeneration);
108237
+ } finally {
108238
+ if (this.routeAuthorityWait === wait)
108239
+ this.routeAuthorityWait = undefined;
108240
+ }
108241
+ }
108242
+ this.reportStartupPhase("resource-preparation");
107928
108243
  this.applyCandidateThinking(candidate);
107929
108244
  const stageOptions = buildStageSessionOptions({
107930
108245
  effectiveStageOptions: this.effectiveStageOptions,
@@ -107937,10 +108252,19 @@ class StageSessionController {
107937
108252
  try {
107938
108253
  created = this.opts.adapters.agentSession ? await this.opts.adapters.agentSession.create(stripWorkflowOnlyOptions(stageOptions, this.opts.defaultSessionDir, this.meta, this.opts.pendingStageDelivery), {
107939
108254
  ...this.meta,
108255
+ startupSignal: this.startupWait.signal,
108256
+ onStartupPhase: (phase) => {
108257
+ if (this.abortGeneration === startGeneration)
108258
+ this.reportStartupPhase(phase);
108259
+ },
107940
108260
  stageOptions,
107941
108261
  ...this.sharedOrchestrationContext !== undefined ? { orchestrationContext: this.sharedOrchestrationContext } : {}
107942
108262
  }) : missingAdapter(consumer);
107943
108263
  } catch (error) {
108264
+ if (error instanceof StageSessionBindingCleanupFailure) {
108265
+ this.bindingCleanupFailure = error;
108266
+ throw error;
108267
+ }
107944
108268
  if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
107945
108269
  throw this.disposed ? new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`) : this.staleCreationReason(startGeneration);
107946
108270
  throw error;
@@ -107951,20 +108275,28 @@ class StageSessionController {
107951
108275
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
107952
108276
  throw this.staleCreationReason(startGeneration);
107953
108277
  }
108278
+ this.reportStartupPhase("session-attachment");
107954
108279
  const session = attachCreatedStageSession(created, this.disposed, this.opts.stageName, (result) => this.attachSession(result));
107955
108280
  const attachedSession = session instanceof Promise ? await session : session;
107956
- const pendingStageDeliveryReady = this.sharedOrchestrationContext?.pendingStageDelivery?.ready();
107957
- if (pendingStageDeliveryReady !== undefined) {
108281
+ this.reportStartupPhase("delivery-readiness");
108282
+ try {
108283
+ await this.sharedOrchestrationContext?.pendingStageDelivery?.ready();
108284
+ await this.opts.onSessionReady?.();
108285
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== startGeneration)
108286
+ throw this.staleCreationReason(startGeneration);
108287
+ } catch (reason) {
108288
+ if (this.session === attachedSession)
108289
+ this.session = undefined;
107958
108290
  try {
107959
- await pendingStageDeliveryReady;
108291
+ await cleanupFailedStageSessionBinding(attachedSession, reason);
107960
108292
  } catch (error) {
107961
- if (this.session === attachedSession)
107962
- this.session = undefined;
107963
- await disposeStageSession(attachedSession).catch(() => {});
108293
+ if (error instanceof StageSessionBindingCleanupFailure)
108294
+ this.bindingCleanupFailure = error;
107964
108295
  throw error;
107965
108296
  }
108297
+ throw reason;
107966
108298
  }
107967
- await this.opts.onSessionReady?.();
108299
+ this.reportStartupPhase("ready");
107968
108300
  return attachedSession;
107969
108301
  }
107970
108302
  attachSession(created) {
@@ -108004,12 +108336,16 @@ class StageSessionController {
108004
108336
  });
108005
108337
  return result.session;
108006
108338
  }
108007
- async disposeCurrentSession() {
108339
+ async disposeCurrentSession(nextCandidateIndex) {
108340
+ if (this.sessionShutdownPromise !== undefined)
108341
+ await this.sessionShutdownPromise;
108342
+ const startGeneration = this.abortGeneration;
108008
108343
  this.abortThrownErrorRetries(new Error(`atomic-workflows: stage "${this.opts.stageName}" session was replaced`));
108009
108344
  const current = this.session;
108010
108345
  this.messageAdmission.reset();
108011
108346
  this.replacement.retire(current);
108012
108347
  this.session = undefined;
108348
+ this.activeCandidateIndex = nextCandidateIndex;
108013
108349
  if (this.sessionPromise !== this.ownedCreationPromise)
108014
108350
  this.sessionPromise = undefined;
108015
108351
  this.sessionSettingsManager = undefined;
@@ -108020,6 +108356,15 @@ class StageSessionController {
108020
108356
  this.unsubscribeTerminateWatcher?.();
108021
108357
  this.unsubscribeTerminateWatcher = undefined;
108022
108358
  this.terminatingToolCallIds.clear();
108359
+ const shutdown = Promise.resolve().then(() => shutdownStageSession(current));
108360
+ this.sessionShutdownPromise = shutdown;
108361
+ shutdown.then(() => {
108362
+ if (this.sessionShutdownPromise === shutdown)
108363
+ this.sessionShutdownPromise = undefined;
108364
+ }, () => {});
108365
+ await shutdown;
108366
+ if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
108367
+ throw this.staleCreationReason(startGeneration);
108023
108368
  }
108024
108369
  async promptWithPauseResume(activeSession, initialText, sdkOptions) {
108025
108370
  let nextText = initialText;
@@ -108037,6 +108382,7 @@ class StageSessionController {
108037
108382
  this.lastPromptStartIndex = promptStartIndex;
108038
108383
  this.unresolvedContextOverflowMessage = undefined;
108039
108384
  try {
108385
+ this.reportStartupPhase("first-dispatch");
108040
108386
  await activeSession.prompt(nextText, sdkOptions);
108041
108387
  const pendingPauseAfterPrompt = this.pauseControl.currentResume();
108042
108388
  if (pendingPauseAfterPrompt) {
@@ -108129,12 +108475,13 @@ class StageSessionController {
108129
108475
  throw err;
108130
108476
  }
108131
108477
  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])}.`);
108132
- await this.disposeCurrentSession();
108133
- this.activeCandidateIndex = resumedOverflowNextIndex;
108478
+ await this.disposeCurrentSession(resumedOverflowNextIndex);
108134
108479
  return false;
108135
108480
  }
108136
108481
  }
108137
108482
  async handleCandidateFailure(err, candidate, candidates, index) {
108483
+ if (err instanceof StageSessionBindingCleanupFailure)
108484
+ throw err;
108138
108485
  const message = errorMessage4(err);
108139
108486
  const terminalStageDelivery = isWorkflowPendingStageDeliveryFailure(err);
108140
108487
  if (!terminalStageDelivery && this.capturedStructuredOutputForAttempt() && isRetryableModelFailure(err)) {
@@ -108150,7 +108497,7 @@ class StageSessionController {
108150
108497
  ...usage === undefined ? {} : { usage },
108151
108498
  error: message
108152
108499
  });
108153
- if (this.opts.signal?.aborted || terminalStageDelivery || !isRetryableModelFailure(err) || index === candidates.length - 1) {
108500
+ if (this.opts.signal?.aborted || this.startupWait.signal.aborted || terminalStageDelivery || !isRetryableModelFailure(err) || index === candidates.length - 1) {
108154
108501
  this.modelWarnings.push(...this.pendingFallbackWarnings);
108155
108502
  this.pendingFallbackWarnings.length = 0;
108156
108503
  this.notifyModelFallbackMetaChange();
@@ -108158,7 +108505,7 @@ class StageSessionController {
108158
108505
  }
108159
108506
  const nextCandidate = candidates[index + 1];
108160
108507
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${message}. Retrying with ${candidateLabel(nextCandidate)}.`);
108161
- await this.disposeCurrentSession();
108508
+ await this.disposeCurrentSession(index + 1);
108162
108509
  return "retry";
108163
108510
  }
108164
108511
  capturedStructuredOutputForAttempt() {
@@ -108799,9 +109146,7 @@ function createWorkflowStageFactory(input) {
108799
109146
  toolEvents: [],
108800
109147
  pendingStageDeliveryAvailable,
108801
109148
  ...shouldReplay ? {
108802
- startedAt: Date.now(),
108803
- endedAt: Date.now(),
108804
- durationMs: 0,
109149
+ ...stageTimingFields(replaySource),
108805
109150
  ...replaySource.result !== undefined ? { result: replaySource.result } : {},
108806
109151
  ...replaySource.sessionId !== undefined ? { sessionId: replaySource.sessionId } : {},
108807
109152
  ...replaySource.sessionFile !== undefined ? { sessionFile: replaySource.sessionFile } : {},
@@ -108864,11 +109209,19 @@ function createWorkflowStageFactory(input) {
108864
109209
  signal: input.signal,
108865
109210
  stageOptions: stageOptionsForContext,
108866
109211
  ...pendingStageDeliveryAvailable ? {
109212
+ routeAuthorityReady: () => workflowPendingStageRouteReady(input.activeStore, input.runId),
108867
109213
  pendingStageDelivery: createWorkflowPendingStageDelivery(input.activeStore, input.runId, stageId, name)
108868
109214
  } : {},
108869
109215
  models: input.opts.models,
108870
109216
  executionMode: input.opts.executionMode,
108871
109217
  defaultSessionDir: input.opts.defaultSessionDir,
109218
+ onStartupChange(startup) {
109219
+ const current = input.activeStore.runs().find((run) => run.id === input.runId);
109220
+ if (!current?.stages.includes(stageSnapshot))
109221
+ return;
109222
+ stageSnapshot.startup = startup;
109223
+ input.activeStore.recordStageStart(input.runId, stageSnapshot);
109224
+ },
108872
109225
  onModelFallbackMetaChange(meta) {
108873
109226
  applyModelFallbackMeta(meta);
108874
109227
  if (stageSnapshot.status === "running")
@@ -109031,6 +109384,7 @@ function createWorkflowStageFactory(input) {
109031
109384
  stageId,
109032
109385
  status: stageSnapshot.status,
109033
109386
  durationMs: stageSnapshot.durationMs,
109387
+ endedAt: stageSnapshot.endedAt,
109034
109388
  ...stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {},
109035
109389
  ...stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {},
109036
109390
  ...stageSnapshot.failureCode !== undefined ? { failureCode: stageSnapshot.failureCode } : {},
@@ -109636,6 +109990,7 @@ async function run(def, inputs, opts = {}) {
109636
109990
  };
109637
109991
  };
109638
109992
  const resolvePromptNodeTopology = createDurableStageTopologyResolver(durableBackend, runId);
109993
+ const priorPromptStageCheckpoints = durableBackend.listCheckpoints(runId).filter((checkpoint) => checkpoint.kind === "stage");
109639
109994
  let promptNodeUi;
109640
109995
  const getPromptNodeUi = () => {
109641
109996
  promptNodeUi ??= buildPromptNodeUiAdapter({
@@ -109652,6 +110007,7 @@ async function run(def, inputs, opts = {}) {
109652
110007
  workflowExitSkippedReason: exit.workflowExitSkippedReason,
109653
110008
  preserveWorkflowExitSkippedReason: exit.preserveWorkflowExitSkippedReason,
109654
110009
  durableTopologyForReplayKey: resolvePromptNodeTopology,
110010
+ durableTimingForStageId: (stageId) => priorPromptStageCheckpoints.find((checkpoint) => checkpoint.topology?.stageId === stageId && checkpoint.topology.status === "completed" && (checkpoint.topology.run === undefined || checkpoint.topology.run.runId === runId)),
109655
110011
  onPendingStage: async (pendingRunId, snapshot) => pendingRunId === runId ? void await recordDurableActiveStage(durableStageDeps, snapshot) : undefined
109656
110012
  });
109657
110013
  return promptNodeUi;
@@ -112905,11 +113261,14 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112905
113261
  return payload.handled && await payload.completion === true;
112906
113262
  };
112907
113263
  const announcedRoutes = new Map;
113264
+ const routeCompletions = new Map;
113265
+ const disposeReadiness = registerWorkflowPendingStageRouteReadiness(activeStore, (runId) => routeCompletions.get(runId));
112908
113266
  const announceRoutes = () => {
112909
113267
  if (disposed)
112910
113268
  return;
112911
113269
  const ownedRunIds = new Set;
112912
113270
  const runs = activeStore.runs();
113271
+ const resolveBoundarySegments = createWorkflowBoundarySegmentsResolver(runs);
112913
113272
  for (const run of runs) {
112914
113273
  const rootRunId = durableRootRunIdForRun(runs, run.id);
112915
113274
  if (rootRunId === undefined)
@@ -112930,7 +113289,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112930
113289
  ...run.stages.filter((stage) => !isNonAgentStage(stage) && workflowInvocationOwnsGroup(workflowInvocationIntercomGroup(rootRunId), stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId))).map((stage) => ({
112931
113290
  stageId: stage.id,
112932
113291
  stageName: stage.name,
112933
- target: stageRouteTarget(runs, rootRunId, run.id, stage.id),
113292
+ target: stageRouteTarget(runs, rootRunId, run.id, stage.id, resolveBoundarySegments),
112934
113293
  lifecycle: stage.sessionId === undefined && stage.sessionFile === undefined ? "pending" : "running",
112935
113294
  routeEligible: stage.pendingStageDeliveryAvailable === true && (stage.status === "pending" || stage.status === "running" || stage.status === "awaiting_input" || stage.status === "paused" || stage.status === "blocked"),
112936
113295
  group: stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId)
@@ -112938,7 +113297,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112938
113297
  ...nonAgentNodes(run).map((node) => ({
112939
113298
  stageId: node.id,
112940
113299
  stageName: node.name,
112941
- target: stageRouteTarget(runs, rootRunId, run.id, node.id),
113300
+ target: stageRouteTarget(runs, rootRunId, run.id, node.id, resolveBoundarySegments),
112942
113301
  lifecycle: "pending",
112943
113302
  routeEligible: false,
112944
113303
  recipientPurpose: "control",
@@ -112957,12 +113316,17 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112957
113316
  } catch (error) {
112958
113317
  if (announcedRoutes.get(run.id) === entry)
112959
113318
  announcedRoutes.delete(run.id);
113319
+ const failure = Promise.reject(error);
113320
+ failure.catch(() => {});
113321
+ routeCompletions.set(run.id, failure);
112960
113322
  throw error;
112961
113323
  }
112962
113324
  const completion = announcement.completion;
112963
113325
  if (completion === undefined) {
112964
113326
  announcedRoutes.delete(run.id);
113327
+ routeCompletions.delete(run.id);
112965
113328
  } else {
113329
+ routeCompletions.set(run.id, completion);
112966
113330
  completion.catch(() => {
112967
113331
  if (announcedRoutes.get(run.id) === entry)
112968
113332
  announcedRoutes.delete(run.id);
@@ -112973,6 +113337,10 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112973
113337
  if (!ownedRunIds.has(runId))
112974
113338
  announcedRoutes.delete(runId);
112975
113339
  }
113340
+ for (const runId of routeCompletions.keys()) {
113341
+ if (!ownedRunIds.has(runId))
113342
+ routeCompletions.delete(runId);
113343
+ }
112976
113344
  sweepPromise = sweepPromise.then(() => settleUndeliverablePendingStageMessages(activeStore, notifyUndeliverable)).then(() => {
112977
113345
  return;
112978
113346
  }).catch((error) => reportWarning(`atomic-workflows: pending stage delivery sweep failed: ${error.message}`));
@@ -113051,6 +113419,8 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
113051
113419
  });
113052
113420
  const dispose = () => {
113053
113421
  disposed = true;
113422
+ disposeReadiness();
113423
+ routeCompletions.clear();
113054
113424
  unsubscribeStore();
113055
113425
  if (typeof subscription === "function")
113056
113426
  subscription();
@@ -113061,8 +113431,8 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
113061
113431
  pi.on?.("session_shutdown", dispose);
113062
113432
  return dispose;
113063
113433
  }
113064
- function stageRouteTarget(runs, rootRunId, runId, stageId) {
113065
- const boundarySegments = runId === rootRunId ? [] : workflowBoundarySegments(runs, runId);
113434
+ function stageRouteTarget(runs, rootRunId, runId, stageId, resolveBoundarySegments = (id) => workflowBoundarySegments(runs, id)) {
113435
+ const boundarySegments = runId === rootRunId ? [] : resolveBoundarySegments(runId);
113066
113436
  return formatWorkflowStageTarget(rootRunId, ...boundarySegments ?? [runId], stageId);
113067
113437
  }
113068
113438
  function possibleStageRows(runs, rootRunId, rootRun) {
@@ -113537,6 +113907,8 @@ function resolveSessionCwd(options) {
113537
113907
  return options?.cwd ?? options?.sessionManager?.getCwd() ?? process.cwd();
113538
113908
  }
113539
113909
  async function prepareAtomicStageSessionOptions(options, sdk, prepareOptions = {}) {
113910
+ prepareOptions.signal?.throwIfAborted();
113911
+ prepareOptions.onStartupPhase?.("resource-preparation");
113540
113912
  const atomicOptions = options;
113541
113913
  if (atomicOptions?.resourceLoader !== undefined)
113542
113914
  return atomicOptions;
@@ -113555,7 +113927,8 @@ async function prepareAtomicStageSessionOptions(options, sdk, prepareOptions = {
113555
113927
  resourceLoaderInheritanceSnapshot: inheritanceSnapshot,
113556
113928
  builtinPackagePaths: stageBuiltinPackagePaths(builtinPackagePaths)
113557
113929
  });
113558
- await reloadWorkflowStageResources(resourceLoader);
113930
+ await reloadWorkflowStageResources(resourceLoader, prepareOptions);
113931
+ prepareOptions.signal?.throwIfAborted();
113559
113932
  return {
113560
113933
  ...atomicOptions,
113561
113934
  cwd,
@@ -113592,8 +113965,13 @@ function stageBuiltinPackagePaths(paths) {
113592
113965
  });
113593
113966
  }
113594
113967
  var workflowStageResourceReloadQueue = Promise.resolve();
113595
- async function reloadWorkflowStageResources(resourceLoader) {
113596
- const queuedReload = workflowStageResourceReloadQueue.then(() => resourceLoader.reload());
113968
+ async function reloadWorkflowStageResources(resourceLoader, options) {
113969
+ options.onStartupPhase?.("reload-queued");
113970
+ const queuedReload = workflowStageResourceReloadQueue.then(() => {
113971
+ options.signal?.throwIfAborted();
113972
+ options.onStartupPhase?.("reload-active");
113973
+ return resourceLoader.reload();
113974
+ });
113597
113975
  workflowStageResourceReloadQueue = queuedReload.catch(() => {
113598
113976
  return;
113599
113977
  });
@@ -113628,8 +114006,8 @@ function attachSettingsManager(error, settingsManager) {
113628
114006
  });
113629
114007
  return wrapped;
113630
114008
  }
113631
- async function createPiSdkAgentSession(options, prepareOptions) {
113632
- const sdk = await import("@bastani/atomic");
114009
+ async function createPiSdkAgentSession(options, prepareOptions, injectedSdk) {
114010
+ const sdk = injectedSdk ?? await import("@bastani/atomic");
113633
114011
  let settingsManager;
113634
114012
  try {
113635
114013
  const sessionOptions = await prepareAtomicStageSessionOptions(options, sdk, {
@@ -113640,6 +114018,8 @@ async function createPiSdkAgentSession(options, prepareOptions) {
113640
114018
  }
113641
114019
  });
113642
114020
  settingsManager = sessionOptions?.settingsManager ?? settingsManager;
114021
+ prepareOptions?.signal?.throwIfAborted();
114022
+ prepareOptions?.onStartupPhase?.("sdk-creation");
113643
114023
  const result = await sdk.createAgentSession(sessionOptions);
113644
114024
  const resultSettingsManager = result.session.settingsManager;
113645
114025
  settingsManager = sessionOptions?.settingsManager ?? resultSettingsManager ?? settingsManager;
@@ -113899,18 +114279,28 @@ function makeStageExtensionUiContext(ui, meta, broker) {
113899
114279
  };
113900
114280
  }
113901
114281
  function buildRuntimeAdapters(pi, options = {}) {
113902
- const createSession = options.createAgentSession ?? pi.createAgentSession ?? (isTestContext() ? createTestAgentSession : (sessionOptions) => createPiSdkAgentSession(sessionOptions, {
113903
- resourceLoaderInheritanceSnapshot: pi.getResourceLoaderInheritanceSnapshot?.()
113904
- }));
114282
+ const createSession = options.createAgentSession ?? pi.createAgentSession ?? (isTestContext() && options.sdk === undefined ? createTestAgentSession : (sessionOptions, prepareOptions) => createPiSdkAgentSession(sessionOptions, {
114283
+ resourceLoaderInheritanceSnapshot: pi.getResourceLoaderInheritanceSnapshot?.(),
114284
+ ...prepareOptions
114285
+ }, options.sdk));
113905
114286
  const broker = options.stageUiBroker ?? stageUiBroker;
113906
114287
  const adapters = {
113907
114288
  agentSession: {
113908
114289
  async create(stageOptions, meta) {
113909
114290
  const sessionOptions = withWorkflowStageSessionOptions(stripWorkflowOnlyOptions2(stageOptions) ?? {}, meta, pi);
113910
- const result = await createSession(sessionOptions);
114291
+ const signal = meta?.startupSignal ?? meta?.signal;
114292
+ const result = await createSession(sessionOptions, { signal, onStartupPhase: meta?.onStartupPhase });
113911
114293
  const bindable = result.session;
113912
- if (typeof bindable.bindExtensions === "function") {
113913
- await bindable.bindExtensions(shouldBindStageUiContext(pi, meta) ? { uiContext: makeStageExtensionUiContext(pi.ui ?? {}, meta, broker) } : {});
114294
+ try {
114295
+ signal?.throwIfAborted();
114296
+ meta?.onStartupPhase?.("extension-binding");
114297
+ if (typeof bindable.bindExtensions === "function") {
114298
+ await bindable.bindExtensions(shouldBindStageUiContext(pi, meta) ? { uiContext: makeStageExtensionUiContext(pi.ui ?? {}, meta, broker) } : {});
114299
+ }
114300
+ signal?.throwIfAborted();
114301
+ } catch (error) {
114302
+ await cleanupFailedStageSessionBinding(result.session, error);
114303
+ throw error;
113914
114304
  }
113915
114305
  return result;
113916
114306
  }
@@ -115475,7 +115865,21 @@ Picker requires an interactive UI surface. Pass a runId: /workflow attach <id> [
115475
115865
  return true;
115476
115866
  runId = picked.runId;
115477
115867
  } else if (action === "resume") {
115478
- const backend = getDurableBackend();
115868
+ let backend;
115869
+ try {
115870
+ try {
115871
+ backend = getDurableBackend();
115872
+ } catch (error) {
115873
+ if (!(error instanceof DbosNotReadyError))
115874
+ throw error;
115875
+ await ensureWorkflowResourcesVisible();
115876
+ await deps.runtimeForContext(ctx).prepareDurableResumable(target);
115877
+ backend = getDurableBackend();
115878
+ }
115879
+ } catch (error) {
115880
+ fail(`Failed to resolve workflow resume target: ${error instanceof Error ? error.message : String(error)}`);
115881
+ return true;
115882
+ }
115479
115883
  const localResolution = resolveRunId(target);
115480
115884
  const localBeforePreparation = localResolution.kind === "exact" ? store.runs().find((run) => run.id === localResolution.runId) : undefined;
115481
115885
  const exactBeforePreparation = localBeforePreparation?.id === target ? localBeforePreparation : undefined;
@@ -116329,6 +116733,8 @@ function renderStagesToolContent(result) {
116329
116733
  lines.push("stages:");
116330
116734
  result.stages.forEach((stage, index) => {
116331
116735
  lines.push(`[${index + 1}] ${stage.name} (${stage.id}) ${stage.status}`);
116736
+ if (stage.startup)
116737
+ lines.push(formatStageStartup(stage.startup));
116332
116738
  if (stage.sessionId)
116333
116739
  lines.push(`sessionId: ${stage.sessionId}`);
116334
116740
  if (stage.sessionFile)
@@ -116369,6 +116775,8 @@ function renderStageToolContent(result) {
116369
116775
  `);
116370
116776
  }
116371
116777
  lines.push("stage:");
116778
+ if (result.stage.startup)
116779
+ lines.push(formatStageStartup(result.stage.startup));
116372
116780
  lines.push(JSON.stringify(result.stage, null, 2));
116373
116781
  if (result.stage.sessionFile) {
116374
116782
  lines.push(`transcriptPath: ${result.stage.sessionFile}`);
@@ -116847,6 +117255,7 @@ function summarizeStage(stage) {
116847
117255
  id: stage.id,
116848
117256
  name: stage.name,
116849
117257
  status: stage.status,
117258
+ ...stage.startup === undefined ? {} : { startup: { ...stage.startup } },
116850
117259
  sessionId: stage.sessionId,
116851
117260
  sessionFile: stage.sessionFile,
116852
117261
  transcriptPath: stage.sessionFile,
@@ -117845,10 +118254,14 @@ function registerWorkflowShortcut(pi, overlay) {
117845
118254
  });
117846
118255
  pi.registerShortcut("alt+pageUp", {
117847
118256
  description: "Scroll background workflows up",
118257
+ keybinding: "app.workflows.scrollUp",
118258
+ preferEditor: true,
117848
118259
  handler: () => scrollStoreWidget(store, -1)
117849
118260
  });
117850
118261
  pi.registerShortcut("alt+pageDown", {
117851
118262
  description: "Scroll background workflows down",
118263
+ keybinding: "app.workflows.scrollDown",
118264
+ preferEditor: true,
117852
118265
  handler: () => scrollStoreWidget(store, 1)
117853
118266
  });
117854
118267
  }