@bastani/atomic 0.9.19-alpha.8 → 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 (111) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +38 -0
  3. package/dist/builtin/intercom/README.md +7 -7
  4. package/dist/builtin/intercom/broker/client.ts +4 -0
  5. package/dist/builtin/intercom/broker/send-handler.ts +18 -1
  6. package/dist/builtin/intercom/index.bundle.mjs +20 -17
  7. package/dist/builtin/intercom/package.json +1 -1
  8. package/dist/builtin/intercom/skills/intercom/SKILL.md +3 -3
  9. package/dist/builtin/intercom/types.ts +1 -1
  10. package/dist/builtin/mcp/CHANGELOG.md +8 -0
  11. package/dist/builtin/mcp/package.json +1 -1
  12. package/dist/builtin/subagents/CHANGELOG.md +30 -0
  13. package/dist/builtin/subagents/package.json +1 -1
  14. package/dist/builtin/web-access/CHANGELOG.md +20 -0
  15. package/dist/builtin/web-access/index.bundle.mjs +19 -17
  16. package/dist/builtin/web-access/package.json +1 -1
  17. package/dist/builtin/workflows/CHANGELOG.md +66 -0
  18. package/dist/builtin/workflows/package.json +1 -1
  19. package/dist/builtin/workflows/src/extension/index.bundle.mjs +324 -40
  20. package/dist/builtin/workflows/src/index.js +97 -18
  21. package/dist/core/extensions/api-types.d.ts +2 -0
  22. package/dist/core/extensions/api-types.d.ts.map +1 -1
  23. package/dist/core/extensions/api-types.js.map +1 -1
  24. package/dist/core/extensions/index.d.ts +1 -1
  25. package/dist/core/extensions/index.d.ts.map +1 -1
  26. package/dist/core/extensions/index.js.map +1 -1
  27. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  28. package/dist/core/extensions/loader-api.js.map +1 -1
  29. package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
  30. package/dist/core/extensions/loader-host-modules.js +3 -1
  31. package/dist/core/extensions/loader-host-modules.js.map +1 -1
  32. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  33. package/dist/core/extensions/loader-virtual-modules.js +2 -0
  34. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  35. package/dist/core/extensions/reactive-widget.d.ts +6 -0
  36. package/dist/core/extensions/reactive-widget.d.ts.map +1 -1
  37. package/dist/core/extensions/reactive-widget.js +7 -1
  38. package/dist/core/extensions/reactive-widget.js.map +1 -1
  39. package/dist/core/extensions/runner-shortcuts.d.ts.map +1 -1
  40. package/dist/core/extensions/runner-shortcuts.js +32 -3
  41. package/dist/core/extensions/runner-shortcuts.js.map +1 -1
  42. package/dist/core/extensions/runtime-types.d.ts +4 -0
  43. package/dist/core/extensions/runtime-types.d.ts.map +1 -1
  44. package/dist/core/extensions/runtime-types.js.map +1 -1
  45. package/dist/core/extensions/ui-types.d.ts +20 -0
  46. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  47. package/dist/core/extensions/ui-types.js.map +1 -1
  48. package/dist/core/keybinding-identity.d.ts +4 -0
  49. package/dist/core/keybinding-identity.d.ts.map +1 -0
  50. package/dist/core/keybinding-identity.js +10 -0
  51. package/dist/core/keybinding-identity.js.map +1 -0
  52. package/dist/core/keybindings.d.ts +10 -0
  53. package/dist/core/keybindings.d.ts.map +1 -1
  54. package/dist/core/keybindings.js +2 -0
  55. package/dist/core/keybindings.js.map +1 -1
  56. package/dist/core/tools/bash.d.ts.map +1 -1
  57. package/dist/core/tools/bash.js +7 -1
  58. package/dist/core/tools/bash.js.map +1 -1
  59. package/dist/index-extensions.d.ts +1 -1
  60. package/dist/index-extensions.d.ts.map +1 -1
  61. package/dist/index-extensions.js.map +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/modes/interactive/components/scroll-widget.d.ts +23 -0
  67. package/dist/modes/interactive/components/scroll-widget.d.ts.map +1 -0
  68. package/dist/modes/interactive/components/scroll-widget.js +61 -0
  69. package/dist/modes/interactive/components/scroll-widget.js.map +1 -0
  70. package/dist/modes/interactive/interactive-extension-runtime.js +10 -3
  71. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  72. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  73. package/dist/modes/interactive/interactive-mode-base.js +3 -2
  74. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  75. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +4 -1
  76. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  77. package/dist/modes/interactive-engine/engine-custom-ui.js +15 -7
  78. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  79. package/dist/modes/interactive-engine/engine-dialog-host.d.ts.map +1 -1
  80. package/dist/modes/interactive-engine/engine-dialog-host.js +4 -1
  81. package/dist/modes/interactive-engine/engine-dialog-host.js.map +1 -1
  82. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
  83. package/dist/modes/interactive-engine/extension-ui-bridge.js +1 -1
  84. package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
  85. package/dist/modes/interactive-engine/protocol.d.ts +12 -1
  86. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  87. package/dist/modes/interactive-engine/protocol.js +45 -2
  88. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  89. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  90. package/dist/modes/interactive-engine/remote-component.js +18 -8
  91. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  92. package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-extension-ui.js +2 -1
  94. package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
  95. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  96. package/dist/modes/rpc/rpc-mode.js +1 -0
  97. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  98. package/dist/modes/rpc/rpc-types.d.ts +4 -0
  99. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  100. package/dist/modes/rpc/rpc-types.js.map +1 -1
  101. package/docs/extensions/api-reference.md +2 -0
  102. package/docs/extensions/authoring.md +2 -0
  103. package/docs/intercom/reference.md +4 -0
  104. package/docs/intercom.md +4 -4
  105. package/docs/keybindings.md +17 -0
  106. package/docs/tui.md +34 -0
  107. package/docs/web-access.md +10 -0
  108. package/docs/workflows/api-reference.md +2 -0
  109. package/docs/workflows/operations.md +8 -0
  110. package/npm-shrinkwrap.json +32 -32
  111. 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]));
@@ -79093,7 +79131,9 @@ function plainCollapsed(counts, activeTools) {
79093
79131
  const tools = activeTools > 0 ? ` · ${activeTools} tool${activeTools === 1 ? "" : "s"}` : "";
79094
79132
  return ` ▾ ${total} background · ${counts.active} ●${paused}${quit}${blocked}${tools}`;
79095
79133
  }
79096
- function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
79134
+ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now(), layout) {
79135
+ if (layout)
79136
+ layout.runs = [];
79097
79137
  const display = selectDisplayRuns(snap, now);
79098
79138
  if (display.length === 0)
79099
79139
  return [];
@@ -79112,7 +79152,7 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
79112
79152
  const activeTools = display.filter(isActive).reduce((total, run) => total + activeToolNodes(run).length, 0);
79113
79153
  if (width < COLLAPSED_BREAKPOINT_COLS) {
79114
79154
  return [
79115
- themed ? themedCollapsed(visibleCounts, activeTools, graphTheme) : plainCollapsed(visibleCounts, activeTools)
79155
+ truncateToWidth2(themed ? themedCollapsed(visibleCounts, activeTools, graphTheme) : plainCollapsed(visibleCounts, activeTools), width, "…")
79116
79156
  ];
79117
79157
  }
79118
79158
  const total = display.length;
@@ -79126,6 +79166,11 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
79126
79166
  const run = display[i];
79127
79167
  const runLines = themed ? themedRunLines(run, now, graphTheme, snap.runs, width, expandGraph) : plainRunLines(run, now, snap.runs, width, expandGraph);
79128
79168
  body.push(...runLines);
79169
+ layout?.runs.push({
79170
+ id: run.id,
79171
+ start: body.length - runLines.length + 1,
79172
+ end: body.length + (i === display.length - 1 ? 2 : 1)
79173
+ });
79129
79174
  if (i < display.length - 1)
79130
79175
  body.push("");
79131
79176
  }
@@ -79138,7 +79183,120 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
79138
79183
  });
79139
79184
  }
79140
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
+
79205
+ // dist/builtin/workflows/src/tui/widget-viewport.ts
79206
+ var WORKFLOW_WIDGET_MAX_ROWS = 10;
79207
+
79208
+ class WorkflowWidgetViewport {
79209
+ content;
79210
+ terminalRows;
79211
+ requestRender;
79212
+ getRunRows;
79213
+ getHint;
79214
+ position = 0;
79215
+ version = 0;
79216
+ collapsed = false;
79217
+ state;
79218
+ lines = [];
79219
+ runRows = [];
79220
+ constructor(content, terminalRows, requestRender, getRunRows, getHint = () => "") {
79221
+ this.content = content;
79222
+ this.terminalRows = terminalRows;
79223
+ this.requestRender = requestRender;
79224
+ this.getRunRows = getRunRows;
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;
79234
+ }
79235
+ scroll(direction) {
79236
+ if (this.collapsed)
79237
+ return;
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++;
79244
+ this.requestRender();
79245
+ }
79246
+ render(width) {
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;
79256
+ return nextLines;
79257
+ }
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;
79264
+ this.lines = nextLines;
79265
+ this.runRows = nextRunRows;
79266
+ return nextLines;
79267
+ }
79268
+ resolveAnchor(offset, next) {
79269
+ if (offset === 0 || next.length === 0)
79270
+ return 0;
79271
+ const index = this.runRows.findIndex((run) => offset >= run.start - 1 && offset < run.end);
79272
+ const anchor = this.runRows[index];
79273
+ if (!anchor)
79274
+ return 0;
79275
+ const byId = new Map(next.map((run) => [run.id, run]));
79276
+ const retained = byId.get(anchor.id);
79277
+ if (retained)
79278
+ return Math.min(retained.end - 1, retained.start + offset - anchor.start);
79279
+ const neighbours = [...this.runRows.slice(index + 1), ...this.runRows.slice(0, index).reverse()];
79280
+ for (const neighbour of neighbours) {
79281
+ const survivor = byId.get(neighbour.id);
79282
+ if (survivor)
79283
+ return survivor.start;
79284
+ }
79285
+ return 0;
79286
+ }
79287
+ invalidate() {
79288
+ this.content.invalidate?.();
79289
+ }
79290
+ dispose() {
79291
+ this.content.dispose?.();
79292
+ }
79293
+ }
79294
+
79141
79295
  // dist/builtin/workflows/src/tui/store-widget-installer.ts
79296
+ var widgetViewports = new WeakMap;
79297
+ function scrollStoreWidget(storeInstance, direction) {
79298
+ widgetViewports.get(storeInstance)?.scroll(direction);
79299
+ }
79142
79300
  var defaultTimerApi = {
79143
79301
  setTimeout: (handler, delayMs) => setTimeout(handler, delayMs),
79144
79302
  clearTimeout: (handle) => clearTimeout(handle)
@@ -79169,19 +79327,30 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
79169
79327
  const setWidget = ui.setWidget;
79170
79328
  const requestRender = ui.requestRender;
79171
79329
  const onWidgetRelease = ui.onWidgetRelease;
79330
+ const layout = { runs: [] };
79172
79331
  const controller = installReactiveWidget({
79173
79332
  ui: {
79174
- setWidget: (key, factory, opts) => setWidget.call(ui, key, factory, opts),
79333
+ setWidget: (key, factory, opts) => {
79334
+ if (!factory)
79335
+ widgetViewports.delete(storeInstance);
79336
+ setWidget.call(ui, key, factory ? (tui, theme) => {
79337
+ const host = tui;
79338
+ const viewport = new WorkflowWidgetViewport(factory(tui, theme), () => host?.terminal?.rows ?? 30, () => requestRender ? requestRender.call(ui) : host?.requestRender?.(), () => layout.runs, workflowScrollHint);
79339
+ widgetViewports.set(storeInstance, viewport);
79340
+ return viewport;
79341
+ } : undefined, opts);
79342
+ },
79175
79343
  ...requestRender ? { requestRender: () => requestRender.call(ui) } : {},
79176
79344
  ...onWidgetRelease ? { onWidgetRelease: (key, listener) => onWidgetRelease.call(ui, key, listener) } : {}
79177
79345
  },
79178
79346
  key: WIDGET_KEY,
79179
79347
  placement: "belowEditor",
79348
+ scroll: { maxHeight: 10, maxHeightFraction: 1 / 3 },
79180
79349
  timers,
79181
79350
  getSnapshot: () => liveWidgetSnapshot(storeInstance),
79182
79351
  subscribe: (listener) => subscribeStoreInvalidation(storeInstance, listener),
79183
79352
  getPreviewLines: (snap, now) => buildThemedWidgetLines(snap, undefined, 120, now),
79184
- render: (snap, { theme, width, now }) => buildThemedWidgetLines(snap, theme, width, now),
79353
+ render: (snap, { theme, width, now }) => buildThemedWidgetLines(snap, theme, width, now, layout),
79185
79354
  getNextRefreshDelayMs: (snap, now) => nextWidgetRefreshDelayMs(snap, now),
79186
79355
  requestRenderOnStateNoop: false,
79187
79356
  isStaleError: isStaleExtensionContextError4,
@@ -79189,7 +79358,10 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
79189
79358
  reportWidgetFailure(ui, `Workflow progress widget could not mount: ${error.message}`);
79190
79359
  }
79191
79360
  });
79192
- return () => controller.dispose();
79361
+ return () => {
79362
+ widgetViewports.delete(storeInstance);
79363
+ controller.dispose();
79364
+ };
79193
79365
  }
79194
79366
  function installToolExecutionHooks(pi, storeInstance) {
79195
79367
  const eventBusOn = pi.events?.on;
@@ -106616,16 +106788,52 @@ function stageSessionExtensionRunner(current) {
106616
106788
  }
106617
106789
  return;
106618
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
+ }
106619
106830
  async function disposeStageSession(current) {
106620
106831
  if (!current)
106621
106832
  return;
106622
- const runner = stageSessionExtensionRunner(current);
106623
- if (runner?.hasHandlers("session_shutdown")) {
106624
- try {
106625
- await runner.emit({ type: "session_shutdown", reason: "quit" });
106626
- } catch (error) {
106627
- console.error("atomic-workflows: stage session_shutdown handler failed", error);
106628
- }
106833
+ try {
106834
+ await shutdownStageSession(current);
106835
+ } catch (error) {
106836
+ console.error("atomic-workflows: stage session_shutdown handler failed", error);
106629
106837
  }
106630
106838
  await current.dispose();
106631
106839
  }
@@ -107022,6 +107230,8 @@ class StageSessionController {
107022
107230
  abortReason;
107023
107231
  abortReasonGeneration = 0;
107024
107232
  sessionPromise;
107233
+ sessionShutdownPromise;
107234
+ bindingCleanupFailure;
107025
107235
  reattachSessionFile;
107026
107236
  lastPromptStartIndex;
107027
107237
  attemptUsageStartIndex;
@@ -107138,8 +107348,7 @@ class StageSessionController {
107138
107348
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${error}. Retrying with ${candidateLabel(nextCandidate)}.`);
107139
107349
  this.modelWarnings.push(...this.pendingFallbackWarnings);
107140
107350
  this.pendingFallbackWarnings.length = 0;
107141
- await this.disposeCurrentSession();
107142
- this.activeCandidateIndex = index + 1;
107351
+ await this.disposeCurrentSession(index + 1);
107143
107352
  this.notifyModelFallbackMetaChange();
107144
107353
  return true;
107145
107354
  }
@@ -107167,6 +107376,14 @@ class StageSessionController {
107167
107376
  this.session?.setThinkingLevel(level);
107168
107377
  }
107169
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
+ }
107170
107387
  if (this.disposed)
107171
107388
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
107172
107389
  if (this.session !== undefined)
@@ -107181,14 +107398,16 @@ class StageSessionController {
107181
107398
  };
107182
107399
  pending.then(release, () => {
107183
107400
  release();
107184
- if (this.sessionPromise === pending)
107401
+ if (this.sessionPromise === pending) {
107185
107402
  this.sessionPromise = undefined;
107403
+ this.activeCandidateIndex = undefined;
107404
+ }
107186
107405
  });
107187
107406
  }
107188
107407
  return this.sessionPromise;
107189
107408
  }
107190
107409
  async ensureSessionFromFile(sessionFile, consumer = "prompt") {
107191
- if (!this.sessionPromise && !this.session)
107410
+ if (!this.sessionShutdownPromise && !this.sessionPromise && !this.session)
107192
107411
  this.reattachSessionFile = sessionFile;
107193
107412
  const session = await this.ensureSession(consumer);
107194
107413
  await session.closeWorkflowStageGeneration?.();
@@ -107286,6 +107505,18 @@ class StageSessionController {
107286
107505
  return;
107287
107506
  let index = this.activeCandidateIndex ?? 0;
107288
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
+ }
107289
107520
  const candidate = candidates[index];
107290
107521
  try {
107291
107522
  const created = this.session && this.activeCandidateIndex === index ? this.session : await this.createSessionWithThrownErrorRetry(candidate, consumer);
@@ -107677,7 +107908,8 @@ class StageSessionController {
107677
107908
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
107678
107909
  }
107679
107910
  const candidates = await this.modelCandidates();
107680
- const first = candidates[0];
107911
+ const initialIndex = this.activeCandidateIndex ?? 0;
107912
+ const first = candidates[initialIndex];
107681
107913
  if (first === undefined) {
107682
107914
  return this.createSessionObservingPause(undefined, consumer).catch((error) => this.createInitialSessionWithRetry(undefined, consumer, { error }));
107683
107915
  }
@@ -107690,9 +107922,9 @@ class StageSessionController {
107690
107922
  this.resumeCurrentSession = true;
107691
107923
  return resumed;
107692
107924
  }
107693
- this.activeCandidateIndex = 0;
107925
+ this.activeCandidateIndex = initialIndex;
107694
107926
  this.selectedModel = first.id;
107695
- 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 }));
107696
107928
  }
107697
107929
  async createInitialSessionCandidateWalk(candidates, consumer, startIndex, initialFailure) {
107698
107930
  let index = startIndex;
@@ -107774,6 +108006,8 @@ class StageSessionController {
107774
108006
  }
107775
108007
  return await this.createSessionObservingPause(candidate, consumer);
107776
108008
  } catch (error) {
108009
+ if (error instanceof StageSessionBindingCleanupFailure)
108010
+ throw error;
107777
108011
  const errorSettingsManager = retrySettingsManagerFromError(error);
107778
108012
  if (errorSettingsManager !== undefined)
107779
108013
  this.sessionSettingsManager = errorSettingsManager;
@@ -107807,6 +108041,20 @@ class StageSessionController {
107807
108041
  }
107808
108042
  }
107809
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);
107810
108058
  const creation = this.createSessionAttempt(candidate, consumer, resumeOptions);
107811
108059
  this.activeCreation = creation;
107812
108060
  creation.finally(() => {
@@ -107833,6 +108081,10 @@ class StageSessionController {
107833
108081
  ...this.sharedOrchestrationContext !== undefined ? { orchestrationContext: this.sharedOrchestrationContext } : {}
107834
108082
  }) : missingAdapter(consumer);
107835
108083
  } catch (error) {
108084
+ if (error instanceof StageSessionBindingCleanupFailure) {
108085
+ this.bindingCleanupFailure = error;
108086
+ throw error;
108087
+ }
107836
108088
  if (this.disposed || this.opts.signal?.aborted === true || this.abortGeneration !== startGeneration)
107837
108089
  throw this.disposed ? new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`) : this.staleCreationReason(startGeneration);
107838
108090
  throw error;
@@ -107896,12 +108148,16 @@ class StageSessionController {
107896
108148
  });
107897
108149
  return result.session;
107898
108150
  }
107899
- async disposeCurrentSession() {
108151
+ async disposeCurrentSession(nextCandidateIndex) {
108152
+ if (this.sessionShutdownPromise !== undefined)
108153
+ await this.sessionShutdownPromise;
108154
+ const startGeneration = this.abortGeneration;
107900
108155
  this.abortThrownErrorRetries(new Error(`atomic-workflows: stage "${this.opts.stageName}" session was replaced`));
107901
108156
  const current = this.session;
107902
108157
  this.messageAdmission.reset();
107903
108158
  this.replacement.retire(current);
107904
108159
  this.session = undefined;
108160
+ this.activeCandidateIndex = nextCandidateIndex;
107905
108161
  if (this.sessionPromise !== this.ownedCreationPromise)
107906
108162
  this.sessionPromise = undefined;
107907
108163
  this.sessionSettingsManager = undefined;
@@ -107912,6 +108168,15 @@ class StageSessionController {
107912
108168
  this.unsubscribeTerminateWatcher?.();
107913
108169
  this.unsubscribeTerminateWatcher = undefined;
107914
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);
107915
108180
  }
107916
108181
  async promptWithPauseResume(activeSession, initialText, sdkOptions) {
107917
108182
  let nextText = initialText;
@@ -108021,12 +108286,13 @@ class StageSessionController {
108021
108286
  throw err;
108022
108287
  }
108023
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])}.`);
108024
- await this.disposeCurrentSession();
108025
- this.activeCandidateIndex = resumedOverflowNextIndex;
108289
+ await this.disposeCurrentSession(resumedOverflowNextIndex);
108026
108290
  return false;
108027
108291
  }
108028
108292
  }
108029
108293
  async handleCandidateFailure(err, candidate, candidates, index) {
108294
+ if (err instanceof StageSessionBindingCleanupFailure)
108295
+ throw err;
108030
108296
  const message = errorMessage4(err);
108031
108297
  const terminalStageDelivery = isWorkflowPendingStageDeliveryFailure(err);
108032
108298
  if (!terminalStageDelivery && this.capturedStructuredOutputForAttempt() && isRetryableModelFailure(err)) {
@@ -108050,7 +108316,7 @@ class StageSessionController {
108050
108316
  }
108051
108317
  const nextCandidate = candidates[index + 1];
108052
108318
  this.pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${message}. Retrying with ${candidateLabel(nextCandidate)}.`);
108053
- await this.disposeCurrentSession();
108319
+ await this.disposeCurrentSession(index + 1);
108054
108320
  return "retry";
108055
108321
  }
108056
108322
  capturedStructuredOutputForAttempt() {
@@ -112802,6 +113068,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112802
113068
  return;
112803
113069
  const ownedRunIds = new Set;
112804
113070
  const runs = activeStore.runs();
113071
+ const resolveBoundarySegments = createWorkflowBoundarySegmentsResolver(runs);
112805
113072
  for (const run of runs) {
112806
113073
  const rootRunId = durableRootRunIdForRun(runs, run.id);
112807
113074
  if (rootRunId === undefined)
@@ -112822,7 +113089,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112822
113089
  ...run.stages.filter((stage) => !isNonAgentStage(stage) && workflowInvocationOwnsGroup(workflowInvocationIntercomGroup(rootRunId), stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId))).map((stage) => ({
112823
113090
  stageId: stage.id,
112824
113091
  stageName: stage.name,
112825
- target: stageRouteTarget(runs, rootRunId, run.id, stage.id),
113092
+ target: stageRouteTarget(runs, rootRunId, run.id, stage.id, resolveBoundarySegments),
112826
113093
  lifecycle: stage.sessionId === undefined && stage.sessionFile === undefined ? "pending" : "running",
112827
113094
  routeEligible: stage.pendingStageDeliveryAvailable === true && (stage.status === "pending" || stage.status === "running" || stage.status === "awaiting_input" || stage.status === "paused" || stage.status === "blocked"),
112828
113095
  group: stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId)
@@ -112830,7 +113097,7 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112830
113097
  ...nonAgentNodes(run).map((node) => ({
112831
113098
  stageId: node.id,
112832
113099
  stageName: node.name,
112833
- target: stageRouteTarget(runs, rootRunId, run.id, node.id),
113100
+ target: stageRouteTarget(runs, rootRunId, run.id, node.id, resolveBoundarySegments),
112834
113101
  lifecycle: "pending",
112835
113102
  routeEligible: false,
112836
113103
  recipientPurpose: "control",
@@ -112953,8 +113220,8 @@ function registerPendingStageIntercomBridge(pi, activeStore) {
112953
113220
  pi.on?.("session_shutdown", dispose);
112954
113221
  return dispose;
112955
113222
  }
112956
- function stageRouteTarget(runs, rootRunId, runId, stageId) {
112957
- 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);
112958
113225
  return formatWorkflowStageTarget(rootRunId, ...boundarySegments ?? [runId], stageId);
112959
113226
  }
112960
113227
  function possibleStageRows(runs, rootRunId, rootRun) {
@@ -113801,8 +114068,13 @@ function buildRuntimeAdapters(pi, options = {}) {
113801
114068
  const sessionOptions = withWorkflowStageSessionOptions(stripWorkflowOnlyOptions2(stageOptions) ?? {}, meta, pi);
113802
114069
  const result = await createSession(sessionOptions);
113803
114070
  const bindable = result.session;
113804
- if (typeof bindable.bindExtensions === "function") {
113805
- 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;
113806
114078
  }
113807
114079
  return result;
113808
114080
  }
@@ -117735,6 +118007,18 @@ function registerWorkflowShortcut(pi, overlay) {
117735
118007
  description: "Open workflow orchestrator pane",
117736
118008
  handler: openPane
117737
118009
  });
118010
+ pi.registerShortcut("alt+pageUp", {
118011
+ description: "Scroll background workflows up",
118012
+ keybinding: "app.workflows.scrollUp",
118013
+ preferEditor: true,
118014
+ handler: () => scrollStoreWidget(store, -1)
118015
+ });
118016
+ pi.registerShortcut("alt+pageDown", {
118017
+ description: "Scroll background workflows down",
118018
+ keybinding: "app.workflows.scrollDown",
118019
+ preferEditor: true,
118020
+ handler: () => scrollStoreWidget(store, 1)
118021
+ });
117738
118022
  }
117739
118023
  function registerIntercomControl(pi, intercomControlRef) {
117740
118024
  intercomControlRef.current = subscribeIntercomControl(pi, buildIntercomCallbacks({