parallel-codex-tui 0.1.0

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 (40) hide show
  1. package/.parallel-codex/config.example.toml +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +150 -0
  4. package/dist/bootstrap.js +25 -0
  5. package/dist/cli-args.js +26 -0
  6. package/dist/cli.js +48 -0
  7. package/dist/core/config.js +304 -0
  8. package/dist/core/file-store.js +56 -0
  9. package/dist/core/paths.js +17 -0
  10. package/dist/core/router.js +151 -0
  11. package/dist/core/session-index.js +180 -0
  12. package/dist/core/session-manager.js +264 -0
  13. package/dist/doctor.js +121 -0
  14. package/dist/domain/schemas.js +78 -0
  15. package/dist/orchestrator/collaboration-channel.js +103 -0
  16. package/dist/orchestrator/orchestrator.js +545 -0
  17. package/dist/orchestrator/prompts.js +124 -0
  18. package/dist/orchestrator/supervisor-summary.js +38 -0
  19. package/dist/tui/App.js +740 -0
  20. package/dist/tui/AppShell.js +129 -0
  21. package/dist/tui/InputBar.js +141 -0
  22. package/dist/tui/StatusBar.js +288 -0
  23. package/dist/tui/TerminalOutput.js +22 -0
  24. package/dist/tui/WorkerOutputView.js +4015 -0
  25. package/dist/tui/chat-input.js +37 -0
  26. package/dist/tui/display-width.js +132 -0
  27. package/dist/tui/keyboard.js +38 -0
  28. package/dist/tui/native-input.js +120 -0
  29. package/dist/tui/raw-input-decoder.js +18 -0
  30. package/dist/tui/scrolling.js +25 -0
  31. package/dist/tui/status-line.js +90 -0
  32. package/dist/tui/task-memory.js +26 -0
  33. package/dist/tui/terminal-screen.js +168 -0
  34. package/dist/version.js +1 -0
  35. package/dist/workers/mock-adapter.js +62 -0
  36. package/dist/workers/native-attach.js +189 -0
  37. package/dist/workers/process-adapter.js +265 -0
  38. package/dist/workers/registry.js +32 -0
  39. package/dist/workers/types.js +1 -0
  40. package/package.json +53 -0
@@ -0,0 +1,4015 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { readdir } from "node:fs/promises";
5
+ import { Box, Text } from "ink";
6
+ import { pathExists, readTextIfExists } from "../core/file-store.js";
7
+ import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
8
+ import { selectViewportLines } from "./scrolling.js";
9
+ const WORKER_PANEL_BACKGROUND = "ansi256(235)";
10
+ const WORKER_PANEL_TITLE_BACKGROUND = "ansi256(24)";
11
+ export function WorkerOutputView({ title, role, logPath, scrollOffset = 0, height = 24, terminalWidth = Number(process.stdout.columns) || 120, onViewportChange }) {
12
+ const nanoOutput = isNanoWorkerOutputWidth(terminalWidth);
13
+ const contentKey = workerOutputContentKey(role, logPath, nanoOutput);
14
+ const [contentState, setContentState] = useState({ key: "", lines: [] });
15
+ useEffect(() => {
16
+ let active = true;
17
+ const loadKey = contentKey;
18
+ async function load() {
19
+ if (!logPath) {
20
+ setContentState({
21
+ key: loadKey,
22
+ lines: [{ kind: "content", text: "No worker log selected." }]
23
+ });
24
+ return;
25
+ }
26
+ if (nanoOutput) {
27
+ const lines = await loadNanoWorkerOutputLines(logPath, height);
28
+ if (active) {
29
+ setContentState({
30
+ key: loadKey,
31
+ lines
32
+ });
33
+ }
34
+ return;
35
+ }
36
+ const sections = await loadWorkerOutputSections(role, logPath);
37
+ if (active) {
38
+ setContentState({
39
+ key: loadKey,
40
+ lines: renderLinesFromSections(sections, { nanoProcess: nanoOutput, height })
41
+ });
42
+ }
43
+ }
44
+ void load();
45
+ const interval = setInterval(() => {
46
+ void load();
47
+ }, 1000);
48
+ return () => {
49
+ active = false;
50
+ clearInterval(interval);
51
+ };
52
+ }, [contentKey, height, logPath, nanoOutput, role]);
53
+ const content = contentState.key === contentKey
54
+ ? contentState.lines
55
+ : [{ kind: "content", text: "Loading worker log..." }];
56
+ const sourceLines = isNanoWorkerOutputWidth(terminalWidth) ? tinyWorkerOutputSourceLines(content, height) : content;
57
+ const displayLines = renderDisplayLines(sourceLines, terminalWidth);
58
+ const selection = selectViewportLines(displayLines.map((line) => line.text).join("\n"), height, scrollOffset);
59
+ const end = displayLines.length - selection.clampedOffset;
60
+ const rawStart = Math.max(0, end - Math.max(1, height));
61
+ const start = workerOutputVisibleStart(displayLines, rawStart, end, {
62
+ preferGroup: selection.clampedOffset === 0 && selection.maxOffset > 0 ? "process" : null,
63
+ preferLatestSection: selection.clampedOffset === 0 && selection.maxOffset > 0
64
+ });
65
+ const visibleLines = displayLines.slice(start, end);
66
+ const topPaddingLines = start > rawStart
67
+ ? 0
68
+ : workerOutputTailTopPaddingLines(selection.clampedOffset, selection.maxOffset, visibleLines.length, height);
69
+ useEffect(() => {
70
+ onViewportChange?.({
71
+ offset: selection.clampedOffset,
72
+ maxOffset: selection.maxOffset
73
+ });
74
+ }, [onViewportChange, selection.clampedOffset, selection.maxOffset]);
75
+ const panelWidth = workerOutputPanelRailWidth(terminalWidth);
76
+ const scrollLabel = workerOutputScrollDisplay(selection.clampedOffset, selection.maxOffset, terminalWidth);
77
+ const displayTitle = workerOutputHeaderDisplay(title, selection.maxOffset > 0 ? scrollLabel : null, Math.max(1, panelWidth - 2));
78
+ const nanoRender = isNanoWorkerOutputWidth(terminalWidth);
79
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(WorkerOutputTitleRail, { title: displayTitle, width: panelWidth }), Array.from({ length: topPaddingLines }, (_, index) => _jsx(Text, { children: " " }, `tail-pad-${index}`)), visibleLines.length > 0
80
+ ? visibleLines.map((line, index) => nanoRender
81
+ ? _jsx(WorkerOutputNanoLine, { line: line, width: terminalWidth }, index)
82
+ : _jsx(WorkerOutputLine, { line: line }, index))
83
+ : _jsx(Text, { children: "(empty log)" })] }));
84
+ }
85
+ function WorkerOutputTitleRail({ title, width }) {
86
+ const titleText = ` ${title} `;
87
+ const renderWidth = typeof process.stdout.columns === "number"
88
+ ? width
89
+ : null;
90
+ const trailingWidth = renderWidth === null
91
+ ? 0
92
+ : Math.max(0, renderWidth - displayWidth(titleText));
93
+ return (_jsxs(Box, { children: [_jsx(Text, { backgroundColor: WORKER_PANEL_TITLE_BACKGROUND, color: "white", bold: true, children: titleText }), trailingWidth > 0 ? _jsx(Text, { backgroundColor: WORKER_PANEL_BACKGROUND, children: " ".repeat(trailingWidth) }) : null] }));
94
+ }
95
+ function workerOutputPanelRailWidth(terminalWidth) {
96
+ const renderWidth = typeof process.stdout.columns === "number"
97
+ ? Math.max(1, Math.min(terminalWidth, process.stdout.columns))
98
+ : terminalWidth;
99
+ return Math.max(1, renderWidth - 4);
100
+ }
101
+ function tinyWorkerOutputSourceLines(lines, height) {
102
+ const maxSourceLines = Math.max(8, height * 2);
103
+ if (lines.length <= maxSourceLines) {
104
+ return lines;
105
+ }
106
+ const processStart = lastIndexWhere(lines, (line) => line.kind === "group" && line.text === "process");
107
+ const start = processStart >= 0 ? processStart : Math.max(0, lines.length - maxSourceLines);
108
+ const tail = lines.slice(start).filter((line) => line.kind !== "blank" && line.kind !== "code" && line.kind !== "source-line");
109
+ if (tail.length <= maxSourceLines) {
110
+ return tail.length > 0 ? tail : lines.slice(-maxSourceLines);
111
+ }
112
+ const first = tail[0];
113
+ const keepFirst = first && (first.kind === "group" || first.kind === "section" || first.kind === "heading");
114
+ const remainingBudget = keepFirst ? maxSourceLines - 1 : maxSourceLines;
115
+ return [
116
+ ...(keepFirst ? [first] : []),
117
+ ...tail.slice(-Math.max(1, remainingBudget))
118
+ ];
119
+ }
120
+ function isNanoWorkerOutputWidth(width) {
121
+ return width <= 12;
122
+ }
123
+ function lastIndexWhere(values, predicate) {
124
+ for (let index = values.length - 1; index >= 0; index -= 1) {
125
+ if (predicate(values[index])) {
126
+ return index;
127
+ }
128
+ }
129
+ return -1;
130
+ }
131
+ function workerOutputContentKey(role, logPath, nanoOutput = false) {
132
+ return `${role ?? ""}:${logPath ?? ""}:${nanoOutput ? "nano" : "full"}`;
133
+ }
134
+ export function workerOutputTitleDisplay(title, width) {
135
+ const match = title.match(/^(.+?)\s+\(([^)]+)\)\s+output(?:\s+\((\d+\/\d+)\))?$/i);
136
+ if (!match) {
137
+ return title.replace(/\s+output\b/i, "");
138
+ }
139
+ const role = (match[1] ?? "").trim().toLowerCase();
140
+ const provider = (match[2] ?? "").trim().toLowerCase();
141
+ const page = match[3] ?? "";
142
+ const compact = joinWorkerOutputChromeParts([`${role}/${provider}`, page]);
143
+ if (displayWidth(compact) <= width) {
144
+ return compact;
145
+ }
146
+ const roleOnly = joinWorkerOutputChromeParts([role, page]);
147
+ if (displayWidth(roleOnly) <= width) {
148
+ return roleOnly;
149
+ }
150
+ if (page) {
151
+ const tinyTitle = tinyWorkerOutputPageTitle(role, page, width);
152
+ if (tinyTitle && displayWidth(roleOnly) > width) {
153
+ return tinyTitle;
154
+ }
155
+ const roleBudget = Math.max(1, width - displayWidth(page) - 1);
156
+ if (roleBudget < displayWidth(role)) {
157
+ if (tinyTitle) {
158
+ return tinyTitle;
159
+ }
160
+ }
161
+ return [compactEndByDisplayWidth(role, roleBudget), page].filter(Boolean).join(" ");
162
+ }
163
+ return compactEndByDisplayWidth(`${role}/${provider}`, width);
164
+ }
165
+ export function workerOutputHeaderDisplay(title, scrollLabel, width) {
166
+ const label = scrollLabel?.trim() ?? "";
167
+ if (!label) {
168
+ return workerOutputTitleDisplay(title, width);
169
+ }
170
+ const titleOnly = workerOutputTitleDisplay(title, width);
171
+ const separator = " · ";
172
+ const titleBudget = Math.max(1, width - displayWidth(separator) - displayWidth(label));
173
+ const display = `${workerOutputTitleDisplay(title, titleBudget)}${separator}${label}`;
174
+ if (displayWidth(display) <= width) {
175
+ if (shouldPreferWorkerTitleOnlyOverTail(titleOnly, display, label, width)) {
176
+ return titleOnly;
177
+ }
178
+ if (label === "tail" && displayWidth(titleOnly) <= width && /\b\d+\/\d+\b/.test(titleOnly) && !/\b\d+\/\d+\b/.test(display)) {
179
+ return titleOnly;
180
+ }
181
+ return display;
182
+ }
183
+ const tightSeparator = " ";
184
+ const tightTitleBudget = Math.max(1, width - displayWidth(tightSeparator) - displayWidth(label));
185
+ const tightDisplay = `${workerOutputTitleDisplay(title, tightTitleBudget)}${tightSeparator}${label}`;
186
+ if (displayWidth(tightDisplay) <= width) {
187
+ if (shouldPreferWorkerTitleOnlyOverTail(titleOnly, tightDisplay, label, width)) {
188
+ return titleOnly;
189
+ }
190
+ if (displayWidth(titleOnly) <= width && /\b\d+\/\d+\b/.test(titleOnly) && !/\b\d+\/\d+\b/.test(tightDisplay)) {
191
+ return titleOnly;
192
+ }
193
+ return tightDisplay;
194
+ }
195
+ return displayWidth(titleOnly) <= width ? titleOnly : compactEndByDisplayWidth(tightDisplay, width);
196
+ }
197
+ function shouldPreferWorkerTitleOnlyOverTail(titleOnly, display, label, width) {
198
+ if (label !== "tail" || displayWidth(titleOnly) > width) {
199
+ return false;
200
+ }
201
+ const page = titleOnly.match(/\b\d+\/\d+\b/)?.[0];
202
+ return Boolean(page && !display.includes(`· ${page}`));
203
+ }
204
+ export function workerOutputScrollDisplay(offset, maxOffset, width) {
205
+ if (maxOffset <= 0 || offset <= 0) {
206
+ return "tail";
207
+ }
208
+ if (offset >= maxOffset) {
209
+ return "top";
210
+ }
211
+ if (width < 32) {
212
+ return `${offset}/${maxOffset}`;
213
+ }
214
+ return `back ${offset}/${maxOffset}`;
215
+ }
216
+ const WORKER_TAIL_GROUP_ALIGNMENT_MAX_LOST_ROWS = 3;
217
+ const WORKER_TAIL_SECTION_ALIGNMENT_MAX_LOST_ROWS = 8;
218
+ const WORKER_TAIL_NEXT_SECTION_ALIGNMENT_MAX_LOST_ROWS = 12;
219
+ const WORKER_TAIL_ORPHAN_CONTINUATION_MAX_LOST_ROWS = 3;
220
+ export function workerOutputTailTopPaddingLines(scrollOffset, maxOffset, visibleLineCount, bodyHeight) {
221
+ if (scrollOffset !== 0 || maxOffset <= 0 || bodyHeight < 8) {
222
+ return 0;
223
+ }
224
+ if (visibleLineCount > Math.floor(Math.max(1, bodyHeight) / 2)) {
225
+ return 0;
226
+ }
227
+ return Math.max(0, Math.max(1, bodyHeight) - visibleLineCount);
228
+ }
229
+ function joinWorkerOutputChromeParts(parts) {
230
+ return parts.filter(Boolean).join(" · ");
231
+ }
232
+ function tinyWorkerOutputPageTitle(role, page, width) {
233
+ const roleInitial = role.trim().slice(0, 1);
234
+ const candidates = [
235
+ roleInitial ? `${roleInitial} ${page}` : "",
236
+ page,
237
+ roleInitial
238
+ ].filter(Boolean);
239
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
240
+ }
241
+ export function workerOutputVisibleStart(lines, start, end, options = {}) {
242
+ let visibleStart = start;
243
+ while (visibleStart < end - 1 && lines[visibleStart]?.kind === "blank") {
244
+ visibleStart += 1;
245
+ }
246
+ visibleStart = skipOrphanedTailContinuations(lines, visibleStart, end);
247
+ if (options.preferLatestSection) {
248
+ const latestSectionStart = latestSectionBoundaryWithinTail(lines, visibleStart, end);
249
+ if (latestSectionStart !== null) {
250
+ return latestSectionStart;
251
+ }
252
+ }
253
+ if (isMeaningfulTailStartLine(lines[visibleStart])) {
254
+ return visibleStart;
255
+ }
256
+ if (options.preferGroup) {
257
+ for (let index = end - 1; index > visibleStart; index -= 1) {
258
+ const line = lines[index];
259
+ if (line?.kind === "group" && line.text === options.preferGroup) {
260
+ const preferredStart = preferredBoundaryBeforeGroup(lines, visibleStart, index) ?? { index, kind: "group" };
261
+ const maxLostRows = preferredStart.kind === "section" || preferredStart.kind === "group"
262
+ ? WORKER_TAIL_SECTION_ALIGNMENT_MAX_LOST_ROWS
263
+ : WORKER_TAIL_GROUP_ALIGNMENT_MAX_LOST_ROWS;
264
+ const chosenStart = preferredStart.index - visibleStart > maxLostRows
265
+ ? visibleStart
266
+ : preferredStart.index;
267
+ if (isSparseTailStart(chosenStart, start, end)) {
268
+ return compactContextBeforeGroup(lines, visibleStart, index) ?? chosenStart;
269
+ }
270
+ return chosenStart;
271
+ }
272
+ }
273
+ }
274
+ return nextSectionBoundaryWithinTail(lines, visibleStart, end) ?? visibleStart;
275
+ }
276
+ function isSparseTailStart(chosenStart, start, end) {
277
+ const viewportHeight = Math.max(1, end - start);
278
+ return end - chosenStart < Math.max(5, Math.ceil(viewportHeight * 0.65));
279
+ }
280
+ function compactContextBeforeGroup(lines, visibleStart, groupIndex) {
281
+ const minIndex = Math.max(visibleStart, groupIndex - 10, latestStructuralBoundaryBefore(lines, visibleStart, groupIndex));
282
+ for (let index = minIndex; index < groupIndex; index += 1) {
283
+ const line = lines[index];
284
+ if (!line || line.kind === "blank" || line.continuation) {
285
+ continue;
286
+ }
287
+ if (line.kind === "content" && isCompactTailContextLine(line.text?.trim() ?? "")) {
288
+ return index;
289
+ }
290
+ }
291
+ return null;
292
+ }
293
+ function isCompactTailContextLine(text) {
294
+ return /^(?:Verification|Verify|Findings|Blocking|Critic review):\s*/i.test(text) || /^tests\s+\d+\/\d+\b/i.test(text);
295
+ }
296
+ function latestStructuralBoundaryBefore(lines, visibleStart, groupIndex) {
297
+ let boundary = visibleStart;
298
+ for (let index = visibleStart; index < groupIndex; index += 1) {
299
+ const kind = lines[index]?.kind;
300
+ if (kind === "section" || kind === "group") {
301
+ boundary = index;
302
+ }
303
+ }
304
+ return boundary;
305
+ }
306
+ function isMeaningfulTailStartLine(line) {
307
+ if (!line) {
308
+ return false;
309
+ }
310
+ if (line.kind === "group" || line.kind === "section" || line.kind === "heading") {
311
+ return true;
312
+ }
313
+ if (line.kind === "list") {
314
+ return true;
315
+ }
316
+ if (line.kind === "summary" || line.kind === "success" || line.kind === "command" || line.kind === "error") {
317
+ return true;
318
+ }
319
+ return /^APPROVED\b/i.test(line.text?.trim() ?? "");
320
+ }
321
+ function skipOrphanedTailContinuations(lines, start, end) {
322
+ let index = start;
323
+ while (index < end - 1 &&
324
+ index - start < WORKER_TAIL_ORPHAN_CONTINUATION_MAX_LOST_ROWS &&
325
+ isOrphanedTailContinuationLine(lines[index])) {
326
+ index += 1;
327
+ }
328
+ return index;
329
+ }
330
+ function isOrphanedTailContinuationLine(line) {
331
+ return Boolean(line?.continuation) || line?.kind === "list-detail";
332
+ }
333
+ function preferredBoundaryBeforeGroup(lines, start, groupIndex) {
334
+ for (let index = start; index < groupIndex; index += 1) {
335
+ const line = lines[index];
336
+ if (!line || line.kind === "blank") {
337
+ continue;
338
+ }
339
+ if (line.kind === "group" || line.kind === "section" || line.kind === "heading") {
340
+ return { index, kind: line.kind };
341
+ }
342
+ }
343
+ return null;
344
+ }
345
+ function nextSectionBoundaryWithinTail(lines, start, end) {
346
+ const maxIndex = Math.min(end, start + WORKER_TAIL_NEXT_SECTION_ALIGNMENT_MAX_LOST_ROWS + 1);
347
+ for (let index = start + 1; index < maxIndex; index += 1) {
348
+ const line = lines[index];
349
+ if (line?.kind === "section" || line?.kind === "group") {
350
+ return index;
351
+ }
352
+ }
353
+ return null;
354
+ }
355
+ function latestSectionBoundaryWithinTail(lines, start, end) {
356
+ let latestSectionStart = null;
357
+ for (let index = start + 1; index < end; index += 1) {
358
+ if (lines[index]?.kind === "section") {
359
+ latestSectionStart = index;
360
+ }
361
+ }
362
+ return latestSectionStart !== null && end - latestSectionStart >= 3 ? latestSectionStart : null;
363
+ }
364
+ async function loadWorkerOutputSections(role, logPath) {
365
+ const workerDir = dirname(logPath);
366
+ const sections = [];
367
+ const artifactFiles = roleArtifactFiles(role);
368
+ for (const file of artifactFiles) {
369
+ const text = (await readTextIfExists(join(workerDir, file))).trim();
370
+ if (!text) {
371
+ continue;
372
+ }
373
+ sections.push({ group: "role", title: file, text });
374
+ }
375
+ for (const artifact of await featureArtifactSections(role, workerDir)) {
376
+ const text = artifact.text.trim();
377
+ if (!text) {
378
+ continue;
379
+ }
380
+ sections.push({ group: "feature", title: artifact.label, text });
381
+ }
382
+ const rawOutput = (await readTextIfExists(logPath)).trimEnd();
383
+ if (rawOutput) {
384
+ sections.push({ group: "process", title: "output.log", text: rawOutput });
385
+ }
386
+ return sections;
387
+ }
388
+ async function loadNanoWorkerOutputLines(logPath, height) {
389
+ const rawOutput = (await readTextIfExists(logPath)).trimEnd();
390
+ if (!rawOutput) {
391
+ return [{ kind: "content", text: "No worker output." }];
392
+ }
393
+ const lines = renderNanoProcessContent(compactNanoProcessText(rawOutput, height));
394
+ return lines.length > 0
395
+ ? [{ kind: "group", text: "process" }, ...lines]
396
+ : [{ kind: "content", text: "No worker output." }];
397
+ }
398
+ function renderNanoProcessContent(text) {
399
+ const lines = [];
400
+ for (const rawLine of text.split(/\r?\n/)) {
401
+ const line = stripAnsi(rawLine).replace(/\r/g, "").trimEnd();
402
+ const trimmed = line.trim();
403
+ if (!trimmed || workerOutputSourceColumns(trimmed)) {
404
+ continue;
405
+ }
406
+ if (/^(?:ERROR|Error|error):\s+/.test(trimmed) || /\bCodex ran out of room\b/i.test(trimmed)) {
407
+ lines.push({ kind: "error", text: trimmed });
408
+ continue;
409
+ }
410
+ if (/^Smoke test passed\b/i.test(trimmed)) {
411
+ lines.push({ kind: "success", text: trimmed });
412
+ continue;
413
+ }
414
+ if (isProcessStatusWithCommandLine(trimmed) ||
415
+ isBuildOutputSummaryLine(trimmed) ||
416
+ isNoMatchesSummaryLine(trimmed) ||
417
+ /^Node tests passed\b/i.test(trimmed) ||
418
+ /^Dev server fallback\b/i.test(trimmed)) {
419
+ lines.push({ kind: "summary", text: trimmed });
420
+ continue;
421
+ }
422
+ if (isProcessCommandDisplayLine(trimmed)) {
423
+ continue;
424
+ }
425
+ lines.push({ kind: "content", text: trimmed });
426
+ }
427
+ return lines;
428
+ }
429
+ function roleArtifactFiles(role) {
430
+ if (role === "judge") {
431
+ return ["requirements.md", "plan.md", "acceptance.md"];
432
+ }
433
+ if (role === "actor") {
434
+ return ["worklog.md", "actor-worklog.md", "patch.diff", "actor-replies.jsonl"];
435
+ }
436
+ if (role === "critic") {
437
+ return ["review.md", "critic-findings.jsonl"];
438
+ }
439
+ return [];
440
+ }
441
+ async function featureArtifactSections(role, workerDir) {
442
+ const files = featureArtifactFiles(role);
443
+ if (files.length === 0) {
444
+ return [];
445
+ }
446
+ const taskDir = dirname(workerDir);
447
+ const featuresDir = join(taskDir, "features");
448
+ if (!(await pathExists(featuresDir))) {
449
+ return [];
450
+ }
451
+ const entries = await readdir(featuresDir, { withFileTypes: true });
452
+ const featureDirs = entries
453
+ .filter((entry) => entry.isDirectory())
454
+ .map((entry) => entry.name)
455
+ .sort();
456
+ const sections = [];
457
+ for (const featureDir of featureDirs) {
458
+ for (const file of files) {
459
+ const path = join(featuresDir, featureDir, file);
460
+ const text = await readTextIfExists(path);
461
+ if (text.trim()) {
462
+ sections.push({
463
+ label: join("features", basename(featureDir), file),
464
+ text
465
+ });
466
+ }
467
+ }
468
+ }
469
+ return sections;
470
+ }
471
+ function featureArtifactFiles(role) {
472
+ if (role === "actor") {
473
+ return ["actor-worklog.md", "actor-replies.jsonl"];
474
+ }
475
+ if (role === "critic") {
476
+ return ["critic-findings.jsonl", "decisions.md"];
477
+ }
478
+ return [];
479
+ }
480
+ function compactDecisionMarkdown(text) {
481
+ const lines = text.split("\n");
482
+ if (!lines.some((line) => decisionBlockName(line) !== null)) {
483
+ return text;
484
+ }
485
+ const kept = [];
486
+ let currentBlock = null;
487
+ let blockLines = [];
488
+ const flushBlock = () => {
489
+ if (currentBlock === null) {
490
+ kept.push(...blockLines);
491
+ }
492
+ else if (currentBlock === "critic review") {
493
+ kept.push(...compactDecisionReviewBlock(blockLines));
494
+ }
495
+ else if (currentBlock === "critic findings") {
496
+ kept.push("Critic findings:");
497
+ kept.push(...trimOuterBlankLines(blockLines));
498
+ }
499
+ blockLines = [];
500
+ };
501
+ for (const line of lines) {
502
+ const blockName = decisionBlockName(line);
503
+ if (blockName) {
504
+ flushBlock();
505
+ currentBlock = blockName;
506
+ blockLines = [];
507
+ continue;
508
+ }
509
+ blockLines.push(line);
510
+ }
511
+ flushBlock();
512
+ return compactDecisionStatusBlankLines(compactDecisionFeatureTurnLines(compactDecisionBlankLines(kept)
513
+ .filter((line) => !isTruncatedDecisionFragment(line))))
514
+ .join("\n")
515
+ .trim();
516
+ }
517
+ function compactDecisionFeatureTurnLines(lines) {
518
+ const compacted = [];
519
+ for (let index = 0; index < lines.length; index += 1) {
520
+ const line = lines[index] ?? "";
521
+ const next = lines[index + 1] ?? "";
522
+ const feature = line.match(/^Feature:\s*(.+)$/i);
523
+ const turn = next.match(/^Turn:\s*(.+)$/i);
524
+ if (feature && turn) {
525
+ const featureId = feature[1] ?? "";
526
+ const turnId = turn[1] ?? "";
527
+ compacted.push(featureId === turnId ? `Feature ${featureId}` : `Feature ${featureId} · Turn ${turnId}`);
528
+ index += 1;
529
+ continue;
530
+ }
531
+ compacted.push(line);
532
+ }
533
+ return compacted;
534
+ }
535
+ function compactDecisionStatusBlankLines(lines) {
536
+ const compacted = [];
537
+ let blankPending = false;
538
+ for (const line of lines) {
539
+ if (!line.trim()) {
540
+ blankPending = compacted.length > 0;
541
+ continue;
542
+ }
543
+ const previous = compacted[compacted.length - 1] ?? "";
544
+ if (blankPending && !(isDecisionStatusLine(previous) && isDecisionStatusLine(line))) {
545
+ compacted.push("");
546
+ }
547
+ compacted.push(line);
548
+ blankPending = false;
549
+ }
550
+ return compacted;
551
+ }
552
+ function isDecisionStatusLine(line) {
553
+ return /^(?:Feature\s+\S+|Feature:\s*\S+\s+·\s+Turn:\s*\S+|Summary:|Review:|Blocking:|Findings:)/i.test(line.trim());
554
+ }
555
+ function decisionBlockName(line) {
556
+ const normalized = line.trim().replace(/:$/, ":").toLowerCase();
557
+ if (normalized === "requirements:") {
558
+ return "requirements";
559
+ }
560
+ if (normalized === "actor work:") {
561
+ return "actor work";
562
+ }
563
+ if (normalized === "critic review:") {
564
+ return "critic review";
565
+ }
566
+ if (normalized === "critic findings:") {
567
+ return "critic findings";
568
+ }
569
+ return null;
570
+ }
571
+ function compactDecisionReviewBlock(lines) {
572
+ const kept = ["Critic review:"];
573
+ const reviewLines = trimOuterBlankLines(lines);
574
+ let skippedRedundantHeading = false;
575
+ for (let index = 0; index < reviewLines.length; index += 1) {
576
+ const line = reviewLines[index] ?? "";
577
+ const trimmed = line.trim();
578
+ if (index === 0 && isRedundantFeatureReviewHeading(trimmed)) {
579
+ skippedRedundantHeading = true;
580
+ continue;
581
+ }
582
+ if (skippedRedundantHeading && !trimmed) {
583
+ continue;
584
+ }
585
+ skippedRedundantHeading = false;
586
+ if (/^##\s+(User Request|Actor Behavior|Evidence|Verification|Residual Risk)\b/i.test(trimmed)) {
587
+ break;
588
+ }
589
+ kept.push(line);
590
+ }
591
+ return kept;
592
+ }
593
+ function isRedundantFeatureReviewHeading(line) {
594
+ return /^#\s+Critic Review\s*[-–]\s*Feature\s+\S+/i.test(line);
595
+ }
596
+ function trimOuterBlankLines(lines) {
597
+ let start = 0;
598
+ let end = lines.length;
599
+ while (start < end && !lines[start]?.trim()) {
600
+ start += 1;
601
+ }
602
+ while (end > start && !lines[end - 1]?.trim()) {
603
+ end -= 1;
604
+ }
605
+ return lines.slice(start, end);
606
+ }
607
+ function compactDecisionBlankLines(lines) {
608
+ const compacted = [];
609
+ let blankPending = false;
610
+ for (const line of lines) {
611
+ if (!line.trim()) {
612
+ blankPending = compacted.length > 0;
613
+ continue;
614
+ }
615
+ if (blankPending) {
616
+ compacted.push("");
617
+ blankPending = false;
618
+ }
619
+ compacted.push(line);
620
+ }
621
+ return compacted;
622
+ }
623
+ function isTruncatedDecisionFragment(line) {
624
+ const trimmed = line.trim();
625
+ return trimmed === "..." || /`\S*\.\.\.$/.test(trimmed) || /\S\.\.\.$/.test(trimmed);
626
+ }
627
+ function renderLinesFromSections(sections, options = {}) {
628
+ const lines = [];
629
+ let currentGroup = null;
630
+ const supersededArtifacts = supersededRoleArtifactFiles(sections);
631
+ const renderedArtifactFiles = new Set(supersededArtifacts);
632
+ for (const section of sections) {
633
+ if (supersededArtifacts.has(basename(section.title))) {
634
+ continue;
635
+ }
636
+ const displaySection = options.nanoProcess && section.group === "process"
637
+ ? { ...section, text: compactNanoProcessText(section.text, options.height ?? 8) }
638
+ : section;
639
+ const sectionLines = renderSectionContent(displaySection, renderedArtifactFiles);
640
+ if (sectionLines.length === 0) {
641
+ continue;
642
+ }
643
+ if (lines.length > 0) {
644
+ lines.push({ kind: "blank", text: "" });
645
+ }
646
+ if (section.group !== currentGroup) {
647
+ lines.push({ kind: "group", text: groupTitle(section.group) });
648
+ currentGroup = section.group;
649
+ }
650
+ if (section.group !== "process") {
651
+ lines.push({ kind: "section", text: section.title });
652
+ }
653
+ lines.push(...sectionLines);
654
+ if (section.group !== "process") {
655
+ renderedArtifactFiles.add(basename(section.title));
656
+ }
657
+ }
658
+ return lines;
659
+ }
660
+ function compactNanoProcessText(text, height) {
661
+ const rawLines = text.split(/\r?\n/);
662
+ const maxLines = Math.max(8, height * 2);
663
+ if (rawLines.length <= maxLines) {
664
+ return text;
665
+ }
666
+ const compacted = rawLines
667
+ .map((line) => stripAnsi(line).replace(/\r/g, "").trimEnd())
668
+ .filter((line) => {
669
+ const trimmed = line.trim();
670
+ return Boolean(trimmed) && !workerOutputSourceColumns(trimmed);
671
+ });
672
+ const important = compacted.filter(isNanoProcessImportantLine);
673
+ const selected = important.length > 0 ? important : compacted;
674
+ return selected.slice(-maxLines).join("\n");
675
+ }
676
+ function isNanoProcessImportantLine(line) {
677
+ const trimmed = line.trim();
678
+ return (isProcessStatusWithCommandLine(trimmed) ||
679
+ isBuildOutputSummaryLine(trimmed) ||
680
+ isNoMatchesSummaryLine(trimmed) ||
681
+ /^Smoke test passed\b/i.test(trimmed) ||
682
+ /^Node tests passed\b/i.test(trimmed) ||
683
+ /^Dev server fallback\b/i.test(trimmed) ||
684
+ /^(?:ERROR|Error|error):\s+/.test(trimmed) ||
685
+ /\bCodex ran out of room\b/i.test(trimmed));
686
+ }
687
+ function supersededRoleArtifactFiles(sections) {
688
+ const hasFeatureDecisions = sections.some((section) => section.group === "feature" && basename(section.title) === "decisions.md");
689
+ if (!hasFeatureDecisions) {
690
+ return new Set();
691
+ }
692
+ return new Set(sections
693
+ .filter((section) => section.group === "role" && basename(section.title) === "review.md")
694
+ .map((section) => basename(section.title)));
695
+ }
696
+ function renderSectionContent(section, renderedArtifactFiles = new Set()) {
697
+ if (section.title.endsWith(".diff")) {
698
+ return renderDiffContent(section.text);
699
+ }
700
+ const lines = [];
701
+ let inCodeFence = false;
702
+ const sectionText = section.title.endsWith("decisions.md")
703
+ ? compactDecisionMarkdown(section.text)
704
+ : section.text;
705
+ const displayText = section.group === "process"
706
+ ? sanitizeProcessOutput(sectionText, {
707
+ hideAssistantNarration: renderedArtifactFiles.size > 0,
708
+ renderedArtifactFiles
709
+ })
710
+ : sectionText;
711
+ const hiddenProcessDiffFiles = section.group === "process" ? renderedArtifactFiles : new Set();
712
+ const rawLines = displayText.split("\n");
713
+ let codeFenceLanguage = "";
714
+ for (let index = 0; index < rawLines.length;) {
715
+ const rawLine = stripAnsi(rawLines[index] ?? "").replace(/\r/g, "");
716
+ const sourceLine = section.group === "process" ? workerOutputSourceColumns(rawLine) : null;
717
+ const line = rawLine.trimEnd();
718
+ const trimmed = line.trim();
719
+ if (sourceLine) {
720
+ lines.push({ kind: "source-line", text: `${sourceLine.lineNumber}\t${sourceLine.code}` });
721
+ index += 1;
722
+ continue;
723
+ }
724
+ if (!trimmed) {
725
+ lines.push({ kind: "blank", text: "" });
726
+ index += 1;
727
+ continue;
728
+ }
729
+ if (section.title.endsWith(".jsonl")) {
730
+ lines.push(...renderJsonLine(trimmed));
731
+ index += 1;
732
+ continue;
733
+ }
734
+ if (trimmed.startsWith("```") && section.group === "process") {
735
+ index += 1;
736
+ continue;
737
+ }
738
+ const codeFence = trimmed.match(/^```\s*([A-Za-z0-9_-]*)/);
739
+ if (codeFence) {
740
+ if (inCodeFence) {
741
+ inCodeFence = false;
742
+ codeFenceLanguage = "";
743
+ }
744
+ else {
745
+ inCodeFence = true;
746
+ codeFenceLanguage = (codeFence[1] ?? "").toLowerCase();
747
+ }
748
+ index += 1;
749
+ continue;
750
+ }
751
+ if (inCodeFence) {
752
+ const codeLine = decodeHtmlEntities(line.trimStart());
753
+ lines.push(isShellCodeFenceLanguage(codeFenceLanguage) && codeLine.trim()
754
+ ? { kind: "command", text: shellCodeFenceCommandLine(codeLine) }
755
+ : { kind: "code", text: codeLine });
756
+ index += 1;
757
+ continue;
758
+ }
759
+ if (isDiffStartLine(line.trimStart())) {
760
+ const diffBlock = collectEmbeddedDiffBlock(rawLines, index);
761
+ if (processDiffBlockTargetsArtifacts(diffBlock.lines, hiddenProcessDiffFiles)) {
762
+ index = diffBlock.nextIndex;
763
+ continue;
764
+ }
765
+ if (lines.length > 0 && lines[lines.length - 1]?.kind !== "blank") {
766
+ lines.push({ kind: "blank", text: "" });
767
+ }
768
+ const diffSummary = section.group === "process" ? summarizeProcessDiffBlock(diffBlock.lines) : null;
769
+ const collapseDiff = Boolean(diffSummary && shouldCollapseProcessDiffBlock(diffSummary));
770
+ lines.push(...(collapseDiff && diffSummary
771
+ ? renderCollapsedProcessDiffContent(diffSummary)
772
+ : renderDiffContent(diffBlock.lines.join("\n"))));
773
+ index = collapseDiff ? skipProcessDiffTailContext(rawLines, diffBlock.nextIndex) : diffBlock.nextIndex;
774
+ continue;
775
+ }
776
+ if (section.group === "process" && hiddenProcessDiffFiles.size > 0 && isDiffHunkLine(line.trimStart())) {
777
+ index = collectBareDiffHunkBlock(rawLines, index).nextIndex;
778
+ continue;
779
+ }
780
+ if (section.group === "process" &&
781
+ isCollapsedOutputSummaryLine(trimmed) &&
782
+ nextSignificantProcessLineStartsDiff(rawLines, index + 1)) {
783
+ index += 1;
784
+ continue;
785
+ }
786
+ if (section.group === "process" && isProcessCodeOutputLine(trimmed)) {
787
+ const codeFragment = collectProcessCodeFragmentRun(rawLines, index);
788
+ if (nextSignificantProcessLineStartsDiff(rawLines, codeFragment.nextIndex)) {
789
+ index = codeFragment.nextIndex;
790
+ continue;
791
+ }
792
+ }
793
+ if (section.group === "process" && isBareProcessDiffBodyLine(line)) {
794
+ const bareDiffBody = collectBareProcessDiffBodyRun(rawLines, index);
795
+ if (bareDiffBody.bodyLines >= 1) {
796
+ index = bareDiffBody.nextIndex;
797
+ continue;
798
+ }
799
+ }
800
+ if (isMarkdownTableSeparator(trimmed)) {
801
+ index += 1;
802
+ continue;
803
+ }
804
+ if (isMarkdownTableRow(trimmed)) {
805
+ lines.push({ kind: "table", text: renderMarkdownTableRow(trimmed) });
806
+ index += 1;
807
+ continue;
808
+ }
809
+ lines.push(classifyRenderedLine(section, line));
810
+ index += 1;
811
+ }
812
+ return compactRenderedBlankLines(removeRedundantSectionHeading(section, compactVerificationListBlocks(compactWorkerHeadingValuePairs(compactRenderedBlankLines(refineListDetailLines(section.group === "process" ? cleanProcessRenderLines(lines) : lines))))));
813
+ }
814
+ function processDiffBlockTargetsArtifacts(diffLines, artifactFiles) {
815
+ return diffLines.some((line) => {
816
+ const title = renderDiffFileTitle(line.trimStart());
817
+ if (!title) {
818
+ return false;
819
+ }
820
+ return artifactFiles.has(diffTitlePath(title));
821
+ });
822
+ }
823
+ function compactRenderedBlankLines(lines) {
824
+ const compacted = [];
825
+ let blankPending = false;
826
+ for (const line of lines) {
827
+ if (line.kind === "blank" || !line.text.trim()) {
828
+ blankPending = compacted.length > 0;
829
+ continue;
830
+ }
831
+ const previousLine = compacted[compacted.length - 1];
832
+ if (blankPending &&
833
+ previousLine?.kind !== "blank" &&
834
+ shouldKeepBlankBetweenRenderedLines(previousLine, line)) {
835
+ compacted.push({ kind: "blank", text: "" });
836
+ }
837
+ compacted.push(line);
838
+ blankPending = false;
839
+ }
840
+ return compacted;
841
+ }
842
+ function refineListDetailLines(lines) {
843
+ const refined = [];
844
+ let inFileDetailRun = false;
845
+ for (const line of lines) {
846
+ if (line.kind !== "list") {
847
+ refined.push(line);
848
+ if (line.kind === "blank" || !line.text.trim() || line.kind === "heading" || line.kind === "section" || line.kind === "group") {
849
+ inFileDetailRun = false;
850
+ }
851
+ continue;
852
+ }
853
+ const fileWithDescription = splitFilePathListItemDescription(line.text);
854
+ if (fileWithDescription) {
855
+ refined.push({ kind: "list", text: fileWithDescription.path });
856
+ refined.push({ kind: "list-detail", text: fileWithDescription.description });
857
+ inFileDetailRun = true;
858
+ continue;
859
+ }
860
+ if (isFilePathListItem(line.text)) {
861
+ refined.push(line);
862
+ inFileDetailRun = true;
863
+ continue;
864
+ }
865
+ refined.push(inFileDetailRun ? { kind: "list-detail", text: line.text } : line);
866
+ }
867
+ return refined;
868
+ }
869
+ function compactWorkerHeadingValuePairs(lines) {
870
+ const compacted = [];
871
+ for (let index = 0; index < lines.length; index += 1) {
872
+ const line = lines[index];
873
+ const next = lines[index + 1];
874
+ if (line && next && shouldCompactWorkerHeadingValuePair(line, next)) {
875
+ const headingText = compactWorkerHeadingLabelText(line.text);
876
+ compacted.push({
877
+ kind: "content",
878
+ text: compactWorkerHeadingValueText(headingText, next.text)
879
+ });
880
+ index += 1;
881
+ continue;
882
+ }
883
+ if (line) {
884
+ compacted.push(line);
885
+ }
886
+ }
887
+ return compacted;
888
+ }
889
+ function compactVerificationListBlocks(lines) {
890
+ const compacted = [];
891
+ for (let index = 0; index < lines.length; index += 1) {
892
+ const line = lines[index];
893
+ if (!line || line.kind !== "heading" || !/^Verification$/i.test(line.text.trim())) {
894
+ if (line) {
895
+ compacted.push(line);
896
+ }
897
+ continue;
898
+ }
899
+ const items = [];
900
+ let cursor = index + 1;
901
+ while (cursor < lines.length && lines[cursor]?.kind === "blank") {
902
+ cursor += 1;
903
+ }
904
+ while (cursor < lines.length) {
905
+ const candidate = lines[cursor];
906
+ if (!candidate || (candidate.kind !== "list" && candidate.kind !== "list-detail")) {
907
+ break;
908
+ }
909
+ items.push(candidate.text);
910
+ cursor += 1;
911
+ }
912
+ const summary = verificationListSummary(items);
913
+ if (!summary) {
914
+ compacted.push(line);
915
+ continue;
916
+ }
917
+ compacted.push({ kind: "content", text: `Verification: ${summary}` });
918
+ index = cursor - 1;
919
+ }
920
+ return compacted;
921
+ }
922
+ function verificationListSummary(items) {
923
+ if (items.length < 2) {
924
+ return null;
925
+ }
926
+ const signals = [];
927
+ const joined = items.join(" ");
928
+ const focusedTests = joined.match(/\bnode --test\b.*?\bpassed,\s*(\d+\/\d+)/i);
929
+ if (focusedTests) {
930
+ signals.push(`unit ${focusedTests[1] ?? ""}`);
931
+ }
932
+ const npmTests = joined.match(/\bnpm test\b.*?\bpassed,\s*(\d+\/\d+)/i);
933
+ if (npmTests) {
934
+ signals.push(`tests ${npmTests[1] ?? ""}`);
935
+ }
936
+ else if (/\bnpm test\b.*?\bpassed\b/i.test(joined)) {
937
+ signals.push("tests passed");
938
+ }
939
+ if (/\bnpm run smoke\b.*?\bpassed\b/i.test(joined)) {
940
+ signals.push("smoke passed");
941
+ }
942
+ if (/\bnpm run build\b.*?\bpassed\b/i.test(joined)) {
943
+ signals.push("build passed");
944
+ }
945
+ if (/\bnpm run dev\b.*?\b(?:could not bind|fallback|dist\/?\s*fallback)\b/i.test(joined)) {
946
+ signals.push("dev fallback");
947
+ }
948
+ return signals.length >= 2 ? signals.join(" · ") : null;
949
+ }
950
+ function shouldCompactWorkerHeadingValuePair(heading, value) {
951
+ if (!isCompactWorkerHeadingLine(heading) || value.kind !== "content") {
952
+ return false;
953
+ }
954
+ const label = compactWorkerHeadingLabelText(heading.text).toLowerCase();
955
+ if (!COMPACT_WORKER_HEADING_VALUE_LABELS.has(label)) {
956
+ return false;
957
+ }
958
+ const text = value.text.trim();
959
+ if (label === "blocking findings" && isEmptyBlockingFindingsText(text)) {
960
+ return true;
961
+ }
962
+ if (label === "critic findings" && isEmptyCriticFindingsText(text)) {
963
+ return true;
964
+ }
965
+ return Boolean(text) && displayWidth(text) <= 64 && !isMarkdownListLikeLine(text);
966
+ }
967
+ function compactWorkerHeadingValueText(headingText, valueText) {
968
+ const heading = headingText.trim();
969
+ const value = valueText.trim();
970
+ if (/^supervisor summary$/i.test(heading)) {
971
+ return compactSupervisorSummaryForWidth(`${heading}: ${value}`, 80);
972
+ }
973
+ if (/^critic review$/i.test(heading)) {
974
+ return compactCriticReviewBodyForWidth(`${heading}: ${value}`, 80);
975
+ }
976
+ if (/^blocking findings$/i.test(heading) && isEmptyBlockingFindingsText(value)) {
977
+ return "Blocking: none";
978
+ }
979
+ if (/^critic findings$/i.test(heading) && isEmptyCriticFindingsText(value)) {
980
+ return "Findings: none";
981
+ }
982
+ return `${heading}: ${value}`;
983
+ }
984
+ function isEmptyBlockingFindingsText(text) {
985
+ return /^(?:no findings\.?|none\.?|\(empty\))$/i.test(text);
986
+ }
987
+ function isEmptyCriticFindingsText(text) {
988
+ return (/^(?:no findings\.?|none\.?|\(empty\))$/i.test(text) ||
989
+ /^No active Critic findings were present for this feature;/.test(text));
990
+ }
991
+ function isCompactWorkerHeadingLine(line) {
992
+ return line.kind === "heading" || (line.kind === "content" && /[::]\s*$/.test(line.text.trim()));
993
+ }
994
+ function compactWorkerHeadingLabelText(text) {
995
+ return text.trim().replace(/[::]\s*$/, "");
996
+ }
997
+ const COMPACT_WORKER_HEADING_VALUE_LABELS = new Set([
998
+ "blocking findings",
999
+ "critic findings",
1000
+ "critic review",
1001
+ "feature",
1002
+ "supervisor summary",
1003
+ "turn",
1004
+ "user request"
1005
+ ]);
1006
+ function removeRedundantSectionHeading(section, lines) {
1007
+ const redundantHeading = redundantSectionHeading(section.title);
1008
+ if (!redundantHeading) {
1009
+ return lines;
1010
+ }
1011
+ const firstContentIndex = lines.findIndex((line) => line.kind !== "blank" && line.text.trim());
1012
+ if (firstContentIndex < 0) {
1013
+ return lines;
1014
+ }
1015
+ const firstContent = lines[firstContentIndex];
1016
+ if (firstContent?.kind !== "heading" || firstContent.text.trim().toLowerCase() !== redundantHeading) {
1017
+ return lines;
1018
+ }
1019
+ return lines.filter((_, index) => index !== firstContentIndex);
1020
+ }
1021
+ function redundantSectionHeading(title) {
1022
+ const file = basename(title).toLowerCase();
1023
+ if (file === "actor-worklog.md") {
1024
+ return "actor feature worklog";
1025
+ }
1026
+ if (file === "decisions.md") {
1027
+ return "decisions";
1028
+ }
1029
+ return null;
1030
+ }
1031
+ function splitFilePathListItemDescription(text) {
1032
+ const match = text.trim().match(/^(\S+)\s+-\s+(.+)$/);
1033
+ if (!match) {
1034
+ return null;
1035
+ }
1036
+ const path = match[1] ?? "";
1037
+ const description = match[2] ?? "";
1038
+ return isFilePathListItem(path) && description.trim()
1039
+ ? { path, description: description.trim() }
1040
+ : null;
1041
+ }
1042
+ function isFilePathListItem(text) {
1043
+ const trimmed = text.trim();
1044
+ if (trimmed.includes(",")) {
1045
+ const paths = trimmed.split(",").map((path) => path.trim()).filter(Boolean);
1046
+ return paths.length > 1 && paths.every((path) => isSingleFilePathListItem(path));
1047
+ }
1048
+ return isSingleFilePathListItem(trimmed);
1049
+ }
1050
+ function isSingleFilePathListItem(trimmed) {
1051
+ return (/^[A-Za-z0-9._@+-]+(?:\/[A-Za-z0-9._@+*?-]+)*\.[A-Za-z0-9._@+*?-]+$/.test(trimmed) ||
1052
+ /^[A-Za-z0-9._@+-]+\/[A-Za-z0-9._@+*?/-]+$/.test(trimmed));
1053
+ }
1054
+ function cleanProcessRenderLines(lines) {
1055
+ return removePatchDiffReadbackNoise(removeDiffAdjacentCodeSummaries(mergeSmokeStatusSuccessLines(lines)));
1056
+ }
1057
+ function mergeSmokeStatusSuccessLines(lines) {
1058
+ const merged = [];
1059
+ for (let index = 0; index < lines.length; index += 1) {
1060
+ const line = lines[index];
1061
+ if (!line) {
1062
+ continue;
1063
+ }
1064
+ if (line.kind === "command" && isSmokeCommandLine(line.text)) {
1065
+ const status = nextNonBlankRenderLine(lines, index + 1);
1066
+ const duration = status?.line ? succeededStatusDuration(status.line) : null;
1067
+ const success = status ? nextNonBlankRenderLine(lines, status.index + 1) : null;
1068
+ if (duration && success?.line.kind === "success" && isSmokePassedLine(success.line.text)) {
1069
+ merged.push({
1070
+ kind: "success",
1071
+ text: mergeSmokeStatusText(success.line.text, duration)
1072
+ });
1073
+ index = success.index;
1074
+ continue;
1075
+ }
1076
+ }
1077
+ const smokeStatus = smokeStatusDuration(line);
1078
+ if (!smokeStatus) {
1079
+ merged.push(line);
1080
+ continue;
1081
+ }
1082
+ const next = nextNonBlankRenderLine(lines, index + 1);
1083
+ if (!next?.line || next.line.kind !== "success" || !isSmokePassedLine(next.line.text)) {
1084
+ merged.push(line);
1085
+ continue;
1086
+ }
1087
+ merged.push({
1088
+ kind: "success",
1089
+ text: mergeSmokeStatusText(next.line.text, smokeStatus.duration)
1090
+ });
1091
+ index = next.index;
1092
+ }
1093
+ return merged;
1094
+ }
1095
+ function smokeStatusDuration(line) {
1096
+ if (line.kind !== "summary") {
1097
+ return null;
1098
+ }
1099
+ const match = line.text.match(/^succeeded\s+in\s+(\d+(?:ms|s|m)?):?\s+\(\$\s+npm\s+run\s+smoke\b[^)]*\)$/i);
1100
+ return match ? { duration: match[1] ?? "" } : null;
1101
+ }
1102
+ function succeededStatusDuration(line) {
1103
+ if (line.kind !== "summary") {
1104
+ return null;
1105
+ }
1106
+ const match = line.text.match(/^succeeded\s+in\s+(\d+(?:ms|s|m)?):?$/i);
1107
+ return match?.[1] ?? null;
1108
+ }
1109
+ function isSmokeCommandLine(text) {
1110
+ return /^\$\s+npm\s+run\s+smoke\b/i.test(text.trim());
1111
+ }
1112
+ function isSmokePassedLine(text) {
1113
+ return /^Smoke test passed\b/i.test(text.trim());
1114
+ }
1115
+ function mergeSmokeStatusText(text, duration) {
1116
+ const match = text.trim().match(/^Smoke test passed:?\s*(.*)$/i);
1117
+ const detail = match?.[1]?.trim() ?? "";
1118
+ return [`Smoke test passed in ${duration}`, detail ? `: ${detail}` : ""].join("");
1119
+ }
1120
+ function removeDiffAdjacentCodeSummaries(lines) {
1121
+ return lines.filter((line, index) => {
1122
+ if (line.kind !== "summary" || !/^Collapsed code output:/i.test(line.text.trim())) {
1123
+ return true;
1124
+ }
1125
+ return !(nearbyNonBlankLineIsCollapsedDiff(lines, index, -1) ||
1126
+ nearbyNonBlankLineIsCollapsedDiff(lines, index, 1));
1127
+ });
1128
+ }
1129
+ function nearbyNonBlankLineIsCollapsedDiff(lines, startIndex, direction) {
1130
+ for (let index = startIndex + direction; index >= 0 && index < lines.length; index += direction) {
1131
+ const line = lines[index];
1132
+ if (!line || line.kind === "blank" || !line.text.trim()) {
1133
+ continue;
1134
+ }
1135
+ return line.kind === "summary" && /^Collapsed diff:/i.test(line.text.trim());
1136
+ }
1137
+ return false;
1138
+ }
1139
+ function removePatchDiffReadbackNoise(lines) {
1140
+ const hidden = new Set();
1141
+ for (let index = 0; index < lines.length; index += 1) {
1142
+ const line = lines[index];
1143
+ if (!line || line.kind !== "command" || !isPatchDiffReadbackCommand(line.text)) {
1144
+ continue;
1145
+ }
1146
+ const status = nextNonBlankRenderLine(lines, index + 1);
1147
+ const afterStatus = status ? nextNonBlankRenderLine(lines, status.index + 1) : null;
1148
+ if (status?.line.kind === "summary" &&
1149
+ isProcessStatusLine(status.line.text.trim()) &&
1150
+ afterStatus?.line.kind === "summary" &&
1151
+ /^Collapsed diff:/i.test(afterStatus.line.text.trim())) {
1152
+ hidden.add(index);
1153
+ hidden.add(status.index);
1154
+ }
1155
+ }
1156
+ return lines.filter((_, index) => !hidden.has(index));
1157
+ }
1158
+ function nextNonBlankRenderLine(lines, startIndex) {
1159
+ for (let index = startIndex; index < lines.length; index += 1) {
1160
+ const line = lines[index];
1161
+ if (line && line.kind !== "blank" && line.text.trim()) {
1162
+ return { line, index };
1163
+ }
1164
+ }
1165
+ return null;
1166
+ }
1167
+ function isPatchDiffReadbackCommand(text) {
1168
+ return /^\$\s+(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(text.trim()) && /\bpatch\.diff\b/.test(text);
1169
+ }
1170
+ function shouldKeepBlankBetweenRenderedLines(previousLine, nextLine) {
1171
+ if (isCollapsedDiffSummaryLine(previousLine) && isCollapsedDiffSummaryLine(nextLine)) {
1172
+ return false;
1173
+ }
1174
+ if (isCompactStatusRenderLine(previousLine) && isCompactStatusRenderLine(nextLine)) {
1175
+ return false;
1176
+ }
1177
+ if (previousLine.kind === "heading" || nextLine.kind === "heading") {
1178
+ return false;
1179
+ }
1180
+ return !(isDenseProcessEventLine(previousLine) && isDenseProcessEventLine(nextLine));
1181
+ }
1182
+ function isCompactStatusRenderLine(line) {
1183
+ return line.kind === "content" && isDecisionStatusLine(line.text);
1184
+ }
1185
+ function isDenseProcessEventLine(line) {
1186
+ if (isCollapsedDiffSummaryLine(line)) {
1187
+ return false;
1188
+ }
1189
+ return line.kind === "summary" || line.kind === "success" || line.kind === "command" || line.kind === "error";
1190
+ }
1191
+ function isCollapsedDiffSummaryLine(line) {
1192
+ return line.kind === "summary" && /^Collapsed diff:/i.test(line.text.trim());
1193
+ }
1194
+ function diffTitlePath(title) {
1195
+ const match = title.match(/^Update\((.+?)(?: -> .+)?\)$/);
1196
+ return match?.[1] ?? title;
1197
+ }
1198
+ const SOURCE_OUTPUT_COLLAPSE_MIN_LINES = 8;
1199
+ const CODE_OUTPUT_COLLAPSE_MIN_LINES = 4;
1200
+ const FILE_LIST_OUTPUT_COLLAPSE_MIN_LINES = 12;
1201
+ const READ_SUMMARY_RUN_COLLAPSE_MIN_ITEMS = 3;
1202
+ const NODE_TEST_OUTPUT_COLLAPSE_MIN_PASSES = 3;
1203
+ const PROCESS_DIFF_COLLAPSE_MIN_BODY_LINES = 1;
1204
+ const PROCESS_DIFF_COLLAPSE_MIN_FILES = 4;
1205
+ function sanitizeProcessOutput(text, options = {}) {
1206
+ const rawLines = text.split("\n");
1207
+ const kept = [];
1208
+ const hideAssistantNarration = options.hideAssistantNarration ?? false;
1209
+ const renderedArtifactFiles = options.renderedArtifactFiles ?? new Set();
1210
+ for (let index = 0; index < rawLines.length;) {
1211
+ const line = normalizedProcessLine(rawLines[index] ?? "");
1212
+ const trimmed = line.trim();
1213
+ if (shouldDropNoisyProcessLine(trimmed, line)) {
1214
+ index = skipNoisyProcessLine(rawLines, index);
1215
+ continue;
1216
+ }
1217
+ if (isNpmLifecycleEchoStart(trimmed)) {
1218
+ index = skipNpmLifecycleEchoBlock(rawLines, index);
1219
+ continue;
1220
+ }
1221
+ if (isCodexStartupPreamble(trimmed)) {
1222
+ index = skipCodexStartupPreamble(rawLines, index);
1223
+ continue;
1224
+ }
1225
+ if (hideAssistantNarration && isAssistantNarrativeMarker(trimmed)) {
1226
+ index = skipAssistantNarrativeBlock(rawLines, index);
1227
+ continue;
1228
+ }
1229
+ if (hideAssistantNarration && isUnmarkedAssistantNarrativeStart(trimmed)) {
1230
+ index = skipAssistantNarrativeTextBlock(rawLines, index);
1231
+ continue;
1232
+ }
1233
+ const npmStatusCommand = npmLifecycleCommandAfterStatus(rawLines, index);
1234
+ if (npmStatusCommand) {
1235
+ kept.push(`${trimmed} ($ ${npmStatusCommand})`);
1236
+ index += 1;
1237
+ continue;
1238
+ }
1239
+ if (isRolePromptTranscriptStart(trimmed)) {
1240
+ index = skipRolePromptTranscript(rawLines, index);
1241
+ continue;
1242
+ }
1243
+ const execCommand = collectProcessExecCommand(rawLines, index);
1244
+ if (execCommand) {
1245
+ if (processCommandReadsRenderedArtifact(execCommand.command, renderedArtifactFiles) ||
1246
+ processCommandReadsParallelCodexTaskFile(execCommand.command) ||
1247
+ processCommandInspectsSessionMetadata(execCommand.command) ||
1248
+ processCommandReadsSessionSystemFile(execCommand.command) ||
1249
+ processCommandReadsSkillDocument(execCommand.command)) {
1250
+ index = skipProcessCommandOutputBlock(rawLines, execCommand.nextIndex);
1251
+ continue;
1252
+ }
1253
+ kept.push(execCommand.commandLine);
1254
+ index = execCommand.nextIndex;
1255
+ continue;
1256
+ }
1257
+ kept.push(rawLines[index] ?? "");
1258
+ index += 1;
1259
+ }
1260
+ return collapseProcessOutputLines(compactReadSummaryRuns(compactNoMatchSearchCommandBlocks(compactAnnotatedStatusCommandPairs(compactNodeTestOutputBlocks(compactBuildOutputBlocks(compactDevServerFallbackBlocks(compactCollapsedCommandBlocks(dropSuccessfulInternalLaunchCommands(collapseVerboseProcessOutput(kept)))))))))).join("\n").trimEnd();
1261
+ }
1262
+ function collectProcessExecCommand(rawLines, startIndex) {
1263
+ const line = normalizedProcessLine(rawLines[startIndex] ?? "");
1264
+ const trimmed = line.trim();
1265
+ if (trimmed === "exec") {
1266
+ const nextLine = normalizedProcessLine(rawLines[startIndex + 1] ?? "").trim();
1267
+ const command = parseShellExecCommand(nextLine);
1268
+ if (command) {
1269
+ return {
1270
+ command,
1271
+ commandLine: `$ ${command}`,
1272
+ nextIndex: startIndex + 2
1273
+ };
1274
+ }
1275
+ return null;
1276
+ }
1277
+ const command = parseShellExecCommand(trimmed);
1278
+ if (!command) {
1279
+ return null;
1280
+ }
1281
+ return {
1282
+ command,
1283
+ commandLine: `$ ${command}`,
1284
+ nextIndex: startIndex + 1
1285
+ };
1286
+ }
1287
+ function processCommandReadsRenderedArtifact(command, artifactFiles) {
1288
+ if (artifactFiles.size === 0) {
1289
+ return false;
1290
+ }
1291
+ const trimmed = command.trim();
1292
+ if (!/^(?:cat|sed|nl|head|tail|wc|bat|less|more)\b/.test(trimmed)) {
1293
+ return false;
1294
+ }
1295
+ if (!trimmed.includes(".parallel-codex/sessions/")) {
1296
+ return false;
1297
+ }
1298
+ return Array.from(artifactFiles).some((file) => trimmed.endsWith(file) ||
1299
+ trimmed.includes(`/${file}`) ||
1300
+ trimmed.includes(` ${file}`));
1301
+ }
1302
+ function processCommandReadsParallelCodexTaskFile(command) {
1303
+ const trimmed = command.trim();
1304
+ if (!/^(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(trimmed)) {
1305
+ return false;
1306
+ }
1307
+ if (!trimmed.includes(".parallel-codex/sessions/")) {
1308
+ return false;
1309
+ }
1310
+ return /\.(?:md|json|jsonl|diff|toml|txt)\b/.test(trimmed);
1311
+ }
1312
+ function processCommandInspectsSessionMetadata(command) {
1313
+ const trimmed = command.trim();
1314
+ return (/^(?:find|ls|wc|stat|du)\b/.test(trimmed) &&
1315
+ trimmed.includes(".parallel-codex/sessions/"));
1316
+ }
1317
+ function processCommandReadsSessionSystemFile(command) {
1318
+ const trimmed = command.trim();
1319
+ if (!/^(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(trimmed)) {
1320
+ return false;
1321
+ }
1322
+ if (!trimmed.includes(".parallel-codex/sessions/")) {
1323
+ return false;
1324
+ }
1325
+ return /\/(?:prompt\.md|meta\.json|user-request\.md|native-session\.json)\b/.test(trimmed);
1326
+ }
1327
+ function processCommandReadsSkillDocument(command) {
1328
+ const trimmed = command.trim();
1329
+ return (/^(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(trimmed) &&
1330
+ trimmed.includes("/.codex/") &&
1331
+ trimmed.includes("SKILL.md"));
1332
+ }
1333
+ function skipProcessCommandOutputBlock(rawLines, startIndex) {
1334
+ let index = startIndex;
1335
+ while (index < rawLines.length) {
1336
+ const line = normalizedProcessLine(rawLines[index] ?? "");
1337
+ const trimmed = line.trim();
1338
+ if (trimmed === "exec" ||
1339
+ trimmed.startsWith("$ ") ||
1340
+ parseShellExecCommand(trimmed) !== null ||
1341
+ isAssistantNarrativeMarker(trimmed) ||
1342
+ isDiffStartLine(trimmed)) {
1343
+ return index;
1344
+ }
1345
+ index += 1;
1346
+ }
1347
+ return index;
1348
+ }
1349
+ function parseShellExecCommand(line) {
1350
+ const match = line.match(/^\/(?:[\w.-]+\/)*(?:zsh|bash|sh)\s+-lc\s+(.+)$/);
1351
+ if (!match) {
1352
+ return null;
1353
+ }
1354
+ const rest = match[1]?.trim() ?? "";
1355
+ const quoted = readQuotedShellArgument(rest);
1356
+ if (quoted) {
1357
+ return normalizeShellDisplayCommand(quoted.value);
1358
+ }
1359
+ return rest.replace(/\s+in\s+\/.+$/, "").trim() || null;
1360
+ }
1361
+ function normalizeShellDisplayCommand(command) {
1362
+ return command.replace(/'"([^']*?)"'/g, "'$1'");
1363
+ }
1364
+ function readQuotedShellArgument(text) {
1365
+ if (text[0] !== "\"" && text[0] !== "'") {
1366
+ return null;
1367
+ }
1368
+ let value = "";
1369
+ let index = 0;
1370
+ while (index < text.length) {
1371
+ const char = text[index] ?? "";
1372
+ if (/\s/.test(char)) {
1373
+ break;
1374
+ }
1375
+ if (char === "\"" || char === "'") {
1376
+ const segment = readQuotedShellSegment(text, index);
1377
+ if (!segment) {
1378
+ return null;
1379
+ }
1380
+ value += segment.value;
1381
+ index = segment.endIndex;
1382
+ continue;
1383
+ }
1384
+ value += char;
1385
+ index += 1;
1386
+ }
1387
+ return { value, endIndex: index };
1388
+ }
1389
+ function readQuotedShellSegment(text, startIndex) {
1390
+ const quote = text[startIndex];
1391
+ if (quote !== "\"" && quote !== "'") {
1392
+ return null;
1393
+ }
1394
+ let value = "";
1395
+ for (let index = startIndex + 1; index < text.length; index += 1) {
1396
+ const char = text[index] ?? "";
1397
+ if (char === "\\" && quote === "\"") {
1398
+ const next = text[index + 1];
1399
+ if (next) {
1400
+ value += next;
1401
+ index += 1;
1402
+ continue;
1403
+ }
1404
+ }
1405
+ if (char === quote) {
1406
+ return { value, endIndex: index + 1 };
1407
+ }
1408
+ value += char;
1409
+ }
1410
+ return null;
1411
+ }
1412
+ function isAssistantNarrativeMarker(trimmed) {
1413
+ return trimmed === "codex" || trimmed === "assistant";
1414
+ }
1415
+ function skipAssistantNarrativeBlock(rawLines, startIndex) {
1416
+ return skipAssistantNarrativeTextBlock(rawLines, startIndex + 1);
1417
+ }
1418
+ function skipAssistantNarrativeTextBlock(rawLines, startIndex) {
1419
+ let index = startIndex;
1420
+ while (index < rawLines.length) {
1421
+ const line = normalizedProcessLine(rawLines[index] ?? "");
1422
+ const trimmed = line.trim();
1423
+ if (shouldDropNoisyProcessLine(trimmed, line)) {
1424
+ index = skipNoisyProcessLine(rawLines, index);
1425
+ continue;
1426
+ }
1427
+ if (isAssistantNarrativeBoundary(trimmed) || isDiffStartLine(trimmed) || isRolePromptTranscriptStart(trimmed)) {
1428
+ return index;
1429
+ }
1430
+ index += 1;
1431
+ }
1432
+ return index;
1433
+ }
1434
+ function isUnmarkedAssistantNarrativeStart(trimmed) {
1435
+ return (/^Blocking findings:/i.test(trimmed) ||
1436
+ /^I(?:'ve| have) completed the critic review\b/i.test(trimmed) ||
1437
+ /^Wrote\s+`?APPROVED`?\b/i.test(trimmed) ||
1438
+ /^Verified:\s*$/i.test(trimmed) ||
1439
+ /^已在\s+worker\s+目录写好/.test(trimmed) ||
1440
+ /^我只写了任务文档/.test(trimmed));
1441
+ }
1442
+ function isAssistantNarrativeBoundary(trimmed) {
1443
+ if (!trimmed) {
1444
+ return false;
1445
+ }
1446
+ return (trimmed.startsWith("$ ") ||
1447
+ trimmed === "exec" ||
1448
+ parseShellExecCommand(trimmed) !== null ||
1449
+ /^(succeeded|failed) in \d+/i.test(trimmed) ||
1450
+ /^(ERROR|Error|error|Traceback|Exception|Failed|failed|Failure|failure)\b/.test(trimmed));
1451
+ }
1452
+ function normalizedProcessLine(line) {
1453
+ return stripAnsi(line).replace(/\r/g, "");
1454
+ }
1455
+ function collapseVerboseProcessOutput(lines) {
1456
+ const collapsed = [];
1457
+ for (let index = 0; index < lines.length;) {
1458
+ const fileListRun = collectFileListOutputRun(lines, index);
1459
+ if (fileListRun && fileListRun.pathLines >= FILE_LIST_OUTPUT_COLLAPSE_MIN_LINES) {
1460
+ collapsed.push(`Collapsed file list output: ${fileListRun.pathLines} paths`);
1461
+ index = fileListRun.nextIndex;
1462
+ continue;
1463
+ }
1464
+ const sourceRun = collectSourceOutputRun(lines, index);
1465
+ if (sourceRun && sourceRun.sourceLines >= SOURCE_OUTPUT_COLLAPSE_MIN_LINES) {
1466
+ collapsed.push(`Collapsed source output: ${sourceRun.sourceLines} ${pluralizeLine(sourceRun.sourceLines)}`);
1467
+ index = sourceRun.nextIndex;
1468
+ continue;
1469
+ }
1470
+ const codeRun = collectCodeOutputRun(lines, index);
1471
+ if (codeRun && codeRun.codeLines >= CODE_OUTPUT_COLLAPSE_MIN_LINES) {
1472
+ collapsed.push(`Collapsed code output: ${codeRun.codeLines} ${pluralizeLine(codeRun.codeLines)}`);
1473
+ index = codeRun.nextIndex;
1474
+ continue;
1475
+ }
1476
+ collapsed.push(lines[index] ?? "");
1477
+ index += 1;
1478
+ }
1479
+ return collapsed;
1480
+ }
1481
+ function compactCollapsedCommandBlocks(lines) {
1482
+ const compacted = [];
1483
+ const pendingCommands = [];
1484
+ for (let index = 0; index < lines.length;) {
1485
+ const currentLine = normalizedProcessLine(lines[index] ?? "").trim();
1486
+ if (isProcessCommandDisplayLine(currentLine)) {
1487
+ pendingCommands.push(lines[index] ?? "");
1488
+ index += 1;
1489
+ continue;
1490
+ }
1491
+ if (!currentLine && pendingCommands.length > 0 && nextNonBlankLineStartsCollapsedStatusBlock(lines, index + 1)) {
1492
+ index += 1;
1493
+ continue;
1494
+ }
1495
+ const collapsedBlock = collectStatusCollapsedOutputBlock(lines, index);
1496
+ if (collapsedBlock) {
1497
+ const commandLine = pendingCommands.shift();
1498
+ const summaryCommand = commandLine
1499
+ ? summaryCommandForCollapsedOutput(normalizedProcessLine(commandLine).trim(), collapsedBlock.collapsedLine)
1500
+ : null;
1501
+ compacted.push(summaryCommand
1502
+ ? `${collapsedBlock.statusLine} ${collapsedBlock.collapsedLine} (${summaryCommand})`
1503
+ : `${collapsedBlock.statusLine} ${collapsedBlock.collapsedLine}`);
1504
+ index = collapsedBlock.nextIndex;
1505
+ continue;
1506
+ }
1507
+ compacted.push(...pendingCommands);
1508
+ pendingCommands.length = 0;
1509
+ compacted.push(lines[index] ?? "");
1510
+ index += 1;
1511
+ }
1512
+ compacted.push(...pendingCommands);
1513
+ return compacted;
1514
+ }
1515
+ function compactReadSummaryRuns(lines) {
1516
+ const compacted = [];
1517
+ for (let index = 0; index < lines.length;) {
1518
+ const run = collectReadSummaryRun(lines, index);
1519
+ if (run.items.length >= READ_SUMMARY_RUN_COLLAPSE_MIN_ITEMS) {
1520
+ compacted.push(formatReadSummaryRun(run.items));
1521
+ index = run.nextIndex;
1522
+ continue;
1523
+ }
1524
+ compacted.push(lines[index] ?? "");
1525
+ index += 1;
1526
+ }
1527
+ return compacted;
1528
+ }
1529
+ function collectReadSummaryRun(lines, startIndex) {
1530
+ const items = [];
1531
+ let index = startIndex;
1532
+ while (index < lines.length) {
1533
+ const currentLine = normalizedProcessLine(lines[index] ?? "");
1534
+ const item = readSummaryLine(currentLine);
1535
+ if (!item) {
1536
+ if (items.length > 0 && !currentLine.trim()) {
1537
+ index += 1;
1538
+ continue;
1539
+ }
1540
+ break;
1541
+ }
1542
+ items.push(item);
1543
+ index += 1;
1544
+ }
1545
+ return { items, nextIndex: index };
1546
+ }
1547
+ function readSummaryLine(line) {
1548
+ const match = normalizedProcessLine(line)
1549
+ .trim()
1550
+ .match(/^((?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?):?\s+(Collapsed (?:code|source) output: (\d+) lines)(?:\s+\((\$ .+)\))?$/i);
1551
+ if (!match?.[4]) {
1552
+ return null;
1553
+ }
1554
+ const target = readTargetFromCommand(match[4]);
1555
+ if (!target) {
1556
+ return null;
1557
+ }
1558
+ return {
1559
+ target: formatReadTarget(target).replace(/:\d+-\d+$/, ""),
1560
+ lineCount: Number.parseInt(match[3] ?? "0", 10)
1561
+ };
1562
+ }
1563
+ function formatReadSummaryRun(items) {
1564
+ const totalLines = items.reduce((sum, item) => sum + item.lineCount, 0);
1565
+ const targets = compactReadSummaryTargets(items.map((item) => item.target));
1566
+ return `Collapsed read summaries: ${items.length} chunks, ${totalLines} lines (${targets})`;
1567
+ }
1568
+ function compactReadSummaryTargets(targets) {
1569
+ const uniqueTargets = Array.from(new Set(targets));
1570
+ const visibleTargets = uniqueTargets.slice(0, 3).join(", ");
1571
+ const remaining = uniqueTargets.length - 3;
1572
+ return remaining > 0 ? `${visibleTargets}, +${remaining} more` : visibleTargets;
1573
+ }
1574
+ function nextNonBlankLineStartsCollapsedStatusBlock(lines, startIndex) {
1575
+ for (let index = startIndex; index < lines.length; index += 1) {
1576
+ const trimmed = normalizedProcessLine(lines[index] ?? "").trim();
1577
+ if (!trimmed) {
1578
+ continue;
1579
+ }
1580
+ return collectStatusCollapsedOutputBlock(lines, index) !== null;
1581
+ }
1582
+ return false;
1583
+ }
1584
+ function collectStatusCollapsedOutputBlock(lines, startIndex) {
1585
+ const statusLine = normalizedProcessLine(lines[startIndex] ?? "").trim();
1586
+ if (!isProcessStatusLine(statusLine)) {
1587
+ return null;
1588
+ }
1589
+ for (let index = startIndex + 1; index < lines.length; index += 1) {
1590
+ const line = normalizedProcessLine(lines[index] ?? "");
1591
+ const trimmed = line.trim();
1592
+ if (isCollapsedOutputSummaryLine(trimmed)) {
1593
+ return {
1594
+ statusLine,
1595
+ collapsedLine: trimmed,
1596
+ nextIndex: index + 1
1597
+ };
1598
+ }
1599
+ if (!isIgnorableOutputPreludeBeforeCollapsedSummary(trimmed)) {
1600
+ return null;
1601
+ }
1602
+ }
1603
+ return null;
1604
+ }
1605
+ function isIgnorableOutputPreludeBeforeCollapsedSummary(line) {
1606
+ return !line || /^total\s+\d+$/i.test(line) || /^[dl-][rwx-]{9}@?\s+\d+\s+\S+\s+\S+\s+\d+\s+\w{3}\s+\d+\s+\d{1,2}:\d{2}\s+.+$/.test(line);
1607
+ }
1608
+ function summaryCommandForCollapsedOutput(commandLine, collapsedLine) {
1609
+ if (/^Collapsed file list output:/i.test(collapsedLine) && /^\$\s+pwd\s+&&\s+rg\s+--files\b/.test(commandLine)) {
1610
+ return "$ pwd && rg --files";
1611
+ }
1612
+ return commandLine;
1613
+ }
1614
+ function dropSuccessfulInternalLaunchCommands(lines) {
1615
+ const filtered = [];
1616
+ for (let index = 0; index < lines.length; index += 1) {
1617
+ const line = normalizedProcessLine(lines[index] ?? "").trim();
1618
+ if (isInternalWorkerLaunchCommand(line) && !launchCommandHasFailureContext(lines, index + 1)) {
1619
+ continue;
1620
+ }
1621
+ filtered.push(lines[index] ?? "");
1622
+ }
1623
+ return filtered;
1624
+ }
1625
+ function compactNoMatchSearchCommandBlocks(lines) {
1626
+ const compacted = [];
1627
+ for (let index = 0; index < lines.length;) {
1628
+ const commandLine = normalizedProcessLine(lines[index] ?? "").trim();
1629
+ const statusLine = normalizedProcessLine(lines[index + 1] ?? "").trim();
1630
+ const noMatch = noMatchSearchStatusLine(commandLine, statusLine);
1631
+ if (noMatch && nextLineHasNoCommandOutput(lines, index + 2)) {
1632
+ compacted.push(noMatch);
1633
+ index += 2;
1634
+ continue;
1635
+ }
1636
+ compacted.push(lines[index] ?? "");
1637
+ index += 1;
1638
+ }
1639
+ return compacted;
1640
+ }
1641
+ function compactDevServerFallbackBlocks(lines) {
1642
+ const compacted = [];
1643
+ for (let index = 0; index < lines.length;) {
1644
+ const line = normalizedProcessLine(lines[index] ?? "").trim();
1645
+ if (/^Unable to bind a local dev server in this environment\.?$/i.test(line)) {
1646
+ compacted.push("Dev server fallback: dist/index.html");
1647
+ index += 1;
1648
+ while (index < lines.length && normalizedProcessLine(lines[index] ?? "").trim() === "") {
1649
+ index += 1;
1650
+ }
1651
+ if (/^Built static app in dist\/?$/i.test(normalizedProcessLine(lines[index] ?? "").trim())) {
1652
+ index += 1;
1653
+ }
1654
+ while (index < lines.length && normalizedProcessLine(lines[index] ?? "").trim() === "") {
1655
+ index += 1;
1656
+ }
1657
+ if (isDevServerFallbackOpenLine(normalizedProcessLine(lines[index] ?? "").trim())) {
1658
+ index += 1;
1659
+ }
1660
+ continue;
1661
+ }
1662
+ compacted.push(lines[index] ?? "");
1663
+ index += 1;
1664
+ }
1665
+ return compacted;
1666
+ }
1667
+ function isDevServerFallbackOpenLine(line) {
1668
+ return /^Open\s+(?:file:\/\/\S+\/)?dist\/index\.html\b/i.test(line);
1669
+ }
1670
+ function compactBuildOutputBlocks(lines) {
1671
+ const compacted = [];
1672
+ for (let index = 0; index < lines.length;) {
1673
+ const line = normalizedProcessLine(lines[index] ?? "").trim();
1674
+ const nextIndex = nextNonBlankLineIndex(lines, index + 1);
1675
+ const next = nextIndex === null ? "" : normalizedProcessLine(lines[nextIndex] ?? "").trim();
1676
+ if (nextIndex !== null && isNpmBuildStatusCommand(line) && /^Built static app in dist\/?$/i.test(next)) {
1677
+ compacted.push(`${line} Build output: dist/`);
1678
+ index = nextIndex + 1;
1679
+ continue;
1680
+ }
1681
+ compacted.push(lines[index] ?? "");
1682
+ index += 1;
1683
+ }
1684
+ return compacted;
1685
+ }
1686
+ function nextNonBlankLineIndex(lines, startIndex) {
1687
+ for (let index = startIndex; index < lines.length; index += 1) {
1688
+ if (normalizedProcessLine(lines[index] ?? "").trim() !== "") {
1689
+ return index;
1690
+ }
1691
+ }
1692
+ return null;
1693
+ }
1694
+ function isNpmBuildStatusCommand(line) {
1695
+ return isProcessStatusWithCommandLine(line) && /\(\$\s+npm\s+run\s+build\b/.test(line);
1696
+ }
1697
+ function compactAnnotatedStatusCommandPairs(lines) {
1698
+ const compacted = [];
1699
+ const annotatedCommands = new Set(lines
1700
+ .map((line) => annotatedStatusCommand(normalizedProcessLine(line).trim()))
1701
+ .filter((command) => command !== null));
1702
+ for (let index = 0; index < lines.length; index += 1) {
1703
+ const commandLine = normalizedProcessLine(lines[index] ?? "").trim();
1704
+ if (isProcessCommandDisplayLine(commandLine) && annotatedCommands.has(commandLine)) {
1705
+ continue;
1706
+ }
1707
+ if (isProcessCommandDisplayLine(commandLine)) {
1708
+ const next = nextNonBlankLine(lines, index + 1);
1709
+ if (next && annotatedStatusCommand(next.line) === commandLine) {
1710
+ continue;
1711
+ }
1712
+ }
1713
+ compacted.push(lines[index] ?? "");
1714
+ }
1715
+ return compacted;
1716
+ }
1717
+ function compactNodeTestOutputBlocks(lines) {
1718
+ const compacted = [];
1719
+ for (let index = 0; index < lines.length;) {
1720
+ const block = collectNodeTestOutputBlock(lines, index);
1721
+ if (block && block.passCount >= NODE_TEST_OUTPUT_COLLAPSE_MIN_PASSES) {
1722
+ compacted.push(formatNodeTestOutputSummary(block));
1723
+ index = block.nextIndex;
1724
+ continue;
1725
+ }
1726
+ compacted.push(lines[index] ?? "");
1727
+ index += 1;
1728
+ }
1729
+ return compacted;
1730
+ }
1731
+ function collectNodeTestOutputBlock(lines, startIndex) {
1732
+ let index = startIndex;
1733
+ let passLines = 0;
1734
+ let totalCount = null;
1735
+ let passCount = null;
1736
+ let failCount = null;
1737
+ let skippedCount = 0;
1738
+ let duration = null;
1739
+ let sawInfoLine = false;
1740
+ while (index < lines.length) {
1741
+ const trimmed = normalizedProcessLine(lines[index] ?? "").trim();
1742
+ if (!trimmed && passLines > 0) {
1743
+ index += 1;
1744
+ continue;
1745
+ }
1746
+ if (isNodeTestPassLine(trimmed)) {
1747
+ passLines += 1;
1748
+ index += 1;
1749
+ continue;
1750
+ }
1751
+ if (isNodeTestFailureLine(trimmed)) {
1752
+ return null;
1753
+ }
1754
+ const info = parseNodeTestInfoLine(trimmed);
1755
+ if (info && passLines > 0) {
1756
+ sawInfoLine = true;
1757
+ if (info.key === "tests") {
1758
+ totalCount = info.value;
1759
+ }
1760
+ else if (info.key === "pass") {
1761
+ passCount = info.value;
1762
+ }
1763
+ else if (info.key === "fail" && info.value > 0) {
1764
+ return null;
1765
+ }
1766
+ else if (info.key === "fail") {
1767
+ failCount = info.value;
1768
+ }
1769
+ else if (info.key === "skipped" || info.key === "cancelled" || info.key === "todo") {
1770
+ skippedCount += info.value;
1771
+ }
1772
+ else if (info.key === "duration_ms") {
1773
+ duration = formatWorkerDuration(`${info.rawValue}ms`);
1774
+ }
1775
+ index += 1;
1776
+ continue;
1777
+ }
1778
+ break;
1779
+ }
1780
+ if (passLines === 0 || (!sawInfoLine && passLines < NODE_TEST_OUTPUT_COLLAPSE_MIN_PASSES)) {
1781
+ return null;
1782
+ }
1783
+ if (failCount !== null && failCount > 0) {
1784
+ return null;
1785
+ }
1786
+ const resolvedPassCount = passCount ?? passLines;
1787
+ return {
1788
+ passCount: resolvedPassCount,
1789
+ totalCount: totalCount ?? resolvedPassCount,
1790
+ skippedCount,
1791
+ duration,
1792
+ nextIndex: index
1793
+ };
1794
+ }
1795
+ function isNodeTestPassLine(line) {
1796
+ return /^✔\s+.+\(\d+(?:\.\d+)?ms\)$/.test(line);
1797
+ }
1798
+ function isNodeTestFailureLine(line) {
1799
+ return /^✖\s+/.test(line) || /^not ok\b/i.test(line);
1800
+ }
1801
+ function parseNodeTestInfoLine(line) {
1802
+ const match = line.match(/^ℹ\s+(tests|suites|pass|fail|cancelled|skipped|todo|duration_ms)\s+(\d+(?:\.\d+)?)$/);
1803
+ if (!match) {
1804
+ return null;
1805
+ }
1806
+ return {
1807
+ key: (match[1] ?? "").toLowerCase(),
1808
+ value: Number.parseFloat(match[2] ?? "0"),
1809
+ rawValue: match[2] ?? "0"
1810
+ };
1811
+ }
1812
+ function formatNodeTestOutputSummary(block) {
1813
+ const total = Math.max(block.totalCount, block.passCount);
1814
+ const skipped = block.skippedCount > 0 ? `, ${block.skippedCount} skipped` : "";
1815
+ const duration = block.duration ? ` in ${block.duration}` : "";
1816
+ return `Node tests passed: ${block.passCount}/${total}${skipped}${duration}`;
1817
+ }
1818
+ function formatWorkerDuration(value) {
1819
+ const trimmed = value.trim();
1820
+ const milliseconds = trimmed.match(/^(\d+(?:\.\d+)?)ms$/i);
1821
+ if (milliseconds) {
1822
+ return `${Math.max(0, Math.round(Number.parseFloat(milliseconds[1] ?? "0")))}ms`;
1823
+ }
1824
+ return trimmed;
1825
+ }
1826
+ function nextNonBlankLine(lines, startIndex) {
1827
+ for (let index = startIndex; index < lines.length; index += 1) {
1828
+ const line = normalizedProcessLine(lines[index] ?? "").trim();
1829
+ if (line) {
1830
+ return { line, index };
1831
+ }
1832
+ }
1833
+ return null;
1834
+ }
1835
+ function annotatedStatusCommand(line) {
1836
+ const match = line.match(/^(?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?(?::)?\s+\((\$ .+)\)(?:\s+Build output:\s*.+)?$/i);
1837
+ return match?.[1] ?? null;
1838
+ }
1839
+ function noMatchSearchStatusLine(commandLine, statusLine) {
1840
+ if (!isNoMatchSearchCommand(commandLine)) {
1841
+ return null;
1842
+ }
1843
+ const match = statusLine.match(/^exited\s+1\s+in\s+(\d+(?:ms|s|m)?):?$/i);
1844
+ return match ? `No matches in ${match[1] ?? ""} (${commandLine})`.trim() : null;
1845
+ }
1846
+ function isNoMatchSearchCommand(commandLine) {
1847
+ return /^\$\s+(?:rg|grep|git\s+grep)\b/.test(commandLine);
1848
+ }
1849
+ function nextLineHasNoCommandOutput(lines, startIndex) {
1850
+ for (let index = startIndex; index < lines.length; index += 1) {
1851
+ const trimmed = normalizedProcessLine(lines[index] ?? "").trim();
1852
+ if (!trimmed) {
1853
+ continue;
1854
+ }
1855
+ return (isProcessCommandDisplayLine(trimmed) ||
1856
+ isProcessStatusLine(trimmed) ||
1857
+ isNoMatchesSummaryLine(trimmed) ||
1858
+ trimmed === "exec" ||
1859
+ isAssistantNarrativeMarker(trimmed) ||
1860
+ isDiffStartLine(trimmed));
1861
+ }
1862
+ return true;
1863
+ }
1864
+ function isInternalWorkerLaunchCommand(line) {
1865
+ return (/^\$\s+codex\s+exec\b/.test(line) ||
1866
+ /^\$\s+claude\s+--print\b/.test(line));
1867
+ }
1868
+ function launchCommandHasFailureContext(lines, startIndex) {
1869
+ for (let index = startIndex; index < Math.min(lines.length, startIndex + 5); index += 1) {
1870
+ const trimmed = normalizedProcessLine(lines[index] ?? "").trim();
1871
+ if (!trimmed) {
1872
+ continue;
1873
+ }
1874
+ if (trimmed.startsWith("$ ")) {
1875
+ return false;
1876
+ }
1877
+ return /^(error|Error|failed|Failed|Usage:|Not inside|permission denied|command not found)\b/.test(trimmed);
1878
+ }
1879
+ return false;
1880
+ }
1881
+ function isProcessCommandDisplayLine(line) {
1882
+ return line.startsWith("$ ");
1883
+ }
1884
+ function isProcessStatusLine(line) {
1885
+ return /^(?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?(?::)?$/i.test(line);
1886
+ }
1887
+ function isProcessStatusWithCommandLine(line) {
1888
+ return /^(?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?(?::)?\s+\(\$ .+\)$/i.test(line);
1889
+ }
1890
+ function isBuildOutputSummaryLine(line) {
1891
+ return /^(?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?(?::)?\s+\(\$ .+\)\s+Build output:\s*.+$/i.test(line);
1892
+ }
1893
+ function isNoMatchesSummaryLine(line) {
1894
+ return /^No matches in \d+(?:ms|s|m)?(?:\s+\(\$ .+\))?$/i.test(line);
1895
+ }
1896
+ function isCollapsedOutputSummaryLine(line) {
1897
+ return /^Collapsed (?:code|file list|source) output: \d+ (?:lines|paths)$/i.test(line);
1898
+ }
1899
+ function isCollapsedOutputSummaryDisplayLine(line) {
1900
+ return (/\bCollapsed (?:code|file list|source) output: \d+ (?:lines|paths)\b/i.test(line) ||
1901
+ /^Collapsed read summaries: \d+ chunks, \d+ lines\b/i.test(line) ||
1902
+ /^Node tests passed: \d+\/\d+/i.test(line) ||
1903
+ /^Dev server fallback:/i.test(line));
1904
+ }
1905
+ function collectFileListOutputRun(lines, startIndex) {
1906
+ let index = startIndex;
1907
+ let pathLines = 0;
1908
+ let consumedBlank = false;
1909
+ while (index < lines.length) {
1910
+ const line = normalizedProcessLine(lines[index] ?? "");
1911
+ const trimmed = line.trim();
1912
+ if (trimmed && isFileListOutputLine(trimmed)) {
1913
+ pathLines += 1;
1914
+ consumedBlank = false;
1915
+ index += 1;
1916
+ continue;
1917
+ }
1918
+ if (!trimmed && pathLines > 0) {
1919
+ consumedBlank = true;
1920
+ index += 1;
1921
+ continue;
1922
+ }
1923
+ break;
1924
+ }
1925
+ if (pathLines === 0) {
1926
+ return null;
1927
+ }
1928
+ if (consumedBlank) {
1929
+ index -= 1;
1930
+ }
1931
+ return {
1932
+ pathLines,
1933
+ nextIndex: index
1934
+ };
1935
+ }
1936
+ function isFileListOutputLine(line) {
1937
+ if (line.startsWith("$ ") ||
1938
+ isActionableProcessBoundary(line) ||
1939
+ isDiffStartLine(line) ||
1940
+ isDiffHunkLine(line)) {
1941
+ return false;
1942
+ }
1943
+ const pathSegment = String.raw `[A-Za-z0-9._@+-]+`;
1944
+ const pathPattern = new RegExp(String.raw `^(?:/)?${pathSegment}(?:/${pathSegment})+(?:\s+[A-Za-z0-9._@+-]+)?$`);
1945
+ if (pathPattern.test(line)) {
1946
+ return true;
1947
+ }
1948
+ return /^[A-Za-z0-9._@+-]+\.(?:cjs|css|diff|html|js|json|jsonl|lock|md|mjs|toml|ts|tsx|txt|yml|yaml)$/.test(line);
1949
+ }
1950
+ function collectSourceOutputRun(lines, startIndex) {
1951
+ let index = startIndex;
1952
+ let sourceLines = 0;
1953
+ let consumedBlank = false;
1954
+ while (index < lines.length) {
1955
+ const line = normalizedProcessLine(lines[index] ?? "");
1956
+ const trimmed = line.trim();
1957
+ if (workerOutputSourceColumns(line)) {
1958
+ sourceLines += 1;
1959
+ consumedBlank = false;
1960
+ index += 1;
1961
+ continue;
1962
+ }
1963
+ if (!trimmed && sourceLines > 0) {
1964
+ consumedBlank = true;
1965
+ index += 1;
1966
+ continue;
1967
+ }
1968
+ break;
1969
+ }
1970
+ if (sourceLines === 0) {
1971
+ return null;
1972
+ }
1973
+ if (consumedBlank) {
1974
+ index -= 1;
1975
+ }
1976
+ return {
1977
+ sourceLines,
1978
+ nextIndex: index
1979
+ };
1980
+ }
1981
+ function collectCodeOutputRun(lines, startIndex) {
1982
+ let index = startIndex;
1983
+ let codeLines = 0;
1984
+ let consumedBlank = false;
1985
+ while (index < lines.length) {
1986
+ const line = normalizedProcessLine(lines[index] ?? "");
1987
+ const trimmed = line.trim();
1988
+ if (trimmed && isProcessCodeOutputLine(trimmed)) {
1989
+ codeLines += 1;
1990
+ consumedBlank = false;
1991
+ index += 1;
1992
+ continue;
1993
+ }
1994
+ if (!trimmed && codeLines > 0) {
1995
+ consumedBlank = true;
1996
+ index += 1;
1997
+ continue;
1998
+ }
1999
+ break;
2000
+ }
2001
+ if (codeLines === 0) {
2002
+ return null;
2003
+ }
2004
+ if (consumedBlank) {
2005
+ index -= 1;
2006
+ }
2007
+ return {
2008
+ codeLines,
2009
+ nextIndex: index
2010
+ };
2011
+ }
2012
+ function isProcessCodeOutputLine(line) {
2013
+ return isCodeLikeProcessLine(line) || isJavaScriptLikeProcessLine(line) || isCssLikeProcessLine(line);
2014
+ }
2015
+ function isCssLikeProcessLine(line) {
2016
+ const selectorPart = String.raw `[.#]?[A-Za-z0-9_-]+(?:\[[^\]]+\])?(?::[A-Za-z-]+)?`;
2017
+ return (/^@media\b.*\{$/.test(line) ||
2018
+ /^:[A-Za-z-]+(?:,|\s*\{)$/.test(line) ||
2019
+ /^\*,$/.test(line) ||
2020
+ new RegExp(`^(?:${selectorPart}|\\*)(?:::[A-Za-z-]+)?(?:\\s+${selectorPart})*(?:,|\\s*\\{)$`).test(line) ||
2021
+ /^[A-Za-z-]+:\s*$/.test(line) ||
2022
+ /^--[A-Za-z0-9-]+:\s*.+;?$/.test(line) ||
2023
+ /^(?:radial-gradient|linear-gradient)\(.*[;,]$/.test(line) ||
2024
+ /^-?[A-Za-z][A-Za-z0-9-]*,$/.test(line) ||
2025
+ /^"[^"]+",$/.test(line) ||
2026
+ /^[A-Za-z][A-Za-z0-9-]*;$/.test(line));
2027
+ }
2028
+ function isJavaScriptLikeProcessLine(line) {
2029
+ return (/^(?:const|let|var|import|export|await|return|if|else|for|while|switch|try|catch|finally|function|class|new)\b/.test(line) ||
2030
+ /^\}\s*else\b/.test(line) ||
2031
+ /^\.[A-Za-z_$][\w$]*(?:\s*\(|\.)/.test(line) ||
2032
+ /^[A-Za-z_$][\w$.[\]'"]*\s*(?:[+\-*/%]?=|\(|\.)/.test(line) ||
2033
+ /^[A-Za-z_$][\w$]*,?$/.test(line) ||
2034
+ /^[A-Za-z_$][\w$]*(?:\[[^\]]+\])+\s*=/.test(line) ||
2035
+ /^(?:\d+|["']?[A-Za-z_$][\w$-]*["']?):\s*.+,?$/.test(line) ||
2036
+ /^\}\s+from\s+["']/.test(line) ||
2037
+ /^#[A-Za-z_$][\w$]*(?:\([^)]*\))?\s*[;{]?$/.test(line) ||
2038
+ /^\*::[A-Za-z-]+(?:,|\s*\{)?$/.test(line) ||
2039
+ /^\[[\s\S]*[,;\]]?$/.test(line) ||
2040
+ /^\.\.\.[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*(?:\([^)]*\))?[,;]?$/.test(line) ||
2041
+ /^[A-Za-z_$][\w$]*\?\.\(/.test(line) ||
2042
+ /^["'`].*[,;]?$/.test(line) ||
2043
+ /^[{[(]+[,;)]*$/.test(line) ||
2044
+ /^[}\])]+[,;)]*$/.test(line) ||
2045
+ /^[);]+$/.test(line) ||
2046
+ /=>/.test(line));
2047
+ }
2048
+ function skipNoisyProcessLine(rawLines, startIndex) {
2049
+ const trimmed = normalizedProcessLine(rawLines[startIndex] ?? "").trim();
2050
+ if (/^tokens used$/i.test(trimmed)) {
2051
+ let index = startIndex + 1;
2052
+ while (index < rawLines.length && /^\d[\d,]*$/.test(normalizedProcessLine(rawLines[index] ?? "").trim())) {
2053
+ index += 1;
2054
+ }
2055
+ return index;
2056
+ }
2057
+ return startIndex + 1;
2058
+ }
2059
+ function shouldDropNoisyProcessLine(trimmed, line) {
2060
+ if (!trimmed) {
2061
+ return false;
2062
+ }
2063
+ return (/^tokens used$/i.test(trimmed) ||
2064
+ trimmed.includes("codex_models_manager::manager: failed to refresh available models") ||
2065
+ (trimmed.includes("failed to decode models response") && trimmed.includes("body: {\"data\"")) ||
2066
+ trimmed.startsWith("body: {\"data\"") ||
2067
+ /^202\d-\d\d-\d\dT.*codex_models_manager::manager/.test(trimmed) ||
2068
+ /^reasoning summaries:/i.test(trimmed) ||
2069
+ /^session id:/i.test(trimmed) ||
2070
+ /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]$/.test(trimmed) ||
2071
+ line === "--------");
2072
+ }
2073
+ function isNpmLifecycleEchoStart(trimmed) {
2074
+ return /^>\s+(?:@[^/\s]+\/)?[^@\s]+@\S+\s+\S+/.test(trimmed);
2075
+ }
2076
+ function npmLifecycleCommandAfterStatus(rawLines, statusIndex) {
2077
+ const statusLine = normalizedProcessLine(rawLines[statusIndex] ?? "").trim();
2078
+ if (!isProcessStatusLine(statusLine)) {
2079
+ return null;
2080
+ }
2081
+ let index = statusIndex + 1;
2082
+ while (index < rawLines.length && !normalizedProcessLine(rawLines[index] ?? "").trim()) {
2083
+ index += 1;
2084
+ }
2085
+ const lifecycleLine = normalizedProcessLine(rawLines[index] ?? "").trim();
2086
+ const match = lifecycleLine.match(/^>\s+(?:@[^/\s]+\/)?[^@\s]+@\S+\s+([^\s]+)$/);
2087
+ if (!match) {
2088
+ return null;
2089
+ }
2090
+ const scriptName = match[1] ?? "";
2091
+ return scriptName === "test" ? "npm test" : `npm run ${scriptName}`;
2092
+ }
2093
+ function skipNpmLifecycleEchoBlock(rawLines, startIndex) {
2094
+ let index = startIndex + 1;
2095
+ const nextLine = normalizedProcessLine(rawLines[index] ?? "").trim();
2096
+ if (/^>\s+\S+/.test(nextLine) && !isNpmLifecycleEchoStart(nextLine)) {
2097
+ index += 1;
2098
+ }
2099
+ if (!normalizedProcessLine(rawLines[index] ?? "").trim()) {
2100
+ index += 1;
2101
+ }
2102
+ return index;
2103
+ }
2104
+ function isCodexStartupPreamble(trimmed) {
2105
+ return /^OpenAI Codex v/i.test(trimmed);
2106
+ }
2107
+ function skipCodexStartupPreamble(rawLines, startIndex) {
2108
+ let index = startIndex + 1;
2109
+ while (index < rawLines.length) {
2110
+ const trimmed = normalizedProcessLine(rawLines[index] ?? "").trim();
2111
+ if (trimmed === "user") {
2112
+ return index + 1;
2113
+ }
2114
+ if (isRolePromptTranscriptStart(trimmed) || isActionableProcessBoundary(trimmed) || isDiffStartLine(trimmed)) {
2115
+ return index;
2116
+ }
2117
+ index += 1;
2118
+ }
2119
+ return index;
2120
+ }
2121
+ function isRolePromptTranscriptStart(trimmed) {
2122
+ return /^#?\s*Role:\s+(Actor|Critic|Judge)\b/i.test(trimmed);
2123
+ }
2124
+ function skipRolePromptTranscript(rawLines, startIndex) {
2125
+ let index = startIndex + 1;
2126
+ let inUserRequest = false;
2127
+ let sawUserRequest = false;
2128
+ while (index < rawLines.length) {
2129
+ const line = normalizedProcessLine(rawLines[index] ?? "");
2130
+ const trimmed = line.trim();
2131
+ if (shouldDropNoisyProcessLine(trimmed, line)) {
2132
+ index = skipNoisyProcessLine(rawLines, index);
2133
+ continue;
2134
+ }
2135
+ if (/^User request:\s*$/i.test(trimmed)) {
2136
+ sawUserRequest = true;
2137
+ inUserRequest = true;
2138
+ index += 1;
2139
+ continue;
2140
+ }
2141
+ if (inUserRequest) {
2142
+ if (!trimmed) {
2143
+ inUserRequest = false;
2144
+ }
2145
+ index += 1;
2146
+ continue;
2147
+ }
2148
+ if ((sawUserRequest || trimmed === "codex" || trimmed === "assistant") && isActionableProcessBoundary(trimmed)) {
2149
+ return index;
2150
+ }
2151
+ if (isDiffStartLine(trimmed)) {
2152
+ return index;
2153
+ }
2154
+ index += 1;
2155
+ }
2156
+ return index;
2157
+ }
2158
+ function isActionableProcessBoundary(trimmed) {
2159
+ if (!trimmed) {
2160
+ return false;
2161
+ }
2162
+ return (trimmed.startsWith("$ ") ||
2163
+ trimmed === "exec" ||
2164
+ /^\/.*\s+in\s+\/.+/.test(trimmed) ||
2165
+ /^(succeeded|failed) in \d+/i.test(trimmed) ||
2166
+ /^(ERROR|Error|error|Traceback|Exception|Failed|failed|Failure|failure)\b/.test(trimmed) ||
2167
+ /^(✓|✔|\[ok\]|\[done\])/.test(trimmed) ||
2168
+ /\b(passed|success|succeeded|complete)\b/i.test(trimmed));
2169
+ }
2170
+ function collapseProcessOutputLines(lines) {
2171
+ const collapsed = [];
2172
+ let previousContent = "";
2173
+ let blankPending = false;
2174
+ for (const rawLine of lines) {
2175
+ const line = rawLine.replace(/\r$/, "");
2176
+ const trimmed = normalizedProcessLine(line).trim();
2177
+ if (!trimmed) {
2178
+ blankPending = collapsed.length > 0;
2179
+ continue;
2180
+ }
2181
+ if (trimmed === previousContent) {
2182
+ blankPending = false;
2183
+ continue;
2184
+ }
2185
+ if (blankPending) {
2186
+ collapsed.push("");
2187
+ blankPending = false;
2188
+ }
2189
+ collapsed.push(line);
2190
+ previousContent = trimmed;
2191
+ }
2192
+ return collapsed;
2193
+ }
2194
+ function collectEmbeddedDiffBlock(rawLines, startIndex) {
2195
+ const lines = [];
2196
+ let index = startIndex;
2197
+ let insideHunk = false;
2198
+ let hunkCounts = null;
2199
+ while (index < rawLines.length) {
2200
+ const line = stripAnsiForDiff(rawLines[index] ?? "");
2201
+ const trimmedStart = line.trimStart();
2202
+ if (lines.length === 0) {
2203
+ if (!isDiffStartLine(trimmedStart)) {
2204
+ break;
2205
+ }
2206
+ lines.push(trimmedStart);
2207
+ index += 1;
2208
+ continue;
2209
+ }
2210
+ if (isDiffStartLine(trimmedStart)) {
2211
+ lines.push(trimmedStart);
2212
+ insideHunk = false;
2213
+ hunkCounts = null;
2214
+ index += 1;
2215
+ continue;
2216
+ }
2217
+ if (isDiffHunkLine(trimmedStart)) {
2218
+ lines.push(trimmedStart);
2219
+ insideHunk = true;
2220
+ hunkCounts = parseHunkCounts(trimmedStart);
2221
+ index += 1;
2222
+ continue;
2223
+ }
2224
+ if (isDiffMetaLine(trimmedStart)) {
2225
+ lines.push(trimmedStart);
2226
+ index += 1;
2227
+ continue;
2228
+ }
2229
+ if (insideHunk && isDiffBodyLine(line)) {
2230
+ if (hunkCounts && hunkCounts.oldRemaining <= 0 && hunkCounts.newRemaining <= 0) {
2231
+ break;
2232
+ }
2233
+ lines.push(line);
2234
+ hunkCounts = consumeHunkLine(line, hunkCounts);
2235
+ index += 1;
2236
+ continue;
2237
+ }
2238
+ break;
2239
+ }
2240
+ return { lines, nextIndex: index };
2241
+ }
2242
+ function collectBareDiffHunkBlock(rawLines, startIndex) {
2243
+ let index = startIndex;
2244
+ let insideHunk = false;
2245
+ let hunkCounts = null;
2246
+ while (index < rawLines.length) {
2247
+ const line = stripAnsiForDiff(rawLines[index] ?? "");
2248
+ const trimmedStart = line.trimStart();
2249
+ if (isDiffHunkLine(trimmedStart)) {
2250
+ insideHunk = true;
2251
+ hunkCounts = parseHunkCounts(trimmedStart);
2252
+ index += 1;
2253
+ continue;
2254
+ }
2255
+ if (insideHunk && isDiffBodyLine(line)) {
2256
+ if (hunkCounts && hunkCounts.oldRemaining <= 0 && hunkCounts.newRemaining <= 0) {
2257
+ break;
2258
+ }
2259
+ hunkCounts = consumeHunkLine(line, hunkCounts);
2260
+ index += 1;
2261
+ continue;
2262
+ }
2263
+ if (insideHunk && isDiffMetaLine(trimmedStart)) {
2264
+ index += 1;
2265
+ continue;
2266
+ }
2267
+ break;
2268
+ }
2269
+ return { nextIndex: index };
2270
+ }
2271
+ function collectBareProcessDiffBodyRun(rawLines, startIndex) {
2272
+ let index = startIndex;
2273
+ let bodyLines = 0;
2274
+ while (index < rawLines.length) {
2275
+ const line = stripAnsiForDiff(rawLines[index] ?? "").trimEnd();
2276
+ if (!isBareProcessDiffBodyLine(line)) {
2277
+ break;
2278
+ }
2279
+ bodyLines += 1;
2280
+ index += 1;
2281
+ }
2282
+ return { bodyLines, nextIndex: index };
2283
+ }
2284
+ function isBareProcessDiffBodyLine(line) {
2285
+ const trimmedStart = line.trimStart();
2286
+ if (trimmedStart.startsWith("+++") || trimmedStart.startsWith("---")) {
2287
+ return false;
2288
+ }
2289
+ if (/^[-*]\s+\S/.test(trimmedStart)) {
2290
+ return false;
2291
+ }
2292
+ return /^[+-](?:\S|\s{2,}|$)/.test(trimmedStart);
2293
+ }
2294
+ function collectProcessCodeFragmentRun(rawLines, startIndex) {
2295
+ let index = startIndex;
2296
+ let codeLines = 0;
2297
+ while (index < rawLines.length) {
2298
+ const trimmed = normalizedProcessLine(rawLines[index] ?? "").trim();
2299
+ if (!trimmed || !isProcessCodeOutputLine(trimmed)) {
2300
+ break;
2301
+ }
2302
+ codeLines += 1;
2303
+ index += 1;
2304
+ }
2305
+ return { codeLines, nextIndex: index };
2306
+ }
2307
+ function nextSignificantProcessLineStartsDiff(rawLines, startIndex) {
2308
+ for (let index = startIndex; index < rawLines.length; index += 1) {
2309
+ const line = normalizedProcessLine(rawLines[index] ?? "");
2310
+ const trimmed = line.trim();
2311
+ if (!trimmed ||
2312
+ isBareProcessDiffBodyLine(line) ||
2313
+ isProcessCodeOutputLine(trimmed) ||
2314
+ isCollapsedOutputSummaryLine(trimmed)) {
2315
+ continue;
2316
+ }
2317
+ return isDiffStartLine(trimmed);
2318
+ }
2319
+ return false;
2320
+ }
2321
+ function summarizeProcessDiffBlock(diffLines) {
2322
+ const files = [];
2323
+ let currentFile = null;
2324
+ let insideHunk = false;
2325
+ let hunkCounts = null;
2326
+ let bodyLines = 0;
2327
+ const flushCurrentFile = () => {
2328
+ if (currentFile) {
2329
+ files.push(currentFile);
2330
+ currentFile = null;
2331
+ }
2332
+ };
2333
+ for (const rawLine of diffLines) {
2334
+ const line = stripAnsiForDiff(rawLine);
2335
+ const trimmedStart = line.trimStart();
2336
+ const fileTitle = renderDiffFileTitle(trimmedStart);
2337
+ if (fileTitle) {
2338
+ flushCurrentFile();
2339
+ currentFile = { title: fileTitle, added: 0, removed: 0 };
2340
+ insideHunk = false;
2341
+ hunkCounts = null;
2342
+ continue;
2343
+ }
2344
+ if (parseHunkStart(trimmedStart)) {
2345
+ insideHunk = true;
2346
+ hunkCounts = parseHunkCounts(trimmedStart);
2347
+ continue;
2348
+ }
2349
+ if (!currentFile || !insideHunk || !isDiffBodyLine(line)) {
2350
+ continue;
2351
+ }
2352
+ if (hunkCounts && hunkCounts.oldRemaining <= 0 && hunkCounts.newRemaining <= 0) {
2353
+ insideHunk = false;
2354
+ continue;
2355
+ }
2356
+ if (line.startsWith("+")) {
2357
+ currentFile.added += 1;
2358
+ }
2359
+ else if (line.startsWith("-")) {
2360
+ currentFile.removed += 1;
2361
+ }
2362
+ bodyLines += 1;
2363
+ hunkCounts = consumeHunkLine(line, hunkCounts);
2364
+ }
2365
+ flushCurrentFile();
2366
+ if (files.length === 0) {
2367
+ return null;
2368
+ }
2369
+ return {
2370
+ files,
2371
+ added: files.reduce((sum, file) => sum + file.added, 0),
2372
+ removed: files.reduce((sum, file) => sum + file.removed, 0),
2373
+ bodyLines
2374
+ };
2375
+ }
2376
+ function shouldCollapseProcessDiffBlock(summary) {
2377
+ return (summary.bodyLines >= PROCESS_DIFF_COLLAPSE_MIN_BODY_LINES ||
2378
+ summary.files.length >= PROCESS_DIFF_COLLAPSE_MIN_FILES);
2379
+ }
2380
+ function renderCollapsedProcessDiffContent(summary) {
2381
+ const changedFiles = summary.files.filter((file) => file.added > 0 || file.removed > 0);
2382
+ if (changedFiles.length === 0) {
2383
+ return [];
2384
+ }
2385
+ const changedSummary = {
2386
+ files: changedFiles,
2387
+ added: changedFiles.reduce((sum, file) => sum + file.added, 0),
2388
+ removed: changedFiles.reduce((sum, file) => sum + file.removed, 0)
2389
+ };
2390
+ return [
2391
+ {
2392
+ kind: "summary",
2393
+ text: `Collapsed diff: ${changedSummary.files.length} ${pluralizeFile(changedSummary.files.length)}, added ${changedSummary.added} ${pluralizeLine(changedSummary.added)}, removed ${changedSummary.removed} ${pluralizeLine(changedSummary.removed)} (${formatProcessDiffSummaryTargets(changedFiles)})`
2394
+ }
2395
+ ];
2396
+ }
2397
+ function formatProcessDiffSummaryTargets(files) {
2398
+ const targets = files.map((file) => diffTitlePath(file.title));
2399
+ const visibleTargets = targets.slice(0, 4).join(", ");
2400
+ const remaining = targets.length - 4;
2401
+ return remaining > 0 ? `${visibleTargets}, +${remaining} more` : visibleTargets;
2402
+ }
2403
+ function skipProcessDiffTailContext(rawLines, startIndex) {
2404
+ let index = startIndex;
2405
+ let skippedTail = false;
2406
+ while (index < rawLines.length) {
2407
+ const line = normalizedProcessLine(rawLines[index] ?? "");
2408
+ const trimmed = line.trim();
2409
+ if (!trimmed && (skippedTail || nextLineLooksLikeProcessDiffTail(rawLines, index + 1))) {
2410
+ index += 1;
2411
+ continue;
2412
+ }
2413
+ if (!trimmed ||
2414
+ isHardProcessBoundary(trimmed) ||
2415
+ isDiffStartLine(trimmed) ||
2416
+ isAssistantNarrativeMarker(trimmed) ||
2417
+ isRolePromptTranscriptStart(trimmed)) {
2418
+ return index;
2419
+ }
2420
+ if (line.startsWith(" ") && isProcessCodeOutputLine(trimmed)) {
2421
+ skippedTail = true;
2422
+ index += 1;
2423
+ continue;
2424
+ }
2425
+ return index;
2426
+ }
2427
+ return index;
2428
+ }
2429
+ function nextLineLooksLikeProcessDiffTail(rawLines, startIndex) {
2430
+ for (let index = startIndex; index < rawLines.length; index += 1) {
2431
+ const line = normalizedProcessLine(rawLines[index] ?? "");
2432
+ const trimmed = line.trim();
2433
+ if (!trimmed) {
2434
+ continue;
2435
+ }
2436
+ return line.startsWith(" ") && isProcessCodeOutputLine(trimmed);
2437
+ }
2438
+ return false;
2439
+ }
2440
+ function isHardProcessBoundary(trimmed) {
2441
+ return (trimmed.startsWith("$ ") ||
2442
+ trimmed === "exec" ||
2443
+ parseShellExecCommand(trimmed) !== null ||
2444
+ isProcessStatusLine(trimmed) ||
2445
+ isProcessStatusWithCommandLine(trimmed) ||
2446
+ /^(ERROR|Error|error|Traceback|Exception|Failed|failed|Failure|failure)\b/.test(trimmed));
2447
+ }
2448
+ function renderDiffContent(text) {
2449
+ const rendered = [];
2450
+ let currentFile = null;
2451
+ let oldLineNumber = 0;
2452
+ let newLineNumber = 0;
2453
+ let insideHunk = false;
2454
+ const flushCurrentFile = () => {
2455
+ if (!currentFile) {
2456
+ return;
2457
+ }
2458
+ rendered.push({ kind: "diff-file", text: currentFile.title });
2459
+ rendered.push({ kind: "diff-summary", text: summarizeDiffStats(currentFile.added, currentFile.removed) });
2460
+ rendered.push(...currentFile.lines);
2461
+ currentFile = null;
2462
+ };
2463
+ for (const rawLine of text.split("\n")) {
2464
+ const line = stripAnsiForDiff(rawLine);
2465
+ const trimmedStart = line.trimStart();
2466
+ const isBlankContextLine = Boolean(currentFile && insideHunk && line.startsWith(" "));
2467
+ if (!line.trim() && !isBlankContextLine) {
2468
+ continue;
2469
+ }
2470
+ const fileTitle = renderDiffFileTitle(trimmedStart);
2471
+ if (fileTitle) {
2472
+ flushCurrentFile();
2473
+ currentFile = { title: fileTitle, added: 0, removed: 0, lines: [] };
2474
+ oldLineNumber = 0;
2475
+ newLineNumber = 0;
2476
+ insideHunk = false;
2477
+ continue;
2478
+ }
2479
+ const hunkStart = parseHunkStart(trimmedStart);
2480
+ if (hunkStart) {
2481
+ oldLineNumber = hunkStart.oldStart;
2482
+ newLineNumber = hunkStart.newStart;
2483
+ insideHunk = true;
2484
+ continue;
2485
+ }
2486
+ if (currentFile && insideHunk && line.startsWith("+")) {
2487
+ currentFile.added += 1;
2488
+ currentFile.lines.push({
2489
+ kind: "diff-add",
2490
+ text: formatDiffCodeLine(newLineNumber, "+", line.slice(1))
2491
+ });
2492
+ newLineNumber += 1;
2493
+ continue;
2494
+ }
2495
+ if (currentFile && insideHunk && line.startsWith("-")) {
2496
+ currentFile.removed += 1;
2497
+ currentFile.lines.push({
2498
+ kind: "diff-remove",
2499
+ text: formatDiffCodeLine(oldLineNumber, "-", line.slice(1))
2500
+ });
2501
+ oldLineNumber += 1;
2502
+ continue;
2503
+ }
2504
+ if (currentFile && insideHunk && line.startsWith(" ")) {
2505
+ currentFile.lines.push({
2506
+ kind: "diff-context",
2507
+ text: formatDiffCodeLine(newLineNumber, " ", line.slice(1))
2508
+ });
2509
+ oldLineNumber += 1;
2510
+ newLineNumber += 1;
2511
+ continue;
2512
+ }
2513
+ if (isDiffMetaLine(trimmedStart)) {
2514
+ continue;
2515
+ }
2516
+ if (currentFile) {
2517
+ currentFile.lines.push({ kind: "diff-meta", text: trimmedStart });
2518
+ continue;
2519
+ }
2520
+ rendered.push({ kind: "diff-meta", text: trimmedStart });
2521
+ }
2522
+ flushCurrentFile();
2523
+ return rendered;
2524
+ }
2525
+ function renderDiffFileLine(line) {
2526
+ const title = renderDiffFileTitle(line);
2527
+ if (!title) {
2528
+ return null;
2529
+ }
2530
+ return {
2531
+ kind: "diff-file",
2532
+ text: title
2533
+ };
2534
+ }
2535
+ function renderDiffFileTitle(line) {
2536
+ const match = line.match(/^diff --git a\/(.+) b\/(.+)$/);
2537
+ const unifiedMatch = line.match(/^diff -u a\/(.+) b\/(.+)$/);
2538
+ if (!match && !unifiedMatch) {
2539
+ return null;
2540
+ }
2541
+ const oldPath = match?.[1] ?? unifiedMatch?.[1] ?? "";
2542
+ const newPath = match?.[2] ?? unifiedMatch?.[2] ?? "";
2543
+ const oldDisplayPath = formatDiffDisplayPath(oldPath);
2544
+ const newDisplayPath = formatDiffDisplayPath(newPath);
2545
+ if (oldDisplayPath === newDisplayPath) {
2546
+ return `Update(${newDisplayPath})`;
2547
+ }
2548
+ return `Update(${oldDisplayPath} -> ${newDisplayPath})`;
2549
+ }
2550
+ function formatDiffDisplayPath(path) {
2551
+ const workerArtifact = path.match(/^\.parallel-codex\/sessions\/[^/]+\/[^/]+\/(.+)$/);
2552
+ if (workerArtifact) {
2553
+ return workerArtifact[1] ?? path;
2554
+ }
2555
+ return path;
2556
+ }
2557
+ function isDiffStartLine(line) {
2558
+ return line.startsWith("diff --git ") || line.startsWith("diff -u ");
2559
+ }
2560
+ function isDiffHunkLine(line) {
2561
+ return line.startsWith("@@");
2562
+ }
2563
+ function isDiffMetaLine(line) {
2564
+ return (line.startsWith("index ") ||
2565
+ line.startsWith("--- ") ||
2566
+ line.startsWith("+++ ") ||
2567
+ line.startsWith("old mode ") ||
2568
+ line.startsWith("new mode ") ||
2569
+ line.startsWith("deleted file mode ") ||
2570
+ line.startsWith("new file mode ") ||
2571
+ line.startsWith("similarity index ") ||
2572
+ line.startsWith("dissimilarity index ") ||
2573
+ line.startsWith("rename from ") ||
2574
+ line.startsWith("rename to ") ||
2575
+ line.startsWith("copy from ") ||
2576
+ line.startsWith("copy to ") ||
2577
+ line.startsWith("Binary files ") ||
2578
+ line.startsWith("GIT binary patch") ||
2579
+ /^literal \d+/.test(line) ||
2580
+ /^delta \d+/.test(line) ||
2581
+ line.startsWith("\"));
2582
+ }
2583
+ function isDiffBodyLine(line) {
2584
+ return line.startsWith("+") || line.startsWith("-") || line.startsWith(" ");
2585
+ }
2586
+ function parseHunkCounts(line) {
2587
+ const match = line.match(/^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/);
2588
+ if (!match) {
2589
+ return null;
2590
+ }
2591
+ return {
2592
+ oldRemaining: Number.parseInt(match[1] ?? "1", 10),
2593
+ newRemaining: Number.parseInt(match[2] ?? "1", 10)
2594
+ };
2595
+ }
2596
+ function consumeHunkLine(line, counts) {
2597
+ if (!counts || line.startsWith("\\ ")) {
2598
+ return counts;
2599
+ }
2600
+ if (line.startsWith("+")) {
2601
+ return { ...counts, newRemaining: Math.max(0, counts.newRemaining - 1) };
2602
+ }
2603
+ if (line.startsWith("-")) {
2604
+ return { ...counts, oldRemaining: Math.max(0, counts.oldRemaining - 1) };
2605
+ }
2606
+ if (line.startsWith(" ")) {
2607
+ return {
2608
+ oldRemaining: Math.max(0, counts.oldRemaining - 1),
2609
+ newRemaining: Math.max(0, counts.newRemaining - 1)
2610
+ };
2611
+ }
2612
+ return counts;
2613
+ }
2614
+ function parseHunkStart(line) {
2615
+ const match = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
2616
+ if (!match) {
2617
+ return null;
2618
+ }
2619
+ return {
2620
+ oldStart: Number.parseInt(match[1] ?? "0", 10),
2621
+ newStart: Number.parseInt(match[2] ?? "0", 10)
2622
+ };
2623
+ }
2624
+ function summarizeDiffStats(added, removed) {
2625
+ const parts = [];
2626
+ if (added > 0) {
2627
+ parts.push(`Added ${added} ${pluralizeLine(added)}`);
2628
+ }
2629
+ if (removed > 0) {
2630
+ const prefix = parts.length > 0 ? "removed" : "Removed";
2631
+ parts.push(`${prefix} ${removed} ${pluralizeLine(removed)}`);
2632
+ }
2633
+ return parts.length > 0 ? parts.join(", ") : "No line changes";
2634
+ }
2635
+ function pluralizeLine(count) {
2636
+ return count === 1 ? "line" : "lines";
2637
+ }
2638
+ function pluralizeFile(count) {
2639
+ return count === 1 ? "file" : "files";
2640
+ }
2641
+ function formatDiffCodeLine(lineNumber, marker, code) {
2642
+ return `${String(lineNumber).padStart(3, " ")} ${marker} ${code}`;
2643
+ }
2644
+ function classifyRenderedLine(section, line) {
2645
+ const trimmed = line.trimStart();
2646
+ const decodedLine = decodeHtmlEntities(line);
2647
+ const decodedTrimmed = decodedLine.trimStart();
2648
+ const isProcessSection = section.group === "process";
2649
+ if (section.title.endsWith(".diff") || trimmed.startsWith("diff --git")) {
2650
+ const fileLine = renderDiffFileLine(trimmed);
2651
+ if (fileLine) {
2652
+ return fileLine;
2653
+ }
2654
+ if (trimmed.startsWith("@@")) {
2655
+ return { kind: "diff-hunk", text: trimmed };
2656
+ }
2657
+ if (trimmed.startsWith("+")) {
2658
+ return { kind: "diff-add", text: trimmed.slice(1) };
2659
+ }
2660
+ if (trimmed.startsWith("-")) {
2661
+ return { kind: "diff-remove", text: trimmed.slice(1) };
2662
+ }
2663
+ }
2664
+ const heading = trimmed.match(/^(#{1,6})\s+(.+)$/);
2665
+ if (heading) {
2666
+ return { kind: "heading", text: stripInlineMarkdown(heading[2] ?? trimmed) };
2667
+ }
2668
+ if (/^([-*_])(?:\s*\1){2,}\s*$/.test(trimmed)) {
2669
+ return { kind: "rule", text: "────" };
2670
+ }
2671
+ const workerSectionHeading = workerSectionHeadingText(trimmed);
2672
+ if (workerSectionHeading) {
2673
+ return { kind: "heading", text: workerSectionHeading };
2674
+ }
2675
+ if (isProcessStatusLine(trimmed) || isProcessStatusWithCommandLine(trimmed) || isBuildOutputSummaryLine(trimmed)) {
2676
+ return { kind: "summary", text: trimmed };
2677
+ }
2678
+ if (isNoMatchesSummaryLine(trimmed)) {
2679
+ return { kind: "summary", text: trimmed };
2680
+ }
2681
+ if ((isProcessSection || isExplicitErrorLine(trimmed)) &&
2682
+ isErrorProcessLine(trimmed) &&
2683
+ !isMarkdownListLikeLine(trimmed)) {
2684
+ return { kind: "error", text: trimmed };
2685
+ }
2686
+ if (isCollapsedOutputSummaryDisplayLine(trimmed)) {
2687
+ return { kind: "summary", text: trimmed };
2688
+ }
2689
+ if (isProcessSection && isCodeLikeProcessLine(decodedTrimmed)) {
2690
+ return { kind: "code", text: decodedTrimmed };
2691
+ }
2692
+ const quote = trimmed.match(/^>\s?(.+)$/);
2693
+ if (quote) {
2694
+ return { kind: "quote", text: stripInlineMarkdown(quote[1] ?? trimmed) };
2695
+ }
2696
+ const taskItem = trimmed.match(/^[-*]\s+\[([ xX])\]\s+(.+)$/);
2697
+ if (taskItem) {
2698
+ const checked = taskItem[1]?.toLowerCase() === "x";
2699
+ return {
2700
+ kind: "task",
2701
+ text: `${checked ? "☑" : "☐"} ${stripInlineMarkdown(taskItem[2] ?? "")}`
2702
+ };
2703
+ }
2704
+ const orderedItem = trimmed.match(/^(\d+)[.)]\s+(.+)$/);
2705
+ if (orderedItem) {
2706
+ return {
2707
+ kind: "ordered-list",
2708
+ text: `${orderedItem[1] ?? "1"}. ${stripInlineMarkdown(orderedItem[2] ?? "")}`
2709
+ };
2710
+ }
2711
+ const listItem = trimmed.match(/^[-*]\s+(.+)$/);
2712
+ if (listItem) {
2713
+ return { kind: "list", text: stripInlineMarkdown(listItem[1] ?? trimmed) };
2714
+ }
2715
+ if (trimmed.startsWith("$ ") || trimmed.startsWith("> ")) {
2716
+ return { kind: "command", text: trimmed };
2717
+ }
2718
+ if (isProcessSection && isCodeLikeProcessLine(trimmed)) {
2719
+ return { kind: "code", text: decodedTrimmed };
2720
+ }
2721
+ if ((isProcessSection || isExplicitErrorLine(trimmed)) &&
2722
+ isErrorProcessLine(trimmed) &&
2723
+ !isMarkdownListLikeLine(trimmed)) {
2724
+ return { kind: "error", text: trimmed };
2725
+ }
2726
+ if (isCollapsedOutputSummaryDisplayLine(trimmed)) {
2727
+ return { kind: "summary", text: trimmed };
2728
+ }
2729
+ if (isProcessSection && /^(✓|✔|\[ok\]|\[done\])|\b(passed|success|succeeded|complete)\b/i.test(trimmed)) {
2730
+ return { kind: "success", text: trimmed };
2731
+ }
2732
+ return { kind: "content", text: stripInlineMarkdown(decodedLine) };
2733
+ }
2734
+ function isErrorProcessLine(line) {
2735
+ return /\b(error|failed|failure|exception|traceback)\b/i.test(line);
2736
+ }
2737
+ function isExplicitErrorLine(line) {
2738
+ return /^(?:ERROR|Error|error|Traceback|Exception|Failed|failed|Failure|failure)\b/.test(line);
2739
+ }
2740
+ function workerSectionHeadingText(line) {
2741
+ const normalized = line.replace(/[::]$/, "").trim();
2742
+ if (!normalized) {
2743
+ return null;
2744
+ }
2745
+ if (WORKER_SECTION_HEADINGS.has(normalized.toLowerCase())) {
2746
+ return normalized;
2747
+ }
2748
+ if (/^任务\s*\d+\s*[::]\s*\S+/.test(normalized)) {
2749
+ return normalized;
2750
+ }
2751
+ return null;
2752
+ }
2753
+ function isShellCodeFenceLanguage(language) {
2754
+ return language === "bash" || language === "sh" || language === "shell" || language === "zsh";
2755
+ }
2756
+ function shellCodeFenceCommandLine(line) {
2757
+ const trimmed = line.trim();
2758
+ return trimmed.startsWith("$") ? trimmed : `$ ${trimmed}`;
2759
+ }
2760
+ const WORKER_SECTION_HEADINGS = new Set([
2761
+ "acceptance",
2762
+ "artifacts",
2763
+ "blocking findings",
2764
+ "change made",
2765
+ "code quality",
2766
+ "critic findings",
2767
+ "dev server / browser check",
2768
+ "evidence",
2769
+ "feature",
2770
+ "files changed",
2771
+ "files changed in this turn",
2772
+ "final verification commands run after implementation",
2773
+ "findings",
2774
+ "judge files read",
2775
+ "non-blocking findings",
2776
+ "open questions",
2777
+ "observed expected red state",
2778
+ "project files reviewed",
2779
+ "recommendation",
2780
+ "residual risk",
2781
+ "review",
2782
+ "risks",
2783
+ "summary",
2784
+ "task",
2785
+ "tdd note",
2786
+ "tdd record",
2787
+ "tdd/verification",
2788
+ "test coverage",
2789
+ "user request",
2790
+ "verification",
2791
+ "verdict",
2792
+ "where things are written",
2793
+ "ui 验收",
2794
+ "ui/体验需求",
2795
+ "不可接受情况",
2796
+ "代码质量验收",
2797
+ "功能需求",
2798
+ "功能验收",
2799
+ "技术约束",
2800
+ "架构原则",
2801
+ "目标",
2802
+ "必须通过的命令",
2803
+ "输入需求",
2804
+ "输入验收",
2805
+ "现有项目上下文",
2806
+ "用户目标",
2807
+ "验收标准",
2808
+ "非目标"
2809
+ ]);
2810
+ function isMarkdownListLikeLine(line) {
2811
+ return /^[-*]\s+/.test(line) || /^\d+[.)]\s+/.test(line);
2812
+ }
2813
+ function isCodeLikeProcessLine(line) {
2814
+ return (/^<![A-Za-z][\s\S]*>?$/i.test(line) ||
2815
+ /^<\/?[A-Za-z][\s\S]*>?$/.test(line) ||
2816
+ /^>\s*<\/?[A-Za-z][\s\S]*>?$/.test(line) ||
2817
+ /^[A-Za-z_:][-A-Za-z0-9_:.]*=(?:"[^"]*"|'[^']*'|[^\s>]+),?$/.test(line) ||
2818
+ /^[.#]?[A-Za-z0-9_-]+\s*\{/.test(line) ||
2819
+ /^[A-Za-z-]+\s*:\s*.+;?$/.test(line) ||
2820
+ line === "}" ||
2821
+ line.startsWith("</"));
2822
+ }
2823
+ function isMarkdownTableSeparator(line) {
2824
+ if (!line.includes("|")) {
2825
+ return false;
2826
+ }
2827
+ return line
2828
+ .split("|")
2829
+ .map((cell) => cell.trim())
2830
+ .filter(Boolean)
2831
+ .every((cell) => /^:?-{3,}:?$/.test(cell));
2832
+ }
2833
+ function isMarkdownTableRow(line) {
2834
+ return line.startsWith("|") && line.endsWith("|") && line.split("|").length > 2;
2835
+ }
2836
+ function renderMarkdownTableRow(line) {
2837
+ return line
2838
+ .split("|")
2839
+ .map((cell) => stripInlineMarkdown(cell.trim()))
2840
+ .filter(Boolean)
2841
+ .join(" ");
2842
+ }
2843
+ function stripInlineMarkdown(text) {
2844
+ return text
2845
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 <$2>")
2846
+ .replace(/`([^`]+)`/g, "$1")
2847
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
2848
+ .replace(/__([^_]+)__/g, "$1")
2849
+ .replace(/\*([^*]+)\*/g, "$1")
2850
+ .replace(/_([^_]+)_/g, "$1")
2851
+ .trim();
2852
+ }
2853
+ function decodeHtmlEntities(text) {
2854
+ return text
2855
+ .replace(/&larr;/g, "←")
2856
+ .replace(/&rarr;/g, "→")
2857
+ .replace(/&uarr;/g, "↑")
2858
+ .replace(/&darr;/g, "↓")
2859
+ .replace(/&middot;/g, "·")
2860
+ .replace(/&nbsp;/g, " ")
2861
+ .replace(/&amp;/g, "&")
2862
+ .replace(/&lt;/g, "<")
2863
+ .replace(/&gt;/g, ">")
2864
+ .replace(/&quot;/g, "\"")
2865
+ .replace(/&#39;/g, "'");
2866
+ }
2867
+ function renderJsonLine(line) {
2868
+ try {
2869
+ const value = JSON.parse(line);
2870
+ const summary = summarizeJsonRecord(value);
2871
+ return [
2872
+ {
2873
+ kind: "json",
2874
+ text: summary.meta
2875
+ },
2876
+ ...(summary.message ? [{ kind: "json-message", text: summary.message }] : [])
2877
+ ];
2878
+ }
2879
+ catch {
2880
+ return [{ kind: "content", text: line }];
2881
+ }
2882
+ }
2883
+ function summarizeJsonRecord(value) {
2884
+ const severity = stringField(value, "severity");
2885
+ const id = stringField(value, "id");
2886
+ const target = stringField(value, "to") || stringField(value, "from");
2887
+ const file = stringField(value, "file") || stringField(value, "path");
2888
+ const message = stringField(value, "message") || stringField(value, "summary") || JSON.stringify(value);
2889
+ const meta = [
2890
+ severity ? `[${severity}]` : "",
2891
+ id,
2892
+ target ? `to ${target}` : "",
2893
+ file
2894
+ ].filter(Boolean).join(" ");
2895
+ return {
2896
+ meta: meta || "json",
2897
+ message
2898
+ };
2899
+ }
2900
+ function stringField(value, key) {
2901
+ const field = value[key];
2902
+ return typeof field === "string" ? field.trim() : "";
2903
+ }
2904
+ function stripAnsi(value) {
2905
+ return value.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "");
2906
+ }
2907
+ function stripAnsiForDiff(value) {
2908
+ return stripAnsi(value).replace(/\r$/, "");
2909
+ }
2910
+ function groupTitle(group) {
2911
+ if (group === "role") {
2912
+ return "artifacts";
2913
+ }
2914
+ if (group === "feature") {
2915
+ return "mailbox";
2916
+ }
2917
+ return "process";
2918
+ }
2919
+ export function workerOutputLineTheme(kind) {
2920
+ if (kind === "group") {
2921
+ return { backgroundColor: "ansi256(24)", bold: true, color: "white" };
2922
+ }
2923
+ if (kind === "section") {
2924
+ return { backgroundColor: "ansi256(236)", color: "yellow" };
2925
+ }
2926
+ if (kind === "heading") {
2927
+ return { backgroundColor: "ansi256(235)", bold: true, color: "white" };
2928
+ }
2929
+ if (kind === "quote") {
2930
+ return { backgroundColor: "ansi256(235)", color: "gray" };
2931
+ }
2932
+ if (kind === "table") {
2933
+ return { backgroundColor: "ansi256(236)", color: "white" };
2934
+ }
2935
+ if (kind === "rule") {
2936
+ return { color: "gray", dimColor: true };
2937
+ }
2938
+ if (kind === "code") {
2939
+ return { backgroundColor: "ansi256(236)", color: "gray" };
2940
+ }
2941
+ if (kind === "source-line") {
2942
+ return { backgroundColor: "ansi256(235)", color: "white" };
2943
+ }
2944
+ if (kind === "summary") {
2945
+ return { backgroundColor: "ansi256(236)", color: "gray" };
2946
+ }
2947
+ if (kind === "json") {
2948
+ return { backgroundColor: "ansi256(53)", color: "magenta" };
2949
+ }
2950
+ if (kind === "json-message") {
2951
+ return { backgroundColor: "ansi256(235)" };
2952
+ }
2953
+ if (kind === "command") {
2954
+ return { backgroundColor: "ansi256(17)", color: "cyan" };
2955
+ }
2956
+ if (kind === "success") {
2957
+ return { backgroundColor: "ansi256(22)", color: "green" };
2958
+ }
2959
+ if (kind === "error") {
2960
+ return { backgroundColor: "ansi256(52)", color: "red" };
2961
+ }
2962
+ if (kind === "diff-file") {
2963
+ return { bold: true, color: "cyan" };
2964
+ }
2965
+ if (kind === "diff-summary") {
2966
+ return { color: "white" };
2967
+ }
2968
+ if (kind === "diff-hunk" || kind === "diff-meta") {
2969
+ return { backgroundColor: "ansi256(236)", color: "gray" };
2970
+ }
2971
+ if (kind === "diff-add") {
2972
+ return { backgroundColor: "ansi256(22)", color: "green" };
2973
+ }
2974
+ if (kind === "diff-remove") {
2975
+ return { backgroundColor: "ansi256(52)", color: "red" };
2976
+ }
2977
+ if (kind === "diff-context") {
2978
+ return { color: "gray" };
2979
+ }
2980
+ return {};
2981
+ }
2982
+ export function workerOutputLineLayout(kind, text) {
2983
+ if (kind === "section") {
2984
+ return { gutter: "", body: `file · ${formatWorkerSectionTitle(text)}` };
2985
+ }
2986
+ if (kind === "content") {
2987
+ return { gutter: "", body: formatPlainDisplayText(text) };
2988
+ }
2989
+ if (kind === "heading") {
2990
+ return { gutter: "", body: text };
2991
+ }
2992
+ if (kind === "list") {
2993
+ return { gutter: "", body: `• ${formatPlainDisplayText(text)}` };
2994
+ }
2995
+ if (kind === "list-detail") {
2996
+ return { gutter: "", body: ` ${formatPlainDisplayText(text)}` };
2997
+ }
2998
+ if (kind === "ordered-list" || kind === "task") {
2999
+ return { gutter: "", body: text };
3000
+ }
3001
+ if (kind === "quote") {
3002
+ return { gutter: "", body: `> ${text}` };
3003
+ }
3004
+ if (kind === "table" || kind === "rule") {
3005
+ return { gutter: "", body: text };
3006
+ }
3007
+ if (kind === "code") {
3008
+ return { gutter: "", body: `| ${text}` };
3009
+ }
3010
+ if (kind === "source-line") {
3011
+ return { gutter: "", body: text };
3012
+ }
3013
+ if (kind === "summary") {
3014
+ return { gutter: "", body: `· ${formatSummaryDisplayText(text)}` };
3015
+ }
3016
+ if (kind === "json") {
3017
+ return { gutter: "", body: text };
3018
+ }
3019
+ if (kind === "json-message") {
3020
+ return { gutter: "", body: text };
3021
+ }
3022
+ if (kind === "command") {
3023
+ return { gutter: "", body: formatCommandDisplayText(text) };
3024
+ }
3025
+ if (kind === "success") {
3026
+ return { gutter: "", body: `· ${formatSuccessDisplayText(text)}` };
3027
+ }
3028
+ if (kind === "error") {
3029
+ return { gutter: "", body: formatErrorDisplayText(text) };
3030
+ }
3031
+ if (kind === "diff-file") {
3032
+ return { gutter: "", body: `● ${text}` };
3033
+ }
3034
+ if (kind === "diff-summary") {
3035
+ return { gutter: "", body: `└ ${text}` };
3036
+ }
3037
+ if (kind === "diff-hunk") {
3038
+ return { gutter: "", body: `hunk ${text}` };
3039
+ }
3040
+ if (kind === "diff-context") {
3041
+ return { gutter: "", body: text };
3042
+ }
3043
+ if (kind === "diff-meta") {
3044
+ return { gutter: "", body: `meta ${text}` };
3045
+ }
3046
+ if (kind === "diff-add") {
3047
+ return { gutter: "", body: text };
3048
+ }
3049
+ if (kind === "diff-remove") {
3050
+ return { gutter: "", body: text };
3051
+ }
3052
+ return { gutter: "", body: text };
3053
+ }
3054
+ function formatWorkerSectionTitle(title) {
3055
+ return title.replace(/^features\//, "");
3056
+ }
3057
+ function formatSummaryDisplayText(text) {
3058
+ const readRun = text.match(/^Collapsed read summaries: (\d+) chunks, (\d+) lines(?: \((.+)\))?$/i);
3059
+ if (readRun) {
3060
+ return [
3061
+ `read ${readRun[1] ?? "0"} chunks`,
3062
+ `${readRun[2] ?? "0"} lines`,
3063
+ readRun[3] ?? ""
3064
+ ].filter(Boolean).join(" · ");
3065
+ }
3066
+ const nodeTests = text.match(/^Node tests passed: (\d+)\/(\d+)(?:, (\d+) skipped)?(?: in (.+))?$/i);
3067
+ if (nodeTests) {
3068
+ return [
3069
+ nodeTests[1] === nodeTests[2]
3070
+ ? `tests ${nodeTests[1] ?? "0"} passed`
3071
+ : `tests ${nodeTests[1] ?? "0"}/${nodeTests[2] ?? "0"} passed`,
3072
+ nodeTests[3] ? `${nodeTests[3]} skipped` : "",
3073
+ nodeTests[4] ?? ""
3074
+ ].filter(Boolean).join(" · ");
3075
+ }
3076
+ const collapsedDiff = text.match(/^Collapsed diff: (\d+) files?, added (\d+) lines?, removed (\d+) lines?(?: \((.+)\))?$/i);
3077
+ if (collapsedDiff) {
3078
+ return [
3079
+ `diff ${collapsedDiff[1] ?? "0"} ${Number(collapsedDiff[1] ?? 0) === 1 ? "file" : "files"}`,
3080
+ Number(collapsedDiff[2] ?? 0) > 0 ? `+${collapsedDiff[2] ?? "0"}` : "",
3081
+ Number(collapsedDiff[3] ?? 0) > 0 ? `-${collapsedDiff[3] ?? "0"}` : "",
3082
+ collapsedDiff[4] ?? ""
3083
+ ].filter(Boolean).join(" · ");
3084
+ }
3085
+ const devFallback = text.match(/^Dev server fallback:\s*(.+)$/i);
3086
+ if (devFallback) {
3087
+ return ["dev server unavailable", "built dist fallback", devFallback[1] ?? ""].filter(Boolean).join(" · ");
3088
+ }
3089
+ const match = text.match(/^((?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?):?\s+(Collapsed (?:code|file list|source) output: \d+ (?:lines|paths))(?:\s+\((\$ .+)\))?$/i);
3090
+ if (match) {
3091
+ const readSummary = match[3] ? formatReadSummary(match[3], match[2] ?? "", match[1] ?? "") : null;
3092
+ if (readSummary) {
3093
+ return readSummary;
3094
+ }
3095
+ return [
3096
+ formatSummaryStatus(match[1] ?? ""),
3097
+ formatCollapsedSummary(match[2] ?? ""),
3098
+ match[3] ? formatSummaryCommand(match[3]) : ""
3099
+ ].filter(Boolean).join(" · ");
3100
+ }
3101
+ if (isProcessStatusLine(text.trim())) {
3102
+ return formatSummaryStatus(text.replace(/:$/, ""));
3103
+ }
3104
+ const buildOutput = text.match(/^((?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?):?\s+\((\$ .+)\)\s+Build output:\s*(.+)$/i);
3105
+ if (buildOutput) {
3106
+ return [
3107
+ formatSummaryStatus(buildOutput[1] ?? ""),
3108
+ formatSummaryCommand(buildOutput[2] ?? ""),
3109
+ `built ${formatBuildOutputTarget(buildOutput[3] ?? "")}`.trim()
3110
+ ].filter(Boolean).join(" · ");
3111
+ }
3112
+ const statusCommand = text.match(/^((?:succeeded|failed|exited\s+\d+)\s+in\s+\d+(?:ms|s|m)?):?\s+\((\$ .+)\)$/i);
3113
+ if (statusCommand) {
3114
+ return [
3115
+ formatSummaryStatus(statusCommand[1] ?? ""),
3116
+ formatSummaryCommand(statusCommand[2] ?? "")
3117
+ ].filter(Boolean).join(" · ");
3118
+ }
3119
+ const noMatches = text.match(/^No matches in (\d+(?:ms|s|m)?)(?:\s+\((\$ .+)\))?$/i);
3120
+ if (noMatches) {
3121
+ return [
3122
+ `no matches ${noMatches[1] ?? ""}`.trim(),
3123
+ noMatches[2] ? formatNoMatchSearchTarget(noMatches[2]) : ""
3124
+ ].filter(Boolean).join(" · ");
3125
+ }
3126
+ return text;
3127
+ }
3128
+ function formatErrorDisplayText(text) {
3129
+ const message = text.replace(/^(?:ERROR|Error|error):\s*/, "").trim();
3130
+ if (/Codex ran out of room\b.*context window/i.test(message)) {
3131
+ return "error · Codex context window full · start a new thread or clear history";
3132
+ }
3133
+ return `error · ${message || text}`;
3134
+ }
3135
+ function formatSuccessDisplayText(text) {
3136
+ const smokeWithDuration = text.match(/^Smoke test passed in (\d+(?:ms|s|m)?):?\s*(.*)$/i);
3137
+ if (smokeWithDuration) {
3138
+ const detail = compactSmokeSuccessDetail(smokeWithDuration[2]?.trim() ?? "");
3139
+ return [
3140
+ "smoke passed",
3141
+ smokeWithDuration[1] ?? "",
3142
+ detail
3143
+ ].filter(Boolean).join(" · ");
3144
+ }
3145
+ const smoke = text.match(/^Smoke test passed:?\s*(.*)$/i);
3146
+ if (smoke) {
3147
+ return ["smoke passed", compactSmokeSuccessDetail(smoke[1]?.trim() ?? "")].filter(Boolean).join(" · ");
3148
+ }
3149
+ return text;
3150
+ }
3151
+ function compactSmokeSuccessDetail(detail) {
3152
+ return /\bDOM\/canvas\b/i.test(detail) ? "DOM/canvas ok" : detail;
3153
+ }
3154
+ function formatReadSummary(command, summary, status) {
3155
+ const readTarget = readTargetFromCommand(command);
3156
+ if (!readTarget) {
3157
+ return null;
3158
+ }
3159
+ const parts = [
3160
+ isOkSummaryStatus(status) ? "" : formatSummaryStatus(status),
3161
+ formatReadTarget(readTarget),
3162
+ formatCollapsedReadSummary(summary)
3163
+ ].filter(Boolean);
3164
+ return parts.join(" · ");
3165
+ }
3166
+ function formatReadTarget(target) {
3167
+ return target
3168
+ .replace(/^src\//, "")
3169
+ .replace(/^\.\/src\//, "");
3170
+ }
3171
+ function readTargetFromCommand(command) {
3172
+ const sed = command.match(/^\$\s+sed\s+-n\s+'(\d+),(\d+)p'\s+(.+)$/);
3173
+ if (sed) {
3174
+ return `${sed[3] ?? ""}:${sed[1] ?? ""}-${sed[2] ?? ""}`;
3175
+ }
3176
+ const chainedSed = command.match(/^\$\s+.+?\s+&&\s+sed\s+-n\s+'(\d+),(\d+)p'\s+(.+)$/);
3177
+ if (chainedSed) {
3178
+ return `${chainedSed[3] ?? ""}:${chainedSed[1] ?? ""}-${chainedSed[2] ?? ""}`;
3179
+ }
3180
+ const nlSed = command.match(/^\$\s+nl\s+-ba\s+(\S+)\s+\|\s+sed\s+-n\s+'(\d+),(\d+)p'$/);
3181
+ if (nlSed) {
3182
+ return `${nlSed[1] ?? ""}:${nlSed[2] ?? ""}-${nlSed[3] ?? ""}`;
3183
+ }
3184
+ return null;
3185
+ }
3186
+ function formatCollapsedReadSummary(summary) {
3187
+ const match = summary.match(/^Collapsed (code|file list|source) output: (\d+) (lines|paths)$/i);
3188
+ if (!match) {
3189
+ return summary;
3190
+ }
3191
+ const kind = (match[1] ?? "").toLowerCase() === "file list" ? "files" : (match[1] ?? "").toLowerCase();
3192
+ return `${match[2] ?? "0"} ${kind}`.trim();
3193
+ }
3194
+ function isOkSummaryStatus(status) {
3195
+ return /^succeeded\s+in\s+\d+(?:ms|s|m)?$/i.test(status.replace(/:$/, ""));
3196
+ }
3197
+ function formatCollapsedSummary(summary) {
3198
+ const match = summary.match(/^Collapsed (code|file list|source) output: (\d+) (lines|paths)$/i);
3199
+ if (!match) {
3200
+ return summary;
3201
+ }
3202
+ const kind = (match[1] ?? "").toLowerCase() === "file list" ? "files" : (match[1] ?? "").toLowerCase();
3203
+ return `${kind} ${match[2] ?? "0"} ${match[3] ?? ""}`.trim();
3204
+ }
3205
+ function formatSummaryStatus(status) {
3206
+ const succeeded = status.match(/^succeeded\s+in\s+(.+)$/i);
3207
+ if (succeeded) {
3208
+ return `ok ${succeeded[1] ?? ""}`.trim();
3209
+ }
3210
+ const failed = status.match(/^failed\s+in\s+(.+)$/i);
3211
+ if (failed) {
3212
+ return `fail ${failed[1] ?? ""}`.trim();
3213
+ }
3214
+ const exited = status.match(/^exited\s+(\d+)\s+in\s+(.+)$/i);
3215
+ if (exited) {
3216
+ return `exit ${exited[1] ?? ""} ${exited[2] ?? ""}`.trim();
3217
+ }
3218
+ return status;
3219
+ }
3220
+ function formatBuildOutputTarget(target) {
3221
+ return target.trim().replace(/\/$/, "");
3222
+ }
3223
+ function formatSummaryCommand(command) {
3224
+ const readTarget = readTargetFromCommand(command);
3225
+ if (readTarget) {
3226
+ return formatReadTarget(readTarget);
3227
+ }
3228
+ if (/^\$\s+pwd\s+&&\s+rg\s+--files\b/.test(command)) {
3229
+ return "$ rg --files";
3230
+ }
3231
+ return command;
3232
+ }
3233
+ function formatNoMatchSearchTarget(command) {
3234
+ if (/^\$\s+rg\s+TODO markers\b/i.test(command)) {
3235
+ return "TODO markers";
3236
+ }
3237
+ const markerScan = command.match(/^\$\s+rg\s+(?:-[\w-]+\s+)*"([^"]+)"\s+.+$/i);
3238
+ if (markerScan && /\b(?:TBD|TODO)\b|占位|待定/i.test(markerScan[1] ?? "")) {
3239
+ return "TODO markers";
3240
+ }
3241
+ const todoScan = command.match(/^\$\s+rg\s+TODO\s+(.+)$/i);
3242
+ if (todoScan) {
3243
+ return `TODO ${formatCommandPath(todoScan[1] ?? "")}`.trim();
3244
+ }
3245
+ const query = command.match(/^\$\s+(?:rg|grep|git\s+grep)\s+(.+)$/i);
3246
+ return query ? `search ${compactEndByDisplayWidth(query[1] ?? "", 32)}` : command;
3247
+ }
3248
+ function formatCommandDisplayText(text) {
3249
+ const todoScan = text.match(/^\$\s+rg\s+-n\s+"TBD\|TODO\|implement later\|fill in\|占位\|待定"\s+(.+)$/);
3250
+ if (todoScan) {
3251
+ return `$ rg TODO markers ${formatCommandPath(todoScan[1] ?? "")}`;
3252
+ }
3253
+ return text.replace(/(?:^|\s)(\S*\.parallel-codex\/sessions\/task-\d{8}-\d{6}-\d+\/\S+)/g, (match, path) => {
3254
+ const prefix = match.startsWith(" ") ? " " : "";
3255
+ return `${prefix}${formatCommandPath(path)}`;
3256
+ });
3257
+ }
3258
+ function formatCommandPath(path) {
3259
+ return path
3260
+ .replace(/.*\.parallel-codex\/sessions\/task-\d{8}-\d{6}-\d+\//, ".parallel-codex/<task>/")
3261
+ .replace(/\.parallel-codex\/<task>\/(?:[^/\s]+\/)*([^/\s]+\/\*\.md)$/, "$1");
3262
+ }
3263
+ function formatPlainDisplayText(text) {
3264
+ return text
3265
+ .replace(/\bfile:\/\/\/\S+/g, (url) => formatFileUrlDisplay(url))
3266
+ .replace(/\bFeature mailbox features\/(\d{4}\/[A-Za-z0-9._@+-]+)\b/g, "mailbox $1")
3267
+ .replace(/\bfeatures\/(\d{4}\/[A-Za-z0-9._@+-]+)\b/g, "$1");
3268
+ }
3269
+ function formatFileUrlDisplay(url) {
3270
+ const path = url.replace(/^file:\/\//, "");
3271
+ const distMatch = path.match(/\/(dist\/\S+)$/);
3272
+ if (distMatch) {
3273
+ return distMatch[1] ?? url;
3274
+ }
3275
+ const segments = path.split("/").filter(Boolean);
3276
+ return segments.slice(-2).join("/") || url;
3277
+ }
3278
+ function WorkerOutputLine({ line }) {
3279
+ const theme = workerOutputLineTheme(line.kind);
3280
+ if (line.kind === "blank") {
3281
+ return _jsx(Text, { children: " " });
3282
+ }
3283
+ if (line.kind === "group") {
3284
+ return (_jsx(Box, { children: _jsxs(Text, { ...theme, children: [" ", line.text, " "] }) }));
3285
+ }
3286
+ if (line.preformatted) {
3287
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { ...theme, wrap: "truncate-end", children: line.text || " " })] }));
3288
+ }
3289
+ const layout = workerOutputLineLayout(line.kind, line.text);
3290
+ if (line.kind === "code") {
3291
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { ...theme, wrap: "wrap", children: line.text || " " })] }));
3292
+ }
3293
+ if (line.kind === "source-line") {
3294
+ const sourceParts = sourceDisplayLineParts(line.text);
3295
+ if (sourceParts) {
3296
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { backgroundColor: WORKER_PANEL_BACKGROUND, color: "gray", children: sourceParts.gutter }), _jsx(Text, { backgroundColor: WORKER_PANEL_BACKGROUND, color: "white", wrap: "truncate-end", children: sourceParts.code })] }));
3297
+ }
3298
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { ...theme, children: line.text || " " })] }));
3299
+ }
3300
+ if (line.continuation && isDiffCodeKind(line.kind)) {
3301
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { ...theme, wrap: "truncate-end", children: line.text || " " })] }));
3302
+ }
3303
+ const diffCodeLine = diffCodeLineParts(line);
3304
+ if (diffCodeLine) {
3305
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { ...theme, children: `${diffCodeLine.lineNumber} ${diffCodeLine.sign} ` }), _jsx(Text, { ...theme, wrap: "wrap", children: diffCodeLine.code || " " })] }));
3306
+ }
3307
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), layout.gutter ? _jsx(Text, { ...theme, children: formatGutter(layout.gutter) }) : null, _jsx(Text, { ...theme, wrap: "truncate-end", children: layout.body || " " })] }));
3308
+ }
3309
+ function WorkerOutputNanoLine({ line, width }) {
3310
+ const theme = workerOutputLineTheme(line.kind);
3311
+ const text = compactEndByDisplayWidth(workerOutputNanoLineText(line), Math.max(1, width));
3312
+ return _jsx(Text, { ...theme, wrap: "truncate-end", children: text || " " });
3313
+ }
3314
+ function workerOutputNanoLineText(line) {
3315
+ if (line.kind === "blank") {
3316
+ return "";
3317
+ }
3318
+ if (line.kind === "group") {
3319
+ return line.text;
3320
+ }
3321
+ if (line.preformatted) {
3322
+ return line.text;
3323
+ }
3324
+ return workerOutputLineLayout(line.kind, line.text).body;
3325
+ }
3326
+ function sourceDisplayLineParts(text) {
3327
+ const numbered = text.match(/^(\s*\d+)(\s{2})(.*)$/);
3328
+ if (numbered) {
3329
+ return {
3330
+ gutter: `${numbered[1] ?? ""}${numbered[2] ?? ""}`,
3331
+ code: numbered[3] ?? ""
3332
+ };
3333
+ }
3334
+ const blankNumbered = text.match(/^(\s*\d+)$/);
3335
+ if (blankNumbered) {
3336
+ return {
3337
+ gutter: `${blankNumbered[1] ?? ""} `,
3338
+ code: ""
3339
+ };
3340
+ }
3341
+ const continuation = text.match(/^(\s{6,})(.*)$/);
3342
+ return continuation
3343
+ ? {
3344
+ gutter: continuation[1] ?? "",
3345
+ code: continuation[2] ?? ""
3346
+ }
3347
+ : null;
3348
+ }
3349
+ function diffCodeLineParts(line) {
3350
+ if (line.kind !== "diff-add" && line.kind !== "diff-remove" && line.kind !== "diff-context") {
3351
+ return null;
3352
+ }
3353
+ return workerOutputDiffColumns(line.text);
3354
+ }
3355
+ export function workerOutputDiffColumns(text) {
3356
+ const match = text.match(/^(\s*\d+)\s([+\- ])\s(.*)$/);
3357
+ if (!match) {
3358
+ return null;
3359
+ }
3360
+ const sign = match[2];
3361
+ if (sign !== "+" && sign !== "-" && sign !== " ") {
3362
+ return null;
3363
+ }
3364
+ return {
3365
+ lineNumber: match[1] ?? "",
3366
+ sign,
3367
+ code: match[3] ?? ""
3368
+ };
3369
+ }
3370
+ function renderDisplayLines(lines, width = Number(process.stdout.columns) || 120) {
3371
+ const contentWidth = Math.max(1, width - 4);
3372
+ if (isNanoWorkerOutputWidth(width)) {
3373
+ return renderNanoDisplayLines(lines, contentWidth);
3374
+ }
3375
+ return lines.flatMap((line) => {
3376
+ if (line.kind === "blank" || line.kind === "group") {
3377
+ return [line];
3378
+ }
3379
+ if (line.kind === "source-line") {
3380
+ return workerOutputSourceDisplayLines(line.text, contentWidth).map((text, index) => ({
3381
+ kind: line.kind,
3382
+ text,
3383
+ continuation: index > 0
3384
+ }));
3385
+ }
3386
+ if (line.kind === "code") {
3387
+ return workerOutputCodeDisplayLines(line.text, contentWidth).map((text, index) => ({
3388
+ kind: line.kind,
3389
+ text,
3390
+ continuation: index > 0
3391
+ }));
3392
+ }
3393
+ if (isDiffCodeKind(line.kind)) {
3394
+ return workerOutputDiffDisplayLines(line.text, contentWidth).map((text, index) => ({
3395
+ kind: line.kind,
3396
+ text,
3397
+ continuation: index > 0
3398
+ }));
3399
+ }
3400
+ return workerOutputBodyDisplayLines(line.kind, line.text, contentWidth).map((text, index) => ({
3401
+ kind: line.kind,
3402
+ text,
3403
+ continuation: index > 0,
3404
+ preformatted: true
3405
+ }));
3406
+ });
3407
+ }
3408
+ function renderNanoDisplayLines(lines, contentWidth) {
3409
+ const displayLines = [];
3410
+ const sourceLines = tinyWorkerOutputSourceLines(lines, 8);
3411
+ for (const line of sourceLines) {
3412
+ if (line.kind === "blank" || line.kind === "group") {
3413
+ pushNanoDisplayLine(displayLines, line);
3414
+ continue;
3415
+ }
3416
+ if (line.kind === "source-line" || line.kind === "code") {
3417
+ continue;
3418
+ }
3419
+ const texts = workerOutputBodyDisplayLines(line.kind, line.text, contentWidth).slice(0, 2);
3420
+ for (const text of texts) {
3421
+ pushNanoDisplayLine(displayLines, {
3422
+ kind: line.kind,
3423
+ text,
3424
+ preformatted: true
3425
+ });
3426
+ }
3427
+ }
3428
+ return displayLines.length > 0 ? displayLines : [{ kind: "content", text: "empty", preformatted: true }];
3429
+ }
3430
+ function pushNanoDisplayLine(displayLines, line) {
3431
+ const previous = displayLines[displayLines.length - 1];
3432
+ if (previous?.kind === line.kind && previous.text === line.text) {
3433
+ return;
3434
+ }
3435
+ displayLines.push(line);
3436
+ }
3437
+ export function workerOutputBodyDisplayLines(kind, text, width) {
3438
+ const layout = workerOutputLineLayout(kind, text);
3439
+ const rawBody = `${layout.gutter ? formatGutter(layout.gutter) : ""}${layout.body}`;
3440
+ const body = compactWorkerBodyForWidth(kind, rawBody, Math.max(1, width));
3441
+ const continuationPrefix = workerOutputContinuationPrefix(kind, body);
3442
+ return wrapBodyWithContinuation(body, Math.max(1, width), continuationPrefix);
3443
+ }
3444
+ function compactWorkerBodyForWidth(kind, body, width) {
3445
+ if (kind === "summary") {
3446
+ return compactSummaryBodyForWidth(body, width);
3447
+ }
3448
+ if (kind === "success") {
3449
+ return compactSuccessBodyForWidth(body, width);
3450
+ }
3451
+ if (kind === "error" && /Codex context window full/i.test(body)) {
3452
+ return compactContextWindowErrorForWidth(body, width);
3453
+ }
3454
+ if ((kind === "content" || kind === "list" || kind === "list-detail" || kind === "quote" || kind === "ordered-list" || kind === "task") &&
3455
+ /^Verification:\s+/i.test(body)) {
3456
+ return compactVerificationBodyForWidth(body, width);
3457
+ }
3458
+ if (kind === "heading" && /^Critic Findings$/i.test(body) && width < 16) {
3459
+ return width < 8 ? "Find" : "Findings";
3460
+ }
3461
+ if (kind === "content" && /^Summary:\s*done$/i.test(body) && width < 14) {
3462
+ return "Done";
3463
+ }
3464
+ if (kind === "content" && /^Review:\s*approved$/i.test(body) && width < 16) {
3465
+ return "Approved";
3466
+ }
3467
+ if (kind === "content" && /^Blocking:\s*none$/i.test(body) && width < 44) {
3468
+ return compactBlockingNoneForWidth(width);
3469
+ }
3470
+ if (kind === "content" && /^Findings:\s*none$/i.test(body) && width < 44) {
3471
+ return compactFindingsNoneForWidth(width);
3472
+ }
3473
+ if (kind === "content" && /^No active Critic findings were present for this feature;/.test(body)) {
3474
+ return width < 12 ? "None." : "No findings.";
3475
+ }
3476
+ if (width < 40 &&
3477
+ displayWidth(body) > width &&
3478
+ (kind === "content" || kind === "list" || kind === "list-detail" || kind === "quote" || kind === "ordered-list" || kind === "task")) {
3479
+ return compactNarrowWorkerBody(body, width);
3480
+ }
3481
+ return body;
3482
+ }
3483
+ function compactContextWindowErrorForWidth(body, width) {
3484
+ if (width < 7) {
3485
+ return "ctx";
3486
+ }
3487
+ if (width < 10) {
3488
+ return "err ctx";
3489
+ }
3490
+ if (width < 14) {
3491
+ return "err · ctx";
3492
+ }
3493
+ if (width < 18) {
3494
+ return "err · ctx full";
3495
+ }
3496
+ if (width < 26) {
3497
+ return "err · ctx full · start new thread";
3498
+ }
3499
+ if (displayWidth(body) <= width) {
3500
+ return body;
3501
+ }
3502
+ const readable = "error · context full; new thread";
3503
+ if (displayWidth(readable) <= width) {
3504
+ return readable;
3505
+ }
3506
+ const compact = "err · ctx full; new thread";
3507
+ if (displayWidth(compact) <= width) {
3508
+ return compact;
3509
+ }
3510
+ return body;
3511
+ }
3512
+ function compactSuccessBodyForWidth(body, width) {
3513
+ const bullet = body.match(/^·\s+/)?.[0] ?? "";
3514
+ const text = bullet ? body.slice(bullet.length) : body;
3515
+ if (!/^smoke passed\b/i.test(text)) {
3516
+ return body;
3517
+ }
3518
+ if (width < 14) {
3519
+ return `${bullet}smoke`;
3520
+ }
3521
+ if (width < 28) {
3522
+ return `${bullet}smoke passed`;
3523
+ }
3524
+ const smoke = text.match(/^smoke passed(?: · ([^·]+))?(?: · (.+))?$/i);
3525
+ const maybeDuration = smoke?.[1]?.trim() ?? "";
3526
+ const detail = smoke?.[2]?.trim() ?? "";
3527
+ const duration = /^\d+(?:ms|s|m)$/i.test(maybeDuration) ? maybeDuration : "";
3528
+ const visibleDetail = duration ? detail : [maybeDuration, detail].filter(Boolean).join(" · ");
3529
+ if (/\bDOM\/canvas\b/i.test(visibleDetail)) {
3530
+ const candidates = [
3531
+ duration ? `${bullet}smoke passed · ${duration} · DOM/canvas ok` : "",
3532
+ `${bullet}smoke passed · DOM/canvas ok`,
3533
+ `${bullet}smoke passed`
3534
+ ].filter(Boolean);
3535
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? `${bullet}smoke passed`;
3536
+ }
3537
+ return body;
3538
+ }
3539
+ function compactNarrowWorkerBody(body, width) {
3540
+ if (/^No active Critic findings were present for this feature;/.test(body)) {
3541
+ return "No findings.";
3542
+ }
3543
+ if (/^Verification:\s+/i.test(body)) {
3544
+ return compactVerificationBodyForWidth(body, width);
3545
+ }
3546
+ if (/^Supervisor summary:\s+/i.test(body)) {
3547
+ return compactSupervisorSummaryForWidth(body, width);
3548
+ }
3549
+ if (/^Critic review:\s+/i.test(body)) {
3550
+ return compactCriticReviewBodyForWidth(body, width);
3551
+ }
3552
+ if (/^Feature:\s+/i.test(body) && /\s+·\s+Turn:\s+/i.test(body)) {
3553
+ return compactFeatureTurnBodyForWidth(body, width);
3554
+ }
3555
+ if (/^Blocking:\s*none$/i.test(body)) {
3556
+ return width < 44 ? compactBlockingNoneForWidth(width) : body;
3557
+ }
3558
+ if (/^Findings:\s*none$/i.test(body)) {
3559
+ return width < 24 ? compactFindingsNoneForWidth(width) : body;
3560
+ }
3561
+ const findingsLabel = width < 24 ? "findings" : "findings.jsonl";
3562
+ const repliesLabel = width < 24 ? "replies" : "replies.jsonl";
3563
+ const worklogLabel = width < 24 ? "worklog" : "worklog.md";
3564
+ return body
3565
+ .replace(/^(•\s+)?npm run dev could not bind\b.*$/i, (_match, marker) => `${marker ?? ""}dev fallback.`)
3566
+ .replace(/\bcritic-findings\.jsonl\b/g, findingsLabel)
3567
+ .replace(/\bactor-replies\.jsonl\b/g, repliesLabel)
3568
+ .replace(/\bactor-worklog\.md\b/g, worklogLabel)
3569
+ .replace(/\bdist\/\s+fallback\b/g, "dist fallback");
3570
+ }
3571
+ function compactBlockingNoneForWidth(width) {
3572
+ return width < 14 ? "No block" : "No blockers.";
3573
+ }
3574
+ function compactFindingsNoneForWidth(width) {
3575
+ return width < 14 ? "No find" : "No findings.";
3576
+ }
3577
+ function compactVerificationBodyForWidth(body, width) {
3578
+ const tests = body.match(/\btests\s+(\d+\/\d+)/i)?.[1];
3579
+ const parts = [
3580
+ tests ? `tests ${tests}` : /\btests passed\b/i.test(body) ? "tests" : "",
3581
+ /\bsmoke passed\b/i.test(body) ? "smoke" : "",
3582
+ /\bbuild passed\b/i.test(body) ? "build" : "",
3583
+ /\bdev fallback\b/i.test(body) ? "dev" : ""
3584
+ ].filter(Boolean);
3585
+ if (parts.length < 2) {
3586
+ return body;
3587
+ }
3588
+ if (width < 44) {
3589
+ return compactMidWidthVerificationParts(parts).join(" · ");
3590
+ }
3591
+ const compact = `Verify: ${parts.join(" · ")}`;
3592
+ return displayWidth(body) <= width ? body : compact;
3593
+ }
3594
+ function compactMidWidthVerificationParts(parts) {
3595
+ const buildIndex = parts.indexOf("build");
3596
+ const devIndex = parts.indexOf("dev");
3597
+ if (buildIndex < 0 || devIndex < 0 || devIndex !== buildIndex + 1) {
3598
+ return parts;
3599
+ }
3600
+ return [
3601
+ ...parts.slice(0, buildIndex),
3602
+ "build+dev",
3603
+ ...parts.slice(devIndex + 1)
3604
+ ];
3605
+ }
3606
+ function compactSupervisorSummaryForWidth(body, width) {
3607
+ if (/\bcompleted\b|\bdone\b/i.test(body)) {
3608
+ return width < 14 ? "Done" : "Summary: done";
3609
+ }
3610
+ return body.replace(/^Supervisor summary:/i, "Summary:");
3611
+ }
3612
+ function compactCriticReviewBodyForWidth(body, width) {
3613
+ if (/\bAPPROVED\b/i.test(body)) {
3614
+ return width < 16 ? "Approved" : "Review: approved";
3615
+ }
3616
+ return body.replace(/^Critic review:/i, "Review:");
3617
+ }
3618
+ function compactFeatureTurnBodyForWidth(body, width) {
3619
+ const match = body.match(/^Feature:\s*(\S+)\s+·\s+Turn:\s*(\S+)/i);
3620
+ if (!match) {
3621
+ return body;
3622
+ }
3623
+ const feature = match[1] ?? "";
3624
+ const turn = match[2] ?? "";
3625
+ const candidates = [
3626
+ `Feature ${feature}`,
3627
+ feature === turn ? `F ${feature}` : `F ${feature} T ${turn}`
3628
+ ];
3629
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? body;
3630
+ }
3631
+ function compactSummaryBodyForWidth(body, width) {
3632
+ const commandSummary = compactCommandSummaryForWidth(body, width);
3633
+ if (commandSummary) {
3634
+ return commandSummary;
3635
+ }
3636
+ const fallbackSummary = compactDevFallbackSummaryForWidth(body, width);
3637
+ if (fallbackSummary) {
3638
+ return fallbackSummary;
3639
+ }
3640
+ const fileListSummary = compactFileListSummaryForWidth(body, width);
3641
+ if (fileListSummary) {
3642
+ return fileListSummary;
3643
+ }
3644
+ const nodeTestSummary = compactNodeTestSummaryForWidth(body, width);
3645
+ if (nodeTestSummary) {
3646
+ return nodeTestSummary;
3647
+ }
3648
+ const formattedCommandSummary = compactFormattedCommandSummaryForWidth(body, width);
3649
+ if (formattedCommandSummary) {
3650
+ return formattedCommandSummary;
3651
+ }
3652
+ const noMatchSummary = compactNoMatchSummaryForWidth(body, width);
3653
+ if (noMatchSummary) {
3654
+ return noMatchSummary;
3655
+ }
3656
+ if (displayWidth(body) <= width) {
3657
+ return body;
3658
+ }
3659
+ const readRunSummary = compactReadRunSummaryForWidth(body, width);
3660
+ if (readRunSummary) {
3661
+ return readRunSummary;
3662
+ }
3663
+ const diffSummary = compactDiffSummaryForWidth(body, width);
3664
+ if (diffSummary) {
3665
+ return diffSummary;
3666
+ }
3667
+ const parts = body.split(" · ");
3668
+ if (parts.length < 2) {
3669
+ return body;
3670
+ }
3671
+ const lastPart = parts[parts.length - 1] ?? "";
3672
+ const targetCompacted = compactSummaryTargetSegment(parts, lastPart, width);
3673
+ if (targetCompacted) {
3674
+ return targetCompacted;
3675
+ }
3676
+ if (looksLikePathTarget(lastPart)) {
3677
+ const shortenedPath = [...parts.slice(0, -1), basename(lastPart)].join(" · ");
3678
+ if (displayWidth(shortenedPath) <= width) {
3679
+ return shortenedPath;
3680
+ }
3681
+ }
3682
+ const withoutTarget = parts.slice(0, -1).join(" · ");
3683
+ return displayWidth(withoutTarget) <= width ? withoutTarget : body;
3684
+ }
3685
+ function compactCommandSummaryForWidth(body, width) {
3686
+ if (width >= 48 || !body.includes("$ npm")) {
3687
+ return null;
3688
+ }
3689
+ const compacted = body
3690
+ .replace(/\$ npm run build\b/g, "build")
3691
+ .replace(/\$ npm run smoke\b/g, "smoke")
3692
+ .replace(/\$ npm run dev\b/g, "dev")
3693
+ .replace(/\$ npm test\b/g, "test");
3694
+ const candidates = [
3695
+ compacted.replace(/\s+·\s+built dist\b/i, " · dist"),
3696
+ compacted,
3697
+ compacted.replace(/\s+·\s+dist\b/i, ""),
3698
+ compacted.replace(/\s+·\s+built dist\b/i, ""),
3699
+ ...compactTinyNpmCommandSummaryCandidates(compacted, width)
3700
+ ];
3701
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3702
+ }
3703
+ function compactTinyNpmCommandSummaryCandidates(body, width) {
3704
+ const tinyFirst = width <= 8;
3705
+ if (/\bbuild\b/i.test(body)) {
3706
+ return tinyFirst ? ["· build", "· build ok", "· build · dist"] : ["· build · dist", "· build ok", "· build"];
3707
+ }
3708
+ if (/\bsmoke\b/i.test(body)) {
3709
+ return tinyFirst ? ["· smoke", "· smoke ok"] : ["· smoke ok", "· smoke"];
3710
+ }
3711
+ if (/\btest\b/i.test(body)) {
3712
+ return tinyFirst ? ["· test", "· test ok"] : ["· test ok", "· test"];
3713
+ }
3714
+ if (/\bdev\b/i.test(body)) {
3715
+ return tinyFirst ? ["· dev", "· dev ok"] : ["· dev ok", "· dev"];
3716
+ }
3717
+ return [];
3718
+ }
3719
+ function compactFileListSummaryForWidth(body, width) {
3720
+ if (width >= 32) {
3721
+ return null;
3722
+ }
3723
+ const match = body.match(/^· (?:ok \d+(?:ms|s|m) · )?files (\d+) paths(?: · .+)?$/i);
3724
+ if (!match) {
3725
+ return null;
3726
+ }
3727
+ const count = match[1] ?? "0";
3728
+ const candidates = [
3729
+ `· files ${count} paths`,
3730
+ `· files ${count}`
3731
+ ];
3732
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3733
+ }
3734
+ function compactNodeTestSummaryForWidth(body, width) {
3735
+ if (width >= 24) {
3736
+ return null;
3737
+ }
3738
+ const match = body.match(/^· tests (\d+)(?:\/(\d+))? passed(?: · .+)?$/i);
3739
+ if (!match) {
3740
+ return null;
3741
+ }
3742
+ const count = match[2] && match[1] !== match[2]
3743
+ ? `${match[1] ?? "0"}/${match[2] ?? "0"}`
3744
+ : match[1] ?? "0";
3745
+ const candidates = [
3746
+ `· tests ${count} ok`,
3747
+ `· tests ${count}`,
3748
+ `· ${count} tests ok`,
3749
+ `· ${count} ok`
3750
+ ];
3751
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3752
+ }
3753
+ function compactNoMatchSummaryForWidth(body, width) {
3754
+ if (width >= 24) {
3755
+ return null;
3756
+ }
3757
+ const match = body.match(/^· no matches \d+(?:ms|s|m)?(?: · (.+))?$/i);
3758
+ if (!match) {
3759
+ return null;
3760
+ }
3761
+ const target = match[1]?.trim() ?? "";
3762
+ if (/^TODO markers$/i.test(target)) {
3763
+ const candidates = width < 20
3764
+ ? ["· no TODO", "· none"]
3765
+ : ["· no TODO markers", "· no TODO", "· none"];
3766
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3767
+ }
3768
+ const candidates = [
3769
+ target ? `· no ${target}` : "",
3770
+ "· no matches",
3771
+ "· none"
3772
+ ].filter(Boolean);
3773
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3774
+ }
3775
+ function compactFormattedCommandSummaryForWidth(body, width) {
3776
+ if (width >= 18) {
3777
+ return null;
3778
+ }
3779
+ const match = body.match(/^· ok \d+(?:ms|s|m) · (build|dev|smoke|test)(?: · (?:built )?dist)?$/i);
3780
+ if (!match) {
3781
+ return null;
3782
+ }
3783
+ const action = (match[1] ?? "").toLowerCase();
3784
+ const hasDist = /\bdist\b/i.test(body);
3785
+ const candidates = [
3786
+ hasDist ? `· ${action} · dist` : "",
3787
+ `· ${action} ok`,
3788
+ `· ${action}`
3789
+ ].filter(Boolean);
3790
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3791
+ }
3792
+ function compactDiffSummaryForWidth(body, width) {
3793
+ if (width >= 32) {
3794
+ return null;
3795
+ }
3796
+ const match = body.match(/^· diff (\d+) files? · \+(\d+)(?: · -(\d+))?(?: · .+)?$/i);
3797
+ if (!match) {
3798
+ return null;
3799
+ }
3800
+ const files = match[1] ?? "0";
3801
+ const added = match[2] ?? "0";
3802
+ const removed = match[3] ?? "";
3803
+ const candidates = [
3804
+ [`· diff ${files}`, `+${added}`, removed ? `-${removed}` : ""].filter(Boolean).join(" · "),
3805
+ [`· diff ${files}`, `+${added}`].join(" · "),
3806
+ `· diff ${files}`
3807
+ ];
3808
+ return candidates.find((candidate) => displayWidth(candidate) <= width) ?? null;
3809
+ }
3810
+ function compactDevFallbackSummaryForWidth(body, width) {
3811
+ if (width >= 48 || !/^· dev server unavailable\b/i.test(body)) {
3812
+ return null;
3813
+ }
3814
+ for (const candidate of [
3815
+ "· dev fallback · dist",
3816
+ "· dev fallback",
3817
+ "· dev · dist",
3818
+ "· dev"
3819
+ ]) {
3820
+ if (displayWidth(candidate) <= width) {
3821
+ return candidate;
3822
+ }
3823
+ }
3824
+ return null;
3825
+ }
3826
+ function compactReadRunSummaryForWidth(body, width) {
3827
+ if (width >= 32) {
3828
+ return null;
3829
+ }
3830
+ const match = body.match(/^· read (\d+) chunks · ([\d,]+) lines(?: · .+)?$/i);
3831
+ if (!match) {
3832
+ return null;
3833
+ }
3834
+ const chunks = match[1] ?? "0";
3835
+ const lines = match[2] ?? "0";
3836
+ for (const candidate of [
3837
+ `· read ${chunks} · ${lines} lines`,
3838
+ `· read ${chunks} · ${lines}`,
3839
+ `· read ${chunks}`
3840
+ ]) {
3841
+ if (displayWidth(candidate) <= width) {
3842
+ return candidate;
3843
+ }
3844
+ }
3845
+ return null;
3846
+ }
3847
+ function compactSummaryTargetSegment(parts, targetSegment, width) {
3848
+ if (!targetSegment.includes(",")) {
3849
+ return null;
3850
+ }
3851
+ const parsed = parseSummaryTargets(targetSegment);
3852
+ if (!parsed) {
3853
+ return null;
3854
+ }
3855
+ for (let visibleCount = parsed.targets.length; visibleCount >= 1; visibleCount -= 1) {
3856
+ const hidden = parsed.targets.length - visibleCount + parsed.hidden;
3857
+ const visibleTargets = parsed.targets.slice(0, visibleCount);
3858
+ const compactedSegment = [
3859
+ ...visibleTargets,
3860
+ hidden > 0 ? `+${hidden} more` : ""
3861
+ ].filter(Boolean).join(", ");
3862
+ const compacted = [...parts.slice(0, -1), compactedSegment].join(" · ");
3863
+ if (displayWidth(compacted) <= width) {
3864
+ return compacted;
3865
+ }
3866
+ if (visibleCount === 1 && looksLikePathTarget(visibleTargets[0] ?? "")) {
3867
+ const shortenedSegment = [
3868
+ basename(visibleTargets[0] ?? ""),
3869
+ hidden > 0 ? `+${hidden} more` : ""
3870
+ ].filter(Boolean).join(", ");
3871
+ const shortened = [...parts.slice(0, -1), shortenedSegment].join(" · ");
3872
+ if (displayWidth(shortened) <= width) {
3873
+ return shortened;
3874
+ }
3875
+ }
3876
+ }
3877
+ const hiddenOnly = [...parts.slice(0, -1), `${parsed.targets.length + parsed.hidden} targets`].join(" · ");
3878
+ if (displayWidth(hiddenOnly) <= width) {
3879
+ return hiddenOnly;
3880
+ }
3881
+ const withoutTarget = parts.slice(0, -1).join(" · ");
3882
+ return displayWidth(withoutTarget) <= width ? withoutTarget : null;
3883
+ }
3884
+ function parseSummaryTargets(segment) {
3885
+ const pieces = segment.split(",").map((piece) => piece.trim()).filter(Boolean);
3886
+ if (pieces.length < 2) {
3887
+ return null;
3888
+ }
3889
+ let hidden = 0;
3890
+ const last = pieces[pieces.length - 1] ?? "";
3891
+ const hiddenMatch = last.match(/^\+(\d+)\s+more$/i);
3892
+ if (hiddenMatch) {
3893
+ hidden = Number.parseInt(hiddenMatch[1] ?? "0", 10);
3894
+ pieces.pop();
3895
+ }
3896
+ if (pieces.length === 0 || pieces.some((piece) => piece.startsWith("$ "))) {
3897
+ return null;
3898
+ }
3899
+ return { targets: pieces, hidden };
3900
+ }
3901
+ function looksLikePathTarget(value) {
3902
+ return /^[A-Za-z0-9._@+-]+\/[A-Za-z0-9._@+/-]+$/.test(value);
3903
+ }
3904
+ function wrapBodyWithContinuation(body, width, continuationPrefix) {
3905
+ if (displayWidth(body) <= width) {
3906
+ return [body];
3907
+ }
3908
+ const lines = [];
3909
+ let remaining = body;
3910
+ let nextWidth = width;
3911
+ const continuationWidth = Math.max(1, width - displayWidth(continuationPrefix));
3912
+ while (remaining) {
3913
+ const chunk = wrapByDisplayWidth(remaining, nextWidth)[0] ?? remaining;
3914
+ if (!chunk) {
3915
+ break;
3916
+ }
3917
+ let displayChunk = chunk.trimEnd();
3918
+ let nextRemaining = remaining.slice(chunk.length).replace(/^\s+/, "");
3919
+ if (nextRemaining && displayChunk.endsWith("·")) {
3920
+ const withoutSeparator = displayChunk.replace(/\s*·$/, "");
3921
+ if (withoutSeparator.trim()) {
3922
+ displayChunk = withoutSeparator;
3923
+ }
3924
+ }
3925
+ if (nextRemaining.startsWith("· ")) {
3926
+ nextRemaining = nextRemaining.replace(/^·\s+/, "");
3927
+ }
3928
+ lines.push(lines.length === 0 ? displayChunk : `${continuationPrefix}${displayChunk.trimStart()}`.trimEnd());
3929
+ remaining = nextRemaining;
3930
+ nextWidth = continuationWidth;
3931
+ }
3932
+ return lines.length > 0 ? lines : [body];
3933
+ }
3934
+ function workerOutputContinuationPrefix(kind, body) {
3935
+ if (kind === "ordered-list") {
3936
+ const marker = body.match(/^\d+[.)]\s+/);
3937
+ return marker ? " ".repeat(displayWidth(marker[0])) : " ";
3938
+ }
3939
+ if (kind === "list" ||
3940
+ kind === "list-detail" ||
3941
+ kind === "task" ||
3942
+ kind === "quote" ||
3943
+ kind === "summary" ||
3944
+ kind === "success" ||
3945
+ kind === "command" ||
3946
+ kind === "diff-file" ||
3947
+ kind === "diff-summary" ||
3948
+ kind === "diff-meta") {
3949
+ return " ";
3950
+ }
3951
+ return "";
3952
+ }
3953
+ export function workerOutputDiffDisplayLines(text, width) {
3954
+ const columns = workerOutputDiffColumns(text);
3955
+ if (!columns) {
3956
+ return [text];
3957
+ }
3958
+ const prefix = `${columns.lineNumber} ${columns.sign} `;
3959
+ const continuationPrefix = " ".repeat(prefix.length);
3960
+ const codeWidth = Math.max(1, width - prefix.length);
3961
+ const chunks = wrapTextByWidth(columns.code || " ", codeWidth);
3962
+ return chunks.map((chunk, index) => `${index === 0 ? prefix : continuationPrefix}${chunk}`.trimEnd());
3963
+ }
3964
+ export function workerOutputCodeDisplayLines(text, width) {
3965
+ const prefix = "| ";
3966
+ const continuationPrefix = " ";
3967
+ const codeWidth = Math.max(1, width - prefix.length);
3968
+ const chunks = wrapTextByWidth(text || " ", codeWidth);
3969
+ return chunks.map((chunk, index) => `${index === 0 ? prefix : continuationPrefix}${chunk}`.trimEnd());
3970
+ }
3971
+ function wrapTextByWidth(text, width) {
3972
+ return wrapByDisplayWidth(text, width);
3973
+ }
3974
+ function isDiffCodeKind(kind) {
3975
+ return kind === "diff-add" || kind === "diff-remove" || kind === "diff-context";
3976
+ }
3977
+ export function workerOutputSourceColumns(text) {
3978
+ const match = text.match(/^(\s*)(\d+)(?:(\t)(.*)| {2,}(.*)|)$/);
3979
+ if (!match) {
3980
+ return null;
3981
+ }
3982
+ const hasTabSeparator = match[3] !== undefined;
3983
+ const hasSpaceSeparator = match[5] !== undefined;
3984
+ if (!hasTabSeparator && !hasSpaceSeparator) {
3985
+ return null;
3986
+ }
3987
+ return {
3988
+ lineNumber: match[2] ?? "",
3989
+ code: match[4] ?? match[5] ?? ""
3990
+ };
3991
+ }
3992
+ export function workerOutputSourceDisplayLines(text, width) {
3993
+ const columns = workerOutputSourceColumns(text) ?? parseFormattedSourceLine(text);
3994
+ if (!columns) {
3995
+ return [text];
3996
+ }
3997
+ const prefix = `${columns.lineNumber.padStart(4, " ")} `;
3998
+ const continuationPrefix = " ".repeat(prefix.length);
3999
+ const codeWidth = Math.max(1, width - prefix.length);
4000
+ const chunks = wrapTextByWidth(columns.code || " ", codeWidth);
4001
+ return chunks.map((chunk, index) => `${index === 0 ? prefix : continuationPrefix}${chunk}`.trimEnd());
4002
+ }
4003
+ function parseFormattedSourceLine(text) {
4004
+ const match = text.match(/^(\s*\d+)\s{2}(.*)$/);
4005
+ if (!match) {
4006
+ return null;
4007
+ }
4008
+ return {
4009
+ lineNumber: (match[1] ?? "").trim(),
4010
+ code: match[2] ?? ""
4011
+ };
4012
+ }
4013
+ function formatGutter(value) {
4014
+ return value ? `${value.padEnd(4, " ")} | ` : " ";
4015
+ }