@bastani/atomic 0.9.19-alpha.9 → 0.9.19

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 (101) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +24 -0
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/CHANGELOG.md +8 -0
  5. package/dist/builtin/mcp/package.json +1 -1
  6. package/dist/builtin/subagents/CHANGELOG.md +30 -0
  7. package/dist/builtin/subagents/package.json +1 -1
  8. package/dist/builtin/web-access/CHANGELOG.md +14 -0
  9. package/dist/builtin/web-access/package.json +1 -1
  10. package/dist/builtin/workflows/CHANGELOG.md +59 -0
  11. package/dist/builtin/workflows/package.json +1 -1
  12. package/dist/builtin/workflows/src/extension/index.bundle.mjs +236 -68
  13. package/dist/builtin/workflows/src/index.js +97 -18
  14. package/dist/core/extensions/api-types.d.ts +2 -0
  15. package/dist/core/extensions/api-types.d.ts.map +1 -1
  16. package/dist/core/extensions/api-types.js.map +1 -1
  17. package/dist/core/extensions/index.d.ts +1 -1
  18. package/dist/core/extensions/index.d.ts.map +1 -1
  19. package/dist/core/extensions/index.js.map +1 -1
  20. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  21. package/dist/core/extensions/loader-api.js.map +1 -1
  22. package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
  23. package/dist/core/extensions/loader-host-modules.js +3 -1
  24. package/dist/core/extensions/loader-host-modules.js.map +1 -1
  25. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  26. package/dist/core/extensions/loader-virtual-modules.js +2 -0
  27. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  28. package/dist/core/extensions/reactive-widget.d.ts +6 -0
  29. package/dist/core/extensions/reactive-widget.d.ts.map +1 -1
  30. package/dist/core/extensions/reactive-widget.js +7 -1
  31. package/dist/core/extensions/reactive-widget.js.map +1 -1
  32. package/dist/core/extensions/runner-shortcuts.d.ts.map +1 -1
  33. package/dist/core/extensions/runner-shortcuts.js +32 -3
  34. package/dist/core/extensions/runner-shortcuts.js.map +1 -1
  35. package/dist/core/extensions/runtime-types.d.ts +4 -0
  36. package/dist/core/extensions/runtime-types.d.ts.map +1 -1
  37. package/dist/core/extensions/runtime-types.js.map +1 -1
  38. package/dist/core/extensions/ui-types.d.ts +20 -0
  39. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  40. package/dist/core/extensions/ui-types.js.map +1 -1
  41. package/dist/core/keybinding-identity.d.ts +4 -0
  42. package/dist/core/keybinding-identity.d.ts.map +1 -0
  43. package/dist/core/keybinding-identity.js +10 -0
  44. package/dist/core/keybinding-identity.js.map +1 -0
  45. package/dist/core/keybindings.d.ts +10 -0
  46. package/dist/core/keybindings.d.ts.map +1 -1
  47. package/dist/core/keybindings.js +2 -0
  48. package/dist/core/keybindings.js.map +1 -1
  49. package/dist/core/tools/bash.d.ts.map +1 -1
  50. package/dist/core/tools/bash.js +7 -1
  51. package/dist/core/tools/bash.js.map +1 -1
  52. package/dist/index-extensions.d.ts +1 -1
  53. package/dist/index-extensions.d.ts.map +1 -1
  54. package/dist/index-extensions.js.map +1 -1
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/modes/interactive/components/scroll-widget.d.ts +23 -0
  60. package/dist/modes/interactive/components/scroll-widget.d.ts.map +1 -0
  61. package/dist/modes/interactive/components/scroll-widget.js +61 -0
  62. package/dist/modes/interactive/components/scroll-widget.js.map +1 -0
  63. package/dist/modes/interactive/interactive-extension-runtime.js +10 -3
  64. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  65. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  66. package/dist/modes/interactive/interactive-mode-base.js +3 -2
  67. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  68. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +4 -1
  69. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  70. package/dist/modes/interactive-engine/engine-custom-ui.js +15 -7
  71. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  72. package/dist/modes/interactive-engine/engine-dialog-host.d.ts.map +1 -1
  73. package/dist/modes/interactive-engine/engine-dialog-host.js +4 -1
  74. package/dist/modes/interactive-engine/engine-dialog-host.js.map +1 -1
  75. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
  76. package/dist/modes/interactive-engine/extension-ui-bridge.js +1 -1
  77. package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
  78. package/dist/modes/interactive-engine/protocol.d.ts +12 -1
  79. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  80. package/dist/modes/interactive-engine/protocol.js +45 -2
  81. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  82. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  83. package/dist/modes/interactive-engine/remote-component.js +18 -8
  84. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  85. package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
  86. package/dist/modes/rpc/rpc-extension-ui.js +2 -1
  87. package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
  88. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  89. package/dist/modes/rpc/rpc-mode.js +1 -0
  90. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  91. package/dist/modes/rpc/rpc-types.d.ts +4 -0
  92. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-types.js.map +1 -1
  94. package/docs/extensions/api-reference.md +2 -0
  95. package/docs/extensions/authoring.md +2 -0
  96. package/docs/keybindings.md +17 -0
  97. package/docs/tui.md +34 -0
  98. package/docs/workflows/api-reference.md +2 -0
  99. package/docs/workflows/operations.md +5 -1
  100. package/npm-shrinkwrap.json +32 -32
  101. package/package.json +3 -3
@@ -68525,19 +68525,57 @@ function formatWorkflowStageTarget(rootRunId, ...segments) {
68525
68525
 
68526
68526
  // dist/builtin/workflows/src/shared/pending-stage-status.ts
68527
68527
  function workflowBoundarySegments(runs, runId) {
68528
+ return createWorkflowBoundarySegmentsResolver(runs)(runId);
68529
+ }
68530
+ function createWorkflowBoundarySegmentsResolver(runs) {
68528
68531
  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;
68532
+ const stagesByRun = new Map;
68533
+ const childrenByParent = new Map;
68534
+ for (const run of runs) {
68535
+ const byId = new Map;
68536
+ const byName = new Map;
68537
+ for (const stage of run.stages) {
68538
+ const ids = byId.get(stage.id) ?? [];
68539
+ ids.push(stage);
68540
+ byId.set(stage.id, ids);
68541
+ const names = byName.get(stage.name) ?? [];
68542
+ names.push(stage);
68543
+ byName.set(stage.name, names);
68544
+ }
68545
+ stagesByRun.set(run.id, { byId, byName });
68546
+ if (run.parentRunId === undefined || run.parentStageId === undefined)
68547
+ continue;
68548
+ const byStage = childrenByParent.get(run.parentRunId) ?? new Map;
68549
+ const children = byStage.get(run.parentStageId) ?? [];
68550
+ children.push(run);
68551
+ byStage.set(run.parentStageId, children);
68552
+ childrenByParent.set(run.parentRunId, byStage);
68539
68553
  }
68540
- return current === undefined ? undefined : segments;
68554
+ const cachedSegments = new Map;
68555
+ return (runId) => {
68556
+ if (cachedSegments.has(runId))
68557
+ return cachedSegments.get(runId);
68558
+ const segments = [];
68559
+ let current = runById.get(runId);
68560
+ while (current !== undefined && current.parentRunId !== undefined) {
68561
+ const parent = runById.get(current.parentRunId);
68562
+ const stages = stagesByRun.get(current.parentRunId);
68563
+ const boundary = current.parentStageId === undefined ? undefined : stages?.byId.get(current.parentStageId)?.[0];
68564
+ if (parent === undefined || stages === undefined || boundary === undefined) {
68565
+ cachedSegments.set(runId, undefined);
68566
+ return;
68567
+ }
68568
+ const boundaryName = boundary.name;
68569
+ const matchingBoundaries = stages.byId.get(boundaryName) ?? stages.byName.get(boundaryName);
68570
+ const matchingBoundary = matchingBoundaries?.length === 1 ? matchingBoundaries[0] : undefined;
68571
+ const children = matchingBoundary === undefined ? undefined : childrenByParent.get(parent.id)?.get(matchingBoundary.id);
68572
+ segments.unshift(boundaryName.length > 0 && !boundaryName.includes("/") && !boundaryName.includes("*") && children?.length === 1 && children[0]?.id === current.id ? boundaryName : current.id);
68573
+ current = parent;
68574
+ }
68575
+ const result = current === undefined ? undefined : segments;
68576
+ cachedSegments.set(runId, result);
68577
+ return result;
68578
+ };
68541
68579
  }
68542
68580
  function workflowBoundaryHops(runs, runId) {
68543
68581
  const runById = new Map(runs.map((run) => [run.id, run]));
@@ -79145,8 +79183,26 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now(), la
79145
79183
  });
79146
79184
  }
79147
79185
 
79186
+ // dist/builtin/workflows/src/tui/widget-scroll-hint.ts
79187
+ import { keybindingIdentity } from "@bastani/atomic";
79188
+ import { getKeybindings, matchesKey as matchesKey2 } from "@earendil-works/pi-tui";
79189
+ var legacyInputs = Array.from({ length: 128 }, (_, code) => String.fromCharCode(code)).flatMap((data) => [
79190
+ data,
79191
+ `\x1B${data}`
79192
+ ]);
79193
+ function workflowScrollHint(bindings = getKeybindings().getResolvedBindings(), platform = process.platform) {
79194
+ const keys = ["app.workflows.scrollUp", "app.workflows.scrollDown"].map((action) => {
79195
+ const configured = bindings[action] ?? [];
79196
+ return (Array.isArray(configured) ? configured : [configured]).find((key) => {
79197
+ const inputs = legacyInputs.filter((data) => matchesKey2(data, key));
79198
+ 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)))));
79199
+ });
79200
+ }).filter((key) => key !== undefined);
79201
+ const label = keys.map((key) => key.replace(/^alt\+/i, platform === "darwin" ? "Option+" : "Alt+")).join("/");
79202
+ return ` ↑↓ scroll${label ? ` · ${label}` : ""}`;
79203
+ }
79204
+
79148
79205
  // dist/builtin/workflows/src/tui/widget-viewport.ts
79149
- import { ScrollableComponentViewport } from "@bastani/atomic";
79150
79206
  var WORKFLOW_WIDGET_MAX_ROWS = 10;
79151
79207
 
79152
79208
  class WorkflowWidgetViewport {
@@ -79154,53 +79210,60 @@ class WorkflowWidgetViewport {
79154
79210
  terminalRows;
79155
79211
  requestRender;
79156
79212
  getRunRows;
79157
- viewport = new ScrollableComponentViewport;
79213
+ getHint;
79214
+ position = 0;
79215
+ version = 0;
79216
+ collapsed = false;
79217
+ state;
79158
79218
  lines = [];
79159
- scrollable = false;
79160
79219
  runRows = [];
79161
- constructor(content, terminalRows, requestRender, getRunRows) {
79220
+ constructor(content, terminalRows, requestRender, getRunRows, getHint = () => "") {
79162
79221
  this.content = content;
79163
79222
  this.terminalRows = terminalRows;
79164
79223
  this.requestRender = requestRender;
79165
79224
  this.getRunRows = getRunRows;
79166
- this.viewport.setComponents([{ render: () => this.lines, invalidate() {} }]);
79167
- this.viewport.scrollTo(0);
79225
+ this.getHint = getHint;
79226
+ }
79227
+ getScrollRequest() {
79228
+ return { version: this.version, scrollTop: this.collapsed ? 0 : this.position };
79229
+ }
79230
+ onScroll(state) {
79231
+ this.state = state;
79232
+ if (!this.collapsed)
79233
+ this.position = state.scrollTop;
79168
79234
  }
79169
79235
  scroll(direction) {
79170
- if (!this.scrollable)
79236
+ if (this.collapsed)
79171
79237
  return;
79172
- this.viewport.scrollBy(direction);
79238
+ const height = this.state?.viewportHeight ?? Math.max(1, Math.min(WORKFLOW_WIDGET_MAX_ROWS, Math.floor(this.terminalRows() / 3)));
79239
+ const next = Math.max(0, Math.min(Math.max(0, this.lines.length - height), this.position + direction));
79240
+ if (next === this.position)
79241
+ return;
79242
+ this.position = next;
79243
+ this.version++;
79173
79244
  this.requestRender();
79174
79245
  }
79175
79246
  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;
79247
+ const content = this.content.render(width);
79248
+ const hint = content.length > 1 ? this.getHint() : "";
79249
+ const nextLines = hint ? [...content, truncateToWidth2(hint, width, "…")] : content;
79250
+ const nextRunRows = (this.getRunRows?.() ?? []).map((run, index, rows) => hint && index === rows.length - 1 ? { ...run, end: run.end + 1 } : run);
79251
+ const collapsed = Boolean(this.getRunRows && nextLines.length === 1 && nextRunRows.length === 0);
79252
+ if (collapsed) {
79253
+ if (!this.collapsed)
79254
+ this.version++;
79255
+ this.collapsed = true;
79181
79256
  return nextLines;
79182
79257
  }
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;
79258
+ const anchoredOffset = this.getRunRows ? this.resolveAnchor(this.position, nextRunRows) : this.position;
79259
+ if (this.collapsed || anchoredOffset !== this.position) {
79260
+ this.position = anchoredOffset;
79261
+ this.version++;
79262
+ }
79263
+ this.collapsed = false;
79186
79264
  this.lines = nextLines;
79187
79265
  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, "…")];
79266
+ return nextLines;
79204
79267
  }
79205
79268
  resolveAnchor(offset, next) {
79206
79269
  if (offset === 0 || next.length === 0)
@@ -79272,7 +79335,7 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
79272
79335
  widgetViewports.delete(storeInstance);
79273
79336
  setWidget.call(ui, key, factory ? (tui, theme) => {
79274
79337
  const host = tui;
79275
- const viewport = new WorkflowWidgetViewport(factory(tui, theme), () => host?.terminal?.rows ?? 30, () => requestRender ? requestRender.call(ui) : host?.requestRender?.(), () => layout.runs);
79338
+ const viewport = new WorkflowWidgetViewport(factory(tui, theme), () => host?.terminal?.rows ?? 30, () => requestRender ? requestRender.call(ui) : host?.requestRender?.(), () => layout.runs, workflowScrollHint);
79276
79339
  widgetViewports.set(storeInstance, viewport);
79277
79340
  return viewport;
79278
79341
  } : undefined, opts);
@@ -79282,6 +79345,7 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
79282
79345
  },
79283
79346
  key: WIDGET_KEY,
79284
79347
  placement: "belowEditor",
79348
+ scroll: { maxHeight: 10, maxHeightFraction: 1 / 3 },
79285
79349
  timers,
79286
79350
  getSnapshot: () => liveWidgetSnapshot(storeInstance),
79287
79351
  subscribe: (listener) => subscribeStoreInvalidation(storeInstance, listener),
@@ -106724,16 +106788,52 @@ function stageSessionExtensionRunner(current) {
106724
106788
  }
106725
106789
  return;
106726
106790
  }
106791
+ var shutdowns = new WeakMap;
106792
+ function shutdownStageSession(current) {
106793
+ if (!current)
106794
+ return Promise.resolve();
106795
+ const existing = shutdowns.get(current);
106796
+ if (existing)
106797
+ return existing;
106798
+ const shutdown = Promise.resolve().then(async () => {
106799
+ const runner = stageSessionExtensionRunner(current);
106800
+ if (runner?.hasHandlers("session_shutdown"))
106801
+ await runner.emit({ type: "session_shutdown", reason: "quit" });
106802
+ });
106803
+ shutdowns.set(current, shutdown);
106804
+ return shutdown;
106805
+ }
106806
+
106807
+ class StageSessionBindingCleanupFailure extends AggregateError {
106808
+ constructor(bindingError, cleanupErrors) {
106809
+ super([bindingError, ...cleanupErrors], "atomic-workflows: failed binding cleanup did not release session ownership", {
106810
+ cause: bindingError
106811
+ });
106812
+ this.name = "StageSessionBindingCleanupFailure";
106813
+ }
106814
+ }
106815
+ async function cleanupFailedStageSessionBinding(current, bindingError) {
106816
+ const cleanupErrors = [];
106817
+ try {
106818
+ await shutdownStageSession(current);
106819
+ } catch (error) {
106820
+ cleanupErrors.push(error);
106821
+ }
106822
+ try {
106823
+ await current.dispose();
106824
+ } catch (error) {
106825
+ cleanupErrors.push(error);
106826
+ }
106827
+ if (cleanupErrors.length > 0)
106828
+ throw new StageSessionBindingCleanupFailure(bindingError, cleanupErrors);
106829
+ }
106727
106830
  async function disposeStageSession(current) {
106728
106831
  if (!current)
106729
106832
  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
- }
106833
+ try {
106834
+ await shutdownStageSession(current);
106835
+ } catch (error) {
106836
+ console.error("atomic-workflows: stage session_shutdown handler failed", error);
106737
106837
  }
106738
106838
  await current.dispose();
106739
106839
  }
@@ -107130,6 +107230,8 @@ class StageSessionController {
107130
107230
  abortReason;
107131
107231
  abortReasonGeneration = 0;
107132
107232
  sessionPromise;
107233
+ sessionShutdownPromise;
107234
+ bindingCleanupFailure;
107133
107235
  reattachSessionFile;
107134
107236
  lastPromptStartIndex;
107135
107237
  attemptUsageStartIndex;
@@ -107246,8 +107348,7 @@ class StageSessionController {
107246
107348
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${error}. Retrying with ${candidateLabel(nextCandidate)}.`);
107247
107349
  this.modelWarnings.push(...this.pendingFallbackWarnings);
107248
107350
  this.pendingFallbackWarnings.length = 0;
107249
- await this.disposeCurrentSession();
107250
- this.activeCandidateIndex = index + 1;
107351
+ await this.disposeCurrentSession(index + 1);
107251
107352
  this.notifyModelFallbackMetaChange();
107252
107353
  return true;
107253
107354
  }
@@ -107275,6 +107376,14 @@ class StageSessionController {
107275
107376
  this.session?.setThinkingLevel(level);
107276
107377
  }
107277
107378
  async ensureSession(consumer = "prompt") {
107379
+ if (this.bindingCleanupFailure !== undefined)
107380
+ throw this.bindingCleanupFailure;
107381
+ if (this.sessionShutdownPromise !== undefined) {
107382
+ const generation = this.abortGeneration;
107383
+ await this.sessionShutdownPromise;
107384
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== generation)
107385
+ throw this.staleCreationReason(generation);
107386
+ }
107278
107387
  if (this.disposed)
107279
107388
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
107280
107389
  if (this.session !== undefined)
@@ -107289,14 +107398,16 @@ class StageSessionController {
107289
107398
  };
107290
107399
  pending.then(release, () => {
107291
107400
  release();
107292
- if (this.sessionPromise === pending)
107401
+ if (this.sessionPromise === pending) {
107293
107402
  this.sessionPromise = undefined;
107403
+ this.activeCandidateIndex = undefined;
107404
+ }
107294
107405
  });
107295
107406
  }
107296
107407
  return this.sessionPromise;
107297
107408
  }
107298
107409
  async ensureSessionFromFile(sessionFile, consumer = "prompt") {
107299
- if (!this.sessionPromise && !this.session)
107410
+ if (!this.sessionShutdownPromise && !this.sessionPromise && !this.session)
107300
107411
  this.reattachSessionFile = sessionFile;
107301
107412
  const session = await this.ensureSession(consumer);
107302
107413
  await session.closeWorkflowStageGeneration?.();
@@ -107394,6 +107505,18 @@ class StageSessionController {
107394
107505
  return;
107395
107506
  let index = this.activeCandidateIndex ?? 0;
107396
107507
  while (index < candidates.length) {
107508
+ if (this.session === undefined && this.ownedCreationPromise !== undefined) {
107509
+ try {
107510
+ await this.ownedCreationPromise;
107511
+ } catch (error) {
107512
+ if (error instanceof StageSessionCreationCancelled)
107513
+ return;
107514
+ throw error;
107515
+ }
107516
+ index = this.activeCandidateIndex ?? index;
107517
+ promptText = this.pendingCreationResumeMessage ?? promptText;
107518
+ this.pendingCreationResumeMessage = undefined;
107519
+ }
107397
107520
  const candidate = candidates[index];
107398
107521
  try {
107399
107522
  const created = this.session && this.activeCandidateIndex === index ? this.session : await this.createSessionWithThrownErrorRetry(candidate, consumer);
@@ -107785,7 +107908,8 @@ class StageSessionController {
107785
107908
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
107786
107909
  }
107787
107910
  const candidates = await this.modelCandidates();
107788
- const first = candidates[0];
107911
+ const initialIndex = this.activeCandidateIndex ?? 0;
107912
+ const first = candidates[initialIndex];
107789
107913
  if (first === undefined) {
107790
107914
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
107791
107915
  }
@@ -107798,9 +107922,9 @@ class StageSessionController {
107798
107922
  this.resumeCurrentSession = true;
107799
107923
  return resumed;
107800
107924
  }
107801
- this.activeCandidateIndex = 0;
107925
+ this.activeCandidateIndex = initialIndex;
107802
107926
  this.selectedModel = first.id;
107803
- return this.createSessionObservingPause(first, consumer).catch((error) => this.createInitialSessionCandidateWalk(candidates, consumer, 0, { error }));
107927
+ return this.createSessionObservingPause(first, consumer).catch((error) => this.createInitialSessionCandidateWalk(candidates, consumer, initialIndex, { error }));
107804
107928
  }
107805
107929
  async createInitialSessionCandidateWalk(candidates, consumer, startIndex, initialFailure) {
107806
107930
  let index = startIndex;
@@ -107882,6 +108006,8 @@ class StageSessionController {
107882
108006
  }
107883
108007
  return await this.createSessionObservingPause(candidate, consumer);
107884
108008
  } catch (error) {
108009
+ if (error instanceof StageSessionBindingCleanupFailure)
108010
+ throw error;
107885
108011
  const errorSettingsManager = retrySettingsManagerFromError(error);
107886
108012
  if (errorSettingsManager !== undefined)
107887
108013
  this.sessionSettingsManager = errorSettingsManager;
@@ -107915,6 +108041,20 @@ class StageSessionController {
107915
108041
  }
107916
108042
  }
107917
108043
  createSession(candidate, consumer, resumeOptions) {
108044
+ if (this.bindingCleanupFailure !== undefined)
108045
+ return Promise.reject(this.bindingCleanupFailure);
108046
+ if (this.sessionShutdownPromise !== undefined) {
108047
+ const generation = this.abortGeneration;
108048
+ return this.sessionShutdownPromise.then(() => {
108049
+ if (this.disposed || this.opts.signal?.aborted || this.abortGeneration !== generation)
108050
+ throw this.staleCreationReason(generation);
108051
+ return this.createSession(candidate, consumer, resumeOptions);
108052
+ });
108053
+ }
108054
+ if (this.activeCreation !== undefined)
108055
+ return this.activeCreation;
108056
+ if (this.session !== undefined)
108057
+ return Promise.resolve(this.session);
107918
108058
  const creation = this.createSessionAttempt(candidate, consumer, resumeOptions);
107919
108059
  this.activeCreation = creation;
107920
108060
  creation.finally(() => {
@@ -107941,6 +108081,10 @@ class StageSessionController {
107941
108081
  ...this.sharedOrchestrationContext !== undefined ? { orchestrationContext: this.sharedOrchestrationContext } : {}
107942
108082
  }) : missingAdapter(consumer);
107943
108083
  } catch (error) {
108084
+ if (error instanceof StageSessionBindingCleanupFailure) {
108085
+ this.bindingCleanupFailure = error;
108086
+ throw error;
108087
+ }
107944
108088
  if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
107945
108089
  throw this.disposed ? new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`) : this.staleCreationReason(startGeneration);
107946
108090
  throw error;
@@ -108004,12 +108148,16 @@ class StageSessionController {
108004
108148
  });
108005
108149
  return result.session;
108006
108150
  }
108007
- async disposeCurrentSession() {
108151
+ async disposeCurrentSession(nextCandidateIndex) {
108152
+ if (this.sessionShutdownPromise !== undefined)
108153
+ await this.sessionShutdownPromise;
108154
+ const startGeneration = this.abortGeneration;
108008
108155
  this.abortThrownErrorRetries(new Error(`atomic-workflows: stage "${this.opts.stageName}" session was replaced`));
108009
108156
  const current = this.session;
108010
108157
  this.messageAdmission.reset();
108011
108158
  this.replacement.retire(current);
108012
108159
  this.session = undefined;
108160
+ this.activeCandidateIndex = nextCandidateIndex;
108013
108161
  if (this.sessionPromise !== this.ownedCreationPromise)
108014
108162
  this.sessionPromise = undefined;
108015
108163
  this.sessionSettingsManager = undefined;
@@ -108020,6 +108168,15 @@ class StageSessionController {
108020
108168
  this.unsubscribeTerminateWatcher?.();
108021
108169
  this.unsubscribeTerminateWatcher = undefined;
108022
108170
  this.terminatingToolCallIds.clear();
108171
+ const shutdown = Promise.resolve().then(() => shutdownStageSession(current));
108172
+ this.sessionShutdownPromise = shutdown;
108173
+ shutdown.then(() => {
108174
+ if (this.sessionShutdownPromise === shutdown)
108175
+ this.sessionShutdownPromise = undefined;
108176
+ }, () => {});
108177
+ await shutdown;
108178
+ if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
108179
+ throw this.staleCreationReason(startGeneration);
108023
108180
  }
108024
108181
  async promptWithPauseResume(activeSession, initialText, sdkOptions) {
108025
108182
  let nextText = initialText;
@@ -108129,12 +108286,13 @@ class StageSessionController {
108129
108286
  throw err;
108130
108287
  }
108131
108288
  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;
108289
+ await this.disposeCurrentSession(resumedOverflowNextIndex);
108134
108290
  return false;
108135
108291
  }
108136
108292
  }
108137
108293
  async handleCandidateFailure(err, candidate, candidates, index) {
108294
+ if (err instanceof StageSessionBindingCleanupFailure)
108295
+ throw err;
108138
108296
  const message = errorMessage4(err);
108139
108297
  const terminalStageDelivery = isWorkflowPendingStageDeliveryFailure(err);
108140
108298
  if (!terminalStageDelivery && this.capturedStructuredOutputForAttempt() && isRetryableModelFailure(err)) {
@@ -108158,7 +108316,7 @@ class StageSessionController {
108158
108316
  }
108159
108317
  const nextCandidate = candidates[index + 1];
108160
108318
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${message}. Retrying with ${candidateLabel(nextCandidate)}.`);
108161
- await this.disposeCurrentSession();
108319
+ await this.disposeCurrentSession(index + 1);
108162
108320
  return "retry";
108163
108321
  }
108164
108322
  capturedStructuredOutputForAttempt() {
@@ -112910,6 +113068,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112910
113068
  return;
112911
113069
  const ownedRunIds = new Set;
112912
113070
  const runs = activeStore.runs();
113071
+ const resolveBoundarySegments = createWorkflowBoundarySegmentsResolver(runs);
112913
113072
  for (const run of runs) {
112914
113073
  const rootRunId = durableRootRunIdForRun(runs, run.id);
112915
113074
  if (rootRunId === undefined)
@@ -112930,7 +113089,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112930
113089
  ...run.stages.filter((stage) => !isNonAgentStage(stage) && workflowInvocationOwnsGroup(workflowInvocationIntercomGroup(rootRunId), stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId))).map((stage) => ({
112931
113090
  stageId: stage.id,
112932
113091
  stageName: stage.name,
112933
- target: stageRouteTarget(runs, rootRunId, run.id, stage.id),
113092
+ target: stageRouteTarget(runs, rootRunId, run.id, stage.id, resolveBoundarySegments),
112934
113093
  lifecycle: stage.sessionId === undefined && stage.sessionFile === undefined ? "pending" : "running",
112935
113094
  routeEligible: stage.pendingStageDeliveryAvailable === true && (stage.status === "pending" || stage.status === "running" || stage.status === "awaiting_input" || stage.status === "paused" || stage.status === "blocked"),
112936
113095
  group: stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId)
@@ -112938,7 +113097,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112938
113097
  ...nonAgentNodes(run).map((node) => ({
112939
113098
  stageId: node.id,
112940
113099
  stageName: node.name,
112941
- target: stageRouteTarget(runs, rootRunId, run.id, node.id),
113100
+ target: stageRouteTarget(runs, rootRunId, run.id, node.id, resolveBoundarySegments),
112942
113101
  lifecycle: "pending",
112943
113102
  routeEligible: false,
112944
113103
  recipientPurpose: "control",
@@ -113061,8 +113220,8 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
113061
113220
  pi.on?.("session_shutdown", dispose);
113062
113221
  return dispose;
113063
113222
  }
113064
- function stageRouteTarget(runs, rootRunId, runId, stageId) {
113065
- const boundarySegments = runId === rootRunId ? [] : workflowBoundarySegments(runs, runId);
113223
+ function stageRouteTarget(runs, rootRunId, runId, stageId, resolveBoundarySegments = (id) => workflowBoundarySegments(runs, id)) {
113224
+ const boundarySegments = runId === rootRunId ? [] : resolveBoundarySegments(runId);
113066
113225
  return formatWorkflowStageTarget(rootRunId, ...boundarySegments ?? [runId], stageId);
113067
113226
  }
113068
113227
  function possibleStageRows(runs, rootRunId, rootRun) {
@@ -113909,8 +114068,13 @@ function buildRuntimeAdapters(pi, options = {}) {
113909
114068
  const sessionOptions = withWorkflowStageSessionOptions(stripWorkflowOnlyOptions2(stageOptions) ?? {}, meta, pi);
113910
114069
  const result = await createSession(sessionOptions);
113911
114070
  const bindable = result.session;
113912
- if (typeof bindable.bindExtensions === "function") {
113913
- await bindable.bindExtensions(shouldBindStageUiContext(pi, meta) ? { uiContext: makeStageExtensionUiContext(pi.ui ?? {}, meta, broker) } : {});
114071
+ try {
114072
+ if (typeof bindable.bindExtensions === "function") {
114073
+ await bindable.bindExtensions(shouldBindStageUiContext(pi, meta) ? { uiContext: makeStageExtensionUiContext(pi.ui ?? {}, meta, broker) } : {});
114074
+ }
114075
+ } catch (error) {
114076
+ await cleanupFailedStageSessionBinding(result.session, error);
114077
+ throw error;
113914
114078
  }
113915
114079
  return result;
113916
114080
  }
@@ -117845,10 +118009,14 @@ function registerWorkflowShortcut(pi, overlay) {
117845
118009
  });
117846
118010
  pi.registerShortcut("alt+pageUp", {
117847
118011
  description: "Scroll background workflows up",
118012
+ keybinding: "app.workflows.scrollUp",
118013
+ preferEditor: true,
117848
118014
  handler: () => scrollStoreWidget(store, -1)
117849
118015
  });
117850
118016
  pi.registerShortcut("alt+pageDown", {
117851
118017
  description: "Scroll background workflows down",
118018
+ keybinding: "app.workflows.scrollDown",
118019
+ preferEditor: true,
117852
118020
  handler: () => scrollStoreWidget(store, 1)
117853
118021
  });
117854
118022
  }