@vzn/vx 0.0.649 → 0.0.651
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 +5 -20
- package/package.json +5 -5
- package/schemas/last.json +15 -0
- package/src/bin.ts +9 -31
- package/src/cache/cache.ts +225 -51
- package/src/cache/file-hashes.ts +3 -0
- package/src/cache/fold-memo.ts +8 -1
- package/src/cache/git-inputs.ts +188 -39
- package/src/cache/index.ts +12 -0
- package/src/cache/inputs.ts +3 -0
- package/src/cache/layer.ts +11 -0
- package/src/cache/layered-cache.ts +4 -0
- package/src/cache/linked-inputs.ts +3 -0
- package/src/cache/lockfile-memo.ts +120 -0
- package/src/cache/output-index.ts +5 -2
- package/src/cache/remote-hits.ts +9 -1
- package/src/cache/remote-lane.ts +2 -1
- package/src/cache/run-history.ts +11 -1
- package/src/cache/run-notes.ts +123 -0
- package/src/cache/schema.ts +54 -1
- package/src/cli/cache.ts +26 -16
- package/src/cli/completions.ts +4 -2
- package/src/cli/docs.ts +10 -11
- package/src/cli/help.ts +12 -23
- package/src/cli/index.ts +4 -9
- package/src/cli/info.ts +15 -7
- package/src/cli/init.ts +23 -15
- package/src/cli/last.ts +224 -223
- package/src/cli/lock.ts +55 -5
- package/src/cli/plan-format.ts +100 -118
- package/src/cli/plugin-commands.ts +10 -3
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/run.ts +46 -27
- package/src/cli/select.ts +43 -5
- package/src/cli/show.ts +38 -42
- package/src/cli/task-verb.ts +22 -0
- package/src/cli/upgrade.ts +3 -1
- package/src/cli/watch-fs.ts +6 -1
- package/src/cli/watch-judge.ts +2 -1
- package/src/cli/watch.ts +83 -44
- package/src/cli/why.ts +126 -92
- package/src/cli/workspace-config.ts +2 -1
- package/src/exec/executor.ts +2 -1
- package/src/exec/kill-tree.ts +58 -5
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/pipes.ts +152 -0
- package/src/exec/runner.ts +199 -182
- package/src/exec/sandbox-binds.ts +1 -1
- package/src/exec/sandbox-runtime.ts +11 -2
- package/src/exec/spawn-off.ts +154 -0
- package/src/exec/warm-shell.ts +146 -0
- package/src/graph/critical-path.ts +40 -28
- package/src/graph/priorities.ts +98 -44
- package/src/graph/scheduler.ts +12 -2
- package/src/graph/task-graph.ts +27 -1
- package/src/handoff.ts +10 -7
- package/src/history/history.ts +39 -2
- package/src/history/index.ts +3 -0
- package/src/history/metrics.ts +9 -1
- package/src/index.ts +12 -3
- package/src/orchestrator/doctor.ts +3 -3
- package/src/orchestrator/execute-task.ts +16 -2
- package/src/orchestrator/graph-memo.ts +50 -1
- package/src/orchestrator/graph-stage.ts +6 -0
- package/src/orchestrator/hit-restore.ts +3 -0
- package/src/orchestrator/index.ts +3 -1
- package/src/orchestrator/live-usage.ts +98 -0
- package/src/orchestrator/locked-digests.ts +63 -0
- package/src/orchestrator/lockfile-claim.ts +176 -102
- package/src/orchestrator/miss-save.ts +31 -3
- package/src/orchestrator/persistent-task.ts +3 -1
- package/src/orchestrator/plan.ts +5 -1
- package/src/orchestrator/plugin-host.ts +61 -0
- package/src/orchestrator/plugin.ts +49 -4
- package/src/orchestrator/prepare.ts +18 -2
- package/src/orchestrator/projects.ts +4 -1
- package/src/orchestrator/run-context.ts +29 -10
- package/src/orchestrator/run-failures.ts +35 -114
- package/src/orchestrator/run-records.ts +10 -0
- package/src/orchestrator/run.ts +89 -26
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/signals.ts +8 -5
- package/src/orchestrator/steps.ts +2 -2
- package/src/orchestrator/task-hash.ts +17 -3
- package/src/report/forecast.ts +12 -33
- package/src/report/framed-output.ts +388 -161
- package/src/report/index.ts +8 -2
- package/src/report/logger.ts +100 -36
- package/src/report/status-line.ts +81 -365
- package/src/report/summary.ts +104 -131
- package/src/ui/ansi.ts +48 -0
- package/src/ui/components.ts +506 -0
- package/src/ui/index.ts +79 -0
- package/src/ui/kit.ts +37 -0
- package/src/ui/live-region.ts +363 -0
- package/src/{util/refusal-notice.ts → ui/refusal.ts} +7 -5
- package/src/ui/terminal.ts +51 -0
- package/src/ui/theme.ts +224 -0
- package/src/ui/version-line.ts +28 -0
- package/src/util/config-frame.ts +65 -10
- package/src/util/edit-distance.ts +6 -1
- package/src/util/hidden-prompt.ts +8 -2
- package/src/util/index.ts +2 -15
- package/src/util/paths.ts +20 -2
- package/src/util/secret-mask.ts +39 -3
- package/src/util/secrets.ts +3 -0
- package/src/util/settle.ts +31 -0
- package/src/util/timing.ts +4 -16
- package/src/workspace/affected.ts +89 -30
- package/src/workspace/config-schema.ts +4 -2
- package/src/workspace/dependencies-lock.ts +191 -0
- package/src/workspace/filter.ts +28 -0
- package/src/workspace/index.ts +9 -0
- package/src/workspace/lockfile.ts +159 -6
- package/src/workspace/migration.ts +4 -3
- package/src/workspace/workspace.ts +3 -0
- package/src/orchestrator/run-steps.ts +0 -93
- package/src/util/colors.ts +0 -130
- package/src/util/report.ts +0 -44
- package/src/util/ui.ts +0 -258
- /package/src/{report/path-links.ts → ui/links.ts} +0 -0
- /package/src/{report → ui}/terminal-signals.ts +0 -0
|
@@ -1,332 +1,33 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// cursor-up — no alternate screen.
|
|
4
|
-
// The writer is the serialization point for ALL logger stdout: every
|
|
5
|
-
// ordinary write erases the status display first, writes its content,
|
|
6
|
-
// then redraws, so the display can never interleave with task output.
|
|
1
|
+
// The live region's panels: Warnings, Running and the live Summary,
|
|
2
|
+
// redrawn in place under the log by the ui module's writer.
|
|
7
3
|
|
|
8
|
-
import {
|
|
4
|
+
import { paint, panelTop, type ColorSupport } from '../ui/index.js'
|
|
9
5
|
import {
|
|
10
|
-
|
|
6
|
+
formatExpected,
|
|
7
|
+
formatRunningHeader,
|
|
11
8
|
formatTaskRow,
|
|
12
9
|
paintIdParts,
|
|
13
10
|
pathNote,
|
|
14
|
-
|
|
11
|
+
type RowCells,
|
|
12
|
+
unknownUsual,
|
|
15
13
|
} from './framed-output.js'
|
|
16
14
|
import type { BlockedTask } from './forecast.js'
|
|
17
15
|
|
|
18
16
|
const NO_COLOR: ColorSupport = { enabled: false }
|
|
19
|
-
// Same palette as framed-output.ts so glyphs and stats agree.
|
|
20
|
-
const ACCENT = '#06b6d4'
|
|
21
|
-
const ERROR = '#ef4444'
|
|
22
|
-
const WARN = '#eab308' // yellow-500, as the summary's
|
|
23
|
-
const SUCCESS = '#22c55e'
|
|
24
|
-
|
|
25
|
-
/** Erase the current line and return the cursor to column 0. */
|
|
26
|
-
const CLEAR = '\x1b[2K\r'
|
|
27
|
-
|
|
28
|
-
export interface StatusStream {
|
|
29
|
-
write(chunk: string): unknown
|
|
30
|
-
isTTY?: boolean
|
|
31
|
-
/**
|
|
32
|
-
* Terminal width in columns. Read fresh on every draw — `process.stdout`
|
|
33
|
-
* updates it on SIGWINCH, so a mid-run resize is picked up. Absent (not a
|
|
34
|
-
* TTY) means the region is never drawn anyway.
|
|
35
|
-
*/
|
|
36
|
-
columns?: number
|
|
37
|
-
/** Terminal height in rows, read per draw as `columns` is. */
|
|
38
|
-
rows?: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** A region's lines, or what builds them when it is drawn. */
|
|
42
|
-
export type Region = readonly string[] | (() => readonly string[])
|
|
43
|
-
|
|
44
|
-
export interface OutputWriter {
|
|
45
|
-
/** True only when the stream is a TTY and the run isn't CI. */
|
|
46
|
-
readonly enabled: boolean
|
|
47
|
-
/** Serialized content write: erase status → content → redraw. */
|
|
48
|
-
write(chunk: string): void
|
|
49
|
-
/**
|
|
50
|
-
* The same, to another stream on the same terminal (stderr): the region
|
|
51
|
-
* on this one is erased first and redrawn after.
|
|
52
|
-
*/
|
|
53
|
-
writeTo(target: StatusStream, chunk: string): void
|
|
54
|
-
/**
|
|
55
|
-
* Replace the status line's content. Redraws are throttled to
|
|
56
|
-
* `minRedrawMs` unless `force` (task start/finish) is set.
|
|
57
|
-
*/
|
|
58
|
-
setStatus(line: string, opts?: { force?: boolean }): void
|
|
59
|
-
/**
|
|
60
|
-
* Replace the multi-line status region. Same throttle contract as
|
|
61
|
-
* `setStatus`; the two share one display slot. A function is called
|
|
62
|
-
* when the region is next drawn, once: a set the throttle coalesces
|
|
63
|
-
* builds nothing.
|
|
64
|
-
*/
|
|
65
|
-
setRegion(lines: Region, opts?: { force?: boolean }): void
|
|
66
|
-
/** Hand over what `coalesce` holds and write straight through from now on. */
|
|
67
|
-
settle(): void
|
|
68
|
-
/** Permanently remove the status display; later set* is a no-op. */
|
|
69
|
-
clearStatus(): void
|
|
70
|
-
/**
|
|
71
|
-
* Erase the status display and draw none until the returned release:
|
|
72
|
-
* a prompt on the terminal (a secret's) waits for its answer there.
|
|
73
|
-
*/
|
|
74
|
-
hold(): () => void
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface OutputWriterOptions {
|
|
78
|
-
/** Master switch on top of the TTY check (false in CI). */
|
|
79
|
-
enabled?: boolean
|
|
80
|
-
/** Minimum interval between unforced redraws. */
|
|
81
|
-
minRedrawMs?: number
|
|
82
|
-
/**
|
|
83
|
-
* Floor between FORCED redraws. Task events force a redraw each; a
|
|
84
|
-
* 3,270-task warm run produced 6,540 forced redraws ≈ 6.7 MB of
|
|
85
|
-
* ANSI to the terminal. A forced set inside the floor marks the
|
|
86
|
-
* content dirty and ONE trailing draw lands the final state when
|
|
87
|
-
* the floor expires. 0 disables (tests asserting synchronously).
|
|
88
|
-
*/
|
|
89
|
-
forceFloorMs?: number
|
|
90
|
-
/** Injectable clock for tests. */
|
|
91
|
-
now?: () => number
|
|
92
|
-
/**
|
|
93
|
-
* Off a TTY, hold writes and hand them to the stream once per turn of
|
|
94
|
-
* the event loop until `settle()`. A warm 476-package run wrote each of
|
|
95
|
-
* its 952 task lines as its own syscall (item 753). Off by default: a
|
|
96
|
-
* caller reading its stream right after a write sees it.
|
|
97
|
-
*/
|
|
98
|
-
coalesce?: boolean
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function createOutputWriter(
|
|
102
|
-
stream: StatusStream,
|
|
103
|
-
opts: OutputWriterOptions = {},
|
|
104
|
-
): OutputWriter {
|
|
105
|
-
const enabled = (opts.enabled ?? true) && stream.isTTY === true && !dumbTerminal()
|
|
106
|
-
const minRedrawMs = opts.minRedrawMs ?? 100
|
|
107
|
-
const forceFloorMs = opts.forceFloorMs ?? 30
|
|
108
|
-
const now = opts.now ?? Date.now
|
|
109
|
-
// The coalesced writes not yet handed over; null once settled or when
|
|
110
|
-
// not coalescing.
|
|
111
|
-
let held: string[] | null = !enabled && opts.coalesce === true ? [] : null
|
|
112
|
-
let flushQueued = false
|
|
113
|
-
const flushHeld = (): void => {
|
|
114
|
-
flushQueued = false
|
|
115
|
-
if (held === null || held.length === 0) return
|
|
116
|
-
const text = held.join('')
|
|
117
|
-
held.length = 0
|
|
118
|
-
stream.write(text)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let current: Region | null = null
|
|
122
|
-
let shown = false
|
|
123
|
-
// PHYSICAL rows the shown region occupies, not logical lines — see
|
|
124
|
-
// `regionRows`.
|
|
125
|
-
let shownRows = 0
|
|
126
|
-
let dead = false
|
|
127
|
-
let holds = 0
|
|
128
|
-
let lastDraw = -Infinity
|
|
129
|
-
// Trailing draw scheduled when a forced set lands inside the floor.
|
|
130
|
-
let trailing: ReturnType<typeof setTimeout> | null = null
|
|
131
|
-
// Streamed task output (focused mode) can end mid-line; redrawing
|
|
132
|
-
// the status display would wipe the partial line, so we hold off
|
|
133
|
-
// until a write restores column 0.
|
|
134
|
-
let atLineStart = true
|
|
135
|
-
|
|
136
|
-
const cancelTrailing = (): void => {
|
|
137
|
-
if (trailing !== null) {
|
|
138
|
-
clearTimeout(trailing)
|
|
139
|
-
trailing = null
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* How many PHYSICAL terminal rows `lines` occupies. The terminal wraps
|
|
145
|
-
* any line wider than the viewport, so a region of N logical lines can
|
|
146
|
-
* cover more than N rows — and erasing by the logical count moves the
|
|
147
|
-
* cursor up too few rows, leaving the top of the region on screen. It
|
|
148
|
-
* accumulates, because every redraw is short by the same amount.
|
|
149
|
-
*
|
|
150
|
-
* Both triggers are ordinary: the summary section is a fixed 62 visible
|
|
151
|
-
* columns (narrower terminal ⇒ every bar row wraps), and a task id is
|
|
152
|
-
* deliberately never truncated (long id ⇒ the worker row wraps at any
|
|
153
|
-
* width). Width is read per draw so a mid-run resize is handled. Width
|
|
154
|
-
* must be VISIBLE width — these lines carry ANSI, which occupies no
|
|
155
|
-
* column.
|
|
156
|
-
*
|
|
157
|
-
* Unknown width falls back to the logical count, which is a KNOWN
|
|
158
|
-
* residual rather than a guarantee: a wrapped line then under-erases
|
|
159
|
-
* exactly as it did before this function existed. It is deliberate,
|
|
160
|
-
* because the alternatives are worse — guessing a width over-erases on a
|
|
161
|
-
* wider terminal, and `ESC[J` clears to end of SCREEN, so over-erasing
|
|
162
|
-
* destroys output above the region instead of merely leaving junk below
|
|
163
|
-
* it. Reachable only on a TTY whose winsize ioctl fails, since a non-TTY
|
|
164
|
-
* disables the region outright.
|
|
165
|
-
*/
|
|
166
|
-
const lineRows = (line: string): number => {
|
|
167
|
-
const cols = stream.columns
|
|
168
|
-
if (cols === undefined || cols <= 0) return 1
|
|
169
|
-
return Math.max(1, Math.ceil(Bun.stringWidth(line) / cols))
|
|
170
|
-
}
|
|
171
|
-
const regionRows = (lines: readonly string[]): number => {
|
|
172
|
-
let rows = 0
|
|
173
|
-
for (const line of lines) rows += lineRows(line)
|
|
174
|
-
return rows
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* The leading lines that fit the terminal's height, one row spared.
|
|
179
|
-
* Cursor-up stops at the top of the screen, and a row that scrolled into
|
|
180
|
-
* the scrollback is out of the erase's reach: a region taller than the
|
|
181
|
-
* terminal left its top behind on every redraw, one stacked block per tick
|
|
182
|
-
* (VS Code's short panel, 2026-10-14). The first line always shows.
|
|
183
|
-
*/
|
|
184
|
-
const fitted = (lines: readonly string[]): readonly string[] => {
|
|
185
|
-
const height = stream.rows
|
|
186
|
-
if (height === undefined || height <= 0) return lines
|
|
187
|
-
let rows = 0
|
|
188
|
-
let n = 0
|
|
189
|
-
for (const line of lines) {
|
|
190
|
-
rows += lineRows(line)
|
|
191
|
-
if (n > 0 && rows > height - 1) break
|
|
192
|
-
n++
|
|
193
|
-
}
|
|
194
|
-
return n === lines.length ? lines : lines.slice(0, n)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Erase sequence for whatever is currently shown. A single row keeps
|
|
198
|
-
// the exact legacy bytes (ESC[2K\r); anything taller moves to its
|
|
199
|
-
// top row and clears to end of screen.
|
|
200
|
-
const eraseSeq = (): string => (shownRows > 1 ? `\r\x1b[${shownRows - 1}A\x1b[J` : CLEAR)
|
|
201
|
-
|
|
202
|
-
const draw = (): void => {
|
|
203
|
-
// Whatever was pending is now painted — the trailing draw would
|
|
204
|
-
// only repeat these bytes.
|
|
205
|
-
cancelTrailing()
|
|
206
|
-
const erase = shown ? eraseSeq() : CLEAR
|
|
207
|
-
// Built here, not at the set: a warm run's task events set the
|
|
208
|
-
// region hundreds of times inside one floor, and each build was a
|
|
209
|
-
// forecast pass and a summary render no frame showed (200 hits on a
|
|
210
|
-
// terminal: run graph 62 → 9 ms). Kept built: a write redraws it.
|
|
211
|
-
if (typeof current === 'function') current = current()
|
|
212
|
-
const lines = fitted(current!)
|
|
213
|
-
stream.write(erase + lines.join('\n'))
|
|
214
|
-
shown = true
|
|
215
|
-
shownRows = regionRows(lines)
|
|
216
|
-
lastDraw = now()
|
|
217
|
-
}
|
|
218
|
-
const set = (lines: Region, o: { force?: boolean }): void => {
|
|
219
|
-
if (!enabled || dead) return
|
|
220
|
-
current = lines
|
|
221
|
-
if (!atLineStart || holds > 0) return
|
|
222
|
-
const since = now() - lastDraw
|
|
223
|
-
if (o.force) {
|
|
224
|
-
if (since >= forceFloorMs) {
|
|
225
|
-
draw()
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
// Coalesce the burst: `current` is already the latest state, so
|
|
229
|
-
// one trailing draw at floor expiry lands it. unref — a stray
|
|
230
|
-
// timer must never hold the process open.
|
|
231
|
-
if (trailing === null) {
|
|
232
|
-
trailing = setTimeout(() => {
|
|
233
|
-
trailing = null
|
|
234
|
-
if (dead || current === null || !atLineStart || holds > 0) return
|
|
235
|
-
draw()
|
|
236
|
-
}, forceFloorMs - since)
|
|
237
|
-
trailing.unref?.()
|
|
238
|
-
}
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
if (since >= minRedrawMs) draw()
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
enabled,
|
|
246
|
-
write(chunk) {
|
|
247
|
-
if (!enabled) {
|
|
248
|
-
if (held === null) {
|
|
249
|
-
stream.write(chunk)
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
held.push(chunk)
|
|
253
|
-
if (!flushQueued) {
|
|
254
|
-
flushQueued = true
|
|
255
|
-
setImmediate(flushHeld)
|
|
256
|
-
}
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
if (shown) {
|
|
260
|
-
stream.write(eraseSeq())
|
|
261
|
-
shown = false
|
|
262
|
-
shownRows = 0
|
|
263
|
-
}
|
|
264
|
-
stream.write(chunk)
|
|
265
|
-
if (chunk.length > 0) atLineStart = chunk.endsWith('\n')
|
|
266
|
-
if (!dead && current !== null && atLineStart && holds === 0) draw()
|
|
267
|
-
},
|
|
268
|
-
writeTo(target, chunk) {
|
|
269
|
-
if (target === stream) return this.write(chunk)
|
|
270
|
-
// Held stdout is older than this chunk.
|
|
271
|
-
flushHeld()
|
|
272
|
-
if (!enabled) {
|
|
273
|
-
target.write(chunk)
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
if (shown) {
|
|
277
|
-
stream.write(eraseSeq())
|
|
278
|
-
shown = false
|
|
279
|
-
shownRows = 0
|
|
280
|
-
}
|
|
281
|
-
target.write(chunk)
|
|
282
|
-
if (!dead && current !== null && atLineStart && holds === 0) draw()
|
|
283
|
-
},
|
|
284
|
-
setStatus(line, o = {}) {
|
|
285
|
-
set([line], o)
|
|
286
|
-
},
|
|
287
|
-
setRegion(lines, o = {}) {
|
|
288
|
-
set(lines, o)
|
|
289
|
-
},
|
|
290
|
-
settle() {
|
|
291
|
-
flushHeld()
|
|
292
|
-
held = null
|
|
293
|
-
},
|
|
294
|
-
clearStatus() {
|
|
295
|
-
if (!enabled || dead) return
|
|
296
|
-
dead = true
|
|
297
|
-
current = null
|
|
298
|
-
cancelTrailing()
|
|
299
|
-
if (shown) {
|
|
300
|
-
stream.write(eraseSeq())
|
|
301
|
-
shown = false
|
|
302
|
-
shownRows = 0
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
hold() {
|
|
306
|
-
if (!enabled || dead) return () => undefined
|
|
307
|
-
holds++
|
|
308
|
-
cancelTrailing()
|
|
309
|
-
if (shown) {
|
|
310
|
-
stream.write(eraseSeq())
|
|
311
|
-
shown = false
|
|
312
|
-
shownRows = 0
|
|
313
|
-
}
|
|
314
|
-
let released = false
|
|
315
|
-
return () => {
|
|
316
|
-
if (released) return
|
|
317
|
-
released = true
|
|
318
|
-
holds--
|
|
319
|
-
if (holds === 0 && !dead && current !== null && atLineStart) draw()
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
17
|
|
|
325
18
|
export interface WorkerSlot {
|
|
326
19
|
id: string
|
|
327
20
|
startedMs: number
|
|
328
21
|
/** Its executor holds it for a machine to begin it (`TaskExecutor.running` is `null`). */
|
|
329
22
|
waiting?: boolean
|
|
23
|
+
/** Its cache cell: `no-cache` for a task that caches nothing, else `miss` (the default). */
|
|
24
|
+
cache?: 'miss' | 'no-cache'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What a running task's process tree uses now. */
|
|
28
|
+
export interface LiveUsage {
|
|
29
|
+
cores: number
|
|
30
|
+
rssBytes: number
|
|
330
31
|
}
|
|
331
32
|
|
|
332
33
|
export interface StatusRegionState {
|
|
@@ -334,8 +35,13 @@ export interface StatusRegionState {
|
|
|
334
35
|
critical?: string
|
|
335
36
|
/** Where a running task runs, when not here (`TaskExecutor.running`); its row shows it. */
|
|
336
37
|
where?: (taskId: string) => string | null | undefined
|
|
337
|
-
/**
|
|
338
|
-
|
|
38
|
+
/**
|
|
39
|
+
* A task's usual duration from history, undefined for one it never
|
|
40
|
+
* timed; its row shows it, `unknown` for none. Absent: no history to ask.
|
|
41
|
+
*/
|
|
42
|
+
usual?: (taskId: string) => number | undefined
|
|
43
|
+
/** A running task's live CPU and memory; its row's `cpu` and `mem`. */
|
|
44
|
+
usage?: (taskId: string) => LiveUsage | undefined
|
|
339
45
|
/**
|
|
340
46
|
* Ready persistent tasks (dev servers …) whose children keep
|
|
341
47
|
* running after their outcome lands. Pinned until runEnd — the
|
|
@@ -395,16 +101,18 @@ export function formatFailureLine(
|
|
|
395
101
|
durationMs: number | null,
|
|
396
102
|
colors: ColorSupport = NO_COLOR,
|
|
397
103
|
cacheWord: 'miss' | 'no-cache' = 'miss',
|
|
104
|
+
cells: RowCells = {},
|
|
398
105
|
): string {
|
|
399
106
|
return formatTaskRow(
|
|
400
|
-
paint(
|
|
107
|
+
paint('error', '\u25fc\uFE0E', colors),
|
|
401
108
|
durationMs,
|
|
402
109
|
'failed',
|
|
403
|
-
|
|
110
|
+
'error',
|
|
404
111
|
cacheWord,
|
|
405
112
|
'',
|
|
406
113
|
paintPinnedId(id, colors),
|
|
407
114
|
colors,
|
|
115
|
+
cells,
|
|
408
116
|
)
|
|
409
117
|
}
|
|
410
118
|
|
|
@@ -423,17 +131,23 @@ export function formatStepLine(
|
|
|
423
131
|
colors: ColorSupport = NO_COLOR,
|
|
424
132
|
): string {
|
|
425
133
|
return formatTaskRow(
|
|
426
|
-
ok ? paint(
|
|
134
|
+
ok ? paint('ok', '\u23fa\uFE0E', colors) : paint('error', '\u25fc\uFE0E', colors),
|
|
427
135
|
durationMs,
|
|
428
136
|
ok ? 'success' : 'failed',
|
|
429
|
-
ok ?
|
|
137
|
+
ok ? 'ok' : 'error',
|
|
430
138
|
INTERNAL,
|
|
431
139
|
'',
|
|
432
140
|
paintPinnedId(id, colors),
|
|
433
141
|
colors,
|
|
142
|
+
{ worker: 'local' },
|
|
434
143
|
)
|
|
435
144
|
}
|
|
436
145
|
|
|
146
|
+
/** `rows` in a panel titled `title`, counted. */
|
|
147
|
+
function panel(title: string, count: string, rows: readonly string[], c: ColorSupport): string[] {
|
|
148
|
+
return [panelTop(title, c, count), ...rows]
|
|
149
|
+
}
|
|
150
|
+
|
|
437
151
|
/**
|
|
438
152
|
* Worker region: pinned ready persistent tasks, one row per worker
|
|
439
153
|
* slot — a task stays in its slot for its whole life so names never
|
|
@@ -446,25 +160,31 @@ export function formatStatusRegion(
|
|
|
446
160
|
s: StatusRegionState,
|
|
447
161
|
colors: ColorSupport = NO_COLOR,
|
|
448
162
|
): string[] {
|
|
449
|
-
const dim = (t: string) => paint('', t, colors
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
163
|
+
const dim = (t: string) => paint('muted', t, colors)
|
|
164
|
+
// A task's usual duration, the live list's `usual` cell: `~18s`.
|
|
165
|
+
const usualOf = (id: string): { text: string; painted: string } => {
|
|
166
|
+
if (s.usual === undefined) return { text: '', painted: '' }
|
|
167
|
+
const ms = s.usual(id)
|
|
168
|
+
if (ms === undefined) return unknownUsual(colors)
|
|
169
|
+
const text = formatExpected(ms)
|
|
170
|
+
return { text, painted: dim(text) }
|
|
171
|
+
}
|
|
172
|
+
// The Running panel's rows; its box and header go round them below.
|
|
173
|
+
const lines: string[] = []
|
|
453
174
|
// Persistent (dev-server) rows: ▸ glyph, no elapsed, `running`, no
|
|
454
175
|
// cache state (it's still alive).
|
|
455
176
|
for (const id of s.pinnedPersistent) {
|
|
456
177
|
lines.push(
|
|
457
178
|
formatTaskRow(
|
|
458
|
-
paint(
|
|
179
|
+
paint('info', '▸', colors),
|
|
459
180
|
null,
|
|
460
181
|
'running',
|
|
461
|
-
|
|
182
|
+
'info',
|
|
462
183
|
'',
|
|
463
184
|
'',
|
|
464
185
|
paintPinnedId(id, colors),
|
|
465
186
|
colors,
|
|
466
|
-
|
|
467
|
-
'',
|
|
187
|
+
{ worker: 'local' },
|
|
468
188
|
),
|
|
469
189
|
)
|
|
470
190
|
}
|
|
@@ -473,8 +193,9 @@ export function formatStatusRegion(
|
|
|
473
193
|
// the ticking time IS the motion), then `running`, the usual duration,
|
|
474
194
|
// how many tasks wait on it, and the full id (never truncated — name is
|
|
475
195
|
// the last column). The rows no task runs in hold the tasks planned
|
|
476
|
-
// next, each nested under what it waits on
|
|
477
|
-
//
|
|
196
|
+
// next, each nested under what it waits on. No blank rows pad the list
|
|
197
|
+
// to the worker count: a run using 2 of 12 workers left 10 empty lines
|
|
198
|
+
// between its rows and the footer (2026-10-29).
|
|
478
199
|
const running = s.slots.filter((x): x is WorkerSlot => x !== null && x.waiting !== true)
|
|
479
200
|
// Placed by an executor (DTE), the list is what runs: blocked tasks are
|
|
480
201
|
// the tasks line's count alone, and its size is no machine's.
|
|
@@ -493,44 +214,44 @@ export function formatStatusRegion(
|
|
|
493
214
|
n++
|
|
494
215
|
}
|
|
495
216
|
}
|
|
496
|
-
let rows = 0
|
|
497
217
|
for (const slot of running) {
|
|
498
|
-
rows++
|
|
499
218
|
if (s.stopping === true) {
|
|
500
219
|
lines.push(
|
|
501
220
|
formatTaskRow(
|
|
502
221
|
' ',
|
|
503
222
|
Math.max(0, s.nowMs - slot.startedMs),
|
|
504
223
|
'aborting',
|
|
505
|
-
|
|
224
|
+
'warn',
|
|
506
225
|
'',
|
|
507
226
|
'',
|
|
508
227
|
paintPinnedId(slot.id, colors),
|
|
509
228
|
colors,
|
|
510
|
-
'',
|
|
229
|
+
{ worker: s.where?.(slot.id) ?? 'local' },
|
|
511
230
|
),
|
|
512
231
|
)
|
|
513
232
|
continue
|
|
514
233
|
}
|
|
515
234
|
const at = s.where?.(slot.id)
|
|
516
|
-
const
|
|
235
|
+
const use = s.usage?.(slot.id)
|
|
517
236
|
lines.push(
|
|
518
237
|
formatTaskRow(
|
|
519
238
|
' ',
|
|
520
239
|
Math.max(0, s.nowMs - slot.startedMs),
|
|
521
240
|
'running',
|
|
522
|
-
|
|
523
|
-
|
|
241
|
+
'info',
|
|
242
|
+
slot.cache ?? 'miss',
|
|
524
243
|
'',
|
|
525
244
|
paintPinnedId(slot.id, colors),
|
|
526
245
|
colors,
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
246
|
+
{
|
|
247
|
+
usual: usualOf(slot.id),
|
|
248
|
+
...(use !== undefined ? { cpu: use.cores, memBytes: use.rssBytes } : {}),
|
|
249
|
+
blocks: String(s.blockedBy?.get(slot.id) ?? 0),
|
|
250
|
+
worker: at ?? 'local',
|
|
251
|
+
},
|
|
252
|
+
) + (s.critical === slot.id ? pathNote(colors) : ''),
|
|
531
253
|
)
|
|
532
254
|
for (const b of nested.get(slot.id) ?? []) {
|
|
533
|
-
rows++
|
|
534
255
|
lines.push(
|
|
535
256
|
formatTaskRow(
|
|
536
257
|
' ',
|
|
@@ -541,44 +262,39 @@ export function formatStatusRegion(
|
|
|
541
262
|
'',
|
|
542
263
|
`${dim('\u2514')} ${paintPinnedId(b.id, colors)}`,
|
|
543
264
|
colors,
|
|
544
|
-
|
|
265
|
+
{ usual: usualOf(b.id) },
|
|
545
266
|
),
|
|
546
267
|
)
|
|
547
268
|
}
|
|
548
269
|
}
|
|
549
|
-
if (!placed) for (; rows < (s.size ?? 0); rows++) lines.push('')
|
|
550
270
|
for (const step of s.steps ?? []) {
|
|
551
271
|
lines.push(
|
|
552
272
|
formatTaskRow(
|
|
553
273
|
' ',
|
|
554
274
|
Math.max(0, s.nowMs - step.startedMs),
|
|
555
275
|
'running',
|
|
556
|
-
|
|
276
|
+
'info',
|
|
557
277
|
INTERNAL,
|
|
558
278
|
'',
|
|
559
279
|
paintPinnedId(step.id, colors),
|
|
560
280
|
colors,
|
|
561
|
-
'',
|
|
281
|
+
{ worker: 'local' },
|
|
562
282
|
),
|
|
563
283
|
)
|
|
564
284
|
}
|
|
565
|
-
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
lines.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (ms < 9950) return `~${(ms / 1000).toFixed(1)}s`
|
|
581
|
-
const s = Math.round(ms / 1000)
|
|
582
|
-
if (s < 60) return `~${s}s`
|
|
583
|
-
return `~${Math.floor(s / 60)}m ${String(s % 60).padStart(2, '0')}s`
|
|
285
|
+
// Panels top to bottom: what runs now, then the live Summary, the same panel the final printout is, so the region
|
|
286
|
+
// BECOMES the summary when the run ends.
|
|
287
|
+
const out: string[] = []
|
|
288
|
+
if (lines.length > 0)
|
|
289
|
+
out.push(
|
|
290
|
+
'',
|
|
291
|
+
...panel(
|
|
292
|
+
'Running',
|
|
293
|
+
String(s.pinnedPersistent.length + running.length + (s.steps?.length ?? 0)),
|
|
294
|
+
[formatRunningHeader(colors), ...lines],
|
|
295
|
+
colors,
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
out.push(...s.summaryLines)
|
|
299
|
+
return out
|
|
584
300
|
}
|