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.
- 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 +19 -0
- package/dist/ui/format.js +89 -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 +92 -35
- package/dist/ui/steps.js +177 -93
- 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/ui/welcome.js +15 -9
- package/dist/ui/welcome.js.map +1 -1
- 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.d.ts
CHANGED
|
@@ -1,22 +1,84 @@
|
|
|
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
|
*/
|
|
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
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
/**
|
|
19
|
-
|
|
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
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
*
|
|
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';
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
47
|
-
export function
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
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
|
+
}
|