@bastani/atomic 0.9.19-alpha.7 → 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.
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +10 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +282 -227
- package/docs/workflows/operations.md +4 -3
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.8",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.8",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.8",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.8",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.19-alpha.8] - 2026-09-12
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Workflow status detail now shows started and ended times in the system local timezone instead of UTC, preserving the compact `HH:mm:ss` display and elapsed durations ([#3008](https://github.com/bastani-inc/atomic/issues/3008)).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Removed generated `root`, `dep`, and `deps` labels from workflow graph nodes and left model text blank when no model is set. Cards omit the extra padding row. Queued-message counts appear on a separate `✉ N queued` row, reusing empty space or expanding occupied cards without hiding status or model details. Dependency edges are unchanged.
|
|
18
|
+
|
|
9
19
|
## [0.9.19-alpha.7] - 2026-09-12
|
|
10
20
|
|
|
11
21
|
### Fixed
|
|
@@ -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.
|
|
69016
|
-
const mm = String(d.
|
|
69017
|
-
const ss = String(d.
|
|
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 =
|
|
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
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
73630
|
-
|
|
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 +
|
|
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);
|
|
@@ -122,7 +122,7 @@ If you copy a HIL workflow example into a headless session, it can pass dispatch
|
|
|
122
122
|
|
|
123
123
|
<p align="center"><img src="../images/workflow-input-picker.png" alt="Workflow Input Picker" width="600" /></p>
|
|
124
124
|
|
|
125
|
-
Graph node cards show each model stage's effective model and thinking level
|
|
125
|
+
Graph node cards show each model stage's effective model and thinking level above its status, including after fallback and durable resume. Long model names are truncated first, preserving the complete thinking level and a canonical `-fast` model suffix. This suffix is model identity, not a separate fast-mode switch or proof of service tier. Thinking `off` is omitted; stages without a model show no model placeholder. Tool nodes retain their `durable tool` body, and the `BACKGROUND` summary is unchanged.
|
|
126
126
|
|
|
127
127
|
## Workflow Commands
|
|
128
128
|
|
|
@@ -159,7 +159,7 @@ Surface behavior:
|
|
|
159
159
|
- **Graph vs. stage chat** - Use `connect` for the workflow graph. Use `attach` when you want a chat pane for a specific stage.
|
|
160
160
|
- **Hierarchy chord** - `ctrl+x` is the workflow hierarchy chord: in an attached stage chat it means **return to graph**, and in the graph it means **return to main chat**. The workflow surface handles `ctrl+x` before configurable editor or tool actions, including while a composer draft, primitive prompt, custom question, stage switcher, or legacy prompt card owns input.
|
|
161
161
|
- **Draft preservation** - Leaving a stage preserves unsent composer and prompt drafts and keeps pending custom questions unresolved so they reappear when you attach again.
|
|
162
|
-
- **Queued-message survival** - Steering and follow-up entries queued from a stage chat live on the stage session, not on the pane. Detaching to the graph and reattaching rehydrates the pending `Steering:` / `Follow-up:` rows, and while you are detached the stage's graph node shows a `✉ N queued` badge so a pending message stays visible without attaching. The attached chat shows the pending text; the detached node shows only their count. Both read one projection that the stage handle keeps current from the session's complete `queue_update` snapshots, so rows and badge shrink together as the agent consumes entries. That projection is fed by the events rather than by a concrete Atomic `AgentSession`, so a stage backed by a custom `AgentSessionAdapter` keeps this behavior as long as it publishes ordinary `queue_update` events; each snapshot replaces the previous steering and follow-up lists rather than adding to them. A queue can also outlive the session holding it — a stage session that fails over to a fallback model hands its pending messages to the session replacing it, and a completed stage reopened as a post-mortem chat is restored holding whatever it was queued. Those messages were announced before the projection could reach the new session, so Atomic reads it once as it attaches and the rows and badge show them too.
|
|
162
|
+
- **Queued-message survival** - Steering and follow-up entries queued from a stage chat live on the stage session, not on the pane. Detaching to the graph and reattaching rehydrates the pending `Steering:` / `Follow-up:` rows, and while you are detached the stage's graph node shows a `✉ N queued` badge on its own row so a pending message stays visible without attaching. The attached chat shows the pending text; the detached node shows only their count. Both read one projection that the stage handle keeps current from the session's complete `queue_update` snapshots, so rows and badge shrink together as the agent consumes entries. That projection is fed by the events rather than by a concrete Atomic `AgentSession`, so a stage backed by a custom `AgentSessionAdapter` keeps this behavior as long as it publishes ordinary `queue_update` events; each snapshot replaces the previous steering and follow-up lists rather than adding to them. A queue can also outlive the session holding it — a stage session that fails over to a fallback model hands its pending messages to the session replacing it, and a completed stage reopened as a post-mortem chat is restored holding whatever it was queued. Those messages were announced before the projection could reach the new session, so Atomic reads it once as it attaches and the rows and badge show them too.
|
|
163
163
|
- **Reserved keys** - `ctrl+d` and `q` do not navigate workflow surfaces; `ctrl+d` keeps its ordinary editor or prompt behavior where applicable, and `q` remains printable in text-owning prompts. Existing `esc`, `ctrl+c`, and graph `h` close/hide controls are unchanged.
|
|
164
164
|
- **Wheel and trackpad** - While the workflow graph is active, vertical wheel/trackpad gestures pan it up and down, and horizontal gestures pan wide graphs left and right when the terminal exposes horizontal wheel events. Focused graph and stage-chat overlays receive those gestures through the fullscreen application route, so scrolling stays inside the active workflow surface instead of falling through to terminal or main-chat scrollback.
|
|
165
165
|
- **Fullscreen mouse routing and selection** - A focused workflow graph or attached stage chat overlay receives wheel/trackpad and click input through the host's application-owned input route before the fullscreen viewport. Events the overlay does not consume fall through to pi-tui's viewport, while non-overlay focused components leave pi-tui's transcript scrolling, scrollbar interaction, and drag-selection path intact. Graph panning, stage-chat scrolling, node click-to-attach, and drag or multi-click selection therefore work without a separate selection mode. Copy uses OSC 52; terminals that refuse OSC 52 writes still support the modifier-drag bypass (Shift/Option, as provided by the terminal). `ctrl+t` is not a workflow control: focused workflow overlays leave it to the host `app.thinking.toggle` action, while inline tree selectors keep `app.tree.filter.noTools`.
|
|
@@ -170,6 +170,7 @@ Surface behavior:
|
|
|
170
170
|
- **Run control** - Use `pause` and `resume` for resumable live work. Pause holds a stage's queued steering and follow-up items in place without dequeuing them or starting continuation; `resume` releases those items once in their existing per-queue order, but queue release alone does not start a model turn. `resume` on a non-paused run reopens the saved snapshot or overlay. Use `quit` to pause a live run gracefully while preserving it for `/workflow resume`. `/workflow pause` selects the active run by default, accepts a full run id or `--all`, and does not open a stage picker. Use the workflow tool's `stageId` for stage or tool-node targeting.
|
|
171
171
|
- **Rediscovery** - Use `/workflow reload` after adding, editing, installing, or removing workflow resources or package manifest workflow entries and you want Atomic to rediscover them in-process ([Reloading workflow resources](/workflows/operations#reloading-workflow-resources)).
|
|
172
172
|
- **Status listing** - `/workflow status` lists all retained active and terminal top-level runs by default; implementation-owned nested child runs are flattened into their parent workflow rather than listed separately. `/workflow status --all` is retained as a compatibility alias.
|
|
173
|
+
- **Status times** - `/workflow status <run-id>` displays `started` and `ended` in the system local timezone as `HH:mm:ss`. If they differ from your expected clock, check the timezone of the machine or container running Atomic and any inherited `TZ` environment setting. Elapsed durations and raw timestamps in structured results are unchanged.
|
|
173
174
|
|
|
174
175
|
`/workflows` is the retained-run history alias for `/workflow resume`: with no id it opens the same mixed picker, but the resumable section lists only runs that the resume path can actually accept and the completed section is read-only inspection. A run with no durable checkpoint, missing/pruned artifacts, or explicit deletion is omitted from the resume picker; an explicit `/workflow resume <id>` still returns an explanatory error. It is intentionally different from `/workflow list`, which lists installed workflow definitions. See [`/workflow resume` — cross-session resume selector](#/workflow-resume-—-cross-session-resume-selector) for the full picker semantics.
|
|
175
176
|
|
|
@@ -464,7 +465,7 @@ Repeated, sibling, sequential, parallel, and multi-level child calls keep indepe
|
|
|
464
465
|
|
|
465
466
|
### `ctx.tool` — durable cached tool execution
|
|
466
467
|
|
|
467
|
-
The `ctx.tool(name, args, fn, options?)` primitive runs arbitrary TypeScript code as a first-class durable graph node and caches the result durably. The node is non-attachable and has no stage chat controls, and its graph card body is the constant `durable tool` in every state
|
|
468
|
+
The `ctx.tool(name, args, fn, options?)` primitive runs arbitrary TypeScript code as a first-class durable graph node and caches the result durably. The node is non-attachable and has no stage chat controls, and its graph card body is the constant `durable tool` in every state. The card shows status separately and does not preview the result or error. In the graph viewer, focusing the node and pressing Enter, clicking it, or choosing it from the switcher opens a read-only host-style operator card from the snapshot: a status-tinted shaded rectangle with the same inner padding and header/body gap as the main-chat tool block, inset from the orchestrator header and footer bars, a `$ <tool-name>` call header, an optional short argument summary, and the result or error as its body. Running and completed call headers have no status marker; pending, failed, cached, and cancelled calls retain their quiet markers. It is collapsed by default and wraps the fully bounded result or error before showing its last visual rows, with `... (N earlier lines, ctrl+o Expand)` above the tail when the action is bound; the configured `app.tools.expand` action (`ctrl+o` by default) toggles the full bounded result or error and then a muted callback-source block when source exists. The graph statusline advertises the resolved expand key with `expand` or `collapse` alongside return-to-graph and scroll hints, including remapped keys, and omits that segment entirely when the action is unbound. The footer says `Took` for settled calls or `Elapsed` for running calls, using the same second-resolution duration as the main-chat tool block, with cached/replayed markers kept as a quiet suffix. The operator surface has no ARGS/RESULT/SOURCE/TIMING/MARKERS debug table and does not expose raw clock fields. Source capture uses `fn.toString()` at registration without re-executing the callback or reading a file. `↑`/`↓`, `PageUp`/`PageDown`, `Home`/`End`, the wheel, and the scrollbar all scroll the block, so a long payload stays readable on a keyboard-only session or a terminal without mouse reporting; Escape or `ctrl+x` returns to the graph. The message block is read-only and never offers chat attachment, steering, pause, or resume. Bounded payloads remain width-safe and mark truncation explicitly with `… [truncated]`; source tabs expand and control bytes become `\xNN`, while cyclic payloads, throwing `toJSON`, or throwing property getters render `<cycle>`, `<unserializable>`, or `<unreadable>` instead of crashing the view. The same cap applies to what the live run snapshot retains for a tool node, while durable checkpoints keep the exact output, raw-args `argsHash`, and replay behavior unchanged.
|
|
468
469
|
|
|
469
470
|
When the workflow body fulfills but one or more admitted tool calls failed, Atomic promotes the first observed failure to the terminal run failure, regardless of admission order, and persists that selected tool-node identity for status inspection and lifecycle output. A direct uncaught `await ctx.tool(...)` rejection keeps the original error and persists its failed-node link through session and durable restore. First-event arbitration also preserves the selected node when concurrent failures throw the same object or primitive; unrelated later stage or body errors do not inherit a caught tool's origin. Tool admission remains open while author code can catch a failure and continue. Once the body settles and failure has won before any real cancellation, Atomic closes admission, cancels remaining non-failed tool nodes, waits for observed failed nodes to finish publication, and publishes the failed root without waiting for callbacks that ignore cancellation.
|
|
470
471
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bastani/atomic",
|
|
9
|
-
"version": "0.9.19-alpha.
|
|
9
|
+
"version": "0.9.19-alpha.8",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bastani/atomic-natives": "0.9.19-alpha.
|
|
13
|
-
"@bastani/pi-ai": "0.9.19-alpha.
|
|
12
|
+
"@bastani/atomic-natives": "0.9.19-alpha.8",
|
|
13
|
+
"@bastani/pi-ai": "0.9.19-alpha.8",
|
|
14
14
|
"@dbos-inc/dbos-sdk": "4.25.14",
|
|
15
15
|
"@earendil-works/pi-agent-core": "0.85.1",
|
|
16
16
|
"@earendil-works/pi-client": "0.85.1",
|
|
@@ -518,18 +518,18 @@
|
|
|
518
518
|
}
|
|
519
519
|
},
|
|
520
520
|
"node_modules/@bastani/atomic-natives": {
|
|
521
|
-
"version": "0.9.19-alpha.
|
|
522
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.19-alpha.
|
|
521
|
+
"version": "0.9.19-alpha.8",
|
|
522
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.19-alpha.8.tgz",
|
|
523
523
|
"license": "MIT",
|
|
524
524
|
"optionalDependencies": {
|
|
525
|
-
"@bastani/atomic-natives-darwin-arm64": "0.9.19-alpha.
|
|
526
|
-
"@bastani/atomic-natives-darwin-x64": "0.9.19-alpha.
|
|
527
|
-
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.19-alpha.
|
|
528
|
-
"@bastani/atomic-natives-linux-arm64-musl": "0.9.19-alpha.
|
|
529
|
-
"@bastani/atomic-natives-linux-x64-gnu": "0.9.19-alpha.
|
|
530
|
-
"@bastani/atomic-natives-linux-x64-musl": "0.9.19-alpha.
|
|
531
|
-
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.19-alpha.
|
|
532
|
-
"@bastani/atomic-natives-win32-x64-msvc": "0.9.19-alpha.
|
|
525
|
+
"@bastani/atomic-natives-darwin-arm64": "0.9.19-alpha.8",
|
|
526
|
+
"@bastani/atomic-natives-darwin-x64": "0.9.19-alpha.8",
|
|
527
|
+
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.19-alpha.8",
|
|
528
|
+
"@bastani/atomic-natives-linux-arm64-musl": "0.9.19-alpha.8",
|
|
529
|
+
"@bastani/atomic-natives-linux-x64-gnu": "0.9.19-alpha.8",
|
|
530
|
+
"@bastani/atomic-natives-linux-x64-musl": "0.9.19-alpha.8",
|
|
531
|
+
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.19-alpha.8",
|
|
532
|
+
"@bastani/atomic-natives-win32-x64-msvc": "0.9.19-alpha.8"
|
|
533
533
|
},
|
|
534
534
|
"engines": {
|
|
535
535
|
"bun": ">=1.4.2",
|
|
@@ -537,8 +537,8 @@
|
|
|
537
537
|
}
|
|
538
538
|
},
|
|
539
539
|
"node_modules/@bastani/atomic-natives-darwin-arm64": {
|
|
540
|
-
"version": "0.9.19-alpha.
|
|
541
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.19-alpha.
|
|
540
|
+
"version": "0.9.19-alpha.8",
|
|
541
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.19-alpha.8.tgz",
|
|
542
542
|
"license": "MIT",
|
|
543
543
|
"os": [
|
|
544
544
|
"darwin"
|
|
@@ -549,8 +549,8 @@
|
|
|
549
549
|
"optional": true
|
|
550
550
|
},
|
|
551
551
|
"node_modules/@bastani/atomic-natives-darwin-x64": {
|
|
552
|
-
"version": "0.9.19-alpha.
|
|
553
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.19-alpha.
|
|
552
|
+
"version": "0.9.19-alpha.8",
|
|
553
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.19-alpha.8.tgz",
|
|
554
554
|
"license": "MIT",
|
|
555
555
|
"os": [
|
|
556
556
|
"darwin"
|
|
@@ -561,8 +561,8 @@
|
|
|
561
561
|
"optional": true
|
|
562
562
|
},
|
|
563
563
|
"node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
|
|
564
|
-
"version": "0.9.19-alpha.
|
|
565
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.19-alpha.
|
|
564
|
+
"version": "0.9.19-alpha.8",
|
|
565
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.19-alpha.8.tgz",
|
|
566
566
|
"license": "MIT",
|
|
567
567
|
"os": [
|
|
568
568
|
"linux"
|
|
@@ -576,8 +576,8 @@
|
|
|
576
576
|
"optional": true
|
|
577
577
|
},
|
|
578
578
|
"node_modules/@bastani/atomic-natives-linux-arm64-musl": {
|
|
579
|
-
"version": "0.9.19-alpha.
|
|
580
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.19-alpha.
|
|
579
|
+
"version": "0.9.19-alpha.8",
|
|
580
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.19-alpha.8.tgz",
|
|
581
581
|
"license": "MIT",
|
|
582
582
|
"os": [
|
|
583
583
|
"linux"
|
|
@@ -591,8 +591,8 @@
|
|
|
591
591
|
"optional": true
|
|
592
592
|
},
|
|
593
593
|
"node_modules/@bastani/atomic-natives-linux-x64-gnu": {
|
|
594
|
-
"version": "0.9.19-alpha.
|
|
595
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.19-alpha.
|
|
594
|
+
"version": "0.9.19-alpha.8",
|
|
595
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.19-alpha.8.tgz",
|
|
596
596
|
"license": "MIT",
|
|
597
597
|
"os": [
|
|
598
598
|
"linux"
|
|
@@ -606,8 +606,8 @@
|
|
|
606
606
|
"optional": true
|
|
607
607
|
},
|
|
608
608
|
"node_modules/@bastani/atomic-natives-linux-x64-musl": {
|
|
609
|
-
"version": "0.9.19-alpha.
|
|
610
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.19-alpha.
|
|
609
|
+
"version": "0.9.19-alpha.8",
|
|
610
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.19-alpha.8.tgz",
|
|
611
611
|
"license": "MIT",
|
|
612
612
|
"os": [
|
|
613
613
|
"linux"
|
|
@@ -621,8 +621,8 @@
|
|
|
621
621
|
"optional": true
|
|
622
622
|
},
|
|
623
623
|
"node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
|
|
624
|
-
"version": "0.9.19-alpha.
|
|
625
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.19-alpha.
|
|
624
|
+
"version": "0.9.19-alpha.8",
|
|
625
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.19-alpha.8.tgz",
|
|
626
626
|
"license": "MIT",
|
|
627
627
|
"os": [
|
|
628
628
|
"win32"
|
|
@@ -633,8 +633,8 @@
|
|
|
633
633
|
"optional": true
|
|
634
634
|
},
|
|
635
635
|
"node_modules/@bastani/atomic-natives-win32-x64-msvc": {
|
|
636
|
-
"version": "0.9.19-alpha.
|
|
637
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.19-alpha.
|
|
636
|
+
"version": "0.9.19-alpha.8",
|
|
637
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.19-alpha.8.tgz",
|
|
638
638
|
"license": "MIT",
|
|
639
639
|
"os": [
|
|
640
640
|
"win32"
|
|
@@ -645,8 +645,8 @@
|
|
|
645
645
|
"optional": true
|
|
646
646
|
},
|
|
647
647
|
"node_modules/@bastani/pi-ai": {
|
|
648
|
-
"version": "0.9.19-alpha.
|
|
649
|
-
"resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.19-alpha.
|
|
648
|
+
"version": "0.9.19-alpha.8",
|
|
649
|
+
"resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.19-alpha.8.tgz",
|
|
650
650
|
"license": "MIT",
|
|
651
651
|
"dependencies": {
|
|
652
652
|
"@anthropic-ai/sdk": "0.124.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.8",
|
|
4
4
|
"description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"atomicConfig": {
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@bastani/atomic-natives": "0.9.19-alpha.
|
|
83
|
-
"@bastani/pi-ai": "0.9.19-alpha.
|
|
82
|
+
"@bastani/atomic-natives": "0.9.19-alpha.8",
|
|
83
|
+
"@bastani/pi-ai": "0.9.19-alpha.8",
|
|
84
84
|
"@dbos-inc/dbos-sdk": "4.25.14",
|
|
85
85
|
"@earendil-works/pi-agent-core": "0.85.1",
|
|
86
86
|
"@earendil-works/pi-client": "0.85.1",
|