@bastani/atomic 0.9.19-alpha.6 → 0.9.19-alpha.8

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 (119) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  7. package/dist/builtin/web-access/index.bundle.mjs +22 -3
  8. package/dist/builtin/web-access/package.json +1 -1
  9. package/dist/builtin/workflows/CHANGELOG.md +19 -0
  10. package/dist/builtin/workflows/README.md +1 -1
  11. package/dist/builtin/workflows/builtin/adversarial-verification.js +2 -2
  12. package/dist/builtin/workflows/builtin/{chunk-vhmnzdj9.js → chunk-1jth021m.js} +1 -1
  13. package/dist/builtin/workflows/builtin/{chunk-p8jjwfg6.js → chunk-2377r57g.js} +4 -2
  14. package/dist/builtin/workflows/builtin/{chunk-hbnwv56f.js → chunk-31j2gs7h.js} +2 -2
  15. package/dist/builtin/workflows/builtin/{chunk-n8zysf1t.js → chunk-ngkqkzej.js} +1 -1
  16. package/dist/builtin/workflows/builtin/{chunk-1nbq96fh.js → chunk-nkrafh9s.js} +1 -1
  17. package/dist/builtin/workflows/builtin/{chunk-1kx4aggw.js → chunk-wf741bv3.js} +2 -2
  18. package/dist/builtin/workflows/builtin/{chunk-t6x2d78s.js → chunk-z29qbehr.js} +1 -1
  19. package/dist/builtin/workflows/builtin/generate-and-filter.js +2 -2
  20. package/dist/builtin/workflows/builtin/goal.js +3 -3
  21. package/dist/builtin/workflows/builtin/index.js +7 -7
  22. package/dist/builtin/workflows/builtin/ralph.js +3 -3
  23. package/dist/builtin/workflows/builtin/tournament.js +2 -2
  24. package/dist/builtin/workflows/package.json +1 -1
  25. package/dist/builtin/workflows/src/extension/index.bundle.mjs +590 -256
  26. package/dist/builtin/workflows/src/index.js +67 -19
  27. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -0
  28. package/dist/modes/interactive/components/task-live-transcript.d.ts +1 -1
  29. package/dist/modes/interactive/components/task-live-transcript.d.ts.map +1 -1
  30. package/dist/modes/interactive/components/task-live-transcript.js +19 -11
  31. package/dist/modes/interactive/components/task-live-transcript.js.map +1 -1
  32. package/docs/background-tasks.md +1 -1
  33. package/docs/build.md +51 -0
  34. package/docs/changelog.mdx +8 -8
  35. package/docs/compaction/reference.md +267 -0
  36. package/docs/compaction.md +36 -219
  37. package/docs/computer-use.md +35 -0
  38. package/docs/containerization.md +2 -0
  39. package/docs/custom-provider/api-reference.md +145 -0
  40. package/docs/custom-provider/oauth.md +131 -0
  41. package/docs/custom-provider/override.md +34 -0
  42. package/docs/custom-provider/registration.md +163 -0
  43. package/docs/custom-provider/streaming.md +214 -0
  44. package/docs/custom-provider.md +43 -607
  45. package/docs/development.md +76 -0
  46. package/docs/docs.json +199 -77
  47. package/docs/environment-variables.md +1 -1
  48. package/docs/extensions/api-reference.md +1001 -0
  49. package/docs/extensions/authoring.md +676 -0
  50. package/docs/extensions/events.md +793 -0
  51. package/docs/extensions/examples.md +94 -0
  52. package/docs/extensions/ui.md +470 -0
  53. package/docs/extensions.md +270 -2768
  54. package/docs/getting-started/authentication.md +55 -0
  55. package/docs/getting-started/first-session.md +172 -0
  56. package/docs/getting-started/installation.md +154 -0
  57. package/docs/getting-started/project-instructions.md +43 -0
  58. package/docs/guides/configuration.md +65 -0
  59. package/docs/guides/intercom.md +24 -0
  60. package/docs/guides/non-interactive.md +33 -0
  61. package/docs/guides/subagents.md +24 -0
  62. package/docs/guides/workflows.md +27 -0
  63. package/docs/guides.md +41 -0
  64. package/docs/index.md +13 -45
  65. package/docs/intercom/operations.md +178 -0
  66. package/docs/intercom/reference.md +146 -0
  67. package/docs/intercom.md +54 -259
  68. package/docs/json.md +17 -0
  69. package/docs/llama-cpp.md +5 -0
  70. package/docs/models/artificial-analysis-index.md +89 -0
  71. package/docs/models/evals.md +2 -2
  72. package/docs/models/model-selection.md +3 -3
  73. package/docs/models/pareto-efficiency.md +2 -2
  74. package/docs/models/reference.md +601 -0
  75. package/docs/models.md +45 -550
  76. package/docs/packages/authoring.md +88 -0
  77. package/docs/packages/reference.md +43 -0
  78. package/docs/packages.md +28 -107
  79. package/docs/programmatic.md +29 -0
  80. package/docs/prompt-templates.md +11 -0
  81. package/docs/providers/reference.md +23 -0
  82. package/docs/providers.md +10 -15
  83. package/docs/quickstart.md +44 -238
  84. package/docs/reference/cli.md +252 -0
  85. package/docs/reference.md +52 -0
  86. package/docs/rpc/examples.md +64 -0
  87. package/docs/rpc/extension-ui.md +198 -0
  88. package/docs/rpc/protocol.md +1341 -0
  89. package/docs/rpc.md +109 -1346
  90. package/docs/sdk/reference.md +1090 -0
  91. package/docs/sdk.md +56 -1002
  92. package/docs/session-format.md +2 -0
  93. package/docs/sessions.md +6 -10
  94. package/docs/settings.md +6 -45
  95. package/docs/skills/authoring.md +95 -0
  96. package/docs/skills/reference.md +60 -0
  97. package/docs/skills.md +20 -122
  98. package/docs/subagents/authoring.md +37 -0
  99. package/docs/subagents/reference.md +60 -0
  100. package/docs/subagents.md +39 -95
  101. package/docs/terminal-setup.md +1 -1
  102. package/docs/themes/reference.md +176 -0
  103. package/docs/themes.md +21 -135
  104. package/docs/tmux.md +5 -0
  105. package/docs/tools/edit.md +5 -0
  106. package/docs/tools.md +5 -0
  107. package/docs/tui/reference.md +343 -0
  108. package/docs/tui.md +23 -286
  109. package/docs/usage.md +26 -209
  110. package/docs/web-access.md +2 -0
  111. package/docs/windows.md +2 -1
  112. package/docs/workflows/api-reference.md +11 -4
  113. package/docs/workflows/authoring.md +4 -2
  114. package/docs/workflows/builtins.md +2 -2
  115. package/docs/workflows/operations.md +15 -10
  116. package/docs/workflows/reliable-design.md +21 -3
  117. package/docs/workflows/verification.md +8 -0
  118. package/npm-shrinkwrap.json +32 -32
  119. package/package.json +3 -3
@@ -69012,9 +69012,9 @@ function renderDetailHintRows(detail, width, theme) {
69012
69012
  }
69013
69013
  function formatTime(ms) {
69014
69014
  const d = new Date(ms);
69015
- const hh = String(d.getUTCHours()).padStart(2, "0");
69016
- const mm = String(d.getUTCMinutes()).padStart(2, "0");
69017
- const ss = String(d.getUTCSeconds()).padStart(2, "0");
69015
+ const hh = String(d.getHours()).padStart(2, "0");
69016
+ const mm = String(d.getMinutes()).padStart(2, "0");
69017
+ const ss = String(d.getSeconds()).padStart(2, "0");
69018
69018
  return `${hh}:${mm}:${ss}`;
69019
69019
  }
69020
69020
 
@@ -72599,7 +72599,7 @@ class GraphCanvas {
72599
72599
 
72600
72600
  // dist/builtin/workflows/src/tui/layout.ts
72601
72601
  var NODE_W = 24;
72602
- var NODE_H = 6;
72602
+ var NODE_H = 5;
72603
72603
  function computeLayout(stages, opts = {}) {
72604
72604
  const colGap = opts.colGap ?? 4;
72605
72605
  const rowGap = opts.rowGap ?? 3;
@@ -72682,17 +72682,255 @@ function computeLayout(stages, opts = {}) {
72682
72682
  rowIndex.set(ids[i], i);
72683
72683
  }
72684
72684
  }
72685
+ const heights = new Map(stages.map((stage) => [stage.id, opts.nodeHeight?.(stage) ?? NODE_H]));
72686
+ const verticalOffsets = new Map;
72687
+ const horizontalOffsets = new Map;
72688
+ let depthY = 0;
72689
+ for (const depth of [...colGroups.keys()].sort((a, b) => a - b)) {
72690
+ const ids = colGroups.get(depth);
72691
+ verticalOffsets.set(depth, depthY);
72692
+ let siblingY = 0;
72693
+ let tallest = 0;
72694
+ for (const id of ids) {
72695
+ const height = heights.get(id);
72696
+ horizontalOffsets.set(id, siblingY);
72697
+ siblingY += height + rowGap;
72698
+ tallest = Math.max(tallest, height);
72699
+ }
72700
+ depthY += tallest + rowGap;
72701
+ }
72685
72702
  const nodes = [];
72686
72703
  for (const s of stages) {
72687
72704
  const col = colMap.get(s.id) ?? 0;
72688
72705
  const row = rowIndex.get(s.id) ?? 0;
72689
72706
  const x = orientation === "vertical" ? row * (NODE_W + colGap) + (centreShift.get(col) ?? 0) : col * (NODE_W + colGap);
72690
- const y = orientation === "vertical" ? col * (NODE_H + rowGap) : row * (NODE_H + rowGap);
72691
- nodes.push({ stage: s, col, row, x, y });
72707
+ const y = orientation === "vertical" ? verticalOffsets.get(col) : horizontalOffsets.get(s.id);
72708
+ nodes.push({ stage: s, col, row, x, y, ...opts.nodeHeight ? { height: heights.get(s.id) } : {} });
72692
72709
  }
72693
72710
  return nodes;
72694
72711
  }
72695
72712
 
72713
+ // dist/builtin/workflows/src/tui/node-card.ts
72714
+ import { stripTerminalSequences } from "@earendil-works/pi-tui";
72715
+ function pulseT(phase) {
72716
+ return (Math.sin(phase * Math.PI * 2 - Math.PI / 2) + 1) / 2;
72717
+ }
72718
+ function queuedBadgeCount(count) {
72719
+ if (typeof count !== "number" || !Number.isFinite(count))
72720
+ return 0;
72721
+ return Math.max(0, Math.trunc(count));
72722
+ }
72723
+ function queuedBadgeText(count) {
72724
+ return `✉ ${count} queued`;
72725
+ }
72726
+ function pickBorder(status, focused, phase, theme) {
72727
+ switch (status) {
72728
+ case "running":
72729
+ if (focused)
72730
+ return theme.warning;
72731
+ return lerpColor(theme.borderDim, theme.warning, pulseT(phase));
72732
+ case "paused":
72733
+ return theme.warning;
72734
+ case "awaiting_input":
72735
+ if (focused)
72736
+ return theme.info;
72737
+ return lerpColor(theme.borderDim, theme.info, pulseT(phase));
72738
+ case "completed":
72739
+ return theme.success;
72740
+ case "failed":
72741
+ return theme.error;
72742
+ case "blocked":
72743
+ return theme.dim;
72744
+ case "skipped":
72745
+ return theme.dim;
72746
+ default:
72747
+ return focused ? theme.borderActive : theme.borderDim;
72748
+ }
72749
+ }
72750
+ function durationColor(status, theme) {
72751
+ switch (status) {
72752
+ case "running":
72753
+ return theme.warning;
72754
+ case "paused":
72755
+ return theme.warning;
72756
+ case "awaiting_input":
72757
+ return theme.info;
72758
+ case "completed":
72759
+ return theme.success;
72760
+ case "failed":
72761
+ return theme.error;
72762
+ default:
72763
+ return theme.dim;
72764
+ }
72765
+ }
72766
+ function blockedBadgeText(stage, stages, width) {
72767
+ const base = "↑ blocked";
72768
+ const blockedBy = stage.blockedByStageId;
72769
+ if (!blockedBy)
72770
+ return base;
72771
+ const upstream = stages?.find((s) => s.id === blockedBy)?.name ?? blockedBy;
72772
+ const withUpstream = `${base} by ${upstream}`;
72773
+ if (visibleWidth(withUpstream) <= width)
72774
+ return withUpstream;
72775
+ return base;
72776
+ }
72777
+ function durationText(stage) {
72778
+ const elapsed = elapsedStageMs(stage);
72779
+ return elapsed === undefined ? "—" : fmtDuration(elapsed);
72780
+ }
72781
+ function metaText(stage) {
72782
+ return stage.topologyState === "unavailable" ? "topology unavailable" : "";
72783
+ }
72784
+ function modelText(stage, innerWidth) {
72785
+ const model = stage.model;
72786
+ if (model === undefined || model === "")
72787
+ return "";
72788
+ const slash = model.lastIndexOf("/");
72789
+ const short = slash >= 0 ? model.slice(slash + 1) : model;
72790
+ const level = stage.thinkingLevel;
72791
+ const showLevel = level !== undefined && level !== "" && level !== "off";
72792
+ const suffix = showLevel ? ` · ${level}` : "";
72793
+ const full = `${short}${suffix}`;
72794
+ if (visibleWidth(full) <= innerWidth)
72795
+ return full;
72796
+ const modelSuffix = short.endsWith("-fast") ? "-fast" : "";
72797
+ const name = modelSuffix ? short.slice(0, -modelSuffix.length) : short;
72798
+ const room = Math.max(1, innerWidth - visibleWidth(modelSuffix + suffix));
72799
+ return `${truncateToWidth2(name, room, "…")}${modelSuffix}${suffix}`;
72800
+ }
72801
+ function workflowChildRunRows(stage, width) {
72802
+ const child = stage.workflowChild ?? stage.workflowChildRun;
72803
+ if (child === undefined)
72804
+ return [];
72805
+ return wrapIdentifierLines(child.runId, Math.max(1, width), "run ", "").map((row) => `${row.prefix}${row.chunk}`);
72806
+ }
72807
+ function workflowChildMetaText(stage) {
72808
+ const completed = stage.workflowChild;
72809
+ if (completed !== undefined) {
72810
+ const outputCount = completed.outputCount ?? Object.keys(completed.outputs).length;
72811
+ return outputCount === 1 ? "1 out" : `${outputCount} outs`;
72812
+ }
72813
+ if (stage.workflowChildRun !== undefined)
72814
+ return "live";
72815
+ return;
72816
+ }
72817
+ function joinCompactStatusMeta(status, meta, width) {
72818
+ const candidates = [`${status} · ${meta}`, `${status} ·${meta}`, `${status}· ${meta}`, `${status}·${meta}`];
72819
+ return candidates.find((candidate) => visibleWidth(candidate) <= width) ?? status;
72820
+ }
72821
+ function nodeCardHeight(stage, opts = {}) {
72822
+ const queuedRows = queuedBadgeCount(opts.queuedMessageCount) > 0 ? 1 : 0;
72823
+ const modelRows = stage.model !== undefined && stage.model !== "" ? 1 : 0;
72824
+ let bodyRows;
72825
+ if (stage.status === "awaiting_input") {
72826
+ bodyRows = 2 + modelRows + queuedRows;
72827
+ } else if (stage.workflowChild !== undefined || stage.workflowChildRun !== undefined) {
72828
+ const innerWidth = Math.max(2, (opts.width ?? NODE_W) - 2);
72829
+ bodyRows = workflowChildRunRows(stage, innerWidth).length + 1 + queuedRows;
72830
+ } else {
72831
+ bodyRows = 2 + modelRows + (metaText(stage) ? 1 : 0) + queuedRows;
72832
+ }
72833
+ return Math.max(NODE_H, bodyRows + 2);
72834
+ }
72835
+ function statusLabel(status) {
72836
+ switch (status) {
72837
+ case "awaiting_input":
72838
+ return "awaiting input";
72839
+ case "completed":
72840
+ return "complete";
72841
+ default:
72842
+ return status.replace(/_/g, " ");
72843
+ }
72844
+ }
72845
+ function truncate(s, maxWidth) {
72846
+ if (maxWidth <= 0)
72847
+ return "";
72848
+ if (visibleWidth(s) <= maxWidth)
72849
+ return s;
72850
+ return truncateToWidth2(s, maxWidth, "…");
72851
+ }
72852
+ function centreColored(content, width, fg, bg, opts = {}) {
72853
+ const safe = truncate(content, width);
72854
+ const safeWidth = visibleWidth(safe);
72855
+ const pad = Math.max(0, width - safeWidth);
72856
+ const left = Math.max(0, Math.floor(pad / 2));
72857
+ const right = Math.max(0, pad - left);
72858
+ const bold = opts.bold ? BOLD : "";
72859
+ return `${bg}${" ".repeat(left)}` + `${hexToAnsi(fg)}${bold}${safe}${RESET}` + `${bg}${" ".repeat(right)}`;
72860
+ }
72861
+ function buildTitleSlot(name, innerWidth, focused, theme, cardBg, compact = false) {
72862
+ const maxName = Math.max(2, compact ? innerWidth - 1 : innerWidth - 4);
72863
+ const safeName = stripTerminalSequences(truncate(name, maxName));
72864
+ if (focused) {
72865
+ const tabText = compact ? safeName : ` ${safeName} `;
72866
+ const styled = `${paint(tabText, theme.surface, {
72867
+ bg: theme.accent,
72868
+ bold: true
72869
+ })}${cardBg}`;
72870
+ return { slot: styled, visibleWidth: visibleWidth(tabText) };
72871
+ }
72872
+ const titleRaw = compact ? safeName : ` ${safeName} `;
72873
+ const styled = `${BOLD}${titleRaw}${RESET}${cardBg}`;
72874
+ return { slot: styled, visibleWidth: visibleWidth(titleRaw) };
72875
+ }
72876
+ function renderNodeCard(stage, opts) {
72877
+ const width = opts.width ?? NODE_W;
72878
+ const height = opts.height ?? nodeCardHeight(stage, opts);
72879
+ const focused = opts.focused ?? false;
72880
+ const phase = opts.pulsePhase ?? 0;
72881
+ const theme = opts.theme;
72882
+ const borderHex = pickBorder(stage.status, focused, phase, theme);
72883
+ const bc = hexToAnsi(borderHex);
72884
+ const bg = DEFAULT_BG;
72885
+ const innerWidth = Math.max(2, width - 2);
72886
+ const child = stage.workflowChild ?? stage.workflowChildRun;
72887
+ const { slot: titleSlot, visibleWidth: titleVisibleWidth } = buildTitleSlot(child === undefined ? stage.name : `↳ ${child.workflow}`, innerWidth, focused, theme, bg, child !== undefined);
72888
+ const titleStart = Math.max(1, Math.floor((innerWidth - titleVisibleWidth) / 2));
72889
+ const titleEnd = titleStart + titleVisibleWidth;
72890
+ const topMiddle = `${bc}${"─".repeat(titleStart)}` + `${titleSlot}${bc}` + `${"─".repeat(Math.max(0, innerWidth - titleEnd))}`;
72891
+ const top = `${bg}${bc}╭${topMiddle}╮${RESET}`;
72892
+ const bottom = `${bg}${bc}╰${"─".repeat(innerWidth)}╯${RESET}`;
72893
+ const bodyText = stage.nodeKind === "tool" ? "durable tool" : stage.status === "blocked" ? blockedBadgeText(stage, opts.stages, innerWidth) : durationText(stage);
72894
+ const bodyHex = durationColor(stage.status, theme);
72895
+ const statusText = `${statusIcon(stage.status)} ${stage.toolStatus ?? statusLabel(stage.status)}`;
72896
+ const queuedCount = queuedBadgeCount(opts.queuedMessageCount);
72897
+ const statusLine = `${bg}${bc}│${RESET}` + centreColored(statusText, innerWidth, bodyHex, bg, {
72898
+ bold: stage.status === "running" || stage.status === "awaiting_input"
72899
+ }) + `${bg}${bc}│${RESET}`;
72900
+ const durLine = `${bg}${bc}│${RESET}` + centreColored(bodyText, innerWidth, bodyHex, bg, {
72901
+ bold: stage.status === "blocked"
72902
+ }) + `${bg}${bc}│${RESET}`;
72903
+ const contentRows = Math.max(0, height - 2);
72904
+ const metaLine = `${bg}${bc}│${RESET}${centreColored(metaText(stage), innerWidth, theme.dim, bg)}${bg}${bc}│${RESET}`;
72905
+ const model = modelText(stage, innerWidth);
72906
+ const modelLine = `${bg}${bc}│${RESET}${centreColored(model, innerWidth, theme.textMuted, bg)}${bg}${bc}│${RESET}`;
72907
+ const childRunLines = workflowChildRunRows(stage, innerWidth).map((row) => `${bg}${bc}│${RESET}${centreColored(row, innerWidth, theme.dim, bg)}${bg}${bc}│${RESET}`);
72908
+ const childMeta = workflowChildMetaText(stage);
72909
+ const childSummary = childMeta === undefined ? undefined : joinCompactStatusMeta(statusText, childMeta, innerWidth);
72910
+ const childSummaryLine = childSummary === undefined ? undefined : `${bg}${bc}│${RESET}` + centreColored(childSummary, innerWidth, bodyHex, bg, {
72911
+ bold: stage.status === "running" || stage.status === "awaiting_input"
72912
+ }) + `${bg}${bc}│${RESET}`;
72913
+ const queuedLines = queuedCount > 0 ? [
72914
+ `${bg}${bc}│${RESET}` + centreColored(queuedBadgeText(queuedCount), innerWidth, theme.info, bg, { bold: true }) + `${bg}${bc}│${RESET}`
72915
+ ] : [];
72916
+ const modelLines = model === "" ? [] : [modelLine];
72917
+ const interior = stage.status === "awaiting_input" ? [
72918
+ ...modelLines,
72919
+ statusLine,
72920
+ ...model === "" && queuedCount === 0 ? [
72921
+ `${bg}${bc}│${RESET}` + centreColored("waiting for response", innerWidth, theme.info, bg) + `${bg}${bc}│${RESET}`
72922
+ ] : [],
72923
+ ...queuedLines,
72924
+ `${bg}${bc}│${RESET}` + centreColored("↵ enter to respond", innerWidth, theme.dim, bg) + `${bg}${bc}│${RESET}`
72925
+ ] : childSummaryLine === undefined ? [durLine, ...modelLines, statusLine, ...metaText(stage) ? [metaLine] : [], ...queuedLines] : [...childRunLines, childSummaryLine, ...queuedLines];
72926
+ while (interior.length < contentRows) {
72927
+ interior.push(`${bg}${bc}│${RESET}${bg}${" ".repeat(innerWidth)}${bg}${bc}│${RESET}`);
72928
+ }
72929
+ if (interior.length > contentRows)
72930
+ interior.length = contentRows;
72931
+ return [top, ...interior, bottom];
72932
+ }
72933
+
72696
72934
  // dist/builtin/workflows/src/tui/prompt-card-select.ts
72697
72935
  import { SelectList, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
72698
72936
  function createPromptSelectList(state, theme, maxVisible = 5) {
@@ -73558,9 +73796,25 @@ class GraphViewState {
73558
73796
  _needsAnimationTick() {
73559
73797
  return this.promptState !== null || this.hasAnimatingStages;
73560
73798
  }
73799
+ _nodeHeight(stage) {
73800
+ const baseHeight = nodeCardHeight(stage);
73801
+ if (!this.getStageQueuedMessageCount || nodeCardHeight(stage, { queuedMessageCount: 1 }) === baseHeight) {
73802
+ return baseHeight;
73803
+ }
73804
+ return nodeCardHeight(stage, { queuedMessageCount: this._stageQueuedMessageCount(stage) });
73805
+ }
73806
+ _refreshQueuedNodeHeights() {
73807
+ if (!this.getStageQueuedMessageCount)
73808
+ return;
73809
+ if (!this.cachedLayout.some((node) => (node.height ?? NODE_H) !== this._nodeHeight(node.stage)))
73810
+ return;
73811
+ this.lastBuiltSnapshotVersion = null;
73812
+ this._rebuildLayout();
73813
+ }
73561
73814
  _rebuildLayout() {
73562
73815
  const version = this.currentSnapshot?.version ?? null;
73563
73816
  if (version !== null && version === this.lastBuiltSnapshotVersion) {
73817
+ this._refreshQueuedNodeHeights();
73564
73818
  return;
73565
73819
  }
73566
73820
  const run = this._getCurrentRun();
@@ -73587,6 +73841,8 @@ class GraphViewState {
73587
73841
  const next = graphStages[index];
73588
73842
  if (!next || node.stage.id !== next.id || node.stage.nodeKind !== next.nodeKind)
73589
73843
  return false;
73844
+ if ((node.height ?? NODE_H) !== this._nodeHeight(next))
73845
+ return false;
73590
73846
  if (node.stage.parentIds.length !== next.parentIds.length)
73591
73847
  return false;
73592
73848
  return node.stage.parentIds.every((parentId, parentIndex) => parentId === next.parentIds[parentIndex]);
@@ -73602,7 +73858,10 @@ class GraphViewState {
73602
73858
  this.lastBuiltSnapshotVersion = version;
73603
73859
  return;
73604
73860
  }
73605
- const nextLayout = computeLayout(graphStages, { orientation: "vertical" });
73861
+ const nextLayout = computeLayout(graphStages, {
73862
+ orientation: "vertical",
73863
+ nodeHeight: (stage) => this._nodeHeight(stage)
73864
+ });
73606
73865
  this.cachedLayout = nextLayout;
73607
73866
  this.cachedDisplayStages = nextLayout.map((node) => node.stage);
73608
73867
  this.cachedRenderGeometry = this._buildRenderGeometry(nextLayout);
@@ -73621,13 +73880,15 @@ class GraphViewState {
73621
73880
  for (let index = 0;index < layout.length; index++) {
73622
73881
  const node = layout[index];
73623
73882
  canvasWidth = Math.max(canvasWidth, node.x + NODE_W);
73624
- totalRows = Math.max(totalRows, node.y + NODE_H);
73883
+ const bottom = node.y + (node.height ?? NODE_H);
73884
+ totalRows = Math.max(totalRows, bottom);
73625
73885
  nodeByStageId.set(node.stage.id, node);
73626
73886
  const band = bandsByTop.get(node.y);
73627
- if (band)
73887
+ if (band) {
73628
73888
  band.nodeIndices.push(index);
73629
- else
73630
- bandsByTop.set(node.y, { top: node.y, bottom: node.y + NODE_H, nodeIndices: [index] });
73889
+ band.bottom = Math.max(band.bottom, bottom);
73890
+ } else
73891
+ bandsByTop.set(node.y, { top: node.y, bottom, nodeIndices: [index] });
73631
73892
  }
73632
73893
  const edges = [];
73633
73894
  for (const node of layout) {
@@ -73640,9 +73901,10 @@ class GraphViewState {
73640
73901
  edges.push({
73641
73902
  parentX: parent.x,
73642
73903
  parentY: parent.y,
73904
+ parentHeight: parent.height ?? NODE_H,
73643
73905
  childX: node.x,
73644
73906
  childY: node.y,
73645
- top: parent.y + NODE_H,
73907
+ top: parent.y + (parent.height ?? NODE_H),
73646
73908
  bottom: node.y,
73647
73909
  left: Math.min(parentCol, childCol),
73648
73910
  right: Math.max(parentCol, childCol) + 1
@@ -73978,10 +74240,10 @@ class GraphViewRenderHelpers extends GraphViewState {
73978
74240
  }
73979
74241
  this._clampGraphHorizontalScroll(totalCols, viewportCols);
73980
74242
  }
73981
- _plotEdge(canvas, px, py, cx, cy, color) {
74243
+ _plotEdge(canvas, px, py, cx, cy, color, parentHeight = NODE_H) {
73982
74244
  const parentCol = px + Math.floor(NODE_W / 2);
73983
74245
  const childCol = cx + Math.floor(NODE_W / 2);
73984
- const parentExitRow = py + NODE_H;
74246
+ const parentExitRow = py + parentHeight;
73985
74247
  const childEntryRow = cy - 1;
73986
74248
  if (childEntryRow < parentExitRow)
73987
74249
  return;
@@ -74040,214 +74302,6 @@ class GraphViewRenderHelpers extends GraphViewState {
74040
74302
  }
74041
74303
  }
74042
74304
 
74043
- // dist/builtin/workflows/src/tui/node-card.ts
74044
- import { stripTerminalSequences } from "@earendil-works/pi-tui";
74045
- function pulseT(phase) {
74046
- return (Math.sin(phase * Math.PI * 2 - Math.PI / 2) + 1) / 2;
74047
- }
74048
- function queuedBadgeCount(count) {
74049
- if (typeof count !== "number" || !Number.isFinite(count))
74050
- return 0;
74051
- return Math.max(0, Math.trunc(count));
74052
- }
74053
- function queuedBadgeText(count) {
74054
- return `✉ ${count} queued`;
74055
- }
74056
- function pickBorder(status, focused, phase, theme) {
74057
- switch (status) {
74058
- case "running":
74059
- if (focused)
74060
- return theme.warning;
74061
- return lerpColor(theme.borderDim, theme.warning, pulseT(phase));
74062
- case "paused":
74063
- return theme.warning;
74064
- case "awaiting_input":
74065
- if (focused)
74066
- return theme.info;
74067
- return lerpColor(theme.borderDim, theme.info, pulseT(phase));
74068
- case "completed":
74069
- return theme.success;
74070
- case "failed":
74071
- return theme.error;
74072
- case "blocked":
74073
- return theme.dim;
74074
- case "skipped":
74075
- return theme.dim;
74076
- default:
74077
- return focused ? theme.borderActive : theme.borderDim;
74078
- }
74079
- }
74080
- function durationColor(status, theme) {
74081
- switch (status) {
74082
- case "running":
74083
- return theme.warning;
74084
- case "paused":
74085
- return theme.warning;
74086
- case "awaiting_input":
74087
- return theme.info;
74088
- case "completed":
74089
- return theme.success;
74090
- case "failed":
74091
- return theme.error;
74092
- default:
74093
- return theme.dim;
74094
- }
74095
- }
74096
- function blockedBadgeText(stage, stages, width) {
74097
- const base = "↑ blocked";
74098
- const blockedBy = stage.blockedByStageId;
74099
- if (!blockedBy)
74100
- return base;
74101
- const upstream = stages?.find((s) => s.id === blockedBy)?.name ?? blockedBy;
74102
- const withUpstream = `${base} by ${upstream}`;
74103
- if (visibleWidth(withUpstream) <= width)
74104
- return withUpstream;
74105
- return base;
74106
- }
74107
- function durationText(stage) {
74108
- const elapsed = elapsedStageMs(stage);
74109
- return elapsed === undefined ? "—" : fmtDuration(elapsed);
74110
- }
74111
- function metaText(stage) {
74112
- if (stage.topologyState === "unavailable")
74113
- return "topology unavailable";
74114
- const deps = stage.parentIds.length;
74115
- const dependencyText = deps === 0 ? "root" : deps === 1 ? "1 dep" : `${deps} deps`;
74116
- return dependencyText;
74117
- }
74118
- function modelText(stage, innerWidth) {
74119
- const model = stage.model;
74120
- if (model === undefined || model === "")
74121
- return "—";
74122
- const slash = model.lastIndexOf("/");
74123
- const short = slash >= 0 ? model.slice(slash + 1) : model;
74124
- const level = stage.thinkingLevel;
74125
- const showLevel = level !== undefined && level !== "" && level !== "off";
74126
- const suffix = showLevel ? ` · ${level}` : "";
74127
- const full = `${short}${suffix}`;
74128
- if (visibleWidth(full) <= innerWidth)
74129
- return full;
74130
- const modelSuffix = short.endsWith("-fast") ? "-fast" : "";
74131
- const name = modelSuffix ? short.slice(0, -modelSuffix.length) : short;
74132
- const room = Math.max(1, innerWidth - visibleWidth(modelSuffix + suffix));
74133
- return `${truncateToWidth2(name, room, "…")}${modelSuffix}${suffix}`;
74134
- }
74135
- function workflowChildRunRows(stage, width) {
74136
- const child = stage.workflowChild ?? stage.workflowChildRun;
74137
- if (child === undefined)
74138
- return [];
74139
- return wrapIdentifierLines(child.runId, Math.max(1, width), "run ", "").map((row) => `${row.prefix}${row.chunk}`);
74140
- }
74141
- function workflowChildMetaText(stage) {
74142
- const completed = stage.workflowChild;
74143
- if (completed !== undefined) {
74144
- const outputCount = completed.outputCount ?? Object.keys(completed.outputs).length;
74145
- return outputCount === 1 ? "1 out" : `${outputCount} outs`;
74146
- }
74147
- if (stage.workflowChildRun !== undefined)
74148
- return "live";
74149
- return;
74150
- }
74151
- function joinCompactStatusMeta(status, meta, width) {
74152
- const candidates = [`${status} · ${meta}`, `${status} ·${meta}`, `${status}· ${meta}`, `${status}·${meta}`];
74153
- return candidates.find((candidate) => visibleWidth(candidate) <= width) ?? meta;
74154
- }
74155
- function statusLabel(status) {
74156
- switch (status) {
74157
- case "awaiting_input":
74158
- return "awaiting input";
74159
- case "completed":
74160
- return "complete";
74161
- default:
74162
- return status.replace(/_/g, " ");
74163
- }
74164
- }
74165
- function truncate(s, maxWidth) {
74166
- if (maxWidth <= 0)
74167
- return "";
74168
- if (visibleWidth(s) <= maxWidth)
74169
- return s;
74170
- return truncateToWidth2(s, maxWidth, "…");
74171
- }
74172
- function centreColored(content, width, fg, bg, opts = {}) {
74173
- const safe = truncate(content, width);
74174
- const safeWidth = visibleWidth(safe);
74175
- const pad = Math.max(0, width - safeWidth);
74176
- const left = Math.max(0, Math.floor(pad / 2));
74177
- const right = Math.max(0, pad - left);
74178
- const bold = opts.bold ? BOLD : "";
74179
- return `${bg}${" ".repeat(left)}` + `${hexToAnsi(fg)}${bold}${safe}${RESET}` + `${bg}${" ".repeat(right)}`;
74180
- }
74181
- function buildTitleSlot(name, innerWidth, focused, theme, cardBg, compact = false) {
74182
- const maxName = Math.max(2, compact ? innerWidth - 1 : innerWidth - 4);
74183
- const safeName = stripTerminalSequences(truncate(name, maxName));
74184
- if (focused) {
74185
- const tabText = compact ? safeName : ` ${safeName} `;
74186
- const styled = `${paint(tabText, theme.surface, {
74187
- bg: theme.accent,
74188
- bold: true
74189
- })}${cardBg}`;
74190
- return { slot: styled, visibleWidth: visibleWidth(tabText) };
74191
- }
74192
- const titleRaw = compact ? safeName : ` ${safeName} `;
74193
- const styled = `${BOLD}${titleRaw}${RESET}${cardBg}`;
74194
- return { slot: styled, visibleWidth: visibleWidth(titleRaw) };
74195
- }
74196
- function renderNodeCard(stage, opts) {
74197
- const width = opts.width ?? NODE_W;
74198
- const height = opts.height ?? NODE_H;
74199
- const focused = opts.focused ?? false;
74200
- const phase = opts.pulsePhase ?? 0;
74201
- const theme = opts.theme;
74202
- const borderHex = pickBorder(stage.status, focused, phase, theme);
74203
- const bc = hexToAnsi(borderHex);
74204
- const bg = DEFAULT_BG;
74205
- const innerWidth = Math.max(2, width - 2);
74206
- const child = stage.workflowChild ?? stage.workflowChildRun;
74207
- const { slot: titleSlot, visibleWidth: titleVisibleWidth } = buildTitleSlot(child === undefined ? stage.name : `↳ ${child.workflow}`, innerWidth, focused, theme, bg, child !== undefined);
74208
- const titleStart = Math.max(1, Math.floor((innerWidth - titleVisibleWidth) / 2));
74209
- const titleEnd = titleStart + titleVisibleWidth;
74210
- const topMiddle = `${bc}${"─".repeat(titleStart)}` + `${titleSlot}${bc}` + `${"─".repeat(Math.max(0, innerWidth - titleEnd))}`;
74211
- const top = `${bg}${bc}╭${topMiddle}╮${RESET}`;
74212
- const bottom = `${bg}${bc}╰${"─".repeat(innerWidth)}╯${RESET}`;
74213
- const bodyText = stage.nodeKind === "tool" ? "durable tool" : stage.status === "blocked" ? blockedBadgeText(stage, opts.stages, innerWidth) : durationText(stage);
74214
- const bodyHex = durationColor(stage.status, theme);
74215
- const statusText = `${statusIcon(stage.status)} ${stage.toolStatus ?? statusLabel(stage.status)}`;
74216
- const statusLine = `${bg}${bc}│${RESET}` + centreColored(statusText, innerWidth, bodyHex, bg, {
74217
- bold: stage.status === "running" || stage.status === "awaiting_input"
74218
- }) + `${bg}${bc}│${RESET}`;
74219
- const durLine = `${bg}${bc}│${RESET}` + centreColored(bodyText, innerWidth, bodyHex, bg, {
74220
- bold: stage.status === "blocked"
74221
- }) + `${bg}${bc}│${RESET}`;
74222
- const contentRows = Math.max(0, height - 2);
74223
- const metaLine = `${bg}${bc}│${RESET}${centreColored(metaText(stage), innerWidth, theme.dim, bg)}${bg}${bc}│${RESET}`;
74224
- const modelLine = `${bg}${bc}│${RESET}${centreColored(modelText(stage, innerWidth), innerWidth, theme.textMuted, bg)}${bg}${bc}│${RESET}`;
74225
- const childRunLines = workflowChildRunRows(stage, innerWidth).map((row) => `${bg}${bc}│${RESET}${centreColored(row, innerWidth, theme.dim, bg)}${bg}${bc}│${RESET}`);
74226
- const queuedCount = queuedBadgeCount(opts.queuedMessageCount);
74227
- const childMeta = workflowChildMetaText(stage);
74228
- const childSummary = childMeta === undefined ? undefined : joinCompactStatusMeta(statusText, queuedCount > 0 ? queuedBadgeText(queuedCount) : childMeta, innerWidth);
74229
- const childSummaryLine = childSummary === undefined ? undefined : `${bg}${bc}│${RESET}` + centreColored(childSummary, innerWidth, bodyHex, bg, {
74230
- bold: stage.status === "running" || stage.status === "awaiting_input"
74231
- }) + `${bg}${bc}│${RESET}`;
74232
- const interior = stage.status === "awaiting_input" ? [
74233
- statusLine,
74234
- `${bg}${bc}│${RESET}` + centreColored("waiting for response", innerWidth, theme.info, bg) + `${bg}${bc}│${RESET}`,
74235
- `${bg}${bc}│${RESET}` + centreColored("↵ enter to respond", innerWidth, theme.dim, bg) + `${bg}${bc}│${RESET}`,
74236
- modelLine
74237
- ] : childSummaryLine === undefined ? [durLine, statusLine, modelLine, metaLine] : [...childRunLines, childSummaryLine];
74238
- const preferredBadgeRow = stage.status === "awaiting_input" ? 1 : childSummaryLine === undefined ? interior.length - 1 : -1;
74239
- while (interior.length < contentRows) {
74240
- interior.push(`${bg}${bc}│${RESET}${bg}${" ".repeat(innerWidth)}${bg}${bc}│${RESET}`);
74241
- }
74242
- if (interior.length > contentRows)
74243
- interior.length = contentRows;
74244
- if (queuedCount > 0 && interior.length > 0 && preferredBadgeRow >= 0) {
74245
- const badgeRow = preferredBadgeRow < interior.length ? preferredBadgeRow : interior.length - 1;
74246
- interior[badgeRow] = `${bg}${bc}│${RESET}` + centreColored(queuedBadgeText(queuedCount), innerWidth, theme.info, bg, { bold: true }) + `${bg}${bc}│${RESET}`;
74247
- }
74248
- return [top, ...interior, bottom];
74249
- }
74250
-
74251
74305
  // dist/builtin/workflows/src/tui/graph-view-graph-render.ts
74252
74306
  class GraphViewGraphRenderer extends GraphViewRenderHelpers {
74253
74307
  _renderGraph(width, viewportTop, viewportRows, renderRun) {
@@ -74292,7 +74346,7 @@ class GraphViewGraphRenderer extends GraphViewRenderHelpers {
74292
74346
  if (edge.bottom <= safeTop || edge.top >= viewportBottom || edge.right <= viewportLeft || edge.left >= viewportRight) {
74293
74347
  continue;
74294
74348
  }
74295
- this._plotEdge(edgeCanvas, edge.parentX, edge.parentY, edge.childX, edge.childY, edgeColor);
74349
+ this._plotEdge(edgeCanvas, edge.parentX, edge.parentY, edge.childX, edge.childY, edgeColor, edge.parentHeight);
74296
74350
  }
74297
74351
  const edgeLines = edgeCanvas.toLines();
74298
74352
  const placements = new Map;
@@ -74306,7 +74360,7 @@ class GraphViewGraphRenderer extends GraphViewRenderHelpers {
74306
74360
  continue;
74307
74361
  const cardLines = renderNodeCard(node.stage, {
74308
74362
  width: NODE_W,
74309
- height: NODE_H,
74363
+ height: node.height ?? NODE_H,
74310
74364
  focused: ni === this.focusedIndex,
74311
74365
  pulsePhase,
74312
74366
  theme: this.graphTheme,
@@ -74363,7 +74417,7 @@ class GraphViewGraphRenderer extends GraphViewRenderHelpers {
74363
74417
  for (const index of band.nodeIndices) {
74364
74418
  const node = this.cachedLayout[index];
74365
74419
  const top = graphStartRow + node.y - viewportTop;
74366
- const bottom = top + NODE_H;
74420
+ const bottom = top + (node.height ?? NODE_H);
74367
74421
  const left = viewport.leftMargin + node.x - this.graphScrollColOffset;
74368
74422
  const right = left + NODE_W;
74369
74423
  const clippedTop = Math.max(visibleTop, top);
@@ -75071,6 +75125,7 @@ class GraphViewRenderer extends GraphViewGraphRenderer {
75071
75125
  render(width) {
75072
75126
  if (this.mode === "widget")
75073
75127
  return this._renderWidget(width);
75128
+ this._refreshQueuedNodeHeights();
75074
75129
  return this._renderOverlay(width);
75075
75130
  }
75076
75131
  dispose() {
@@ -75183,8 +75238,8 @@ class GraphViewRenderer extends GraphViewGraphRenderer {
75183
75238
  let next = scrollView.scrollTop;
75184
75239
  if (node.y < next)
75185
75240
  next = node.y;
75186
- else if (node.y + NODE_H > next + viewportRows)
75187
- next = node.y + NODE_H - viewportRows;
75241
+ else if (node.y + (node.height ?? NODE_H) > next + viewportRows)
75242
+ next = node.y + (node.height ?? NODE_H) - viewportRows;
75188
75243
  scrollView.scrollTo(next);
75189
75244
  const graphInner = Math.max(1, width - 4);
75190
75245
  const leftMargin = Math.max(2, this.cachedRenderGeometry.canvasWidth <= graphInner ? Math.floor((graphInner - this.cachedRenderGeometry.canvasWidth) / 2) : 2);
@@ -76420,6 +76475,7 @@ class StageUiBroker {
76420
76475
  };
76421
76476
  }
76422
76477
  requestCustomUi(runId, stageId, factory, options, signal) {
76478
+ signal = options?.signal ? signal ? AbortSignal.any([signal, options.signal]) : options.signal : signal;
76423
76479
  if (signal?.aborted) {
76424
76480
  return Promise.reject(signal.reason ?? new Error("atomic-workflows: stage UI request aborted"));
76425
76481
  }
@@ -82123,6 +82179,108 @@ function collectDefinitionName(tokens, localValues, workflowNames) {
82123
82179
  }
82124
82180
  return;
82125
82181
  }
82182
+ function discoveryField(object) {
82183
+ if (object?.[0]?.value !== "{" || object.at(-1)?.value !== "}")
82184
+ return;
82185
+ let value2;
82186
+ for (const field of arrayElements(object)) {
82187
+ let key = field;
82188
+ while (["get", "set", "async", "*"].includes(key[0]?.value ?? ""))
82189
+ key = key.slice(1);
82190
+ if (key[0]?.value === "[" || key !== field && key[0]?.value === "possibleStageNames")
82191
+ return;
82192
+ if (field[0]?.value === ".") {
82193
+ if (value2 !== undefined)
82194
+ return;
82195
+ continue;
82196
+ }
82197
+ if (field[0]?.value !== "possibleStageNames")
82198
+ continue;
82199
+ if (value2 !== undefined || field[1]?.value !== ":")
82200
+ return;
82201
+ value2 = field.slice(2);
82202
+ }
82203
+ return value2;
82204
+ }
82205
+ function literalDiscoveryNames(value2) {
82206
+ if (value2?.[0]?.value !== "[" || value2.at(-1)?.value !== "]")
82207
+ return;
82208
+ const names = [];
82209
+ for (const element of arrayElements(value2)) {
82210
+ if (element.length !== 1 || element[0]?.kind !== "string")
82211
+ return;
82212
+ names.push(element[0].value);
82213
+ }
82214
+ return names.length > 0 ? names : undefined;
82215
+ }
82216
+ function discoveryHelpers(tokens) {
82217
+ const helpers = [];
82218
+ for (let index = 0;index < tokens.length; index += 1) {
82219
+ if (tokens[index]?.value !== "function" || tokens[index + 1]?.kind !== "ident")
82220
+ continue;
82221
+ let open = index + 2;
82222
+ if (tokens[open]?.value === "<")
82223
+ open = (matchBracket(tokens, open, "<", ">") ?? tokens.length) + 1;
82224
+ if (tokens[open]?.value !== "(")
82225
+ continue;
82226
+ const close = matchBracket(tokens, open, "(", ")");
82227
+ if (close === undefined)
82228
+ continue;
82229
+ let bodyOpen = close + 1;
82230
+ while (bodyOpen < tokens.length && tokens[bodyOpen]?.value !== "{")
82231
+ bodyOpen += 1;
82232
+ const bodyClose = matchBracket(tokens, bodyOpen, "{", "}");
82233
+ if (bodyClose === undefined)
82234
+ continue;
82235
+ const preceding = tokens[index - (tokens[index - 1]?.value === "async" ? 2 : 1)]?.value;
82236
+ helpers.push({
82237
+ name: tokens[index + 1].value,
82238
+ nameIndex: index + 1,
82239
+ params: splitTopLevelArguments({ method: "parallel", argsOpen: open }, tokens),
82240
+ bodyOpen,
82241
+ bodyClose,
82242
+ declaration: preceding === undefined || ["export", ";", "{", "}"].includes(preceding)
82243
+ });
82244
+ }
82245
+ return helpers;
82246
+ }
82247
+ function hasDiscoveryDestructuringWrite(tokens, parameter) {
82248
+ for (let index = 0;index < tokens.length; index += 1) {
82249
+ const open = tokens[index]?.value;
82250
+ if (open !== "[" && open !== "{")
82251
+ continue;
82252
+ const close = matchBracket(tokens, index, open, open === "[" ? "]" : "}");
82253
+ if (close === undefined || !["=", "of", "in"].includes(tokens[close + 1]?.value ?? ""))
82254
+ continue;
82255
+ if (tokens.slice(index + 1, close).some((token) => token.kind === "ident" && token.value === parameter))
82256
+ return true;
82257
+ }
82258
+ return false;
82259
+ }
82260
+ function discoveryCalls(tokens, name, declaration) {
82261
+ const calls = [];
82262
+ for (let index = 0;index < tokens.length; index += 1) {
82263
+ if (tokens[index]?.value === "import") {
82264
+ while (index < tokens.length && tokens[index]?.kind !== "string")
82265
+ index += 1;
82266
+ continue;
82267
+ }
82268
+ if (tokens[index]?.value === "export" && tokens[index + 1]?.value === "{") {
82269
+ const close = matchBracket(tokens, index + 1, "{", "}") ?? tokens.length;
82270
+ const exported = tokens.slice(index + 2, close);
82271
+ if (exported.some((token, offset) => token.value === name && (declaration === undefined || exported[offset + 1]?.value === "as")))
82272
+ return;
82273
+ index = close;
82274
+ continue;
82275
+ }
82276
+ if (tokens[index]?.kind !== "ident" || tokens[index]?.value !== name || index === declaration)
82277
+ continue;
82278
+ if (tokens[index + 1]?.value !== "(" || [".", "?.", "function"].includes(tokens[index - 1]?.value ?? ""))
82279
+ return;
82280
+ calls.push(index + 1);
82281
+ }
82282
+ return calls;
82283
+ }
82126
82284
 
82127
82285
  class PossibleStagesScanner {
82128
82286
  stages = new Set;
@@ -82131,6 +82289,7 @@ class PossibleStagesScanner {
82131
82289
  maxDepth;
82132
82290
  units = new Map;
82133
82291
  aliasPool = new Set;
82292
+ closure = [];
82134
82293
  constructor(maxDepth) {
82135
82294
  this.maxDepth = maxDepth;
82136
82295
  }
@@ -82166,11 +82325,17 @@ class PossibleStagesScanner {
82166
82325
  this.aliasPool.add(alias);
82167
82326
  }
82168
82327
  }
82169
- for (const path of ordered) {
82170
- const unit = this.units.get(path);
82171
- if (unit === undefined)
82172
- continue;
82173
- this.scanUnit(unit, path, boundaryPrefix, depth, ancestorStack);
82328
+ const previousClosure = this.closure;
82329
+ this.closure = ordered;
82330
+ try {
82331
+ for (const path of ordered) {
82332
+ const unit = this.units.get(path);
82333
+ if (unit === undefined)
82334
+ continue;
82335
+ this.scanUnit(unit, path, boundaryPrefix, depth, ancestorStack);
82336
+ }
82337
+ } finally {
82338
+ this.closure = previousClosure;
82174
82339
  }
82175
82340
  }
82176
82341
  unitFor(path) {
@@ -82284,7 +82449,127 @@ class PossibleStagesScanner {
82284
82449
  }
82285
82450
  return resolveRelativeSpecifier(specifier, importingFile);
82286
82451
  }
82452
+ hasDataOnlyDiscoveryOptions(argument) {
82453
+ if (argument?.[0]?.value !== "{" || argument.at(-1)?.value !== "}")
82454
+ return false;
82455
+ return arrayElements(argument).every((field) => (field[0]?.kind === "ident" || field[0]?.kind === "string") && field[0].value !== "__proto__" && field[1]?.value === ":");
82456
+ }
82457
+ discoveryNames(call, unit, path) {
82458
+ if (call.method !== "parallel")
82459
+ return;
82460
+ const value2 = discoveryField(splitTopLevelArguments(call, unit.tokens)[1]);
82461
+ const literal = literalDiscoveryNames(value2);
82462
+ if (literal !== undefined)
82463
+ return literal;
82464
+ if (value2?.length !== 3 || value2[0]?.kind !== "ident" || value2[1]?.value !== "." || value2[2]?.value !== "possibleStageNames")
82465
+ return;
82466
+ const owner = discoveryHelpers(unit.tokens).filter((helper) => helper.bodyOpen < call.argsOpen && helper.bodyClose > call.argsOpen).at(-1);
82467
+ if (owner === undefined || !owner.declaration)
82468
+ return;
82469
+ const parameter = owner.params.findIndex((param) => param[0]?.value === value2[0]?.value);
82470
+ if (parameter < 0)
82471
+ return;
82472
+ for (const param of owner.params) {
82473
+ if (param[0]?.kind !== "ident")
82474
+ return;
82475
+ const assignment = param.findIndex((token) => token.value === "=");
82476
+ if (assignment < 0)
82477
+ continue;
82478
+ const initial = param.slice(assignment + 1);
82479
+ const emptyObject = initial.length === 2 && initial[0]?.value === "{" && initial[1]?.value === "}";
82480
+ const scalar = initial.length === 1 && (initial[0]?.kind === "string" || ["true", "false", "null", "undefined"].includes(initial[0]?.value ?? ""));
82481
+ if (!emptyObject && !scalar)
82482
+ return;
82483
+ }
82484
+ const body = unit.tokens.slice(owner.bodyOpen, owner.bodyClose);
82485
+ if (body.some((token) => token.kind === "ident" && token.value === "arguments"))
82486
+ return;
82487
+ if (hasDiscoveryDestructuringWrite(body, value2[0].value))
82488
+ return;
82489
+ const forwarded = new Set;
82490
+ const spreadOptions = new Set;
82491
+ for (let index = 0;index < body.length; index += 1) {
82492
+ const parallel = matchCtxCall(body, index, this.aliasPool);
82493
+ if (parallel?.method !== "parallel")
82494
+ continue;
82495
+ const parallelOptions = splitTopLevelArguments(parallel, body)[1];
82496
+ const field = discoveryField(parallelOptions);
82497
+ if (parallelOptions?.[0]?.value === "{") {
82498
+ for (const entry of arrayElements(parallelOptions)) {
82499
+ if (entry.length === 4 && entry.slice(0, 3).every((token) => token.value === "."))
82500
+ spreadOptions.add(entry[3]);
82501
+ }
82502
+ }
82503
+ if (field?.length === 3 && field[0]?.value === value2[0].value && field[1]?.value === "." && field[2]?.value === "possibleStageNames")
82504
+ forwarded.add(field[0]);
82505
+ }
82506
+ for (let index = 0;index < body.length; index += 1) {
82507
+ if (body[index]?.value !== value2[0]?.value)
82508
+ continue;
82509
+ if (body[index + 2]?.value === "possibleStageNames" && !forwarded.has(body[index]))
82510
+ return;
82511
+ const destructured = body[index - 2]?.value === "}" && body[index - 1]?.value === "=" && body[index + 1]?.value === ";";
82512
+ if (destructured) {
82513
+ let matched = false;
82514
+ for (let open = 0;open < index - 2; open += 1) {
82515
+ if (body[open]?.value !== "{" || matchBracket(body, open, "{", "}") !== index - 2)
82516
+ continue;
82517
+ if (body[open - 1]?.value !== "const")
82518
+ return;
82519
+ matched = true;
82520
+ for (const entry of arrayElements(body.slice(open, index - 1))) {
82521
+ if (entry.length === 1 && entry[0]?.kind === "ident" && entry[0].value !== "possibleStageNames")
82522
+ continue;
82523
+ if (entry.length !== 4 || !entry.slice(0, 3).every((token) => token.value === ".") || entry[3]?.kind !== "ident")
82524
+ return;
82525
+ const rest = entry[3];
82526
+ for (let use = 0;use < body.length; use += 1) {
82527
+ const token = body[use];
82528
+ if (token?.kind !== "ident" || token.value !== rest.value || token === rest)
82529
+ continue;
82530
+ if (spreadOptions.has(token))
82531
+ continue;
82532
+ if (body[use - 3]?.value === "const" && body[use - 2]?.kind === "ident" && body[use - 1]?.value === "=" && body[use + 1]?.value === "." && body[use + 2]?.value === "concurrency" && body[use + 3]?.value === ";")
82533
+ continue;
82534
+ return;
82535
+ }
82536
+ }
82537
+ }
82538
+ if (!matched)
82539
+ return;
82540
+ }
82541
+ if (!destructured && !forwarded.has(body[index]))
82542
+ return;
82543
+ }
82544
+ const names = [];
82545
+ for (const callerPath of this.closure) {
82546
+ const caller = this.units.get(callerPath);
82547
+ if (caller === undefined)
82548
+ continue;
82549
+ if ([...caller.imports.values()].some((binding) => binding.importedName === undefined && resolveRelativeSpecifier(binding.specifier, callerPath) === path))
82550
+ return;
82551
+ const references = callerPath === path ? [owner.name] : [...caller.imports].filter(([, binding]) => binding.importedName === owner.name && resolveRelativeSpecifier(binding.specifier, callerPath) === path).map(([local]) => local);
82552
+ for (const reference of references) {
82553
+ const calls = discoveryCalls(caller.tokens, reference, callerPath === path ? owner.nameIndex : undefined);
82554
+ if (calls === undefined)
82555
+ return;
82556
+ for (const argsOpen of calls) {
82557
+ const argument = splitTopLevelArguments({ method: "parallel", argsOpen }, caller.tokens)[parameter];
82558
+ if (!this.hasDataOnlyDiscoveryOptions(argument))
82559
+ return;
82560
+ const declared = literalDiscoveryNames(discoveryField(argument));
82561
+ if (declared === undefined)
82562
+ return;
82563
+ names.push(...declared);
82564
+ }
82565
+ }
82566
+ }
82567
+ return names.length > 0 || this.closure[0] !== path ? names : undefined;
82568
+ }
82287
82569
  stepNamesForStepsCall(call, unit, path) {
82570
+ const declared = this.discoveryNames(call, unit, path);
82571
+ if (declared !== undefined)
82572
+ return declared;
82288
82573
  const first = firstArgumentTokens(call, unit.tokens);
82289
82574
  if (first === undefined || first.length === 0)
82290
82575
  return [];
@@ -90858,7 +91143,8 @@ async function warm_first_fan_out(ctx, steps, prefixKeyOf, options = {}) {
90858
91143
  const warmResults = await ctx.parallel(warmSteps, {
90859
91144
  ...parallelOptions,
90860
91145
  concurrency: boundedConcurrency(warmSteps.length, warmConcurrency ?? DEFAULT_WARM_CONCURRENCY, inheritedConcurrency),
90861
- failFast: false
91146
+ failFast: false,
91147
+ possibleStageNames: options.possibleStageNames
90862
91148
  });
90863
91149
  for (const [resultIndex, result] of warmResults.entries()) {
90864
91150
  const originalIndex = warmIndices[resultIndex];
@@ -90873,7 +91159,8 @@ async function warm_first_fan_out(ctx, steps, prefixKeyOf, options = {}) {
90873
91159
  const restSteps = restIndices.map((index) => steps[index]);
90874
91160
  const restResults = await ctx.parallel(restSteps, {
90875
91161
  ...parallelOptions,
90876
- concurrency: boundedConcurrency(restSteps.length, undefined, inheritedConcurrency)
91162
+ concurrency: boundedConcurrency(restSteps.length, undefined, inheritedConcurrency),
91163
+ possibleStageNames: options.possibleStageNames
90877
91164
  });
90878
91165
  for (const [resultIndex, result] of restResults.entries()) {
90879
91166
  const originalIndex = restIndices[resultIndex];
@@ -91082,7 +91369,7 @@ async function runAdversarialVerification(ctx) {
91082
91369
  const runWave = async (pending, reaskWave) => {
91083
91370
  if (pending.length === 0)
91084
91371
  return { valid: [], invalid: [], results: [] };
91085
- const reports = await warm_first_fan_out(ctx, pending.map((cell) => scoreStep(cell, reaskWave, scoringHead, criteriaPath)), () => scoringHead, { concurrency: Math.min(pending.length, 4), failFast: false });
91372
+ const reports = await warm_first_fan_out(ctx, pending.map((cell) => scoreStep(cell, reaskWave, scoringHead, criteriaPath)), () => scoringHead, { concurrency: Math.min(pending.length, 4), failFast: false, possibleStageNames: ["verifier-*-*-*", "verifier-*-*-*-reask-*"] });
91086
91373
  const byName = new Map;
91087
91374
  for (const report of reports) {
91088
91375
  const name = report.name ?? report.stageName;
@@ -96470,7 +96757,7 @@ async function runTournament(ctx) {
96470
96757
  if (stages.length === 0)
96471
96758
  return { preferences: [], results: [] };
96472
96759
  executed += stages.length;
96473
- const results = await warm_first_fan_out(ctx, stages.map((stage) => stage.step), (_step, index) => `${stages[index].slot1}:${stages[index].slot2}`, { concurrency: maxConcurrency, failFast: false });
96760
+ const results = await warm_first_fan_out(ctx, stages.map((stage) => stage.step), (_step, index) => `${stages[index].slot1}:${stages[index].slot2}`, { concurrency: maxConcurrency, failFast: false, possibleStageNames: ["judge-*-*-*-r*"] });
96474
96761
  const phaseResults = [...results];
96475
96762
  for (const stage of stages) {
96476
96763
  let report = judgeReport(resultFor(results, stage.name)?.structured);
@@ -104255,21 +104542,17 @@ function throwFinalizationError(error) {
104255
104542
  function createTrackedStageCaller(input) {
104256
104543
  const { runtime } = input;
104257
104544
  const readinessGateEnabled = runtime.opts.confirmStageReadiness !== undefined || runtime.opts.usePromptNodesForUi === true;
104258
- const confirmReadiness = async () => {
104259
- try {
104260
- if (runtime.opts.confirmStageReadiness !== undefined) {
104261
- const ready = await runtime.opts.confirmStageReadiness({
104262
- runId: runtime.runId,
104263
- stageId: runtime.stageId,
104264
- stageName: runtime.name,
104265
- signal: runtime.signal
104266
- });
104267
- return ready ? { action: "advance" } : { action: "stay" };
104268
- }
104269
- return await askReadinessViaStageBroker(runtime.runId, runtime.stageId, runtime.signal);
104270
- } catch {
104271
- return { action: "advance" };
104272
- }
104545
+ const confirmReadiness = async (signal) => {
104546
+ if (runtime.opts.confirmStageReadiness !== undefined) {
104547
+ const ready = await raceAbort(runtime.opts.confirmStageReadiness({
104548
+ runId: runtime.runId,
104549
+ stageId: runtime.stageId,
104550
+ stageName: runtime.name,
104551
+ signal
104552
+ }), signal);
104553
+ return ready ? { action: "advance" } : { action: "stay" };
104554
+ }
104555
+ return await askReadinessViaStageBroker(runtime.runId, runtime.stageId, signal);
104273
104556
  };
104274
104557
  const suppressReadinessForCurrentTurn = () => {
104275
104558
  runtime.state.askUserQuestionObservedThisTurn = false;
@@ -104423,9 +104706,35 @@ function createTrackedStageCaller(input) {
104423
104706
  });
104424
104707
  try {
104425
104708
  while (runtime.state.askUserQuestionObservedThisTurn || repeatReadinessAfterChatTurn) {
104426
- const decision = await confirmReadiness();
104709
+ runtime.signal.throwIfAborted();
104710
+ if (runtime.stageSnapshot.status === "paused" || runtime.stageSnapshot.status === "blocked") {
104711
+ await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
104712
+ continue;
104713
+ }
104714
+ const controller = new AbortController;
104715
+ runtime.state.readinessController = controller;
104716
+ let decision;
104717
+ try {
104718
+ decision = await confirmReadiness(AbortSignal.any([runtime.signal, controller.signal]));
104719
+ if (decision.action === "advance" || decision.message !== undefined) {
104720
+ await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
104721
+ }
104722
+ runtime.signal.throwIfAborted();
104723
+ if (controller.signal.aborted) {
104724
+ await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
104725
+ continue;
104726
+ }
104727
+ } catch (error) {
104728
+ runtime.signal.throwIfAborted();
104729
+ if (controller.signal.aborted) {
104730
+ await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
104731
+ continue;
104732
+ }
104733
+ throw error;
104734
+ }
104427
104735
  if (decision.action === "advance")
104428
104736
  break;
104737
+ delete runtime.state.readinessController;
104429
104738
  if (runtime.signal.aborted)
104430
104739
  break;
104431
104740
  runtime.state.askUserQuestionObservedThisTurn = false;
@@ -104568,9 +104877,17 @@ function createTrackedStageCaller(input) {
104568
104877
  const failure = runtime.classifyExecutorFailure(durableCheckpointError.error);
104569
104878
  applyTerminalStageState = () => applyFailureToStage(runtime.stageSnapshot, failure);
104570
104879
  }
104571
- if (!runtime.state.stageFinalized)
104572
- applyTerminalStageState?.();
104573
104880
  try {
104881
+ if (terminalStateIsSuccess && runtime.state.readinessController !== undefined) {
104882
+ let pauseVersion;
104883
+ do {
104884
+ pauseVersion = runtime.state.readinessPauseVersion;
104885
+ await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
104886
+ } while (pauseVersion !== runtime.state.readinessPauseVersion);
104887
+ runtime.signal.throwIfAborted();
104888
+ }
104889
+ if (!runtime.state.stageFinalized)
104890
+ applyTerminalStageState?.();
104574
104891
  await runtime.finalizeStageSnapshot();
104575
104892
  } catch (err) {
104576
104893
  finalizationError ??= { thrown: true, error: err };
@@ -104588,6 +104905,9 @@ function createTrackedStageCaller(input) {
104588
104905
  input.limiter.release();
104589
104906
  if (finalizationError !== undefined)
104590
104907
  throwFinalizationError(finalizationError.error);
104908
+ delete runtime.state.readinessController;
104909
+ if (terminalStateIsSuccess && readinessGateEnabled)
104910
+ runtime.signal.throwIfAborted();
104591
104911
  }
104592
104912
  };
104593
104913
  }
@@ -104908,12 +105228,26 @@ function createStageControlHandle(runtime) {
104908
105228
  async pause() {
104909
105229
  runtime.throwIfStageMutationBlocked();
104910
105230
  const statusBeforePause = runtime.stageSnapshot.status;
105231
+ let readinessPauseVersion;
105232
+ if (runtime.state.readinessController !== undefined) {
105233
+ runtime.scheduler.ensureReleaseBarrier(runtime.stageId);
105234
+ readinessPauseVersion = (runtime.state.readinessPauseVersion ?? 0) + 1;
105235
+ runtime.state.readinessPauseVersion = readinessPauseVersion;
105236
+ }
104911
105237
  if (statusBeforePause === "pending" || statusBeforePause === "running" || runtime.innerCtx.isStreaming) {
104912
- await runtime.innerCtx.__requestPause();
105238
+ try {
105239
+ await runtime.innerCtx.__requestPause();
105240
+ } catch (error) {
105241
+ if (readinessPauseVersion !== undefined && runtime.state.readinessPauseVersion === readinessPauseVersion && runtime.stageSnapshot.status !== "paused" && runtime.stageSnapshot.status !== "blocked" && !runtime.scheduler.hasCascadePauseOwners(runtime.stageId)) {
105242
+ runtime.scheduler.releaseStageBarrier(runtime.stageId);
105243
+ }
105244
+ throw error;
105245
+ }
104913
105246
  }
104914
105247
  const changed = runtime.activeStore.recordStagePaused(runtime.runId, runtime.stageId);
104915
105248
  if (changed) {
104916
105249
  runtime.scheduler.ensureReleaseBarrier(runtime.stageId);
105250
+ runtime.state.readinessController?.abort(new Error("atomic-workflows: readiness paused"));
104917
105251
  await runtime.scheduler.cascadePauseFrom(runtime.stageId);
104918
105252
  const run = runtime.activeStore.runs().find((candidate) => candidate.id === runtime.runId);
104919
105253
  const stillActive = run?.stages.some((stage) => stage.status === "running" && stage.id !== runtime.stageId) ?? false;
@@ -117179,7 +117513,7 @@ var DEFAULT_PROMPT_GUIDANCE = [
117179
117513
  - After creating and reloading a newly authored custom workflow, close the loop by stating the folder containing the workflow file just written, for example \`.atomic/workflows/\`, in this shape: \`Custom workflow created. You can inspect its code at: <workflow-folder-path>\`. Do this only for newly created custom workflows, never builtin or pre-existing workflows.
117180
117514
  - Keep a small, readable workflow in one entry file and write it for human maintainers. Keep the graph and control flow visible in the top-level workflow entry file, use stage names that state each stage's responsibility, and make its inputs, outputs, evidence, and success contract explicit. A developer reading the entry file from top to bottom should be able to identify the graph, branches, gates, artifacts, and stop conditions. Avoid both monolithic prompt blobs and gratuitous fragmentation. Do not split short one-use prompts, create one file per stage, add wrapper-only modules, hide the graph across files, or use line counts alone as a module boundary.
117181
117515
  - When a meaningful source boundary improves clarity, reuse, ownership, or testability, keep the graph and control flow in the top-level workflow entry file and extract cohesive concerns such as long or reused prompt builders, shared TypeBox schemas and workflow-specific types, model-policy constants shared by several stages, deterministic helpers with their own testable behavior, or reusable child workflow definitions into a subdirectory below the top-level discovery directory — either one owned by a single workflow or a shared support directory. Project and user discovery scans only top-level \`.ts\`/\`.js\`/\`.mjs\`/\`.cjs\` files in the workflow directory, so those support modules are not scanned as extra top-level workflow candidates; use \`.js\` import extensions from TypeScript source.
117182
- - Evaluate Atomic extension hooks when a workflow needs fine-grained, cross-cutting tool or session event control. Workflow TypeScript owns the inspectable DAG, stages, handoffs, durable \`ctx.tool\` side effects, and gates; extension hooks own cross-cutting session and model-tool policy such as \`tool_call\` interception, input mutation, or blocking, \`tool_result\` transformation, context and provider hooks, lifecycle observation, or reusable custom tools. Use hooks only when cross-stage or cross-workflow event control is materially clearer than embedding the policy in each stage; do not prescribe a companion extension for ordinary workflow logic. Consult \`packages/coding-agent/docs/extensions.md#events\` for the authoritative hook contracts.
117516
+ - Evaluate Atomic extension hooks when a workflow needs fine-grained, cross-cutting tool or session event control. Workflow TypeScript owns the inspectable DAG, stages, handoffs, durable \`ctx.tool\` side effects, and gates; extension hooks own cross-cutting session and model-tool policy such as \`tool_call\` interception, input mutation, or blocking, \`tool_result\` transformation, context and provider hooks, lifecycle observation, or reusable custom tools. Use hooks only when cross-stage or cross-workflow event control is materially clearer than embedding the policy in each stage; do not prescribe a companion extension for ordinary workflow logic. Consult \`packages/coding-agent/docs/extensions/events.md#events\` for the authoritative hook contracts.
117183
117517
  - When a workflow depends on a companion extension, make the dependency explicit, package and document it with the workflow, include any extension-provided custom tools in stage \`tools\` allowlists, and document hook-driven behavior so the workflow remains inspectable.
117184
117518
  - When creating or editing a workflow that pins an explicit stage \`model\` or \`fallbackModels\`, consult \`packages/coding-agent/docs/models/model-selection.md\` for role-based recommendations and \`packages/coding-agent/docs/models/evals.md\` for the measured per-evaluation scores behind them (its task-type picker maps each stage type to the eval that measures it), treating benchmark thinking levels as measurement configurations rather than universal workflow defaults, then call \`workflow({ action: "models" })\` to see the models actually present in the user's configured catalog. Use only the catalog's returned \`fullId\` values as model strings, and append a thinking suffix only when that exact level appears in the entry's \`availableThinkingLevels\`; apply the stage role and failure-cost policy independently to the primary and every fallback; treat an absent or empty \`availableThinkingLevels\` as no suffix support rather than inferring one. If a guide-recommended model for the role is unavailable, try another guide-recommended model that is present in the catalog. If the selected model does not support the role's level, choose another catalog model that does or leave the stage unpinned rather than inventing or silently promoting a level. An explicit user request for a level overrides the role default, but never fabricate an unsupported catalog level. If no recommendation intersects with the catalog, leave the stage unpinned rather than inventing a substitute, and mention the limitation, asking the user only if the workflow requires an explicit model choice. If the catalog is empty, continue with unpinned stages, state that no configured models were returned, and do not fabricate model IDs. Do not inspect or infer credentials, environment variables, auth files, token validity, entitlements, or the reason a model is absent; \`isCurrent\` marks the active selection, not a quality recommendation.
117185
117519
  - Consult docs/workflows/reliable-design.md and its starter patterns (Classify-and-act, Fan-out-and-synthesize, Adversarial verification, Generate-and-filter, Tournament, Loop until done, and Stacked implementation slices) when designing a stage graph.