@vzn/vx 0.0.650 → 0.0.652
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 +7 -23
- package/package.json +5 -5
- package/schemas/last.json +15 -0
- package/schemas/show.json +14 -20
- package/src/bin.ts +21 -31
- package/src/cache/archive.ts +1 -1
- package/src/cache/cache.ts +221 -30
- package/src/cache/file-hashes.ts +3 -0
- package/src/cache/git-inputs.ts +188 -39
- package/src/cache/history-stream.ts +134 -0
- package/src/cache/history-writer.ts +76 -0
- package/src/cache/index.ts +3 -0
- package/src/cache/inputs.ts +77 -14
- package/src/cache/layer.ts +17 -6
- package/src/cache/layered-cache.ts +4 -0
- package/src/cache/linked-inputs.ts +3 -0
- package/src/cache/lockfile-memo.ts +5 -3
- package/src/cache/output-index.ts +5 -2
- package/src/cache/remote-lane.ts +2 -1
- package/src/cache/run-history.ts +56 -17
- package/src/cache/schema.ts +15 -2
- package/src/cli/cache.ts +28 -18
- package/src/cli/completions.ts +4 -2
- package/src/cli/docs.ts +10 -11
- package/src/cli/help.ts +14 -25
- package/src/cli/index.ts +28 -9
- package/src/cli/info.ts +16 -8
- package/src/cli/init.ts +23 -15
- package/src/cli/last.ts +246 -204
- package/src/cli/lock.ts +55 -5
- package/src/cli/plan-format.ts +100 -118
- package/src/cli/plugin-commands.ts +8 -2
- package/src/cli/plugin-templates.ts +36 -45
- package/src/cli/run.ts +46 -27
- package/src/cli/select.ts +43 -5
- package/src/cli/show.ts +45 -52
- 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 +3 -2
- package/src/cli/watch-set.ts +4 -4
- package/src/cli/watch.ts +94 -52
- package/src/cli/why.ts +123 -89
- package/src/cli/workspace-config.ts +2 -1
- package/src/config.ts +47 -30
- package/src/exec/executor.ts +42 -2
- package/src/exec/index.ts +1 -0
- 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-paths.ts +3 -1
- package/src/exec/sandbox-runtime.ts +317 -23
- package/src/exec/spawn-off.ts +154 -0
- package/src/exec/task-proxy.ts +93 -0
- package/src/exec/warm-shell.ts +146 -0
- package/src/graph/critical-path.ts +40 -28
- package/src/graph/definition.ts +16 -3
- package/src/graph/priorities.ts +98 -44
- package/src/graph/scheduler.ts +17 -11
- package/src/graph/task-graph.ts +51 -24
- package/src/handoff.ts +10 -7
- package/src/history/failure-mode.ts +24 -1
- package/src/history/history.ts +5 -0
- package/src/history/index.ts +1 -0
- package/src/history/metrics.ts +14 -6
- package/src/index.ts +7 -3
- package/src/orchestrator/admission.ts +1 -1
- package/src/orchestrator/command-windows.ts +1 -1
- package/src/orchestrator/deferred-outputs.ts +1 -1
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +5 -7
- package/src/orchestrator/excluded-keys.ts +1 -1
- package/src/orchestrator/execute-task.ts +67 -20
- package/src/orchestrator/graph-memo.ts +50 -1
- package/src/orchestrator/graph-stage.ts +6 -0
- package/src/orchestrator/hit-restore.ts +5 -6
- package/src/orchestrator/index.ts +3 -0
- package/src/orchestrator/keyed-projects.ts +28 -0
- package/src/orchestrator/live-usage.ts +16 -11
- package/src/orchestrator/local-shortcircuit.ts +2 -2
- package/src/orchestrator/locked-digests.ts +63 -0
- package/src/orchestrator/lockfile-claim.ts +108 -29
- package/src/orchestrator/miss-save.ts +58 -11
- package/src/orchestrator/persistent-task.ts +5 -1
- package/src/orchestrator/placement.ts +1 -1
- package/src/orchestrator/plan.ts +7 -3
- package/src/orchestrator/plugin-host.ts +67 -2
- package/src/orchestrator/plugin.ts +53 -2
- package/src/orchestrator/prepare.ts +16 -1
- package/src/orchestrator/projects.ts +4 -1
- package/src/orchestrator/remote-prefetch.ts +1 -1
- package/src/orchestrator/run-context.ts +29 -10
- package/src/orchestrator/run-failures.ts +62 -13
- package/src/orchestrator/run-keys.ts +1 -1
- package/src/orchestrator/run-records.ts +88 -59
- package/src/orchestrator/run-report.ts +1 -1
- package/src/orchestrator/run.ts +145 -41
- package/src/orchestrator/sandbox-request.ts +52 -9
- package/src/orchestrator/shell-verdict.ts +9 -0
- package/src/orchestrator/signals.ts +8 -5
- package/src/orchestrator/stable-keys.ts +17 -18
- package/src/orchestrator/task-artifacts.ts +1 -1
- package/src/orchestrator/task-hash.ts +21 -7
- package/src/orchestrator/telemetry.ts +5 -1
- package/src/report/events.ts +6 -14
- package/src/report/forecast.ts +10 -32
- package/src/report/framed-output.ts +283 -175
- package/src/report/index.ts +8 -3
- package/src/report/logger.ts +74 -39
- package/src/report/plain-output.ts +3 -1
- package/src/report/status-line.ts +49 -357
- package/src/report/summary.ts +105 -136
- package/src/settings.ts +8 -8
- 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 +191 -14
- package/src/util/edit-distance.ts +6 -1
- package/src/util/index.ts +2 -15
- package/src/util/paths.ts +21 -3
- package/src/util/secret-mask.ts +39 -3
- package/src/util/settle.ts +33 -1
- package/src/util/tail.ts +4 -1
- package/src/util/timing.ts +4 -16
- package/src/workspace/affected.ts +89 -30
- package/src/workspace/bracket-literal.ts +2 -2
- package/src/workspace/config-schema.ts +70 -63
- package/src/workspace/dependencies-lock.ts +191 -0
- package/src/workspace/filter.ts +36 -1
- package/src/workspace/index.ts +9 -0
- package/src/workspace/lockfile.ts +159 -6
- package/src/workspace/migration.ts +16 -9
- package/src/workspace/workspace.ts +3 -0
- package/src/util/colors.ts +0 -133
- 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,334 +1,25 @@
|
|
|
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 {
|
|
6
|
+
formatExpected,
|
|
10
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
|
|
330
|
-
/** How long it waited ready for a slot; absent when it started at once. */
|
|
331
|
-
waitedMs?: number
|
|
332
23
|
/** Its cache cell: `no-cache` for a task that caches nothing, else `miss` (the default). */
|
|
333
24
|
cache?: 'miss' | 'no-cache'
|
|
334
25
|
}
|
|
@@ -344,8 +35,11 @@ export interface StatusRegionState {
|
|
|
344
35
|
critical?: string
|
|
345
36
|
/** Where a running task runs, when not here (`TaskExecutor.running`); its row shows it. */
|
|
346
37
|
where?: (taskId: string) => string | null | undefined
|
|
347
|
-
/**
|
|
348
|
-
|
|
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
|
|
349
43
|
/** A running task's live CPU and memory; its row's `cpu` and `mem`. */
|
|
350
44
|
usage?: (taskId: string) => LiveUsage | undefined
|
|
351
45
|
/**
|
|
@@ -410,10 +104,10 @@ export function formatFailureLine(
|
|
|
410
104
|
cells: RowCells = {},
|
|
411
105
|
): string {
|
|
412
106
|
return formatTaskRow(
|
|
413
|
-
paint(
|
|
107
|
+
paint('error', '\u25fc\uFE0E', colors),
|
|
414
108
|
durationMs,
|
|
415
109
|
'failed',
|
|
416
|
-
|
|
110
|
+
'error',
|
|
417
111
|
cacheWord,
|
|
418
112
|
'',
|
|
419
113
|
paintPinnedId(id, colors),
|
|
@@ -437,10 +131,10 @@ export function formatStepLine(
|
|
|
437
131
|
colors: ColorSupport = NO_COLOR,
|
|
438
132
|
): string {
|
|
439
133
|
return formatTaskRow(
|
|
440
|
-
ok ? paint(
|
|
134
|
+
ok ? paint('ok', '\u23fa\uFE0E', colors) : paint('error', '\u25fc\uFE0E', colors),
|
|
441
135
|
durationMs,
|
|
442
136
|
ok ? 'success' : 'failed',
|
|
443
|
-
ok ?
|
|
137
|
+
ok ? 'ok' : 'error',
|
|
444
138
|
INTERNAL,
|
|
445
139
|
'',
|
|
446
140
|
paintPinnedId(id, colors),
|
|
@@ -449,6 +143,11 @@ export function formatStepLine(
|
|
|
449
143
|
)
|
|
450
144
|
}
|
|
451
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
|
+
|
|
452
151
|
/**
|
|
453
152
|
* Worker region: pinned ready persistent tasks, one row per worker
|
|
454
153
|
* slot — a task stays in its slot for its whole life so names never
|
|
@@ -461,25 +160,26 @@ export function formatStatusRegion(
|
|
|
461
160
|
s: StatusRegionState,
|
|
462
161
|
colors: ColorSupport = NO_COLOR,
|
|
463
162
|
): string[] {
|
|
464
|
-
const dim = (t: string) => paint('', t, colors
|
|
163
|
+
const dim = (t: string) => paint('muted', t, colors)
|
|
465
164
|
// A task's usual duration, the live list's `usual` cell: `~18s`.
|
|
466
165
|
const usualOf = (id: string): { text: string; painted: string } => {
|
|
467
|
-
|
|
468
|
-
const
|
|
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)
|
|
469
170
|
return { text, painted: dim(text) }
|
|
470
171
|
}
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
const lines: string[] = ['']
|
|
172
|
+
// The Running panel's rows; its box and header go round them below.
|
|
173
|
+
const lines: string[] = []
|
|
474
174
|
// Persistent (dev-server) rows: ▸ glyph, no elapsed, `running`, no
|
|
475
175
|
// cache state (it's still alive).
|
|
476
176
|
for (const id of s.pinnedPersistent) {
|
|
477
177
|
lines.push(
|
|
478
178
|
formatTaskRow(
|
|
479
|
-
paint(
|
|
179
|
+
paint('info', '▸', colors),
|
|
480
180
|
null,
|
|
481
181
|
'running',
|
|
482
|
-
|
|
182
|
+
'info',
|
|
483
183
|
'',
|
|
484
184
|
'',
|
|
485
185
|
paintPinnedId(id, colors),
|
|
@@ -493,8 +193,9 @@ export function formatStatusRegion(
|
|
|
493
193
|
// the ticking time IS the motion), then `running`, the usual duration,
|
|
494
194
|
// how many tasks wait on it, and the full id (never truncated — name is
|
|
495
195
|
// the last column). The rows no task runs in hold the tasks planned
|
|
496
|
-
// next, each nested under what it waits on
|
|
497
|
-
//
|
|
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).
|
|
498
199
|
const running = s.slots.filter((x): x is WorkerSlot => x !== null && x.waiting !== true)
|
|
499
200
|
// Placed by an executor (DTE), the list is what runs: blocked tasks are
|
|
500
201
|
// the tasks line's count alone, and its size is no machine's.
|
|
@@ -513,16 +214,14 @@ export function formatStatusRegion(
|
|
|
513
214
|
n++
|
|
514
215
|
}
|
|
515
216
|
}
|
|
516
|
-
let rows = 0
|
|
517
217
|
for (const slot of running) {
|
|
518
|
-
rows++
|
|
519
218
|
if (s.stopping === true) {
|
|
520
219
|
lines.push(
|
|
521
220
|
formatTaskRow(
|
|
522
221
|
' ',
|
|
523
222
|
Math.max(0, s.nowMs - slot.startedMs),
|
|
524
223
|
'aborting',
|
|
525
|
-
|
|
224
|
+
'warn',
|
|
526
225
|
'',
|
|
527
226
|
'',
|
|
528
227
|
paintPinnedId(slot.id, colors),
|
|
@@ -539,14 +238,13 @@ export function formatStatusRegion(
|
|
|
539
238
|
' ',
|
|
540
239
|
Math.max(0, s.nowMs - slot.startedMs),
|
|
541
240
|
'running',
|
|
542
|
-
|
|
241
|
+
'info',
|
|
543
242
|
slot.cache ?? 'miss',
|
|
544
243
|
'',
|
|
545
244
|
paintPinnedId(slot.id, colors),
|
|
546
245
|
colors,
|
|
547
246
|
{
|
|
548
247
|
usual: usualOf(slot.id),
|
|
549
|
-
...(slot.waitedMs !== undefined ? { waitedMs: slot.waitedMs } : {}),
|
|
550
248
|
...(use !== undefined ? { cpu: use.cores, memBytes: use.rssBytes } : {}),
|
|
551
249
|
blocks: String(s.blockedBy?.get(slot.id) ?? 0),
|
|
552
250
|
worker: at ?? 'local',
|
|
@@ -554,7 +252,6 @@ export function formatStatusRegion(
|
|
|
554
252
|
) + (s.critical === slot.id ? pathNote(colors) : ''),
|
|
555
253
|
)
|
|
556
254
|
for (const b of nested.get(slot.id) ?? []) {
|
|
557
|
-
rows++
|
|
558
255
|
lines.push(
|
|
559
256
|
formatTaskRow(
|
|
560
257
|
' ',
|
|
@@ -570,14 +267,13 @@ export function formatStatusRegion(
|
|
|
570
267
|
)
|
|
571
268
|
}
|
|
572
269
|
}
|
|
573
|
-
if (!placed) for (; rows < (s.size ?? 0); rows++) lines.push('')
|
|
574
270
|
for (const step of s.steps ?? []) {
|
|
575
271
|
lines.push(
|
|
576
272
|
formatTaskRow(
|
|
577
273
|
' ',
|
|
578
274
|
Math.max(0, s.nowMs - step.startedMs),
|
|
579
275
|
'running',
|
|
580
|
-
|
|
276
|
+
'info',
|
|
581
277
|
INTERNAL,
|
|
582
278
|
'',
|
|
583
279
|
paintPinnedId(step.id, colors),
|
|
@@ -586,23 +282,19 @@ export function formatStatusRegion(
|
|
|
586
282
|
),
|
|
587
283
|
)
|
|
588
284
|
}
|
|
589
|
-
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
lines.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (ms < 9950) return `~${(ms / 1000).toFixed(1)}s`
|
|
605
|
-
const s = Math.round(ms / 1000)
|
|
606
|
-
if (s < 60) return `~${s}s`
|
|
607
|
-
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
|
|
608
300
|
}
|