pipeline-worker 0.1.26 → 0.1.28

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 (56) hide show
  1. package/README.md +6 -2
  2. package/dist/agent/claude.js +32 -1
  3. package/dist/agent/claude.js.map +1 -1
  4. package/dist/agent/types.d.ts +18 -0
  5. package/dist/cli.js +30 -4
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config/loader.js +25 -3
  8. package/dist/config/loader.js.map +1 -1
  9. package/dist/forge/github.js +7 -0
  10. package/dist/forge/github.js.map +1 -1
  11. package/dist/forge/gitlab.js +7 -0
  12. package/dist/forge/gitlab.js.map +1 -1
  13. package/dist/forge/types.d.ts +9 -0
  14. package/dist/state/runState.d.ts +10 -1
  15. package/dist/state/runState.js +20 -2
  16. package/dist/state/runState.js.map +1 -1
  17. package/dist/types.d.ts +4 -0
  18. package/dist/ui/format.d.ts +19 -0
  19. package/dist/ui/format.js +89 -0
  20. package/dist/ui/format.js.map +1 -0
  21. package/dist/ui/renderer.d.ts +41 -0
  22. package/dist/ui/renderer.js +93 -0
  23. package/dist/ui/renderer.js.map +1 -0
  24. package/dist/ui/runTree.d.ts +106 -0
  25. package/dist/ui/runTree.js +133 -0
  26. package/dist/ui/runTree.js.map +1 -0
  27. package/dist/ui/sessions.js +11 -4
  28. package/dist/ui/sessions.js.map +1 -1
  29. package/dist/ui/steps.d.ts +92 -35
  30. package/dist/ui/steps.js +177 -93
  31. package/dist/ui/steps.js.map +1 -1
  32. package/dist/ui/treeRenderer.d.ts +89 -0
  33. package/dist/ui/treeRenderer.js +271 -0
  34. package/dist/ui/treeRenderer.js.map +1 -0
  35. package/dist/ui/welcome.js +15 -9
  36. package/dist/ui/welcome.js.map +1 -1
  37. package/dist/workflow/adoptBranch.js +16 -10
  38. package/dist/workflow/adoptBranch.js.map +1 -1
  39. package/dist/workflow/captureIntent.d.ts +7 -2
  40. package/dist/workflow/captureIntent.js +1 -25
  41. package/dist/workflow/captureIntent.js.map +1 -1
  42. package/dist/workflow/openMergeRequest.d.ts +1 -1
  43. package/dist/workflow/openMergeRequest.js +8 -7
  44. package/dist/workflow/openMergeRequest.js.map +1 -1
  45. package/dist/workflow/orchestrate.d.ts +1 -1
  46. package/dist/workflow/orchestrate.js +61 -39
  47. package/dist/workflow/orchestrate.js.map +1 -1
  48. package/dist/workflow/runPlan.d.ts +18 -0
  49. package/dist/workflow/runPlan.js +53 -0
  50. package/dist/workflow/runPlan.js.map +1 -0
  51. package/dist/workflow/syncTargetBranch.d.ts +37 -0
  52. package/dist/workflow/syncTargetBranch.js +83 -0
  53. package/dist/workflow/syncTargetBranch.js.map +1 -0
  54. package/dist/workflow/watchPipeline.js +61 -23
  55. package/dist/workflow/watchPipeline.js.map +1 -1
  56. package/package.json +1 -1
@@ -1,22 +1,84 @@
1
1
  /**
2
- * Friendly progress narration for `pipeline-worker run`: a bold, colored,
3
- * numbered headline per workflow step (with an icon and a "[n/TOTAL_STAGES]"
4
- * counter) and a dim technical detail line underneath — mirrors how Claude
5
- * Code narrates its own tool calls, so a non-technical user can follow along
6
- * without reading source.
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
  */
13
+ import { type RunStatus, type StepSeed } from './runTree.js';
8
14
  import type { RiskLevel } from '../types.js';
9
15
  import type { AgentInvokeResult } from '../agent/types.js';
10
16
  /**
11
- * Keeps a spinner redraw to a single physical row. Without this, a line
12
- * longer than the terminal's column count auto-wraps, and the `\r\x1b[K`
13
- * redraw in runStep() below only rewinds/clears the row the cursor is on
14
- * not the wrapped continuation from the previous frame so each tick
15
- * appends a new line instead of animating in place.
17
+ * Keeps a spinner/tree redraw to a single physical row. Without this, a line
18
+ * longer than the terminal's column count auto-wraps, and cursor-based
19
+ * erasing only rewinds the rows it knows about not the wrapped
20
+ * continuation so each frame appends garbage instead of animating in place.
16
21
  */
17
22
  export declare function truncateToWidth(text: string, width: number): string;
18
- /** A one-off status line with no associated work to wait on (e.g. a final summary). */
19
- export declare function step(icon: string, title: string, detail?: string): void;
23
+ /**
24
+ * Pure selection logic, kept separate from createRenderer() so it's testable
25
+ * without touching the real process.stdout (flipping process.stdout.isTTY in
26
+ * a test would make a real TreeRenderer hide the cursor on the terminal
27
+ * actually running the test suite). The live tree dashboard needs a real
28
+ * terminal to redraw in place; CI logs, piped output, and
29
+ * PIPELINE_WORKER_PLAIN_OUTPUT (an explicit escape hatch — useful when filing
30
+ * a bug report, or any tool that greps run output) all fall back to the
31
+ * append-only LineRenderer instead.
32
+ */
33
+ export declare function selectRendererMode(isTTY: boolean, plainOutputEnv: string | undefined): 'tree' | 'line';
34
+ /** Starts a new run display. Replaces any previous run's tree (each CLI invocation begins at most one). */
35
+ export declare function beginRun(skeleton: StepSeed[], header: {
36
+ title: string;
37
+ worktreeShortId?: string;
38
+ }): void;
39
+ /**
40
+ * Settles the run display into its terminal status. Idempotent: only the
41
+ * first call paints, so error paths can call endRun('failed') defensively
42
+ * without stomping an earlier, more specific verdict.
43
+ */
44
+ export declare function endRun(status: Exclude<RunStatus, 'running'>, detail?: string): void;
45
+ /** Adds a step that wasn't in the skeleton — the watch loop's fix/rebase attempts, conflict resolution, squash. */
46
+ export declare function addDynamicStep(parentId: string | undefined, id: string, label: string, detail?: string): void;
47
+ /** Marks a step running without tying it to a single task closure — for steps whose phases span several calls (see runPhase). */
48
+ export declare function startStep(id: string, patch?: {
49
+ detail?: string;
50
+ attempt?: number;
51
+ maxAttempts?: number;
52
+ }): void;
53
+ export declare function finishStep(id: string, status?: 'done' | 'failed', patch?: {
54
+ detail?: string;
55
+ }): void;
56
+ export declare function updateStep(id: string, patch: {
57
+ detail?: string;
58
+ attempt?: number;
59
+ maxAttempts?: number;
60
+ }): void;
61
+ /** 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). */
62
+ export declare function skipStep(id: string, reason: string): void;
63
+ /** Runs one step to completion: running → done, or running → failed + rethrow. */
64
+ export declare function runStep<T>(id: string, detail: string, task: () => Promise<T>): Promise<T>;
65
+ /**
66
+ * Runs one phase of an already-running step (a fix attempt's agent turn,
67
+ * local verify, push, ...), mutating the step's detail rather than creating
68
+ * grandchildren — the tree stays one row per attempt, as the dashboard
69
+ * renders it. Failure marks the whole step failed and rethrows; success
70
+ * leaves it running for the next phase (the caller finishes it).
71
+ */
72
+ export declare function runPhase<T>(id: string, detail: string, task: () => Promise<T>): Promise<T>;
73
+ /** Updates the header line: the run's display name (once intent names the branch) and the worktree short id. */
74
+ export declare function setRunHeader(patch: {
75
+ title?: string;
76
+ worktreeShortId?: string;
77
+ }): void;
78
+ /** 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. */
79
+ export declare function seedRunTokens(tokens: number): void;
80
+ /** A bold one-off announcement outside any step (adopting a branch, pipeline failed, ...). */
81
+ export declare function announce(text: string, detail?: string): void;
20
82
  /**
21
83
  * An indented supplementary detail line under the current step (e.g. an
22
84
  * agent's summary, a result). Input is freeform (agent output, forge data)
@@ -26,36 +88,31 @@ export declare function step(icon: string, title: string, detail?: string): void
26
88
  export declare function note(text: string): void;
27
89
  /** Like note(), but colors the text by risk level (green/yellow/red for low/medium/high). */
28
90
  export declare function noteRisk(risk: RiskLevel, reason: string): void;
29
- /**
30
- * Announces a numbered stage that did not run this time, and why, so the
31
- * printed sequence stays gap-free instead of a conditional stage silently
32
- * vanishing from between its neighbors (which reads as a bug, not a choice).
33
- */
34
- export declare function skipStep(stage: number | string, icon: string, title: string, reason: string): void;
35
91
  /**
36
92
  * Reports which agent CLI session handled a turn, how long it took, and the
37
93
  * worktree it ran in, so a user who wants to see how a conflict was resolved
38
94
  * or a CI failure was fixed can look it up afterwards (`claude --resume <id>`
39
95
  * or, for Copilot, `copilot --resume <id>` — see agent/copilot.ts on why that
40
96
  * id is one we assigned rather than one the CLI reported). Session history is
41
- * scoped to the working directory the CLI ran in (see agent/claude.ts:
42
- * `cwd: opts.cwd` is always the worktree, never the caller's own repo), so
43
- * `--resume` only finds the session when run from that same worktree path —
44
- * printing the path lets the user `cd` there first. This function has no way
45
- * to know which adapter produced `result` (`AgentInvokeResult` doesn't carry
46
- * one), so it names the path rather than assembling a `claude`/`copilot`
47
- * command that would guess wrong half the time. A no-op when the adapter
48
- * didn't return a sessionId, which keeps this safe to call unconditionally.
97
+ * scoped to the working directory the CLI ran in, so `--resume` only finds
98
+ * the session when run from that same worktree path printing the path lets
99
+ * the user `cd` there first. A no-op when the adapter didn't return a
100
+ * sessionId, which keeps this safe to call unconditionally.
101
+ *
102
+ * Also attributes the turn's token spend (when the adapter reported any) to
103
+ * the step that ran it, which is what feeds the per-step and header token
104
+ * figures on the dashboard.
49
105
  */
50
106
  export declare function noteSession(result: AgentInvokeResult, worktreePath: string): void;
51
107
  /** Prints an agent's response (truncated) followed by its resumable session info — the shared tail of every conflict/CI-fix agent invocation. */
52
108
  export declare function reportAgentInvocation(result: AgentInvokeResult, worktreePath: string): void;
53
- /**
54
- * Runs one numbered workflow stage with a colored, iconed title, then a live
55
- * status line beneath it while `task` runs: a spinner plus a counting-up
56
- * elapsed timer in a TTY, settling into a green checkmark or red cross with
57
- * the total duration on completion. Falls back to a single static print when
58
- * stdout isn't a TTY (CI logs, redirected files) since carriage-return
59
- * spinners would just garble those.
60
- */
61
- export declare function runStep<T>(stage: number | string, icon: string, title: string, detail: string, task: () => Promise<T>): Promise<T>;
109
+ /** Outputs a formatted section header with title and emoji. */
110
+ export declare function sectionHeader(title: string, emoji: string): void;
111
+ /** Outputs a formatted section with header, bullet points, and spacing. */
112
+ export declare function outputSection(title: string, emoji: string, items: Array<{
113
+ label: string;
114
+ value: string;
115
+ wrap?: boolean;
116
+ }>): void;
117
+ /** Outputs a section footer (blank line for spacing). */
118
+ export declare function sectionFooter(): void;
package/dist/ui/steps.js CHANGED
@@ -1,36 +1,29 @@
1
1
  /**
2
- * Friendly progress narration for `pipeline-worker run`: a bold, colored,
3
- * numbered headline per workflow step (with an icon and a "[n/TOTAL_STAGES]"
4
- * counter) and a dim technical detail line underneath — mirrors how Claude
5
- * Code narrates its own tool calls, so a non-technical user can follow along
6
- * without reading source.
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';
17
+ import { boxHeader, formatBulletBlock } from './format.js';
9
18
  const RISK_COLOR = { low: 'green', medium: 'yellow', high: 'red' };
19
+ let active;
20
+ /** The most recently started step — where noteSession attributes an agent turn's tokens. */
21
+ let lastStepId;
10
22
  /**
11
- * The workflow always runs stages 1-13 in order. Some stages are opt-in or
12
- * conditional on runtime state and are announced via skipStep() with a
13
- * reason instead of running when their condition isn't met (stage 8:
14
- * config.updateChangelog; stage 11: reused when an MR/PR already exists;
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.
23
+ * Keeps a spinner/tree redraw to a single physical row. Without this, a line
24
+ * longer than the terminal's column count auto-wraps, and cursor-based
25
+ * erasing only rewinds the rows it knows about not the wrapped
26
+ * continuation so each frame appends garbage instead of animating in place.
34
27
  */
35
28
  export function truncateToWidth(text, width) {
36
29
  if (width <= 0 || text.length <= width)
@@ -39,16 +32,133 @@ export function truncateToWidth(text, width) {
39
32
  return text.slice(0, 1);
40
33
  return `${text.slice(0, width - 1)}…`;
41
34
  }
42
- /** stage accepts a decimal string (e.g. "12.3") for a sub-step of a numbered stage — see TOTAL_STAGES. */
43
- function stageHeader(stage, icon, title) {
44
- return styleText(['bold', 'cyan'], `[${stage}/${TOTAL_STAGES}] ${icon} ${title}`);
35
+ /**
36
+ * Pure selection logic, kept separate from createRenderer() so it's testable
37
+ * without touching the real process.stdout (flipping process.stdout.isTTY in
38
+ * a test would make a real TreeRenderer hide the cursor on the terminal
39
+ * actually running the test suite). The live tree dashboard needs a real
40
+ * terminal to redraw in place; CI logs, piped output, and
41
+ * PIPELINE_WORKER_PLAIN_OUTPUT (an explicit escape hatch — useful when filing
42
+ * a bug report, or any tool that greps run output) all fall back to the
43
+ * append-only LineRenderer instead.
44
+ */
45
+ export function selectRendererMode(isTTY, plainOutputEnv) {
46
+ const plain = ['true', '1'].includes((plainOutputEnv ?? '').toLowerCase());
47
+ return isTTY && !plain ? 'tree' : 'line';
48
+ }
49
+ function createRenderer() {
50
+ const mode = selectRendererMode(process.stdout.isTTY === true, process.env.PIPELINE_WORKER_PLAIN_OUTPUT);
51
+ return mode === 'tree' ? new TreeRenderer() : new LineRenderer();
52
+ }
53
+ /**
54
+ * Constructs a tree wired to a fresh renderer, then fires one synthetic
55
+ * event so the renderer attaches and paints the initial (all-pending)
56
+ * frame immediately — the skeleton's nodes are inserted directly in the
57
+ * RunTree constructor, with no 'add' events of their own, so without this a
58
+ * TreeRenderer would stay unattached (and crash on the first freeform log()
59
+ * call) until the first real mutation.
60
+ */
61
+ function createActiveRun(skeleton, header) {
62
+ const renderer = createRenderer();
63
+ const tree = new RunTree(skeleton, header, (event) => renderer.onEvent(event, tree));
64
+ renderer.onEvent({ kind: 'header' }, tree);
65
+ return { tree, renderer, ended: false };
66
+ }
67
+ function ensureActive() {
68
+ active ??= createActiveRun([], { title: 'run' });
69
+ return active;
70
+ }
71
+ /** Starts a new run display. Replaces any previous run's tree (each CLI invocation begins at most one). */
72
+ export function beginRun(skeleton, header) {
73
+ active = createActiveRun(skeleton, header);
74
+ lastStepId = undefined;
75
+ }
76
+ /**
77
+ * Settles the run display into its terminal status. Idempotent: only the
78
+ * first call paints, so error paths can call endRun('failed') defensively
79
+ * without stomping an earlier, more specific verdict.
80
+ */
81
+ export function endRun(status, detail) {
82
+ const run = ensureActive();
83
+ if (run.ended)
84
+ return;
85
+ run.ended = true;
86
+ run.tree.setHeader({ status });
87
+ run.renderer.stop(status, detail, run.tree);
88
+ }
89
+ /** Adds a step that wasn't in the skeleton — the watch loop's fix/rebase attempts, conflict resolution, squash. */
90
+ export function addDynamicStep(parentId, id, label, detail = '') {
91
+ ensureActive().tree.add(parentId, { id, label, detail });
92
+ }
93
+ /** Marks a step running without tying it to a single task closure — for steps whose phases span several calls (see runPhase). */
94
+ export function startStep(id, patch = {}) {
95
+ lastStepId = id;
96
+ ensureActive().tree.start(id, patch);
97
+ }
98
+ export function finishStep(id, status = 'done', patch = {}) {
99
+ ensureActive().tree.finish(id, status, patch);
100
+ }
101
+ export function updateStep(id, patch) {
102
+ ensureActive().tree.update(id, patch);
103
+ }
104
+ /** 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). */
105
+ export function skipStep(id, reason) {
106
+ ensureActive().tree.finish(id, 'skipped', { detail: reason });
107
+ }
108
+ /** Runs one step to completion: running → done, or running → failed + rethrow. */
109
+ export async function runStep(id, detail, task) {
110
+ const run = ensureActive();
111
+ lastStepId = id;
112
+ run.tree.start(id, { detail });
113
+ try {
114
+ const result = await task();
115
+ run.tree.finish(id, 'done');
116
+ return result;
117
+ }
118
+ catch (error) {
119
+ run.tree.finish(id, 'failed');
120
+ throw error;
121
+ }
122
+ }
123
+ /**
124
+ * Runs one phase of an already-running step (a fix attempt's agent turn,
125
+ * local verify, push, ...), mutating the step's detail rather than creating
126
+ * grandchildren — the tree stays one row per attempt, as the dashboard
127
+ * renders it. Failure marks the whole step failed and rethrows; success
128
+ * leaves it running for the next phase (the caller finishes it).
129
+ */
130
+ export async function runPhase(id, detail, task) {
131
+ const run = ensureActive();
132
+ const node = run.tree.get(id);
133
+ if (!node || node.status !== 'running') {
134
+ startStep(id, { detail });
135
+ }
136
+ else {
137
+ run.tree.update(id, { detail });
138
+ }
139
+ try {
140
+ return await task();
141
+ }
142
+ catch (error) {
143
+ run.tree.finish(id, 'failed');
144
+ throw error;
145
+ }
146
+ }
147
+ /** Updates the header line: the run's display name (once intent names the branch) and the worktree short id. */
148
+ export function setRunHeader(patch) {
149
+ ensureActive().tree.setHeader(patch);
150
+ }
151
+ /** 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. */
152
+ export function seedRunTokens(tokens) {
153
+ ensureActive().tree.seedTokens(tokens);
45
154
  }
46
- /** A one-off status line with no associated work to wait on (e.g. a final summary). */
47
- export function step(icon, title, detail) {
48
- console.log(); // blank line for clear separation between stages
49
- console.log(styleText('bold', `${icon} ${title}`));
155
+ /** A bold one-off announcement outside any step (adopting a branch, pipeline failed, ...). */
156
+ export function announce(text, detail) {
157
+ const run = ensureActive();
158
+ run.renderer.log('');
159
+ run.renderer.log(styleText('bold', text));
50
160
  if (detail)
51
- console.log(styleText('dim', ` ${detail}`));
161
+ run.renderer.log(styleText('dim', ` ${detail}`));
52
162
  }
53
163
  /**
54
164
  * An indented supplementary detail line under the current step (e.g. an
@@ -57,21 +167,11 @@ export function step(icon, title, detail) {
57
167
  * otherwise a multi-line value would break out of the "one dim line" format.
58
168
  */
59
169
  export function note(text) {
60
- console.log(styleText('dim', ` ${text.replace(/\s*\n\s*/g, ' ')}`));
170
+ ensureActive().renderer.log(styleText('dim', ` ${text.replace(/\s*\n\s*/g, ' ')}`));
61
171
  }
62
172
  /** Like note(), but colors the text by risk level (green/yellow/red for low/medium/high). */
63
173
  export function noteRisk(risk, reason) {
64
- console.log(styleText('dim', ' ') + styleText(RISK_COLOR[risk], `risk: ${risk} — ${reason.replace(/\s*\n\s*/g, ' ')}`));
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, ' ')}`));
174
+ ensureActive().renderer.log(styleText('dim', ' ') + styleText(RISK_COLOR[risk], `risk: ${risk} — ${reason.replace(/\s*\n\s*/g, ' ')}`));
75
175
  }
76
176
  /**
77
177
  * Reports which agent CLI session handled a turn, how long it took, and the
@@ -79,16 +179,19 @@ export function skipStep(stage, icon, title, reason) {
79
179
  * or a CI failure was fixed can look it up afterwards (`claude --resume <id>`
80
180
  * or, for Copilot, `copilot --resume <id>` — see agent/copilot.ts on why that
81
181
  * id is one we assigned rather than one the CLI reported). Session history is
82
- * scoped to the working directory the CLI ran in (see agent/claude.ts:
83
- * `cwd: opts.cwd` is always the worktree, never the caller's own repo), so
84
- * `--resume` only finds the session when run from that same worktree path —
85
- * printing the path lets the user `cd` there first. This function has no way
86
- * to know which adapter produced `result` (`AgentInvokeResult` doesn't carry
87
- * one), so it names the path rather than assembling a `claude`/`copilot`
88
- * command that would guess wrong half the time. A no-op when the adapter
89
- * didn't return a sessionId, which keeps this safe to call unconditionally.
182
+ * scoped to the working directory the CLI ran in, so `--resume` only finds
183
+ * the session when run from that same worktree path printing the path lets
184
+ * the user `cd` there first. A no-op when the adapter didn't return a
185
+ * sessionId, which keeps this safe to call unconditionally.
186
+ *
187
+ * Also attributes the turn's token spend (when the adapter reported any) to
188
+ * the step that ran it, which is what feeds the per-step and header token
189
+ * figures on the dashboard.
90
190
  */
91
191
  export function noteSession(result, worktreePath) {
192
+ if (result.usage?.totalTokens !== undefined && lastStepId !== undefined) {
193
+ ensureActive().tree.addTokens(lastStepId, result.usage.totalTokens);
194
+ }
92
195
  if (!result.sessionId)
93
196
  return;
94
197
  const duration = result.durationMs !== undefined ? ` — ${(result.durationMs / 1000).toFixed(1)}s` : '';
@@ -100,45 +203,26 @@ export function reportAgentInvocation(result, worktreePath) {
100
203
  note(`agent: ${text.slice(0, 300).trim()}${text.length > 300 ? '…' : ''}`);
101
204
  noteSession(result, worktreePath);
102
205
  }
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();
206
+ /** Outputs a formatted section header with title and emoji. */
207
+ export function sectionHeader(title, emoji) {
208
+ const run = ensureActive();
209
+ for (const line of boxHeader(title, emoji)) {
210
+ run.renderer.log(line);
117
211
  }
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;
212
+ }
213
+ /** Outputs a formatted section with header, bullet points, and spacing. */
214
+ export function outputSection(title, emoji, items) {
215
+ const run = ensureActive();
216
+ for (const line of boxHeader(title, emoji)) {
217
+ run.renderer.log(line);
136
218
  }
137
- catch (error) {
138
- clearInterval(timer);
139
- render('✗', 'red');
140
- process.stdout.write('\n');
141
- throw error;
219
+ for (const line of formatBulletBlock(items)) {
220
+ run.renderer.log(line);
142
221
  }
222
+ run.renderer.log('');
223
+ }
224
+ /** Outputs a section footer (blank line for spacing). */
225
+ export function sectionFooter() {
226
+ ensureActive().renderer.log('');
143
227
  }
144
228
  //# sourceMappingURL=steps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/ui/steps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAItC,MAAM,UAAU,GAAkD,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAElH;;;;;;;;;;;GAWG;AACH,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC1E,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,SAAS,aAAa,CAAC,EAAU;IAC/B,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAa;IACzD,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IACpD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;AACxC,CAAC;AAED,0GAA0G;AAC1G,SAAS,WAAW,CAAC,KAAsB,EAAE,IAAY,EAAE,KAAa;IACtE,OAAO,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,KAAK,IAAI,YAAY,KAAK,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,KAAa,EAAE,MAAe;IAC/D,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,iDAAiD;IAChE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;IACnD,IAAI,MAAM;QAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,QAAQ,CAAC,IAAe,EAAE,MAAc;IACtD,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,IAAI,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3H,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAsB,EAAE,IAAY,EAAE,KAAa,EAAE,MAAc;IAC1F,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,YAAY,KAAK,IAAI,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,MAAyB,EAAE,YAAoB;IACzE,IAAI,CAAC,MAAM,CAAC,SAAS;QAAE,OAAO;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACvG,IAAI,CAAC,kBAAkB,MAAM,CAAC,SAAS,GAAG,QAAQ,SAAS,YAAY,qBAAqB,CAAC,CAAC;AAChG,CAAC;AAED,iJAAiJ;AACjJ,MAAM,UAAU,qBAAqB,CAAC,MAAyB,EAAE,YAAoB;IACnF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3E,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAI,KAAsB,EAAE,IAAY,EAAE,KAAa,EAAE,MAAc,EAAE,IAAsB;IAC1H,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,iDAAiD;IAChE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAE7C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;QAC7C,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,QAAiC,KAAK,EAAQ,EAAE;QAC7E,MAAM,IAAI,GAAG,KAAK,KAAK,IAAI,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC;QAC3E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC;QACpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,SAAS,CAAC,KAAK,EAAE,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC;IACF,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC;QAC5C,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAChC,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAExB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
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
+ }