pipeline-worker 0.1.26 → 0.1.27
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/README.md +6 -2
- package/dist/agent/claude.js +32 -1
- package/dist/agent/claude.js.map +1 -1
- package/dist/agent/types.d.ts +18 -0
- package/dist/cli.js +30 -4
- package/dist/cli.js.map +1 -1
- package/dist/config/loader.js +25 -3
- package/dist/config/loader.js.map +1 -1
- package/dist/forge/github.js +7 -0
- package/dist/forge/github.js.map +1 -1
- package/dist/forge/gitlab.js +7 -0
- package/dist/forge/gitlab.js.map +1 -1
- package/dist/forge/types.d.ts +9 -0
- package/dist/state/runState.d.ts +10 -1
- package/dist/state/runState.js +20 -2
- package/dist/state/runState.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/ui/format.d.ts +8 -0
- package/dist/ui/format.js +17 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/renderer.d.ts +41 -0
- package/dist/ui/renderer.js +93 -0
- package/dist/ui/renderer.js.map +1 -0
- package/dist/ui/runTree.d.ts +106 -0
- package/dist/ui/runTree.js +133 -0
- package/dist/ui/runTree.js.map +1 -0
- package/dist/ui/sessions.js +11 -4
- package/dist/ui/sessions.js.map +1 -1
- package/dist/ui/steps.d.ts +82 -35
- package/dist/ui/steps.js +158 -97
- package/dist/ui/steps.js.map +1 -1
- package/dist/ui/treeRenderer.d.ts +89 -0
- package/dist/ui/treeRenderer.js +271 -0
- package/dist/ui/treeRenderer.js.map +1 -0
- package/dist/workflow/adoptBranch.js +16 -10
- package/dist/workflow/adoptBranch.js.map +1 -1
- package/dist/workflow/captureIntent.d.ts +7 -2
- package/dist/workflow/captureIntent.js +1 -25
- package/dist/workflow/captureIntent.js.map +1 -1
- package/dist/workflow/openMergeRequest.d.ts +1 -1
- package/dist/workflow/openMergeRequest.js +8 -7
- package/dist/workflow/openMergeRequest.js.map +1 -1
- package/dist/workflow/orchestrate.d.ts +1 -1
- package/dist/workflow/orchestrate.js +61 -39
- package/dist/workflow/orchestrate.js.map +1 -1
- package/dist/workflow/runPlan.d.ts +18 -0
- package/dist/workflow/runPlan.js +53 -0
- package/dist/workflow/runPlan.js.map +1 -0
- package/dist/workflow/syncTargetBranch.d.ts +37 -0
- package/dist/workflow/syncTargetBranch.js +83 -0
- package/dist/workflow/syncTargetBranch.js.map +1 -0
- package/dist/workflow/watchPipeline.js +61 -23
- package/dist/workflow/watchPipeline.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/steps.js
CHANGED
|
@@ -1,36 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The workflow's one door to the terminal: module-level functions the
|
|
3
|
+
* workflow code calls by step id, backed by a RunTree (the data model) and a
|
|
4
|
+
* Renderer (LineRenderer for non-TTY/plain output, TreeRenderer for the live
|
|
5
|
+
* TTY dashboard). Workflow code never touches process.stdout itself — see
|
|
6
|
+
* CLAUDE.md's terminal-output discipline.
|
|
7
|
+
*
|
|
8
|
+
* The facade is safe to use without beginRun() (unit tests exercise workflow
|
|
9
|
+
* helpers directly): the first call lazily creates an empty tree with a
|
|
10
|
+
* LineRenderer, and unknown step ids materialize as top-level nodes instead
|
|
11
|
+
* of throwing — the UI must never kill the run.
|
|
7
12
|
*/
|
|
8
13
|
import { styleText } from 'node:util';
|
|
14
|
+
import { RunTree } from './runTree.js';
|
|
15
|
+
import { LineRenderer } from './renderer.js';
|
|
16
|
+
import { TreeRenderer } from './treeRenderer.js';
|
|
9
17
|
const RISK_COLOR = { low: 'green', medium: 'yellow', high: 'red' };
|
|
18
|
+
let active;
|
|
19
|
+
/** The most recently started step — where noteSession attributes an agent turn's tokens. */
|
|
20
|
+
let lastStepId;
|
|
10
21
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* stage 13: config.cleanupOnSuccess) — so the numbering stays sequential and
|
|
16
|
-
* a skipped stage is still visible instead of silently vanishing.
|
|
17
|
-
*
|
|
18
|
-
* Stage 12 (watching the pipeline, and fixing/escalating on failure) loops
|
|
19
|
-
* and branches internally, so its sub-steps are numbered 12.1-12.7 (see
|
|
20
|
-
* watchPipeline.ts) rather than each claiming the bare "12".
|
|
21
|
-
*/
|
|
22
|
-
const TOTAL_STAGES = 13;
|
|
23
|
-
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
24
|
-
const SPINNER_INTERVAL_MS = 80;
|
|
25
|
-
function formatElapsed(ms) {
|
|
26
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Keeps a spinner redraw to a single physical row. Without this, a line
|
|
30
|
-
* longer than the terminal's column count auto-wraps, and the `\r\x1b[K`
|
|
31
|
-
* redraw in runStep() below only rewinds/clears the row the cursor is on —
|
|
32
|
-
* not the wrapped continuation from the previous frame — so each tick
|
|
33
|
-
* appends a new line instead of animating in place.
|
|
22
|
+
* Keeps a spinner/tree redraw to a single physical row. Without this, a line
|
|
23
|
+
* longer than the terminal's column count auto-wraps, and cursor-based
|
|
24
|
+
* erasing only rewinds the rows it knows about — not the wrapped
|
|
25
|
+
* continuation — so each frame appends garbage instead of animating in place.
|
|
34
26
|
*/
|
|
35
27
|
export function truncateToWidth(text, width) {
|
|
36
28
|
if (width <= 0 || text.length <= width)
|
|
@@ -39,16 +31,133 @@ export function truncateToWidth(text, width) {
|
|
|
39
31
|
return text.slice(0, 1);
|
|
40
32
|
return `${text.slice(0, width - 1)}…`;
|
|
41
33
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Pure selection logic, kept separate from createRenderer() so it's testable
|
|
36
|
+
* without touching the real process.stdout (flipping process.stdout.isTTY in
|
|
37
|
+
* a test would make a real TreeRenderer hide the cursor on the terminal
|
|
38
|
+
* actually running the test suite). The live tree dashboard needs a real
|
|
39
|
+
* terminal to redraw in place; CI logs, piped output, and
|
|
40
|
+
* PIPELINE_WORKER_PLAIN_OUTPUT (an explicit escape hatch — useful when filing
|
|
41
|
+
* a bug report, or any tool that greps run output) all fall back to the
|
|
42
|
+
* append-only LineRenderer instead.
|
|
43
|
+
*/
|
|
44
|
+
export function selectRendererMode(isTTY, plainOutputEnv) {
|
|
45
|
+
const plain = ['true', '1'].includes((plainOutputEnv ?? '').toLowerCase());
|
|
46
|
+
return isTTY && !plain ? 'tree' : 'line';
|
|
47
|
+
}
|
|
48
|
+
function createRenderer() {
|
|
49
|
+
const mode = selectRendererMode(process.stdout.isTTY === true, process.env.PIPELINE_WORKER_PLAIN_OUTPUT);
|
|
50
|
+
return mode === 'tree' ? new TreeRenderer() : new LineRenderer();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Constructs a tree wired to a fresh renderer, then fires one synthetic
|
|
54
|
+
* event so the renderer attaches and paints the initial (all-pending)
|
|
55
|
+
* frame immediately — the skeleton's nodes are inserted directly in the
|
|
56
|
+
* RunTree constructor, with no 'add' events of their own, so without this a
|
|
57
|
+
* TreeRenderer would stay unattached (and crash on the first freeform log()
|
|
58
|
+
* call) until the first real mutation.
|
|
59
|
+
*/
|
|
60
|
+
function createActiveRun(skeleton, header) {
|
|
61
|
+
const renderer = createRenderer();
|
|
62
|
+
const tree = new RunTree(skeleton, header, (event) => renderer.onEvent(event, tree));
|
|
63
|
+
renderer.onEvent({ kind: 'header' }, tree);
|
|
64
|
+
return { tree, renderer, ended: false };
|
|
65
|
+
}
|
|
66
|
+
function ensureActive() {
|
|
67
|
+
active ??= createActiveRun([], { title: 'run' });
|
|
68
|
+
return active;
|
|
69
|
+
}
|
|
70
|
+
/** Starts a new run display. Replaces any previous run's tree (each CLI invocation begins at most one). */
|
|
71
|
+
export function beginRun(skeleton, header) {
|
|
72
|
+
active = createActiveRun(skeleton, header);
|
|
73
|
+
lastStepId = undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Settles the run display into its terminal status. Idempotent: only the
|
|
77
|
+
* first call paints, so error paths can call endRun('failed') defensively
|
|
78
|
+
* without stomping an earlier, more specific verdict.
|
|
79
|
+
*/
|
|
80
|
+
export function endRun(status, detail) {
|
|
81
|
+
const run = ensureActive();
|
|
82
|
+
if (run.ended)
|
|
83
|
+
return;
|
|
84
|
+
run.ended = true;
|
|
85
|
+
run.tree.setHeader({ status });
|
|
86
|
+
run.renderer.stop(status, detail, run.tree);
|
|
87
|
+
}
|
|
88
|
+
/** Adds a step that wasn't in the skeleton — the watch loop's fix/rebase attempts, conflict resolution, squash. */
|
|
89
|
+
export function addDynamicStep(parentId, id, label, detail = '') {
|
|
90
|
+
ensureActive().tree.add(parentId, { id, label, detail });
|
|
91
|
+
}
|
|
92
|
+
/** Marks a step running without tying it to a single task closure — for steps whose phases span several calls (see runPhase). */
|
|
93
|
+
export function startStep(id, patch = {}) {
|
|
94
|
+
lastStepId = id;
|
|
95
|
+
ensureActive().tree.start(id, patch);
|
|
96
|
+
}
|
|
97
|
+
export function finishStep(id, status = 'done', patch = {}) {
|
|
98
|
+
ensureActive().tree.finish(id, status, patch);
|
|
99
|
+
}
|
|
100
|
+
export function updateStep(id, patch) {
|
|
101
|
+
ensureActive().tree.update(id, patch);
|
|
102
|
+
}
|
|
103
|
+
/** Announces a step that did not run this time, and why — a skipped stage stays visible instead of silently vanishing (which reads as a bug, not a choice). */
|
|
104
|
+
export function skipStep(id, reason) {
|
|
105
|
+
ensureActive().tree.finish(id, 'skipped', { detail: reason });
|
|
106
|
+
}
|
|
107
|
+
/** Runs one step to completion: running → done, or running → failed + rethrow. */
|
|
108
|
+
export async function runStep(id, detail, task) {
|
|
109
|
+
const run = ensureActive();
|
|
110
|
+
lastStepId = id;
|
|
111
|
+
run.tree.start(id, { detail });
|
|
112
|
+
try {
|
|
113
|
+
const result = await task();
|
|
114
|
+
run.tree.finish(id, 'done');
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
run.tree.finish(id, 'failed');
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Runs one phase of an already-running step (a fix attempt's agent turn,
|
|
124
|
+
* local verify, push, ...), mutating the step's detail rather than creating
|
|
125
|
+
* grandchildren — the tree stays one row per attempt, as the dashboard
|
|
126
|
+
* renders it. Failure marks the whole step failed and rethrows; success
|
|
127
|
+
* leaves it running for the next phase (the caller finishes it).
|
|
128
|
+
*/
|
|
129
|
+
export async function runPhase(id, detail, task) {
|
|
130
|
+
const run = ensureActive();
|
|
131
|
+
const node = run.tree.get(id);
|
|
132
|
+
if (!node || node.status !== 'running') {
|
|
133
|
+
startStep(id, { detail });
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
run.tree.update(id, { detail });
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return await task();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
run.tree.finish(id, 'failed');
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Updates the header line: the run's display name (once intent names the branch) and the worktree short id. */
|
|
147
|
+
export function setRunHeader(patch) {
|
|
148
|
+
ensureActive().tree.setHeader(patch);
|
|
45
149
|
}
|
|
46
|
-
/**
|
|
47
|
-
export function
|
|
48
|
-
|
|
49
|
-
|
|
150
|
+
/** Folds a resumed run's persisted token total into the header figure, so the dashboard shows the whole run's spend, not just this process's share. */
|
|
151
|
+
export function seedRunTokens(tokens) {
|
|
152
|
+
ensureActive().tree.seedTokens(tokens);
|
|
153
|
+
}
|
|
154
|
+
/** A bold one-off announcement outside any step (adopting a branch, pipeline failed, ...). */
|
|
155
|
+
export function announce(text, detail) {
|
|
156
|
+
const run = ensureActive();
|
|
157
|
+
run.renderer.log('');
|
|
158
|
+
run.renderer.log(styleText('bold', text));
|
|
50
159
|
if (detail)
|
|
51
|
-
|
|
160
|
+
run.renderer.log(styleText('dim', ` ${detail}`));
|
|
52
161
|
}
|
|
53
162
|
/**
|
|
54
163
|
* An indented supplementary detail line under the current step (e.g. an
|
|
@@ -57,21 +166,11 @@ export function step(icon, title, detail) {
|
|
|
57
166
|
* otherwise a multi-line value would break out of the "one dim line" format.
|
|
58
167
|
*/
|
|
59
168
|
export function note(text) {
|
|
60
|
-
|
|
169
|
+
ensureActive().renderer.log(styleText('dim', ` ${text.replace(/\s*\n\s*/g, ' ')}`));
|
|
61
170
|
}
|
|
62
171
|
/** Like note(), but colors the text by risk level (green/yellow/red for low/medium/high). */
|
|
63
172
|
export function noteRisk(risk, reason) {
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Announces a numbered stage that did not run this time, and why, so the
|
|
68
|
-
* printed sequence stays gap-free instead of a conditional stage silently
|
|
69
|
-
* vanishing from between its neighbors (which reads as a bug, not a choice).
|
|
70
|
-
*/
|
|
71
|
-
export function skipStep(stage, icon, title, reason) {
|
|
72
|
-
console.log();
|
|
73
|
-
console.log(styleText('dim', `[${stage}/${TOTAL_STAGES}] ${icon} ${title} (skipped)`));
|
|
74
|
-
console.log(styleText('dim', ` ${reason.replace(/\s*\n\s*/g, ' ')}`));
|
|
173
|
+
ensureActive().renderer.log(styleText('dim', ' ') + styleText(RISK_COLOR[risk], `risk: ${risk} — ${reason.replace(/\s*\n\s*/g, ' ')}`));
|
|
75
174
|
}
|
|
76
175
|
/**
|
|
77
176
|
* Reports which agent CLI session handled a turn, how long it took, and the
|
|
@@ -79,16 +178,19 @@ export function skipStep(stage, icon, title, reason) {
|
|
|
79
178
|
* or a CI failure was fixed can look it up afterwards (`claude --resume <id>`
|
|
80
179
|
* or, for Copilot, `copilot --resume <id>` — see agent/copilot.ts on why that
|
|
81
180
|
* id is one we assigned rather than one the CLI reported). Session history is
|
|
82
|
-
* scoped to the working directory the CLI ran in
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
181
|
+
* scoped to the working directory the CLI ran in, so `--resume` only finds
|
|
182
|
+
* the session when run from that same worktree path — printing the path lets
|
|
183
|
+
* the user `cd` there first. A no-op when the adapter didn't return a
|
|
184
|
+
* sessionId, which keeps this safe to call unconditionally.
|
|
185
|
+
*
|
|
186
|
+
* Also attributes the turn's token spend (when the adapter reported any) to
|
|
187
|
+
* the step that ran it, which is what feeds the per-step and header token
|
|
188
|
+
* figures on the dashboard.
|
|
90
189
|
*/
|
|
91
190
|
export function noteSession(result, worktreePath) {
|
|
191
|
+
if (result.usage?.totalTokens !== undefined && lastStepId !== undefined) {
|
|
192
|
+
ensureActive().tree.addTokens(lastStepId, result.usage.totalTokens);
|
|
193
|
+
}
|
|
92
194
|
if (!result.sessionId)
|
|
93
195
|
return;
|
|
94
196
|
const duration = result.durationMs !== undefined ? ` — ${(result.durationMs / 1000).toFixed(1)}s` : '';
|
|
@@ -100,45 +202,4 @@ export function reportAgentInvocation(result, worktreePath) {
|
|
|
100
202
|
note(`agent: ${text.slice(0, 300).trim()}${text.length > 300 ? '…' : ''}`);
|
|
101
203
|
noteSession(result, worktreePath);
|
|
102
204
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Runs one numbered workflow stage with a colored, iconed title, then a live
|
|
105
|
-
* status line beneath it while `task` runs: a spinner plus a counting-up
|
|
106
|
-
* elapsed timer in a TTY, settling into a green checkmark or red cross with
|
|
107
|
-
* the total duration on completion. Falls back to a single static print when
|
|
108
|
-
* stdout isn't a TTY (CI logs, redirected files) since carriage-return
|
|
109
|
-
* spinners would just garble those.
|
|
110
|
-
*/
|
|
111
|
-
export async function runStep(stage, icon, title, detail, task) {
|
|
112
|
-
console.log(); // blank line for clear separation between stages
|
|
113
|
-
console.log(stageHeader(stage, icon, title));
|
|
114
|
-
if (!process.stdout.isTTY) {
|
|
115
|
-
console.log(styleText('dim', ` ${detail}`));
|
|
116
|
-
return task();
|
|
117
|
-
}
|
|
118
|
-
const start = Date.now();
|
|
119
|
-
let frame = 0;
|
|
120
|
-
const render = (glyph, color = 'dim') => {
|
|
121
|
-
const line = ` ${glyph} ${detail} (${formatElapsed(Date.now() - start)})`;
|
|
122
|
-
const width = process.stdout.columns ?? line.length;
|
|
123
|
-
process.stdout.write(`\r\x1b[K${styleText(color, truncateToWidth(line, width))}`);
|
|
124
|
-
};
|
|
125
|
-
render(SPINNER_FRAMES[0]);
|
|
126
|
-
const timer = setInterval(() => {
|
|
127
|
-
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
128
|
-
render(SPINNER_FRAMES[frame]);
|
|
129
|
-
}, SPINNER_INTERVAL_MS);
|
|
130
|
-
try {
|
|
131
|
-
const result = await task();
|
|
132
|
-
clearInterval(timer);
|
|
133
|
-
render('✓', 'green');
|
|
134
|
-
process.stdout.write('\n');
|
|
135
|
-
return result;
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
clearInterval(timer);
|
|
139
|
-
render('✗', 'red');
|
|
140
|
-
process.stdout.write('\n');
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
205
|
//# sourceMappingURL=steps.js.map
|
package/dist/ui/steps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/ui/steps.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/ui/steps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live TTY dashboard: a pinned bottom region holding the run header and
|
|
3
|
+
* the step tree, repainted in place, with freeform log lines (notes, agent
|
|
4
|
+
* output, stray console writes) scrolling into the terminal's scrollback
|
|
5
|
+
* ABOVE the region — the same strategy ora/listr2 use, hand-rolled to keep
|
|
6
|
+
* the dependency count at zero.
|
|
7
|
+
*
|
|
8
|
+
* pipeline-worker · fix-login-redirect · worktree a91f · running · 9.4k tok
|
|
9
|
+
* ├─ ✓ capture staged + unstaged diff 0.4s
|
|
10
|
+
* ├─ ● ci-watch pipeline #8123: fixing attempt 2/5 · 4.4k tok
|
|
11
|
+
* └─ ○ merge auto-merge + sync local main
|
|
12
|
+
*
|
|
13
|
+
* Invariants that keep the redraw exact:
|
|
14
|
+
* - Every painted line is pre-truncated to the terminal width, so no line
|
|
15
|
+
* can wrap; `renderedLines` therefore always equals the physical rows the
|
|
16
|
+
* region occupies, and one `ESC[{n}A CR ESC[J` erases exactly the region.
|
|
17
|
+
* - While attached, console.log/error are intercepted and routed through
|
|
18
|
+
* log(), so nothing can print into the middle of the region (CLAUDE.md's
|
|
19
|
+
* terminal-output discipline keeps direct process.stdout writers out of
|
|
20
|
+
* the rest of the codebase).
|
|
21
|
+
* - The cursor is hidden on attach and restored on stop AND on process exit
|
|
22
|
+
* (the 'exit' hook only does a sync write, which is allowed there), so a
|
|
23
|
+
* ctrl-C mid-frame never leaves a cursorless terminal.
|
|
24
|
+
*/
|
|
25
|
+
import { type Renderer } from './renderer.js';
|
|
26
|
+
import type { RunStatus, RunTree, TreeEvent, TreeRow } from './runTree.js';
|
|
27
|
+
/** The slice of a TTY WriteStream the renderer needs — injectable so tests drive a fake with fixed geometry. */
|
|
28
|
+
export interface OutStream {
|
|
29
|
+
write(text: string): void;
|
|
30
|
+
columns?: number;
|
|
31
|
+
rows?: number;
|
|
32
|
+
on?(event: 'resize', listener: () => void): void;
|
|
33
|
+
off?(event: 'resize', listener: () => void): void;
|
|
34
|
+
}
|
|
35
|
+
/** A collapsed run of finished rows, produced by fitToHeight when the tree outgrows the screen. */
|
|
36
|
+
export interface ElisionRow {
|
|
37
|
+
summary: string;
|
|
38
|
+
depth: number;
|
|
39
|
+
}
|
|
40
|
+
export type DisplayRow = TreeRow | ElisionRow;
|
|
41
|
+
/**
|
|
42
|
+
* Elides rows until header + rows fit in `maxRows`, keeping what the user
|
|
43
|
+
* actually watches: the running/failed/pending steps. Two passes, both
|
|
44
|
+
* deterministic (pure function, unit-tested directly):
|
|
45
|
+
*
|
|
46
|
+
* 1. collapse each parent's leading finished children (a long fix-attempt
|
|
47
|
+
* history) into one `… ✓ N earlier attempts` row, keeping the last
|
|
48
|
+
* finished child for context;
|
|
49
|
+
* 2. collapse leading fully-finished top-level steps into `… ✓ N earlier
|
|
50
|
+
* steps`.
|
|
51
|
+
*
|
|
52
|
+
* If the tree still doesn't fit (tiny terminal), keep the tail — the newest
|
|
53
|
+
* rows are the live ones.
|
|
54
|
+
*/
|
|
55
|
+
export declare function fitToHeight(rows: TreeRow[], maxRows: number): DisplayRow[];
|
|
56
|
+
export declare class TreeRenderer implements Renderer {
|
|
57
|
+
private readonly out;
|
|
58
|
+
private tree;
|
|
59
|
+
private renderedLines;
|
|
60
|
+
private frame;
|
|
61
|
+
private timer;
|
|
62
|
+
private stopped;
|
|
63
|
+
private readonly originalConsole;
|
|
64
|
+
private readonly onResize;
|
|
65
|
+
private readonly restoreCursorOnExit;
|
|
66
|
+
constructor(out?: OutStream);
|
|
67
|
+
private columns;
|
|
68
|
+
/**
|
|
69
|
+
* Repaints immediately on every tree mutation — a step finishing or a
|
|
70
|
+
* token count changing must show up right away, not on the next spinner
|
|
71
|
+
* tick. The interval timer (attach()) exists only to keep the spinner
|
|
72
|
+
* glyph and the running step's elapsed-time counter animating during long
|
|
73
|
+
* stretches with no tree events at all (a multi-minute CI poll).
|
|
74
|
+
*/
|
|
75
|
+
onEvent(event: TreeEvent, tree: RunTree): void;
|
|
76
|
+
private attach;
|
|
77
|
+
private eraseRegion;
|
|
78
|
+
/** Freeform text: erase the region, let the text enter scrollback, repaint beneath it. */
|
|
79
|
+
log(text: string): void;
|
|
80
|
+
private headerLine;
|
|
81
|
+
private statusGlyph;
|
|
82
|
+
/** Right-hand figures: 'attempt 2/5 · 4.4k tok · 3.2s' — whichever are known. */
|
|
83
|
+
private figures;
|
|
84
|
+
private branchPrefix;
|
|
85
|
+
private rowLine;
|
|
86
|
+
private buildFrame;
|
|
87
|
+
private paint;
|
|
88
|
+
stop(status: RunStatus, detail: string | undefined, tree: RunTree): void;
|
|
89
|
+
}
|