parallel-codex-tui 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.parallel-codex/config.example.toml +136 -3
- package/README.md +299 -21
- package/dist/bootstrap.js +37 -17
- package/dist/cli-args.js +34 -3
- package/dist/cli-help.js +20 -0
- package/dist/cli-startup-preflight.js +18 -0
- package/dist/cli-startup-recovery.js +82 -0
- package/dist/cli-workspace-picker.js +330 -0
- package/dist/cli-workspace-transition.js +33 -0
- package/dist/cli-workspace.js +7 -71
- package/dist/cli.js +234 -24
- package/dist/core/collaboration-timeline.js +268 -0
- package/dist/core/config-errors.js +14 -0
- package/dist/core/config.js +297 -109
- package/dist/core/file-store.js +119 -6
- package/dist/core/lease-finalization.js +22 -0
- package/dist/core/paths.js +7 -0
- package/dist/core/process-identity.js +48 -0
- package/dist/core/process-mutation-turn.js +128 -0
- package/dist/core/process-ownership.js +276 -0
- package/dist/core/process-tree.js +90 -0
- package/dist/core/router-audit.js +155 -0
- package/dist/core/router-redaction.js +31 -0
- package/dist/core/router.js +462 -35
- package/dist/core/session-index.js +412 -88
- package/dist/core/session-manager.js +1110 -40
- package/dist/core/task-session-details.js +175 -0
- package/dist/core/task-state-machine.js +18 -0
- package/dist/core/workspace-commit-recovery.js +118 -0
- package/dist/core/workspace.js +19 -11
- package/dist/doctor.js +373 -34
- package/dist/domain/schemas.js +142 -7
- package/dist/orchestrator/collaboration-channel.js +289 -6
- package/dist/orchestrator/feature-plan.js +70 -0
- package/dist/orchestrator/final-acceptance.js +86 -0
- package/dist/orchestrator/judge-artifacts.js +236 -0
- package/dist/orchestrator/orchestrator.js +2086 -203
- package/dist/orchestrator/prompts.js +168 -5
- package/dist/orchestrator/supervisor-summary.js +56 -2
- package/dist/orchestrator/workspace-sandbox.js +927 -0
- package/dist/tui/App.js +3187 -161
- package/dist/tui/AppShell.js +196 -25
- package/dist/tui/CollaborationTimelineView.js +327 -0
- package/dist/tui/FeatureBoardView.js +232 -0
- package/dist/tui/InputBar.js +581 -57
- package/dist/tui/RouterDiagnosticsView.js +469 -0
- package/dist/tui/StatusBar.js +610 -57
- package/dist/tui/StatusDetailView.js +164 -0
- package/dist/tui/TaskSessionDetailView.js +222 -0
- package/dist/tui/TaskSessionsView.js +210 -0
- package/dist/tui/TerminalOutput.js +53 -9
- package/dist/tui/WorkerOutputView.js +1404 -161
- package/dist/tui/WorkerOverviewView.js +269 -0
- package/dist/tui/chat-history.js +25 -0
- package/dist/tui/chat-input.js +67 -19
- package/dist/tui/chat-paste.js +76 -0
- package/dist/tui/display-width.js +41 -3
- package/dist/tui/incremental-text-file.js +101 -0
- package/dist/tui/keyboard.js +49 -0
- package/dist/tui/markdown-text.js +14 -0
- package/dist/tui/raw-input-decoder.js +3 -0
- package/dist/tui/scrolling.js +2 -1
- package/dist/tui/status-line.js +360 -11
- package/dist/tui/task-memory.js +13 -1
- package/dist/tui/task-result.js +105 -0
- package/dist/tui/terminal-screen.js +13 -1
- package/dist/tui/theme-contrast.js +144 -0
- package/dist/tui/theme-preview.js +109 -0
- package/dist/tui/theme.js +158 -0
- package/dist/version.js +1 -1
- package/dist/workers/capabilities.js +213 -0
- package/dist/workers/live-probe.js +177 -0
- package/dist/workers/mock-adapter.js +73 -8
- package/dist/workers/native-attach.js +106 -16
- package/dist/workers/process-adapter.js +572 -77
- package/dist/workers/provider.js +26 -0
- package/dist/workers/registry.js +12 -20
- package/package.json +11 -2
|
@@ -1,60 +1,152 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
-
import { readdir } from "node:fs/promises";
|
|
4
|
+
import { readdir, stat } from "node:fs/promises";
|
|
5
5
|
import { Box, Text } from "ink";
|
|
6
6
|
import { pathExists, readTextIfExists } from "../core/file-store.js";
|
|
7
7
|
import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
|
|
8
|
+
import { decodeHtmlEntities } from "./markdown-text.js";
|
|
9
|
+
import { createIncrementalTextFileReader } from "./incremental-text-file.js";
|
|
8
10
|
import { selectViewportLines } from "./scrolling.js";
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
import { TUI_THEME } from "./theme.js";
|
|
12
|
+
const EMPTY_WORKER_OUTPUT_TEXT = "waiting for output";
|
|
13
|
+
const LOADING_WORKER_OUTPUT_TEXT = "loading output";
|
|
14
|
+
const NO_WORKER_OUTPUT_TEXT = "No workers yet · start a complex task";
|
|
15
|
+
export function workerOutputNavigationTargets(lines, height, query = "") {
|
|
16
|
+
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
17
|
+
const maxOffset = Math.max(0, lines.length - viewportHeight);
|
|
18
|
+
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
19
|
+
const searchOffsets = [];
|
|
20
|
+
const searchLineIndexes = [];
|
|
21
|
+
const errorOffsets = [];
|
|
22
|
+
const diffOffsets = [];
|
|
23
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
24
|
+
const line = lines[index];
|
|
25
|
+
if (!line) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const offset = Math.min(maxOffset, Math.max(0, lines.length - 1 - index));
|
|
29
|
+
if (normalizedQuery && line.text.toLocaleLowerCase().includes(normalizedQuery)) {
|
|
30
|
+
searchOffsets.push(offset);
|
|
31
|
+
searchLineIndexes.push(index);
|
|
32
|
+
}
|
|
33
|
+
if (line.kind === "error") {
|
|
34
|
+
errorOffsets.push(offset);
|
|
35
|
+
}
|
|
36
|
+
if (line.kind === "diff-file" || line.kind === "diff-hunk") {
|
|
37
|
+
diffOffsets.push(offset);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { searchOffsets, searchLineIndexes, errorOffsets, diffOffsets };
|
|
41
|
+
}
|
|
42
|
+
export function WorkerOutputView({ title, role, featureId, logPath, scrollOffset = 0, height = 24, terminalWidth = Number(process.stdout.columns) || 120, searchQuery = "", searchMatchIndex = 0, onViewportChange, onNavigationChange }) {
|
|
12
43
|
const nanoOutput = isNanoWorkerOutputWidth(terminalWidth);
|
|
13
|
-
const contentKey = workerOutputContentKey(role, logPath, nanoOutput);
|
|
44
|
+
const contentKey = workerOutputContentKey(role, featureId, logPath, nanoOutput);
|
|
14
45
|
const [contentState, setContentState] = useState({ key: "", lines: [] });
|
|
15
46
|
useEffect(() => {
|
|
16
47
|
let active = true;
|
|
48
|
+
let refreshing = false;
|
|
49
|
+
let refreshQueued = false;
|
|
50
|
+
let rendered = false;
|
|
51
|
+
let artifactSignature = null;
|
|
17
52
|
const loadKey = contentKey;
|
|
53
|
+
const outputReader = logPath ? createIncrementalTextFileReader(logPath) : null;
|
|
18
54
|
async function load() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
try {
|
|
56
|
+
if (!logPath || !outputReader) {
|
|
57
|
+
if (rendered) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
rendered = true;
|
|
61
|
+
setContentState({
|
|
62
|
+
key: loadKey,
|
|
63
|
+
lines: [{ kind: "placeholder", text: NO_WORKER_OUTPUT_TEXT }]
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const snapshot = await outputReader.read();
|
|
68
|
+
const nextArtifactSignature = nanoOutput
|
|
69
|
+
? ""
|
|
70
|
+
: await workerArtifactSignature(role, dirname(logPath), featureId);
|
|
71
|
+
if (rendered && !snapshot.changed && nextArtifactSignature === artifactSignature) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
rendered = true;
|
|
75
|
+
artifactSignature = nextArtifactSignature;
|
|
76
|
+
const output = snapshot.text;
|
|
77
|
+
if (nanoOutput) {
|
|
78
|
+
const lines = await loadNanoWorkerOutputLines(logPath, height, output);
|
|
79
|
+
if (active) {
|
|
80
|
+
setContentState({
|
|
81
|
+
key: loadKey,
|
|
82
|
+
lines
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const sections = await loadWorkerOutputSections(role, logPath, output, featureId);
|
|
28
88
|
if (active) {
|
|
29
89
|
setContentState({
|
|
30
90
|
key: loadKey,
|
|
31
|
-
lines
|
|
91
|
+
lines: renderLinesFromSections(sections, { nanoProcess: nanoOutput, height })
|
|
32
92
|
});
|
|
33
93
|
}
|
|
34
|
-
return;
|
|
35
94
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (active) {
|
|
97
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
98
|
+
setContentState({
|
|
99
|
+
key: loadKey,
|
|
100
|
+
lines: [{ kind: "error", text: `output read failed · ${detail}` }]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
42
103
|
}
|
|
43
104
|
}
|
|
44
|
-
|
|
105
|
+
const refresh = () => {
|
|
106
|
+
if (refreshing) {
|
|
107
|
+
refreshQueued = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
refreshing = true;
|
|
111
|
+
void (async () => {
|
|
112
|
+
do {
|
|
113
|
+
refreshQueued = false;
|
|
114
|
+
await load();
|
|
115
|
+
} while (active && refreshQueued);
|
|
116
|
+
})().finally(() => {
|
|
117
|
+
refreshing = false;
|
|
118
|
+
if (active && refreshQueued) {
|
|
119
|
+
refresh();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
refresh();
|
|
45
124
|
const interval = setInterval(() => {
|
|
46
|
-
|
|
125
|
+
refresh();
|
|
47
126
|
}, 1000);
|
|
48
127
|
return () => {
|
|
49
128
|
active = false;
|
|
50
129
|
clearInterval(interval);
|
|
51
130
|
};
|
|
52
|
-
}, [contentKey, height, logPath, nanoOutput, role]);
|
|
53
|
-
const
|
|
131
|
+
}, [contentKey, featureId, height, logPath, nanoOutput, role]);
|
|
132
|
+
const contentReady = contentState.key === contentKey;
|
|
133
|
+
const content = contentReady
|
|
54
134
|
? contentState.lines
|
|
55
|
-
: [{ kind: "
|
|
135
|
+
: [{ kind: "placeholder", text: LOADING_WORKER_OUTPUT_TEXT }];
|
|
56
136
|
const sourceLines = isNanoWorkerOutputWidth(terminalWidth) ? tinyWorkerOutputSourceLines(content, height) : content;
|
|
57
|
-
const
|
|
137
|
+
const panelWidth = workerOutputPanelRailWidth(terminalWidth);
|
|
138
|
+
const nanoRender = isNanoWorkerOutputWidth(terminalWidth);
|
|
139
|
+
const displayLines = renderDisplayLines(sourceLines, terminalWidth, {
|
|
140
|
+
contentWidth: nanoRender ? undefined : Math.max(1, panelWidth - 2),
|
|
141
|
+
nano: nanoRender
|
|
142
|
+
});
|
|
143
|
+
const navigationTargets = workerOutputNavigationTargets(displayLines, height, searchQuery);
|
|
144
|
+
const selectedSearchIndex = navigationTargets.searchLineIndexes.length > 0
|
|
145
|
+
? Math.min(navigationTargets.searchLineIndexes.length - 1, Math.max(0, Math.trunc(searchMatchIndex)))
|
|
146
|
+
: -1;
|
|
147
|
+
const selectedSearchLineIndex = selectedSearchIndex >= 0
|
|
148
|
+
? navigationTargets.searchLineIndexes[selectedSearchIndex] ?? -1
|
|
149
|
+
: -1;
|
|
58
150
|
const selection = selectViewportLines(displayLines.map((line) => line.text).join("\n"), height, scrollOffset);
|
|
59
151
|
const end = displayLines.length - selection.clampedOffset;
|
|
60
152
|
const rawStart = Math.max(0, end - Math.max(1, height));
|
|
@@ -67,30 +159,58 @@ export function WorkerOutputView({ title, role, logPath, scrollOffset = 0, heigh
|
|
|
67
159
|
? 0
|
|
68
160
|
: workerOutputTailTopPaddingLines(selection.clampedOffset, selection.maxOffset, visibleLines.length, height);
|
|
69
161
|
useEffect(() => {
|
|
162
|
+
if (!contentReady) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
70
165
|
onViewportChange?.({
|
|
71
166
|
offset: selection.clampedOffset,
|
|
72
167
|
maxOffset: selection.maxOffset
|
|
73
168
|
});
|
|
74
|
-
}, [onViewportChange, selection.clampedOffset, selection.maxOffset]);
|
|
75
|
-
|
|
169
|
+
}, [contentReady, onViewportChange, selection.clampedOffset, selection.maxOffset]);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
onNavigationChange?.(navigationTargets);
|
|
172
|
+
}, [
|
|
173
|
+
navigationTargets.diffOffsets.join(","),
|
|
174
|
+
navigationTargets.errorOffsets.join(","),
|
|
175
|
+
navigationTargets.searchLineIndexes.join(","),
|
|
176
|
+
navigationTargets.searchOffsets.join(","),
|
|
177
|
+
onNavigationChange
|
|
178
|
+
]);
|
|
76
179
|
const scrollLabel = workerOutputScrollDisplay(selection.clampedOffset, selection.maxOffset, terminalWidth);
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
180
|
+
const normalizedSearchQuery = searchQuery.trim();
|
|
181
|
+
const navigationLabel = normalizedSearchQuery
|
|
182
|
+
? `find ${selectedSearchIndex >= 0 ? selectedSearchIndex + 1 : 0}/${navigationTargets.searchOffsets.length}`
|
|
183
|
+
: selection.maxOffset > 0 ? scrollLabel : null;
|
|
184
|
+
const displayTitle = workerOutputHeaderDisplay(title, navigationLabel, Math.max(1, panelWidth - 2));
|
|
185
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(WorkerOutputTitleRail, { title: displayTitle, width: panelWidth }), Array.from({ length: topPaddingLines }, (_, index) => _jsx(WorkerOutputBlankLine, { width: panelWidth }, `tail-pad-${index}`)), visibleLines.length > 0
|
|
80
186
|
? visibleLines.map((line, index) => nanoRender
|
|
81
|
-
? _jsx(WorkerOutputNanoLine, { line: line, width: terminalWidth }, index)
|
|
82
|
-
: _jsx(WorkerOutputLine, { line: line }, index))
|
|
83
|
-
: _jsx(Text, { children:
|
|
187
|
+
? _jsx(WorkerOutputNanoLine, { fillWidth: panelWidth, line: line, selected: start + index === selectedSearchLineIndex, width: terminalWidth }, index)
|
|
188
|
+
: _jsx(WorkerOutputLine, { line: line, selected: start + index === selectedSearchLineIndex, width: panelWidth }, index))
|
|
189
|
+
: _jsx(Text, { ...workerOutputEmptyFallbackTheme(), children: EMPTY_WORKER_OUTPUT_TEXT })] }));
|
|
84
190
|
}
|
|
85
191
|
function WorkerOutputTitleRail({ title, width }) {
|
|
86
192
|
const titleText = ` ${title} `;
|
|
193
|
+
const segments = workerOutputTitleSegments(title);
|
|
87
194
|
const renderWidth = typeof process.stdout.columns === "number"
|
|
88
195
|
? width
|
|
89
196
|
: null;
|
|
90
197
|
const trailingWidth = renderWidth === null
|
|
91
198
|
? 0
|
|
92
199
|
: Math.max(0, renderWidth - displayWidth(titleText));
|
|
93
|
-
return (_jsxs(Box, { children: [_jsx(Text, { backgroundColor:
|
|
200
|
+
return (_jsxs(Box, { children: [_jsx(Text, { backgroundColor: TUI_THEME.chrome, children: " " }), _jsx(Text, { backgroundColor: TUI_THEME.chrome, color: TUI_THEME.accent, bold: true, children: segments.identity }), segments.metadata ? (_jsx(Text, { backgroundColor: TUI_THEME.chrome, color: TUI_THEME.muted, children: segments.metadata })) : null, _jsx(Text, { backgroundColor: TUI_THEME.chrome, children: " " }), trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.chrome, children: " ".repeat(trailingWidth) }) : null] }));
|
|
201
|
+
}
|
|
202
|
+
function workerOutputTitleSegments(title) {
|
|
203
|
+
const separatorIndex = title.indexOf(" · ");
|
|
204
|
+
if (separatorIndex >= 0) {
|
|
205
|
+
return {
|
|
206
|
+
identity: title.slice(0, separatorIndex),
|
|
207
|
+
metadata: title.slice(separatorIndex)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const spaceIndex = title.indexOf(" ");
|
|
211
|
+
return spaceIndex > 0
|
|
212
|
+
? { identity: title.slice(0, spaceIndex), metadata: title.slice(spaceIndex) }
|
|
213
|
+
: { identity: title, metadata: "" };
|
|
94
214
|
}
|
|
95
215
|
function workerOutputPanelRailWidth(terminalWidth) {
|
|
96
216
|
const renderWidth = typeof process.stdout.columns === "number"
|
|
@@ -128,17 +248,33 @@ function lastIndexWhere(values, predicate) {
|
|
|
128
248
|
}
|
|
129
249
|
return -1;
|
|
130
250
|
}
|
|
131
|
-
function workerOutputContentKey(role, logPath, nanoOutput = false) {
|
|
132
|
-
return `${role ?? ""}:${logPath ?? ""}:${nanoOutput ? "nano" : "full"}`;
|
|
251
|
+
function workerOutputContentKey(role, featureId, logPath, nanoOutput = false) {
|
|
252
|
+
return `${role ?? ""}:${featureId ?? ""}:${logPath ?? ""}:${nanoOutput ? "nano" : "full"}`;
|
|
133
253
|
}
|
|
134
254
|
export function workerOutputTitleDisplay(title, width) {
|
|
135
|
-
const match = title.match(/^(.+?)\s+\(([^)]+)\)
|
|
255
|
+
const match = title.match(/^(.+?)\s+\(([^)]+)\)(?:\s+·\s+(.+?))?\s+output(?:\s+\((\d+\/\d+)\))?$/i);
|
|
136
256
|
if (!match) {
|
|
137
|
-
return title.replace(/\s+output\b/i, "");
|
|
257
|
+
return compactEndByDisplayWidth(title.replace(/\s+output\b/i, "").trim(), width);
|
|
138
258
|
}
|
|
139
259
|
const role = (match[1] ?? "").trim().toLowerCase();
|
|
140
260
|
const provider = (match[2] ?? "").trim().toLowerCase();
|
|
141
|
-
const
|
|
261
|
+
const detail = (match[3] ?? "").trim();
|
|
262
|
+
const page = match[4] ?? "";
|
|
263
|
+
const detailed = joinWorkerOutputChromeParts([`${role}/${provider}`, detail, page]);
|
|
264
|
+
if (detail && displayWidth(detailed) <= width) {
|
|
265
|
+
return detailed;
|
|
266
|
+
}
|
|
267
|
+
if (detail) {
|
|
268
|
+
const fixed = joinWorkerOutputChromeParts([`${role}/${provider}`, page]);
|
|
269
|
+
const detailBudget = width - displayWidth(fixed) - displayWidth(" · ");
|
|
270
|
+
if (detailBudget >= 8) {
|
|
271
|
+
return joinWorkerOutputChromeParts([
|
|
272
|
+
`${role}/${provider}`,
|
|
273
|
+
compactEndByDisplayWidth(detail, detailBudget),
|
|
274
|
+
page
|
|
275
|
+
]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
142
278
|
const compact = joinWorkerOutputChromeParts([`${role}/${provider}`, page]);
|
|
143
279
|
if (displayWidth(compact) <= width) {
|
|
144
280
|
return compact;
|
|
@@ -361,7 +497,7 @@ function latestSectionBoundaryWithinTail(lines, start, end) {
|
|
|
361
497
|
}
|
|
362
498
|
return latestSectionStart !== null && end - latestSectionStart >= 3 ? latestSectionStart : null;
|
|
363
499
|
}
|
|
364
|
-
async function loadWorkerOutputSections(role, logPath) {
|
|
500
|
+
async function loadWorkerOutputSections(role, logPath, processOutput, featureId) {
|
|
365
501
|
const workerDir = dirname(logPath);
|
|
366
502
|
const sections = [];
|
|
367
503
|
const artifactFiles = roleArtifactFiles(role);
|
|
@@ -372,28 +508,28 @@ async function loadWorkerOutputSections(role, logPath) {
|
|
|
372
508
|
}
|
|
373
509
|
sections.push({ group: "role", title: file, text });
|
|
374
510
|
}
|
|
375
|
-
for (const artifact of await featureArtifactSections(role, workerDir)) {
|
|
511
|
+
for (const artifact of await featureArtifactSections(role, workerDir, featureId)) {
|
|
376
512
|
const text = artifact.text.trim();
|
|
377
513
|
if (!text) {
|
|
378
514
|
continue;
|
|
379
515
|
}
|
|
380
516
|
sections.push({ group: "feature", title: artifact.label, text });
|
|
381
517
|
}
|
|
382
|
-
const rawOutput = (await readTextIfExists(logPath)).trimEnd();
|
|
518
|
+
const rawOutput = (processOutput ?? await readTextIfExists(logPath)).trimEnd();
|
|
383
519
|
if (rawOutput) {
|
|
384
520
|
sections.push({ group: "process", title: "output.log", text: rawOutput });
|
|
385
521
|
}
|
|
386
522
|
return sections;
|
|
387
523
|
}
|
|
388
|
-
async function loadNanoWorkerOutputLines(logPath, height) {
|
|
389
|
-
const rawOutput = (await readTextIfExists(logPath)).trimEnd();
|
|
524
|
+
async function loadNanoWorkerOutputLines(logPath, height, processOutput) {
|
|
525
|
+
const rawOutput = (processOutput ?? await readTextIfExists(logPath)).trimEnd();
|
|
390
526
|
if (!rawOutput) {
|
|
391
|
-
return [{ kind: "
|
|
527
|
+
return [{ kind: "placeholder", text: EMPTY_WORKER_OUTPUT_TEXT }];
|
|
392
528
|
}
|
|
393
529
|
const lines = renderNanoProcessContent(compactNanoProcessText(rawOutput, height));
|
|
394
530
|
return lines.length > 0
|
|
395
531
|
? [{ kind: "group", text: "process" }, ...lines]
|
|
396
|
-
: [{ kind: "
|
|
532
|
+
: [{ kind: "placeholder", text: EMPTY_WORKER_OUTPUT_TEXT }];
|
|
397
533
|
}
|
|
398
534
|
function renderNanoProcessContent(text) {
|
|
399
535
|
const lines = [];
|
|
@@ -438,7 +574,7 @@ function roleArtifactFiles(role) {
|
|
|
438
574
|
}
|
|
439
575
|
return [];
|
|
440
576
|
}
|
|
441
|
-
async function featureArtifactSections(role, workerDir) {
|
|
577
|
+
async function featureArtifactSections(role, workerDir, featureId) {
|
|
442
578
|
const files = featureArtifactFiles(role);
|
|
443
579
|
if (files.length === 0) {
|
|
444
580
|
return [];
|
|
@@ -448,11 +584,7 @@ async function featureArtifactSections(role, workerDir) {
|
|
|
448
584
|
if (!(await pathExists(featuresDir))) {
|
|
449
585
|
return [];
|
|
450
586
|
}
|
|
451
|
-
const
|
|
452
|
-
const featureDirs = entries
|
|
453
|
-
.filter((entry) => entry.isDirectory())
|
|
454
|
-
.map((entry) => entry.name)
|
|
455
|
-
.sort();
|
|
587
|
+
const featureDirs = await scopedFeatureDirectoryNames(featuresDir, workerDir, featureId);
|
|
456
588
|
const sections = [];
|
|
457
589
|
for (const featureDir of featureDirs) {
|
|
458
590
|
for (const file of files) {
|
|
@@ -468,6 +600,57 @@ async function featureArtifactSections(role, workerDir) {
|
|
|
468
600
|
}
|
|
469
601
|
return sections;
|
|
470
602
|
}
|
|
603
|
+
async function workerArtifactSignature(role, workerDir, featureId) {
|
|
604
|
+
const paths = roleArtifactFiles(role).map((file) => join(workerDir, file));
|
|
605
|
+
const featureFiles = featureArtifactFiles(role);
|
|
606
|
+
if (featureFiles.length > 0) {
|
|
607
|
+
const featuresDir = join(dirname(workerDir), "features");
|
|
608
|
+
if (await pathExists(featuresDir)) {
|
|
609
|
+
const featureDirs = await scopedFeatureDirectoryNames(featuresDir, workerDir, featureId);
|
|
610
|
+
for (const featureDir of featureDirs) {
|
|
611
|
+
for (const file of featureFiles) {
|
|
612
|
+
paths.push(join(featuresDir, featureDir, file));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const signatures = await Promise.all(paths.map(async (path) => {
|
|
618
|
+
try {
|
|
619
|
+
const metadata = await stat(path);
|
|
620
|
+
return `${path}\0${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeMs}:${metadata.ctimeMs}`;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
if (error.code === "ENOENT") {
|
|
624
|
+
return `${path}\0missing`;
|
|
625
|
+
}
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
}));
|
|
629
|
+
return signatures.join("\u0001");
|
|
630
|
+
}
|
|
631
|
+
async function scopedFeatureDirectoryNames(featuresDir, workerDir, featureId) {
|
|
632
|
+
if (featureId) {
|
|
633
|
+
return [featureId];
|
|
634
|
+
}
|
|
635
|
+
const turnId = workerTurnIdFromDirectory(workerDir);
|
|
636
|
+
const entries = await readdir(featuresDir, { withFileTypes: true });
|
|
637
|
+
return entries
|
|
638
|
+
.filter((entry) => entry.isDirectory() && (!turnId || entry.name === turnId || entry.name.startsWith(`${turnId}-`)))
|
|
639
|
+
.map((entry) => entry.name)
|
|
640
|
+
.sort();
|
|
641
|
+
}
|
|
642
|
+
function workerTurnIdFromDirectory(workerDir) {
|
|
643
|
+
const workerId = basename(workerDir);
|
|
644
|
+
const waveTurn = workerId.match(/-wave-(\d{4,})-/)?.[1];
|
|
645
|
+
if (waveTurn) {
|
|
646
|
+
return waveTurn;
|
|
647
|
+
}
|
|
648
|
+
const scopedTurn = workerId.match(/-(\d{4,})(?:-|$)/)?.[1];
|
|
649
|
+
if (scopedTurn) {
|
|
650
|
+
return scopedTurn;
|
|
651
|
+
}
|
|
652
|
+
return /^(?:judge|actor|critic)-/.test(workerId) ? "0001" : null;
|
|
653
|
+
}
|
|
471
654
|
function featureArtifactFiles(role) {
|
|
472
655
|
if (role === "actor") {
|
|
473
656
|
return ["actor-worklog.md", "actor-replies.jsonl"];
|
|
@@ -655,7 +838,7 @@ function renderLinesFromSections(sections, options = {}) {
|
|
|
655
838
|
renderedArtifactFiles.add(basename(section.title));
|
|
656
839
|
}
|
|
657
840
|
}
|
|
658
|
-
return lines;
|
|
841
|
+
return lines.length > 0 ? lines : [{ kind: "placeholder", text: EMPTY_WORKER_OUTPUT_TEXT }];
|
|
659
842
|
}
|
|
660
843
|
function compactNanoProcessText(text, height) {
|
|
661
844
|
const rawLines = text.split(/\r?\n/);
|
|
@@ -773,8 +956,16 @@ function renderSectionContent(section, renderedArtifactFiles = new Set()) {
|
|
|
773
956
|
index = collapseDiff ? skipProcessDiffTailContext(rawLines, diffBlock.nextIndex) : diffBlock.nextIndex;
|
|
774
957
|
continue;
|
|
775
958
|
}
|
|
776
|
-
if (section.group === "process" &&
|
|
777
|
-
|
|
959
|
+
if (section.group === "process" && parseHunkStart(line.trimStart())) {
|
|
960
|
+
const bareHunk = collectBareDiffHunkBlock(rawLines, index);
|
|
961
|
+
const bareDiffFile = inferBareDiffFile(rawLines, index);
|
|
962
|
+
if (!bareDiffTargetsRenderedArtifact(bareHunk.lines, bareDiffFile, hiddenProcessDiffFiles)) {
|
|
963
|
+
if (lines.length > 0 && lines[lines.length - 1]?.kind !== "blank") {
|
|
964
|
+
lines.push({ kind: "blank", text: "" });
|
|
965
|
+
}
|
|
966
|
+
lines.push(...renderBareDiffHunkContent(bareHunk.lines, bareDiffFile));
|
|
967
|
+
}
|
|
968
|
+
index = skipBareDiffTailContext(rawLines, bareHunk.nextIndex);
|
|
778
969
|
continue;
|
|
779
970
|
}
|
|
780
971
|
if (section.group === "process" &&
|
|
@@ -797,13 +988,10 @@ function renderSectionContent(section, renderedArtifactFiles = new Set()) {
|
|
|
797
988
|
continue;
|
|
798
989
|
}
|
|
799
990
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (isMarkdownTableRow(trimmed)) {
|
|
805
|
-
lines.push({ kind: "table", text: renderMarkdownTableRow(trimmed) });
|
|
806
|
-
index += 1;
|
|
991
|
+
const tableBlock = collectMarkdownTableBlock(rawLines, index);
|
|
992
|
+
if (tableBlock) {
|
|
993
|
+
lines.push(...tableBlock.lines);
|
|
994
|
+
index = tableBlock.nextIndex;
|
|
807
995
|
continue;
|
|
808
996
|
}
|
|
809
997
|
lines.push(classifyRenderedLine(section, line));
|
|
@@ -820,6 +1008,25 @@ function processDiffBlockTargetsArtifacts(diffLines, artifactFiles) {
|
|
|
820
1008
|
return artifactFiles.has(diffTitlePath(title));
|
|
821
1009
|
});
|
|
822
1010
|
}
|
|
1011
|
+
function bareDiffTargetsRenderedArtifact(diffLines, diffFile, artifactFiles) {
|
|
1012
|
+
if (artifactFiles.size === 0) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
if (artifactFiles.has("patch.diff")) {
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
if (diffFile) {
|
|
1019
|
+
return artifactFiles.has(basename(diffFile));
|
|
1020
|
+
}
|
|
1021
|
+
const additions = diffLines
|
|
1022
|
+
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
|
|
1023
|
+
.map((line) => line.slice(1).trim())
|
|
1024
|
+
.filter(Boolean);
|
|
1025
|
+
return (artifactFiles.has("review.md")
|
|
1026
|
+
&& additions.some((line) => /^(?:#{1,6}\s+)?(?:Critic Review|APPROVED|REVISION_REQUIRED)\b/i.test(line))) || ((artifactFiles.has("worklog.md") || artifactFiles.has("actor-worklog.md"))
|
|
1027
|
+
&& additions.some((line) => /^(?:#{1,6}\s+)?(?:Actor )?Worklog\b/i.test(line))) || (artifactFiles.has("decisions.md")
|
|
1028
|
+
&& additions.some((line) => /^(?:#{1,6}\s+)?Decisions\b/i.test(line)));
|
|
1029
|
+
}
|
|
823
1030
|
function compactRenderedBlankLines(lines) {
|
|
824
1031
|
const compacted = [];
|
|
825
1032
|
let blankPending = false;
|
|
@@ -1052,7 +1259,137 @@ function isSingleFilePathListItem(trimmed) {
|
|
|
1052
1259
|
/^[A-Za-z0-9._@+-]+\/[A-Za-z0-9._@+*?/-]+$/.test(trimmed));
|
|
1053
1260
|
}
|
|
1054
1261
|
function cleanProcessRenderLines(lines) {
|
|
1055
|
-
return removePatchDiffReadbackNoise(removeDiffAdjacentCodeSummaries(mergeSmokeStatusSuccessLines(lines)));
|
|
1262
|
+
return dedupeSupersededChecklistSnapshots(dedupeBareDiffHunks(dedupeCollapsedDiffSummaries(removePatchDiffReadbackNoise(removeDiffAdjacentCodeSummaries(mergeSmokeStatusSuccessLines(lines))))));
|
|
1263
|
+
}
|
|
1264
|
+
function dedupeBareDiffHunks(lines) {
|
|
1265
|
+
const blocks = [];
|
|
1266
|
+
let currentDiffFile = null;
|
|
1267
|
+
for (let index = 0; index < lines.length;) {
|
|
1268
|
+
const line = lines[index];
|
|
1269
|
+
if (!line) {
|
|
1270
|
+
index += 1;
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (line.kind === "diff-file") {
|
|
1274
|
+
currentDiffFile = line.text.trim();
|
|
1275
|
+
index += 1;
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (line.kind !== "diff-hunk") {
|
|
1279
|
+
if (line.kind !== "blank" && !isAnyDiffLine(line)) {
|
|
1280
|
+
currentDiffFile = null;
|
|
1281
|
+
}
|
|
1282
|
+
index += 1;
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
const startIndex = index;
|
|
1286
|
+
index += 1;
|
|
1287
|
+
while (index < lines.length && isDiffHunkBodyLine(lines[index])) {
|
|
1288
|
+
index += 1;
|
|
1289
|
+
}
|
|
1290
|
+
const blockLines = lines.slice(startIndex, index);
|
|
1291
|
+
const diffFile = line.diffFile ?? currentDiffFile;
|
|
1292
|
+
blocks.push({
|
|
1293
|
+
startIndex,
|
|
1294
|
+
endIndex: index,
|
|
1295
|
+
key: [
|
|
1296
|
+
`file\0${diffFile ?? ""}`,
|
|
1297
|
+
...blockLines.map((line) => `${line.kind}\0${line.text}`)
|
|
1298
|
+
].join("\n")
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
const lastBlockByKey = new Map();
|
|
1302
|
+
for (const block of blocks) {
|
|
1303
|
+
lastBlockByKey.set(block.key, block);
|
|
1304
|
+
}
|
|
1305
|
+
const hiddenIndexes = new Set();
|
|
1306
|
+
for (const block of blocks) {
|
|
1307
|
+
if (lastBlockByKey.get(block.key) === block) {
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
for (let index = block.startIndex; index < block.endIndex; index += 1) {
|
|
1311
|
+
hiddenIndexes.add(index);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return hiddenIndexes.size > 0
|
|
1315
|
+
? lines.filter((_line, index) => !hiddenIndexes.has(index))
|
|
1316
|
+
: lines;
|
|
1317
|
+
}
|
|
1318
|
+
function isDiffHunkBodyLine(line) {
|
|
1319
|
+
return line?.kind === "diff-context" ||
|
|
1320
|
+
line?.kind === "diff-meta" ||
|
|
1321
|
+
line?.kind === "diff-add" ||
|
|
1322
|
+
line?.kind === "diff-remove";
|
|
1323
|
+
}
|
|
1324
|
+
function isAnyDiffLine(line) {
|
|
1325
|
+
return line.kind === "diff-file" ||
|
|
1326
|
+
line.kind === "diff-summary" ||
|
|
1327
|
+
line.kind === "diff-hunk" ||
|
|
1328
|
+
line.kind === "diff-context" ||
|
|
1329
|
+
line.kind === "diff-meta" ||
|
|
1330
|
+
line.kind === "diff-add" ||
|
|
1331
|
+
line.kind === "diff-remove";
|
|
1332
|
+
}
|
|
1333
|
+
function dedupeSupersededChecklistSnapshots(lines) {
|
|
1334
|
+
const snapshots = [];
|
|
1335
|
+
for (let index = 0; index < lines.length;) {
|
|
1336
|
+
const firstItem = checklistSnapshotItem(lines[index]);
|
|
1337
|
+
if (!firstItem) {
|
|
1338
|
+
index += 1;
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const items = [];
|
|
1342
|
+
const startIndex = index;
|
|
1343
|
+
while (index < lines.length) {
|
|
1344
|
+
const item = checklistSnapshotItem(lines[index]);
|
|
1345
|
+
if (!item) {
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
items.push(item);
|
|
1349
|
+
index += 1;
|
|
1350
|
+
}
|
|
1351
|
+
if (items.length >= 2) {
|
|
1352
|
+
snapshots.push({ startIndex, endIndex: index, key: items.join("\n") });
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const lastSnapshotByKey = new Map();
|
|
1356
|
+
for (const snapshot of snapshots) {
|
|
1357
|
+
lastSnapshotByKey.set(snapshot.key, snapshot);
|
|
1358
|
+
}
|
|
1359
|
+
const hidden = new Set();
|
|
1360
|
+
for (const snapshot of snapshots) {
|
|
1361
|
+
if (lastSnapshotByKey.get(snapshot.key) === snapshot) {
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
for (let index = snapshot.startIndex; index < snapshot.endIndex; index += 1) {
|
|
1365
|
+
hidden.add(index);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return hidden.size > 0 ? lines.filter((_line, index) => !hidden.has(index)) : lines;
|
|
1369
|
+
}
|
|
1370
|
+
function checklistSnapshotItem(line) {
|
|
1371
|
+
if (!line || (line.kind !== "content" && line.kind !== "success")) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
return line.text.trim().match(/^(?:✓|✔|→|•)\s+(.+)$/)?.[1]?.trim() ?? null;
|
|
1375
|
+
}
|
|
1376
|
+
function dedupeCollapsedDiffSummaries(lines) {
|
|
1377
|
+
const deduped = [];
|
|
1378
|
+
const seen = new Set();
|
|
1379
|
+
for (const line of lines) {
|
|
1380
|
+
if (isCollapsedDiffSummaryLine(line)) {
|
|
1381
|
+
const key = line.text.trim();
|
|
1382
|
+
if (seen.has(key)) {
|
|
1383
|
+
while (deduped[deduped.length - 1]?.kind === "blank") {
|
|
1384
|
+
deduped.pop();
|
|
1385
|
+
}
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
seen.add(key);
|
|
1389
|
+
}
|
|
1390
|
+
deduped.push(line);
|
|
1391
|
+
}
|
|
1392
|
+
return deduped;
|
|
1056
1393
|
}
|
|
1057
1394
|
function mergeSmokeStatusSuccessLines(lines) {
|
|
1058
1395
|
const merged = [];
|
|
@@ -1197,13 +1534,13 @@ function diffTitlePath(title) {
|
|
|
1197
1534
|
}
|
|
1198
1535
|
const SOURCE_OUTPUT_COLLAPSE_MIN_LINES = 8;
|
|
1199
1536
|
const CODE_OUTPUT_COLLAPSE_MIN_LINES = 4;
|
|
1200
|
-
const FILE_LIST_OUTPUT_COLLAPSE_MIN_LINES =
|
|
1537
|
+
const FILE_LIST_OUTPUT_COLLAPSE_MIN_LINES = 4;
|
|
1201
1538
|
const READ_SUMMARY_RUN_COLLAPSE_MIN_ITEMS = 3;
|
|
1202
1539
|
const NODE_TEST_OUTPUT_COLLAPSE_MIN_PASSES = 3;
|
|
1203
1540
|
const PROCESS_DIFF_COLLAPSE_MIN_BODY_LINES = 1;
|
|
1204
1541
|
const PROCESS_DIFF_COLLAPSE_MIN_FILES = 4;
|
|
1205
1542
|
function sanitizeProcessOutput(text, options = {}) {
|
|
1206
|
-
const rawLines = text.split("\n");
|
|
1543
|
+
const rawLines = dedupeRepeatedAssistantNarrativeBlocks(text.split("\n"));
|
|
1207
1544
|
const kept = [];
|
|
1208
1545
|
const hideAssistantNarration = options.hideAssistantNarration ?? false;
|
|
1209
1546
|
const renderedArtifactFiles = options.renderedArtifactFiles ?? new Set();
|
|
@@ -1222,8 +1559,8 @@ function sanitizeProcessOutput(text, options = {}) {
|
|
|
1222
1559
|
index = skipCodexStartupPreamble(rawLines, index);
|
|
1223
1560
|
continue;
|
|
1224
1561
|
}
|
|
1225
|
-
if (
|
|
1226
|
-
index = skipAssistantNarrativeBlock(rawLines, index);
|
|
1562
|
+
if (isAssistantNarrativeMarker(trimmed)) {
|
|
1563
|
+
index = hideAssistantNarration ? skipAssistantNarrativeBlock(rawLines, index) : index + 1;
|
|
1227
1564
|
continue;
|
|
1228
1565
|
}
|
|
1229
1566
|
if (hideAssistantNarration && isUnmarkedAssistantNarrativeStart(trimmed)) {
|
|
@@ -1240,16 +1577,30 @@ function sanitizeProcessOutput(text, options = {}) {
|
|
|
1240
1577
|
index = skipRolePromptTranscript(rawLines, index);
|
|
1241
1578
|
continue;
|
|
1242
1579
|
}
|
|
1580
|
+
const hiddenCommandBatch = collectParallelHiddenCommandBatch(rawLines, index, renderedArtifactFiles);
|
|
1581
|
+
if (hiddenCommandBatch) {
|
|
1582
|
+
kept.push(...hiddenCommandBatch.lines);
|
|
1583
|
+
index = hiddenCommandBatch.nextIndex;
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
const parallelRead = collectParallelReadCommandBatch(rawLines, index, renderedArtifactFiles);
|
|
1587
|
+
if (parallelRead) {
|
|
1588
|
+
kept.push(parallelRead.summary);
|
|
1589
|
+
index = parallelRead.nextIndex;
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1243
1592
|
const execCommand = collectProcessExecCommand(rawLines, index);
|
|
1244
1593
|
if (execCommand) {
|
|
1245
|
-
if (
|
|
1246
|
-
processCommandReadsParallelCodexTaskFile(execCommand.command) ||
|
|
1247
|
-
processCommandInspectsSessionMetadata(execCommand.command) ||
|
|
1248
|
-
processCommandReadsSessionSystemFile(execCommand.command) ||
|
|
1249
|
-
processCommandReadsSkillDocument(execCommand.command)) {
|
|
1594
|
+
if (shouldHideProcessCommand(execCommand, renderedArtifactFiles)) {
|
|
1250
1595
|
index = skipProcessCommandOutputBlock(rawLines, execCommand.nextIndex);
|
|
1251
1596
|
continue;
|
|
1252
1597
|
}
|
|
1598
|
+
const chainedRead = collectChainedReadCommandBlock(rawLines, execCommand.command, execCommand.nextIndex);
|
|
1599
|
+
if (chainedRead) {
|
|
1600
|
+
kept.push(chainedRead.summary);
|
|
1601
|
+
index = chainedRead.nextIndex;
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1253
1604
|
kept.push(execCommand.commandLine);
|
|
1254
1605
|
index = execCommand.nextIndex;
|
|
1255
1606
|
continue;
|
|
@@ -1257,7 +1608,151 @@ function sanitizeProcessOutput(text, options = {}) {
|
|
|
1257
1608
|
kept.push(rawLines[index] ?? "");
|
|
1258
1609
|
index += 1;
|
|
1259
1610
|
}
|
|
1260
|
-
|
|
1611
|
+
const compactedNetworkOutput = compactRecoveredNetworkEvents(kept);
|
|
1612
|
+
const shieldedDiff = shieldProcessDiffBlocks(compactedNetworkOutput);
|
|
1613
|
+
const sanitized = collapseProcessOutputLines(compactReadSummaryRuns(compactNoMatchSearchCommandBlocks(compactAnnotatedStatusCommandPairs(compactNodeTestOutputBlocks(compactBuildOutputBlocks(compactDevServerFallbackBlocks(compactCollapsedCommandBlocks(dropSuccessfulInternalLaunchCommands(collapseVerboseProcessOutput(shieldedDiff.lines))))))))));
|
|
1614
|
+
return sanitized
|
|
1615
|
+
.map((line) => shieldedDiff.originals.get(line) ?? line)
|
|
1616
|
+
.join("\n")
|
|
1617
|
+
.trimEnd();
|
|
1618
|
+
}
|
|
1619
|
+
function shieldProcessDiffBlocks(lines) {
|
|
1620
|
+
const shielded = [];
|
|
1621
|
+
const originals = new Map();
|
|
1622
|
+
const occupied = new Set(lines.map((line) => normalizedProcessLine(line)));
|
|
1623
|
+
let namespace = 0;
|
|
1624
|
+
let tokenPrefix = "pct protected diff line";
|
|
1625
|
+
while (Array.from(occupied).some((line) => line.startsWith(`${tokenPrefix} `))) {
|
|
1626
|
+
namespace += 1;
|
|
1627
|
+
tokenPrefix = `pct protected diff line namespace ${namespace}`;
|
|
1628
|
+
}
|
|
1629
|
+
let tokenIndex = 0;
|
|
1630
|
+
const appendBlock = (blockLines) => {
|
|
1631
|
+
for (const line of blockLines) {
|
|
1632
|
+
const token = `${tokenPrefix} ${String(tokenIndex).padStart(8, "0")}`;
|
|
1633
|
+
tokenIndex += 1;
|
|
1634
|
+
originals.set(token, line);
|
|
1635
|
+
shielded.push(token);
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
for (let index = 0; index < lines.length;) {
|
|
1639
|
+
const line = normalizedProcessLine(lines[index] ?? "");
|
|
1640
|
+
const trimmedStart = line.trimStart();
|
|
1641
|
+
if (isDiffStartLine(trimmedStart)) {
|
|
1642
|
+
const block = collectEmbeddedDiffBlock(lines, index);
|
|
1643
|
+
appendBlock(block.lines);
|
|
1644
|
+
index = block.nextIndex;
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
if (parseHunkStart(trimmedStart)) {
|
|
1648
|
+
const block = collectBareDiffHunkBlock(lines, index);
|
|
1649
|
+
appendBlock(block.lines);
|
|
1650
|
+
index = block.nextIndex;
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
shielded.push(lines[index] ?? "");
|
|
1654
|
+
index += 1;
|
|
1655
|
+
}
|
|
1656
|
+
return { lines: shielded, originals };
|
|
1657
|
+
}
|
|
1658
|
+
function compactRecoveredNetworkEvents(lines) {
|
|
1659
|
+
const compacted = [];
|
|
1660
|
+
const reconnectAttempts = [];
|
|
1661
|
+
let httpsFallbacks = 0;
|
|
1662
|
+
let summaryIndex = null;
|
|
1663
|
+
for (let index = 0; index < lines.length;) {
|
|
1664
|
+
const event = collectRecoveredNetworkEvent(lines, index);
|
|
1665
|
+
if (!event) {
|
|
1666
|
+
compacted.push(lines[index] ?? "");
|
|
1667
|
+
index += 1;
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
summaryIndex ??= compacted.length;
|
|
1671
|
+
reconnectAttempts.push(...event.reconnectAttempts);
|
|
1672
|
+
httpsFallbacks += event.httpsFallbacks;
|
|
1673
|
+
index = event.nextIndex;
|
|
1674
|
+
}
|
|
1675
|
+
if (summaryIndex !== null) {
|
|
1676
|
+
compacted.splice(summaryIndex, 0, formatRecoveredNetworkSummary(reconnectAttempts, httpsFallbacks));
|
|
1677
|
+
}
|
|
1678
|
+
return compacted;
|
|
1679
|
+
}
|
|
1680
|
+
function collectRecoveredNetworkEvent(lines, startIndex) {
|
|
1681
|
+
const first = normalizedProcessLine(lines[startIndex] ?? "").trim();
|
|
1682
|
+
if (!isNetworkRetryDiagnostic(first) && !reconnectAttempt(first)) {
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
const reconnectAttempts = [];
|
|
1686
|
+
let httpsFallbacks = 0;
|
|
1687
|
+
let index = startIndex;
|
|
1688
|
+
let sawNetworkLine = false;
|
|
1689
|
+
while (index < lines.length) {
|
|
1690
|
+
const trimmed = normalizedProcessLine(lines[index] ?? "").trim();
|
|
1691
|
+
const attempt = reconnectAttempt(trimmed);
|
|
1692
|
+
if (isNetworkRetryDiagnostic(trimmed)) {
|
|
1693
|
+
sawNetworkLine = true;
|
|
1694
|
+
index += 1;
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
if (attempt) {
|
|
1698
|
+
sawNetworkLine = true;
|
|
1699
|
+
reconnectAttempts.push(attempt);
|
|
1700
|
+
index += 1;
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
if (isHttpsFallbackLine(trimmed)) {
|
|
1704
|
+
sawNetworkLine = true;
|
|
1705
|
+
httpsFallbacks += 1;
|
|
1706
|
+
index += 1;
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
if (sawNetworkLine && (!trimmed || /^context compacted$/i.test(trimmed))) {
|
|
1710
|
+
index += 1;
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1715
|
+
if (reconnectAttempts.length === 0) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
const nextLine = nextSignificantProcessText(lines, index);
|
|
1719
|
+
if (!nextLine || isTerminalNetworkFailureLine(nextLine)) {
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
return { reconnectAttempts, httpsFallbacks, nextIndex: index };
|
|
1723
|
+
}
|
|
1724
|
+
function nextSignificantProcessText(lines, startIndex) {
|
|
1725
|
+
for (let index = startIndex; index < lines.length; index += 1) {
|
|
1726
|
+
const trimmed = normalizedProcessLine(lines[index] ?? "").trim();
|
|
1727
|
+
if (trimmed) {
|
|
1728
|
+
return trimmed;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return null;
|
|
1732
|
+
}
|
|
1733
|
+
function isNetworkRetryDiagnostic(line) {
|
|
1734
|
+
return /codex_core::responses_retry:.*retrying sampling request/i.test(line);
|
|
1735
|
+
}
|
|
1736
|
+
function reconnectAttempt(line) {
|
|
1737
|
+
return line.match(/^(?:ERROR:\s*)?Reconnecting\.\.\.\s+(\d+\/\d+)/i)?.[1] ?? null;
|
|
1738
|
+
}
|
|
1739
|
+
function isHttpsFallbackLine(line) {
|
|
1740
|
+
return /Falling back from WebSockets to HTTPS transport/i.test(line);
|
|
1741
|
+
}
|
|
1742
|
+
function isTerminalNetworkFailureLine(line) {
|
|
1743
|
+
return (/^(?:ERROR|FATAL)\b/i.test(line) ||
|
|
1744
|
+
/\b(?:request|process|router|worker)\b.*\b(?:timed out|timeout|failed)\b/i.test(line));
|
|
1745
|
+
}
|
|
1746
|
+
function formatRecoveredNetworkSummary(reconnectAttempts, httpsFallbacks) {
|
|
1747
|
+
const reconnect = reconnectAttempts.length === 1
|
|
1748
|
+
? `reconnect ${reconnectAttempts[0] ?? ""}`.trim()
|
|
1749
|
+
: `${reconnectAttempts.length} reconnects`;
|
|
1750
|
+
const fallback = httpsFallbacks === 1
|
|
1751
|
+
? " via HTTPS fallback"
|
|
1752
|
+
: httpsFallbacks > 1
|
|
1753
|
+
? ` with ${httpsFallbacks} HTTPS fallbacks`
|
|
1754
|
+
: "";
|
|
1755
|
+
return `Network recovered after ${reconnect}${fallback}`;
|
|
1261
1756
|
}
|
|
1262
1757
|
function collectProcessExecCommand(rawLines, startIndex) {
|
|
1263
1758
|
const line = normalizedProcessLine(rawLines[startIndex] ?? "");
|
|
@@ -1269,20 +1764,227 @@ function collectProcessExecCommand(rawLines, startIndex) {
|
|
|
1269
1764
|
return {
|
|
1270
1765
|
command,
|
|
1271
1766
|
commandLine: `$ ${command}`,
|
|
1767
|
+
cwd: parseShellExecCwd(nextLine),
|
|
1272
1768
|
nextIndex: startIndex + 2
|
|
1273
1769
|
};
|
|
1274
1770
|
}
|
|
1275
|
-
return null;
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
const command = parseShellExecCommand(trimmed);
|
|
1774
|
+
if (!command) {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
return {
|
|
1778
|
+
command,
|
|
1779
|
+
commandLine: `$ ${command}`,
|
|
1780
|
+
cwd: parseShellExecCwd(trimmed),
|
|
1781
|
+
nextIndex: startIndex + 1
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function collectParallelReadCommandBatch(rawLines, startIndex, renderedArtifactFiles) {
|
|
1785
|
+
const commands = [];
|
|
1786
|
+
let index = startIndex;
|
|
1787
|
+
while (index < rawLines.length) {
|
|
1788
|
+
const command = collectProcessExecCommand(rawLines, index);
|
|
1789
|
+
if (!command) {
|
|
1790
|
+
break;
|
|
1791
|
+
}
|
|
1792
|
+
commands.push(command);
|
|
1793
|
+
index = command.nextIndex;
|
|
1794
|
+
}
|
|
1795
|
+
if (commands.length < 2 || commands.some((command) => shouldHideProcessCommand(command, renderedArtifactFiles))) {
|
|
1796
|
+
return null;
|
|
1797
|
+
}
|
|
1798
|
+
const targets = [];
|
|
1799
|
+
for (const command of commands) {
|
|
1800
|
+
const readTargets = sedReadTargets(command.command);
|
|
1801
|
+
if (!readTargets) {
|
|
1802
|
+
return null;
|
|
1803
|
+
}
|
|
1804
|
+
targets.push(...readTargets);
|
|
1805
|
+
}
|
|
1806
|
+
let totalLines = 0;
|
|
1807
|
+
let contentLines = 0;
|
|
1808
|
+
for (let commandIndex = 0; commandIndex < commands.length; commandIndex += 1) {
|
|
1809
|
+
const status = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
1810
|
+
if (!/^succeeded\s+in\s+\d+(?:ms|s|m)?:?$/i.test(status)) {
|
|
1811
|
+
return null;
|
|
1812
|
+
}
|
|
1813
|
+
const outputStart = index + 1;
|
|
1814
|
+
const outputEnd = parallelReadOutputEnd(rawLines, outputStart, commandIndex < commands.length - 1);
|
|
1815
|
+
if (outputEnd === null) {
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
const outputLines = compactableReadOutputLines(rawLines, outputStart, outputEnd);
|
|
1819
|
+
totalLines += outputLines.length;
|
|
1820
|
+
contentLines += outputLines.filter((line) => normalizedProcessLine(line).trim()).length;
|
|
1821
|
+
index = outputEnd;
|
|
1822
|
+
}
|
|
1823
|
+
if (contentLines < CODE_OUTPUT_COLLAPSE_MIN_LINES) {
|
|
1824
|
+
return null;
|
|
1825
|
+
}
|
|
1826
|
+
return {
|
|
1827
|
+
summary: `Collapsed read summaries: ${targets.length} chunks, ${totalLines} lines (${compactReadSummaryTargets(targets.map(formatReadTarget))})`,
|
|
1828
|
+
nextIndex: index
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
function collectParallelHiddenCommandBatch(rawLines, startIndex, renderedArtifactFiles) {
|
|
1832
|
+
const commands = [];
|
|
1833
|
+
let index = startIndex;
|
|
1834
|
+
while (index < rawLines.length) {
|
|
1835
|
+
const command = collectProcessExecCommand(rawLines, index);
|
|
1836
|
+
if (!command) {
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1839
|
+
commands.push(command);
|
|
1840
|
+
index = command.nextIndex;
|
|
1841
|
+
}
|
|
1842
|
+
if (commands.length < 2 || commands.some((command) => !shouldHideProcessCommand(command, renderedArtifactFiles))) {
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
const output = [];
|
|
1846
|
+
let allSucceeded = true;
|
|
1847
|
+
const includesWorkerLogReadback = commands.some((command) => processCommandReadsWorkerOutputLog(command.command));
|
|
1848
|
+
for (let commandIndex = 0; commandIndex < commands.length; commandIndex += 1) {
|
|
1849
|
+
const status = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
1850
|
+
if (!isProcessStatusLine(status)) {
|
|
1851
|
+
return null;
|
|
1852
|
+
}
|
|
1853
|
+
allSucceeded &&= /^succeeded\s+in\s+/i.test(status);
|
|
1854
|
+
const outputStart = index + 1;
|
|
1855
|
+
const isLastOutput = commandIndex === commands.length - 1;
|
|
1856
|
+
const outputEnd = includesWorkerLogReadback && isLastOutput
|
|
1857
|
+
? nestedWorkerLogReadbackEnd(rawLines, outputStart)
|
|
1858
|
+
: parallelReadOutputEnd(rawLines, outputStart, !isLastOutput);
|
|
1859
|
+
if (outputEnd === null) {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
output.push(status, ...rawLines.slice(outputStart, outputEnd));
|
|
1863
|
+
index = outputEnd;
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
lines: allSucceeded ? [] : [...commands.map((command) => command.commandLine), ...output],
|
|
1867
|
+
nextIndex: index
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function processCommandReadsWorkerOutputLog(command) {
|
|
1871
|
+
const trimmed = command.trim();
|
|
1872
|
+
return (/^(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(trimmed) &&
|
|
1873
|
+
trimmed.includes(".parallel-codex/sessions/") &&
|
|
1874
|
+
/\/output\.log\b/.test(trimmed));
|
|
1875
|
+
}
|
|
1876
|
+
function nestedWorkerLogReadbackEnd(rawLines, startIndex) {
|
|
1877
|
+
for (let index = startIndex; index < rawLines.length - 1; index += 1) {
|
|
1878
|
+
const marker = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
1879
|
+
const command = normalizedProcessLine(rawLines[index + 1] ?? "").trim();
|
|
1880
|
+
if (marker === "exec" && parseShellExecCommand(command) !== null) {
|
|
1881
|
+
return index;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return null;
|
|
1885
|
+
}
|
|
1886
|
+
function parallelReadOutputEnd(rawLines, startIndex, expectsStatus) {
|
|
1887
|
+
for (let index = startIndex; index < rawLines.length; index += 1) {
|
|
1888
|
+
const trimmed = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
1889
|
+
if (expectsStatus && isProcessStatusLine(trimmed)) {
|
|
1890
|
+
return index;
|
|
1891
|
+
}
|
|
1892
|
+
if (isChainedReadOutputBoundary(trimmed)) {
|
|
1893
|
+
return expectsStatus ? null : index;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return null;
|
|
1897
|
+
}
|
|
1898
|
+
function collectChainedReadCommandBlock(rawLines, command, statusIndex) {
|
|
1899
|
+
const targets = sedReadTargets(command);
|
|
1900
|
+
if (!targets || targets.length < 2) {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
const status = normalizedProcessLine(rawLines[statusIndex] ?? "").trim();
|
|
1904
|
+
if (!/^succeeded\s+in\s+\d+(?:ms|s|m)?:?$/i.test(status)) {
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
const outputStart = statusIndex + 1;
|
|
1908
|
+
let nextIndex = outputStart;
|
|
1909
|
+
while (nextIndex < rawLines.length) {
|
|
1910
|
+
const trimmed = normalizedProcessLine(rawLines[nextIndex] ?? "").trim();
|
|
1911
|
+
if (isChainedReadOutputBoundary(trimmed)) {
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
nextIndex += 1;
|
|
1915
|
+
}
|
|
1916
|
+
const outputLines = compactableReadOutputLines(rawLines, outputStart, nextIndex);
|
|
1917
|
+
const contentLineCount = outputLines.filter((line) => normalizedProcessLine(line).trim()).length;
|
|
1918
|
+
if (contentLineCount < CODE_OUTPUT_COLLAPSE_MIN_LINES) {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
const displayTargets = compactReadSummaryTargets(targets.map(formatReadTarget));
|
|
1922
|
+
return {
|
|
1923
|
+
summary: `Collapsed read summaries: ${targets.length} chunks, ${outputLines.length} lines (${displayTargets})`,
|
|
1924
|
+
nextIndex
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
function compactableReadOutputLines(rawLines, startIndex, endIndex) {
|
|
1928
|
+
const outputLines = rawLines
|
|
1929
|
+
.slice(startIndex, endIndex)
|
|
1930
|
+
.filter((line) => {
|
|
1931
|
+
const normalized = normalizedProcessLine(line);
|
|
1932
|
+
return !shouldDropNoisyProcessLine(normalized.trim(), normalized);
|
|
1933
|
+
});
|
|
1934
|
+
while (outputLines.length > 0 && !normalizedProcessLine(outputLines[0] ?? "").trim()) {
|
|
1935
|
+
outputLines.shift();
|
|
1936
|
+
}
|
|
1937
|
+
while (outputLines.length > 0 && !normalizedProcessLine(outputLines[outputLines.length - 1] ?? "").trim()) {
|
|
1938
|
+
outputLines.pop();
|
|
1939
|
+
}
|
|
1940
|
+
return outputLines;
|
|
1941
|
+
}
|
|
1942
|
+
function sedReadTargets(command) {
|
|
1943
|
+
const segments = command.split(/\s+&&\s+/);
|
|
1944
|
+
const targets = [];
|
|
1945
|
+
for (const segment of segments) {
|
|
1946
|
+
const match = segment.trim().match(/^sed\s+-n\s+(\S+)\s+(.+)$/);
|
|
1947
|
+
const range = match ? singleShellTokenValue(match[1] ?? "") : null;
|
|
1948
|
+
const target = match ? singleShellTokenValue(match[2] ?? "") : null;
|
|
1949
|
+
if (!range || !/^\d+,\d+p$/.test(range) || !target) {
|
|
1950
|
+
return null;
|
|
1951
|
+
}
|
|
1952
|
+
targets.push(target);
|
|
1276
1953
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1954
|
+
return targets;
|
|
1955
|
+
}
|
|
1956
|
+
function shouldHideProcessCommand(command, artifactFiles) {
|
|
1957
|
+
return (processCommandReadsRenderedArtifact(command.command, artifactFiles) ||
|
|
1958
|
+
processCommandReadsParallelCodexTaskFile(command.command) ||
|
|
1959
|
+
processCommandInspectsSessionMetadata(command.command) ||
|
|
1960
|
+
processCommandInspectsSessionCwd(command.command, command.cwd) ||
|
|
1961
|
+
processCommandReadsSessionSystemFile(command.command) ||
|
|
1962
|
+
processCommandReadsSkillDocument(command.command));
|
|
1963
|
+
}
|
|
1964
|
+
function singleShellTokenValue(text) {
|
|
1965
|
+
const trimmed = text.trim();
|
|
1966
|
+
if (!trimmed) {
|
|
1279
1967
|
return null;
|
|
1280
1968
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1969
|
+
if (trimmed[0] === "\"" || trimmed[0] === "'") {
|
|
1970
|
+
const segment = readQuotedShellSegment(trimmed, 0);
|
|
1971
|
+
return segment?.endIndex === trimmed.length ? segment.value : null;
|
|
1972
|
+
}
|
|
1973
|
+
return /^[^\s;&|]+$/.test(trimmed) ? trimmed : null;
|
|
1974
|
+
}
|
|
1975
|
+
function isChainedReadOutputBoundary(trimmed) {
|
|
1976
|
+
return (trimmed === "exec" ||
|
|
1977
|
+
trimmed.startsWith("$ ") ||
|
|
1978
|
+
parseShellExecCommand(trimmed) !== null ||
|
|
1979
|
+
isAssistantNarrativeMarker(trimmed) ||
|
|
1980
|
+
/^tokens used$/i.test(trimmed) ||
|
|
1981
|
+
isRolePromptTranscriptStart(trimmed) ||
|
|
1982
|
+
isDiffStartLine(trimmed) ||
|
|
1983
|
+
/^apply patch$/i.test(trimmed) ||
|
|
1984
|
+
/^patch:\s+(?:completed|failed)\b/i.test(trimmed));
|
|
1985
|
+
}
|
|
1986
|
+
function parseShellExecCwd(line) {
|
|
1987
|
+
return line.match(/\s+in\s+(\/.+)$/)?.[1]?.trim() ?? null;
|
|
1286
1988
|
}
|
|
1287
1989
|
function processCommandReadsRenderedArtifact(command, artifactFiles) {
|
|
1288
1990
|
if (artifactFiles.size === 0) {
|
|
@@ -1301,19 +2003,47 @@ function processCommandReadsRenderedArtifact(command, artifactFiles) {
|
|
|
1301
2003
|
}
|
|
1302
2004
|
function processCommandReadsParallelCodexTaskFile(command) {
|
|
1303
2005
|
const trimmed = command.trim();
|
|
2006
|
+
const conditionalTarget = readOnlySessionFileProbeTarget(trimmed);
|
|
2007
|
+
if (conditionalTarget) {
|
|
2008
|
+
return (conditionalTarget.includes(".parallel-codex/sessions/") &&
|
|
2009
|
+
/\.(?:md|json|jsonl|diff|log|toml|txt)$/.test(conditionalTarget));
|
|
2010
|
+
}
|
|
1304
2011
|
if (!/^(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(trimmed)) {
|
|
1305
2012
|
return false;
|
|
1306
2013
|
}
|
|
1307
2014
|
if (!trimmed.includes(".parallel-codex/sessions/")) {
|
|
1308
2015
|
return false;
|
|
1309
2016
|
}
|
|
1310
|
-
return /\.(?:md|json|jsonl|diff|toml|txt)\b/.test(trimmed);
|
|
2017
|
+
return /\.(?:md|json|jsonl|diff|log|toml|txt)\b/.test(trimmed);
|
|
2018
|
+
}
|
|
2019
|
+
function readOnlySessionFileProbeTarget(command) {
|
|
2020
|
+
const parts = command.split(/\s*;\s*/);
|
|
2021
|
+
if (parts.length !== 5 || parts[3] !== "else echo MISSING" || parts[4] !== "fi") {
|
|
2022
|
+
return null;
|
|
2023
|
+
}
|
|
2024
|
+
const existsMatch = parts[0]?.match(/^if\s+\[\s+-f\s+(.+)\s+\]$/);
|
|
2025
|
+
const countMatch = parts[1]?.match(/^then\s+wc\s+-c\s+(.+)$/);
|
|
2026
|
+
const existsTarget = existsMatch ? singleShellTokenValue(existsMatch[1] ?? "") : null;
|
|
2027
|
+
const countTarget = countMatch ? singleShellTokenValue(countMatch[1] ?? "") : null;
|
|
2028
|
+
const readTargets = sedReadTargets(parts[2] ?? "");
|
|
2029
|
+
if (!existsTarget || countTarget !== existsTarget || readTargets?.length !== 1 || readTargets[0] !== existsTarget) {
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
return existsTarget;
|
|
1311
2033
|
}
|
|
1312
2034
|
function processCommandInspectsSessionMetadata(command) {
|
|
1313
2035
|
const trimmed = command.trim();
|
|
1314
2036
|
return (/^(?:find|ls|wc|stat|du)\b/.test(trimmed) &&
|
|
1315
2037
|
trimmed.includes(".parallel-codex/sessions/"));
|
|
1316
2038
|
}
|
|
2039
|
+
function processCommandInspectsSessionCwd(command, cwd) {
|
|
2040
|
+
if (!cwd || !/[\\/]\.parallel-codex[\\/]sessions[\\/]/.test(cwd)) {
|
|
2041
|
+
return false;
|
|
2042
|
+
}
|
|
2043
|
+
const trimmed = command.trim();
|
|
2044
|
+
return (/^(?:rg\s+--files|ls|find|fd|tree|stat|du|wc)\b/.test(trimmed) ||
|
|
2045
|
+
/^pwd\s*&&\s*(?:rg\s+--files|ls|find|fd|tree)\b/.test(trimmed));
|
|
2046
|
+
}
|
|
1317
2047
|
function processCommandReadsSessionSystemFile(command) {
|
|
1318
2048
|
const trimmed = command.trim();
|
|
1319
2049
|
if (!/^(?:cat|sed|nl|head|tail|bat|less|more)\b/.test(trimmed)) {
|
|
@@ -1339,6 +2069,7 @@ function skipProcessCommandOutputBlock(rawLines, startIndex) {
|
|
|
1339
2069
|
trimmed.startsWith("$ ") ||
|
|
1340
2070
|
parseShellExecCommand(trimmed) !== null ||
|
|
1341
2071
|
isAssistantNarrativeMarker(trimmed) ||
|
|
2072
|
+
/^tokens used$/i.test(trimmed) ||
|
|
1342
2073
|
isDiffStartLine(trimmed)) {
|
|
1343
2074
|
return index;
|
|
1344
2075
|
}
|
|
@@ -1412,6 +2143,61 @@ function readQuotedShellSegment(text, startIndex) {
|
|
|
1412
2143
|
function isAssistantNarrativeMarker(trimmed) {
|
|
1413
2144
|
return trimmed === "codex" || trimmed === "assistant";
|
|
1414
2145
|
}
|
|
2146
|
+
function dedupeRepeatedAssistantNarrativeBlocks(rawLines) {
|
|
2147
|
+
const blocks = collectAssistantNarrativeBlocks(rawLines);
|
|
2148
|
+
const lastBlockByKey = new Map();
|
|
2149
|
+
for (const block of blocks) {
|
|
2150
|
+
lastBlockByKey.set(block.key, block);
|
|
2151
|
+
}
|
|
2152
|
+
const removed = new Set();
|
|
2153
|
+
for (const block of blocks) {
|
|
2154
|
+
if (lastBlockByKey.get(block.key) === block) {
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
for (let index = block.startIndex; index < block.endIndex; index += 1) {
|
|
2158
|
+
removed.add(index);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return removed.size > 0
|
|
2162
|
+
? rawLines.filter((_line, index) => !removed.has(index))
|
|
2163
|
+
: rawLines;
|
|
2164
|
+
}
|
|
2165
|
+
function collectAssistantNarrativeBlocks(rawLines) {
|
|
2166
|
+
const blocks = [];
|
|
2167
|
+
for (let index = 0; index < rawLines.length;) {
|
|
2168
|
+
const trimmed = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
2169
|
+
const marked = isAssistantNarrativeMarker(trimmed);
|
|
2170
|
+
const tokenTrailer = /^tokens used$/i.test(trimmed);
|
|
2171
|
+
if (!marked && !tokenTrailer) {
|
|
2172
|
+
index += 1;
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
const contentStart = marked ? index + 1 : skipNoisyProcessLine(rawLines, index);
|
|
2176
|
+
const endIndex = skipAssistantNarrativeTextBlock(rawLines, contentStart);
|
|
2177
|
+
const key = assistantNarrativeBlockKey(rawLines, contentStart, endIndex);
|
|
2178
|
+
if (key) {
|
|
2179
|
+
blocks.push({
|
|
2180
|
+
startIndex: marked ? index : contentStart,
|
|
2181
|
+
endIndex,
|
|
2182
|
+
key
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
index = Math.max(index + 1, endIndex);
|
|
2186
|
+
}
|
|
2187
|
+
return blocks;
|
|
2188
|
+
}
|
|
2189
|
+
function assistantNarrativeBlockKey(rawLines, startIndex, endIndex) {
|
|
2190
|
+
const lines = rawLines
|
|
2191
|
+
.slice(startIndex, endIndex)
|
|
2192
|
+
.map((line) => normalizedProcessLine(line).trimEnd());
|
|
2193
|
+
while (lines.length > 0 && !lines[0]?.trim()) {
|
|
2194
|
+
lines.shift();
|
|
2195
|
+
}
|
|
2196
|
+
while (lines.length > 0 && !lines[lines.length - 1]?.trim()) {
|
|
2197
|
+
lines.pop();
|
|
2198
|
+
}
|
|
2199
|
+
return lines.join("\n");
|
|
2200
|
+
}
|
|
1415
2201
|
function skipAssistantNarrativeBlock(rawLines, startIndex) {
|
|
1416
2202
|
return skipAssistantNarrativeTextBlock(rawLines, startIndex + 1);
|
|
1417
2203
|
}
|
|
@@ -1893,6 +2679,19 @@ function isBuildOutputSummaryLine(line) {
|
|
|
1893
2679
|
function isNoMatchesSummaryLine(line) {
|
|
1894
2680
|
return /^No matches in \d+(?:ms|s|m)?(?:\s+\(\$ .+\))?$/i.test(line);
|
|
1895
2681
|
}
|
|
2682
|
+
function isNetworkRecoverySummaryLine(line) {
|
|
2683
|
+
return networkRecoverySummaryParts(line) !== null;
|
|
2684
|
+
}
|
|
2685
|
+
function networkRecoverySummaryParts(line) {
|
|
2686
|
+
const match = line.match(/^Network recovered after (reconnect \d+\/\d+|\d+ reconnects?)(?: (via HTTPS fallback|with \d+ HTTPS fallbacks))?$/i);
|
|
2687
|
+
if (!match) {
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
return {
|
|
2691
|
+
reconnect: match[1] ?? "",
|
|
2692
|
+
fallback: match[2]?.replace(/^via\s+/i, "").replace(/^with\s+/i, "") ?? ""
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
1896
2695
|
function isCollapsedOutputSummaryLine(line) {
|
|
1897
2696
|
return /^Collapsed (?:code|file list|source) output: \d+ (?:lines|paths)$/i.test(line);
|
|
1898
2697
|
}
|
|
@@ -1945,7 +2744,7 @@ function isFileListOutputLine(line) {
|
|
|
1945
2744
|
if (pathPattern.test(line)) {
|
|
1946
2745
|
return true;
|
|
1947
2746
|
}
|
|
1948
|
-
return /^[A-Za-z0-9._@+-]+\.(?:cjs|css|diff|html|js|json|jsonl|lock|md|mjs|toml|ts|tsx|txt|yml|yaml)$/.test(line);
|
|
2747
|
+
return /^[A-Za-z0-9._@+-]+\.(?:cjs|css|diff|html|js|json|jsonl|lock|log|md|mjs|toml|ts|tsx|txt|yml|yaml)$/.test(line);
|
|
1949
2748
|
}
|
|
1950
2749
|
function collectSourceOutputRun(lines, startIndex) {
|
|
1951
2750
|
let index = startIndex;
|
|
@@ -2026,7 +2825,7 @@ function isCssLikeProcessLine(line) {
|
|
|
2026
2825
|
/^[A-Za-z][A-Za-z0-9-]*;$/.test(line));
|
|
2027
2826
|
}
|
|
2028
2827
|
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) ||
|
|
2828
|
+
return (/^(?:const|let|var|import|export|await|return|delete|throw|yield|break|continue|if|else|for|while|switch|try|catch|finally|function|class|new)\b/.test(line) ||
|
|
2030
2829
|
/^\}\s*else\b/.test(line) ||
|
|
2031
2830
|
/^\.[A-Za-z_$][\w$]*(?:\s*\(|\.)/.test(line) ||
|
|
2032
2831
|
/^[A-Za-z_$][\w$.[\]'"]*\s*(?:[+\-*/%]?=|\(|\.)/.test(line) ||
|
|
@@ -2054,6 +2853,18 @@ function skipNoisyProcessLine(rawLines, startIndex) {
|
|
|
2054
2853
|
}
|
|
2055
2854
|
return index;
|
|
2056
2855
|
}
|
|
2856
|
+
if (isCodexPluginManifestNoise(trimmed)) {
|
|
2857
|
+
const next = normalizedProcessLine(rawLines[startIndex + 1] ?? "").trim();
|
|
2858
|
+
return /^supported path=.*(?:\.codex-plugin\/plugin\.json|\/plugin\.json)$/i.test(next)
|
|
2859
|
+
? startIndex + 2
|
|
2860
|
+
: startIndex + 1;
|
|
2861
|
+
}
|
|
2862
|
+
if (isCodexTelemetryNoise(trimmed)) {
|
|
2863
|
+
const next = normalizedProcessLine(rawLines[startIndex + 1] ?? "").trim();
|
|
2864
|
+
return /^failed:\s+tag value contains invalid characters:/i.test(next)
|
|
2865
|
+
? startIndex + 2
|
|
2866
|
+
: startIndex + 1;
|
|
2867
|
+
}
|
|
2057
2868
|
return startIndex + 1;
|
|
2058
2869
|
}
|
|
2059
2870
|
function shouldDropNoisyProcessLine(trimmed, line) {
|
|
@@ -2061,15 +2872,26 @@ function shouldDropNoisyProcessLine(trimmed, line) {
|
|
|
2061
2872
|
return false;
|
|
2062
2873
|
}
|
|
2063
2874
|
return (/^tokens used$/i.test(trimmed) ||
|
|
2875
|
+
/^apply patch$/i.test(trimmed) ||
|
|
2876
|
+
/^patch:\s+completed$/i.test(trimmed) ||
|
|
2877
|
+
isCodexPluginManifestNoise(trimmed) ||
|
|
2878
|
+
isCodexTelemetryNoise(trimmed) ||
|
|
2064
2879
|
trimmed.includes("codex_models_manager::manager: failed to refresh available models") ||
|
|
2065
2880
|
(trimmed.includes("failed to decode models response") && trimmed.includes("body: {\"data\"")) ||
|
|
2066
2881
|
trimmed.startsWith("body: {\"data\"") ||
|
|
2067
2882
|
/^202\d-\d\d-\d\dT.*codex_models_manager::manager/.test(trimmed) ||
|
|
2068
2883
|
/^reasoning summaries:/i.test(trimmed) ||
|
|
2069
2884
|
/^session id:/i.test(trimmed) ||
|
|
2885
|
+
/^collab:\s*(?:wait|waiting)$/i.test(trimmed) ||
|
|
2070
2886
|
/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]$/.test(trimmed) ||
|
|
2071
2887
|
line === "--------");
|
|
2072
2888
|
}
|
|
2889
|
+
function isCodexPluginManifestNoise(trimmed) {
|
|
2890
|
+
return /codex_?core_?plugins::manifest: ignoring interface\.defaultPrompt/.test(trimmed);
|
|
2891
|
+
}
|
|
2892
|
+
function isCodexTelemetryNoise(trimmed) {
|
|
2893
|
+
return trimmed.includes("codex_otel::events::session_telemetry: metrics counter");
|
|
2894
|
+
}
|
|
2073
2895
|
function isNpmLifecycleEchoStart(trimmed) {
|
|
2074
2896
|
return /^>\s+(?:@[^/\s]+\/)?[^@\s]+@\S+\s+\S+/.test(trimmed);
|
|
2075
2897
|
}
|
|
@@ -2240,6 +3062,7 @@ function collectEmbeddedDiffBlock(rawLines, startIndex) {
|
|
|
2240
3062
|
return { lines, nextIndex: index };
|
|
2241
3063
|
}
|
|
2242
3064
|
function collectBareDiffHunkBlock(rawLines, startIndex) {
|
|
3065
|
+
const lines = [];
|
|
2243
3066
|
let index = startIndex;
|
|
2244
3067
|
let insideHunk = false;
|
|
2245
3068
|
let hunkCounts = null;
|
|
@@ -2249,6 +3072,7 @@ function collectBareDiffHunkBlock(rawLines, startIndex) {
|
|
|
2249
3072
|
if (isDiffHunkLine(trimmedStart)) {
|
|
2250
3073
|
insideHunk = true;
|
|
2251
3074
|
hunkCounts = parseHunkCounts(trimmedStart);
|
|
3075
|
+
lines.push(trimmedStart);
|
|
2252
3076
|
index += 1;
|
|
2253
3077
|
continue;
|
|
2254
3078
|
}
|
|
@@ -2256,17 +3080,63 @@ function collectBareDiffHunkBlock(rawLines, startIndex) {
|
|
|
2256
3080
|
if (hunkCounts && hunkCounts.oldRemaining <= 0 && hunkCounts.newRemaining <= 0) {
|
|
2257
3081
|
break;
|
|
2258
3082
|
}
|
|
3083
|
+
lines.push(line);
|
|
2259
3084
|
hunkCounts = consumeHunkLine(line, hunkCounts);
|
|
2260
3085
|
index += 1;
|
|
2261
3086
|
continue;
|
|
2262
3087
|
}
|
|
2263
3088
|
if (insideHunk && isDiffMetaLine(trimmedStart)) {
|
|
3089
|
+
lines.push(trimmedStart);
|
|
2264
3090
|
index += 1;
|
|
2265
3091
|
continue;
|
|
2266
3092
|
}
|
|
2267
3093
|
break;
|
|
2268
3094
|
}
|
|
2269
|
-
return { nextIndex: index };
|
|
3095
|
+
return { lines, nextIndex: index };
|
|
3096
|
+
}
|
|
3097
|
+
function inferBareDiffFile(rawLines, startIndex) {
|
|
3098
|
+
let newPath = null;
|
|
3099
|
+
let oldPath = null;
|
|
3100
|
+
let inspected = 0;
|
|
3101
|
+
for (let index = startIndex - 1; index >= 0 && inspected < 8; index -= 1) {
|
|
3102
|
+
const trimmed = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
3103
|
+
if (!trimmed) {
|
|
3104
|
+
continue;
|
|
3105
|
+
}
|
|
3106
|
+
inspected += 1;
|
|
3107
|
+
const nextFile = unifiedDiffHeaderPath(trimmed, "+++");
|
|
3108
|
+
if (nextFile !== null) {
|
|
3109
|
+
newPath = nextFile;
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
const previousFile = unifiedDiffHeaderPath(trimmed, "---");
|
|
3113
|
+
if (previousFile !== null) {
|
|
3114
|
+
oldPath = previousFile;
|
|
3115
|
+
const selected = newPath && newPath !== "/dev/null" ? newPath : oldPath;
|
|
3116
|
+
return selected && selected !== "/dev/null" ? formatDiffDisplayPath(selected) : null;
|
|
3117
|
+
}
|
|
3118
|
+
if (!newPath && looksLikeStandaloneDiffPath(trimmed)) {
|
|
3119
|
+
return formatDiffDisplayPath(trimmed);
|
|
3120
|
+
}
|
|
3121
|
+
if (parseHunkStart(trimmed) || isDiffStartLine(trimmed) || isHardProcessBoundary(trimmed)) {
|
|
3122
|
+
break;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
const selected = newPath && newPath !== "/dev/null" ? newPath : oldPath;
|
|
3126
|
+
return selected && selected !== "/dev/null" ? formatDiffDisplayPath(selected) : null;
|
|
3127
|
+
}
|
|
3128
|
+
function unifiedDiffHeaderPath(line, marker) {
|
|
3129
|
+
if (!line.startsWith(`${marker} `) && !line.startsWith(`${marker}\t`)) {
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
const value = line.slice(marker.length).trimStart().split("\t", 1)[0]?.trim() ?? "";
|
|
3133
|
+
return value.replace(/^[ab]\//, "");
|
|
3134
|
+
}
|
|
3135
|
+
function looksLikeStandaloneDiffPath(line) {
|
|
3136
|
+
return !/\s/.test(line)
|
|
3137
|
+
&& /(?:^|\/)[A-Za-z0-9._@+-]+\.[A-Za-z0-9._@+-]+$/.test(line)
|
|
3138
|
+
&& !line.startsWith("+")
|
|
3139
|
+
&& !line.startsWith("-");
|
|
2270
3140
|
}
|
|
2271
3141
|
function collectBareProcessDiffBodyRun(rawLines, startIndex) {
|
|
2272
3142
|
let index = startIndex;
|
|
@@ -2314,7 +3184,7 @@ function nextSignificantProcessLineStartsDiff(rawLines, startIndex) {
|
|
|
2314
3184
|
isCollapsedOutputSummaryLine(trimmed)) {
|
|
2315
3185
|
continue;
|
|
2316
3186
|
}
|
|
2317
|
-
return isDiffStartLine(trimmed);
|
|
3187
|
+
return isDiffStartLine(trimmed) || parseHunkStart(trimmed) !== null;
|
|
2318
3188
|
}
|
|
2319
3189
|
return false;
|
|
2320
3190
|
}
|
|
@@ -2426,6 +3296,58 @@ function skipProcessDiffTailContext(rawLines, startIndex) {
|
|
|
2426
3296
|
}
|
|
2427
3297
|
return index;
|
|
2428
3298
|
}
|
|
3299
|
+
function skipBareDiffTailContext(rawLines, startIndex) {
|
|
3300
|
+
let index = startIndex;
|
|
3301
|
+
let skippedTail = false;
|
|
3302
|
+
while (index < rawLines.length) {
|
|
3303
|
+
const line = normalizedProcessLine(rawLines[index] ?? "");
|
|
3304
|
+
const trimmed = line.trim();
|
|
3305
|
+
if (!trimmed) {
|
|
3306
|
+
if (skippedTail || nextLineLooksLikeBareDiffTail(rawLines, index + 1)) {
|
|
3307
|
+
index += 1;
|
|
3308
|
+
continue;
|
|
3309
|
+
}
|
|
3310
|
+
return index;
|
|
3311
|
+
}
|
|
3312
|
+
if (isHardProcessBoundary(trimmed)
|
|
3313
|
+
|| isDiffStartLine(trimmed)
|
|
3314
|
+
|| parseHunkStart(trimmed)
|
|
3315
|
+
|| isAssistantNarrativeMarker(trimmed)
|
|
3316
|
+
|| isRolePromptTranscriptStart(trimmed)) {
|
|
3317
|
+
return index;
|
|
3318
|
+
}
|
|
3319
|
+
if (isCollapsedOutputSummaryLine(trimmed)
|
|
3320
|
+
|| isProcessCodeOutputLine(trimmed)
|
|
3321
|
+
|| isBareProcessDiffBodyLine(line)
|
|
3322
|
+
|| isIndentedBareDiffTailLine(line)) {
|
|
3323
|
+
skippedTail = true;
|
|
3324
|
+
index += 1;
|
|
3325
|
+
continue;
|
|
3326
|
+
}
|
|
3327
|
+
return index;
|
|
3328
|
+
}
|
|
3329
|
+
return index;
|
|
3330
|
+
}
|
|
3331
|
+
function nextLineLooksLikeBareDiffTail(rawLines, startIndex) {
|
|
3332
|
+
for (let index = startIndex; index < rawLines.length; index += 1) {
|
|
3333
|
+
const line = normalizedProcessLine(rawLines[index] ?? "");
|
|
3334
|
+
const trimmed = line.trim();
|
|
3335
|
+
if (!trimmed) {
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
3338
|
+
return (isCollapsedOutputSummaryLine(trimmed)
|
|
3339
|
+
|| isProcessCodeOutputLine(trimmed)
|
|
3340
|
+
|| isBareProcessDiffBodyLine(line)
|
|
3341
|
+
|| isIndentedBareDiffTailLine(line));
|
|
3342
|
+
}
|
|
3343
|
+
return false;
|
|
3344
|
+
}
|
|
3345
|
+
function isIndentedBareDiffTailLine(line) {
|
|
3346
|
+
const trimmedStart = line.trimStart();
|
|
3347
|
+
return !trimmedStart.startsWith("+++")
|
|
3348
|
+
&& !trimmedStart.startsWith("---")
|
|
3349
|
+
&& /^[+-]\s{2,}\S/.test(trimmedStart);
|
|
3350
|
+
}
|
|
2429
3351
|
function nextLineLooksLikeProcessDiffTail(rawLines, startIndex) {
|
|
2430
3352
|
for (let index = startIndex; index < rawLines.length; index += 1) {
|
|
2431
3353
|
const line = normalizedProcessLine(rawLines[index] ?? "");
|
|
@@ -2470,7 +3392,7 @@ function renderDiffContent(text) {
|
|
|
2470
3392
|
const fileTitle = renderDiffFileTitle(trimmedStart);
|
|
2471
3393
|
if (fileTitle) {
|
|
2472
3394
|
flushCurrentFile();
|
|
2473
|
-
currentFile = { title: fileTitle, added: 0, removed: 0, lines: [] };
|
|
3395
|
+
currentFile = { title: fileTitle, added: 0, removed: 0, hunks: 0, lines: [] };
|
|
2474
3396
|
oldLineNumber = 0;
|
|
2475
3397
|
newLineNumber = 0;
|
|
2476
3398
|
insideHunk = false;
|
|
@@ -2478,6 +3400,12 @@ function renderDiffContent(text) {
|
|
|
2478
3400
|
}
|
|
2479
3401
|
const hunkStart = parseHunkStart(trimmedStart);
|
|
2480
3402
|
if (hunkStart) {
|
|
3403
|
+
if (currentFile) {
|
|
3404
|
+
if (currentFile.hunks > 0) {
|
|
3405
|
+
currentFile.lines.push({ kind: "diff-hunk", text: trimmedStart });
|
|
3406
|
+
}
|
|
3407
|
+
currentFile.hunks += 1;
|
|
3408
|
+
}
|
|
2481
3409
|
oldLineNumber = hunkStart.oldStart;
|
|
2482
3410
|
newLineNumber = hunkStart.newStart;
|
|
2483
3411
|
insideHunk = true;
|
|
@@ -2522,6 +3450,65 @@ function renderDiffContent(text) {
|
|
|
2522
3450
|
flushCurrentFile();
|
|
2523
3451
|
return rendered;
|
|
2524
3452
|
}
|
|
3453
|
+
function renderBareDiffHunkContent(lines, diffFile = null) {
|
|
3454
|
+
const hunks = [];
|
|
3455
|
+
let currentHunk = [];
|
|
3456
|
+
let oldLineNumber = 0;
|
|
3457
|
+
let newLineNumber = 0;
|
|
3458
|
+
let insideHunk = false;
|
|
3459
|
+
const flushHunk = () => {
|
|
3460
|
+
if (currentHunk.some((line) => line.kind === "diff-add" || line.kind === "diff-remove")) {
|
|
3461
|
+
hunks.push(currentHunk);
|
|
3462
|
+
}
|
|
3463
|
+
currentHunk = [];
|
|
3464
|
+
};
|
|
3465
|
+
for (const rawLine of lines) {
|
|
3466
|
+
const line = stripAnsiForDiff(rawLine);
|
|
3467
|
+
const trimmedStart = line.trimStart();
|
|
3468
|
+
const hunkStart = parseHunkStart(trimmedStart);
|
|
3469
|
+
if (hunkStart) {
|
|
3470
|
+
flushHunk();
|
|
3471
|
+
currentHunk.push({
|
|
3472
|
+
kind: "diff-hunk",
|
|
3473
|
+
text: trimmedStart,
|
|
3474
|
+
...(diffFile ? { diffFile } : {})
|
|
3475
|
+
});
|
|
3476
|
+
oldLineNumber = hunkStart.oldStart;
|
|
3477
|
+
newLineNumber = hunkStart.newStart;
|
|
3478
|
+
insideHunk = true;
|
|
3479
|
+
continue;
|
|
3480
|
+
}
|
|
3481
|
+
if (!insideHunk) {
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3484
|
+
if (line.startsWith("+")) {
|
|
3485
|
+
currentHunk.push({
|
|
3486
|
+
kind: "diff-add",
|
|
3487
|
+
text: formatDiffCodeLine(newLineNumber, "+", line.slice(1))
|
|
3488
|
+
});
|
|
3489
|
+
newLineNumber += 1;
|
|
3490
|
+
continue;
|
|
3491
|
+
}
|
|
3492
|
+
if (line.startsWith("-")) {
|
|
3493
|
+
currentHunk.push({
|
|
3494
|
+
kind: "diff-remove",
|
|
3495
|
+
text: formatDiffCodeLine(oldLineNumber, "-", line.slice(1))
|
|
3496
|
+
});
|
|
3497
|
+
oldLineNumber += 1;
|
|
3498
|
+
continue;
|
|
3499
|
+
}
|
|
3500
|
+
if (line.startsWith(" ")) {
|
|
3501
|
+
currentHunk.push({
|
|
3502
|
+
kind: "diff-context",
|
|
3503
|
+
text: formatDiffCodeLine(newLineNumber, " ", line.slice(1))
|
|
3504
|
+
});
|
|
3505
|
+
oldLineNumber += 1;
|
|
3506
|
+
newLineNumber += 1;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
flushHunk();
|
|
3510
|
+
return hunks.flat();
|
|
3511
|
+
}
|
|
2525
3512
|
function renderDiffFileLine(line) {
|
|
2526
3513
|
const title = renderDiffFileTitle(line);
|
|
2527
3514
|
if (!title) {
|
|
@@ -2678,6 +3665,9 @@ function classifyRenderedLine(section, line) {
|
|
|
2678
3665
|
if (isNoMatchesSummaryLine(trimmed)) {
|
|
2679
3666
|
return { kind: "summary", text: trimmed };
|
|
2680
3667
|
}
|
|
3668
|
+
if (isNetworkRecoverySummaryLine(trimmed)) {
|
|
3669
|
+
return { kind: "summary", text: trimmed };
|
|
3670
|
+
}
|
|
2681
3671
|
if ((isProcessSection || isExplicitErrorLine(trimmed)) &&
|
|
2682
3672
|
isErrorProcessLine(trimmed) &&
|
|
2683
3673
|
!isMarkdownListLikeLine(trimmed)) {
|
|
@@ -2833,16 +3823,70 @@ function isMarkdownTableSeparator(line) {
|
|
|
2833
3823
|
function isMarkdownTableRow(line) {
|
|
2834
3824
|
return line.startsWith("|") && line.endsWith("|") && line.split("|").length > 2;
|
|
2835
3825
|
}
|
|
2836
|
-
function
|
|
3826
|
+
function collectMarkdownTableBlock(rawLines, startIndex) {
|
|
3827
|
+
const rows = [];
|
|
3828
|
+
let index = startIndex;
|
|
3829
|
+
let consumed = false;
|
|
3830
|
+
while (index < rawLines.length) {
|
|
3831
|
+
const line = stripAnsi(rawLines[index] ?? "").replace(/\r/g, "").trim();
|
|
3832
|
+
if (!line) {
|
|
3833
|
+
break;
|
|
3834
|
+
}
|
|
3835
|
+
if (isMarkdownTableSeparator(line)) {
|
|
3836
|
+
consumed = true;
|
|
3837
|
+
index += 1;
|
|
3838
|
+
continue;
|
|
3839
|
+
}
|
|
3840
|
+
if (!isMarkdownTableRow(line)) {
|
|
3841
|
+
break;
|
|
3842
|
+
}
|
|
3843
|
+
const row = parseMarkdownTableRow(line);
|
|
3844
|
+
if (row.length > 0) {
|
|
3845
|
+
rows.push(row);
|
|
3846
|
+
}
|
|
3847
|
+
consumed = true;
|
|
3848
|
+
index += 1;
|
|
3849
|
+
}
|
|
3850
|
+
if (!consumed) {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
return {
|
|
3854
|
+
lines: renderMarkdownTableRows(rows),
|
|
3855
|
+
nextIndex: index
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
function parseMarkdownTableRow(line) {
|
|
2837
3859
|
return line
|
|
2838
3860
|
.split("|")
|
|
2839
3861
|
.map((cell) => stripInlineMarkdown(cell.trim()))
|
|
2840
|
-
.filter(Boolean)
|
|
2841
|
-
|
|
3862
|
+
.filter(Boolean);
|
|
3863
|
+
}
|
|
3864
|
+
function renderMarkdownTableRows(rows) {
|
|
3865
|
+
if (rows.length === 0) {
|
|
3866
|
+
return [];
|
|
3867
|
+
}
|
|
3868
|
+
const columnWidths = markdownTableColumnWidths(rows);
|
|
3869
|
+
return rows.map((row) => ({
|
|
3870
|
+
kind: "table",
|
|
3871
|
+
text: row
|
|
3872
|
+
.map((cell, index) => index === row.length - 1
|
|
3873
|
+
? cell
|
|
3874
|
+
: `${cell}${" ".repeat(Math.max(0, (columnWidths[index] ?? 0) - displayWidth(cell)))}`)
|
|
3875
|
+
.join(" ")
|
|
3876
|
+
}));
|
|
3877
|
+
}
|
|
3878
|
+
function markdownTableColumnWidths(rows) {
|
|
3879
|
+
const widths = [];
|
|
3880
|
+
for (const row of rows) {
|
|
3881
|
+
row.forEach((cell, index) => {
|
|
3882
|
+
widths[index] = Math.max(widths[index] ?? 0, displayWidth(cell));
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
return widths;
|
|
2842
3886
|
}
|
|
2843
3887
|
function stripInlineMarkdown(text) {
|
|
2844
3888
|
return text
|
|
2845
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
3889
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, target) => formatMarkdownLink(label, target))
|
|
2846
3890
|
.replace(/`([^`]+)`/g, "$1")
|
|
2847
3891
|
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
2848
3892
|
.replace(/__([^_]+)__/g, "$1")
|
|
@@ -2850,19 +3894,25 @@ function stripInlineMarkdown(text) {
|
|
|
2850
3894
|
.replace(/_([^_]+)_/g, "$1")
|
|
2851
3895
|
.trim();
|
|
2852
3896
|
}
|
|
2853
|
-
function
|
|
3897
|
+
function formatMarkdownLink(label, target) {
|
|
3898
|
+
const text = stripInlineMarkdownLinkText(label);
|
|
3899
|
+
const href = target.trim();
|
|
3900
|
+
if (!href) {
|
|
3901
|
+
return text;
|
|
3902
|
+
}
|
|
3903
|
+
return isExternalMarkdownLinkTarget(href) ? `${text} <${href}>` : text;
|
|
3904
|
+
}
|
|
3905
|
+
function stripInlineMarkdownLinkText(text) {
|
|
2854
3906
|
return text
|
|
2855
|
-
.replace(
|
|
2856
|
-
.replace(
|
|
2857
|
-
.replace(
|
|
2858
|
-
.replace(
|
|
2859
|
-
.replace(
|
|
2860
|
-
.
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
.replace(/"/g, "\"")
|
|
2865
|
-
.replace(/'/g, "'");
|
|
3907
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
3908
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
3909
|
+
.replace(/__([^_]+)__/g, "$1")
|
|
3910
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
3911
|
+
.replace(/_([^_]+)_/g, "$1")
|
|
3912
|
+
.trim();
|
|
3913
|
+
}
|
|
3914
|
+
function isExternalMarkdownLinkTarget(target) {
|
|
3915
|
+
return /^(?:https?:|mailto:)/i.test(target);
|
|
2866
3916
|
}
|
|
2867
3917
|
function renderJsonLine(line) {
|
|
2868
3918
|
try {
|
|
@@ -2873,7 +3923,7 @@ function renderJsonLine(line) {
|
|
|
2873
3923
|
kind: "json",
|
|
2874
3924
|
text: summary.meta
|
|
2875
3925
|
},
|
|
2876
|
-
...
|
|
3926
|
+
...summary.messages.map((message) => ({ kind: "json-message", text: message }))
|
|
2877
3927
|
];
|
|
2878
3928
|
}
|
|
2879
3929
|
catch {
|
|
@@ -2881,26 +3931,86 @@ function renderJsonLine(line) {
|
|
|
2881
3931
|
}
|
|
2882
3932
|
}
|
|
2883
3933
|
function summarizeJsonRecord(value) {
|
|
2884
|
-
const
|
|
2885
|
-
const id = stringField(value, "id");
|
|
2886
|
-
const
|
|
3934
|
+
const state = stringField(value, "status") || stringField(value, "severity");
|
|
3935
|
+
const id = stringField(value, "id") || stringField(value, "finding_id") || stringField(value, "findingId");
|
|
3936
|
+
const to = stringField(value, "to");
|
|
3937
|
+
const from = stringField(value, "from");
|
|
3938
|
+
const direction = from && to ? `${from} -> ${to}` : to ? `to ${to}` : from ? `from ${from}` : "";
|
|
2887
3939
|
const file = stringField(value, "file") || stringField(value, "path");
|
|
2888
|
-
const
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
].filter(Boolean)
|
|
3940
|
+
const line = numberLikeField(value, "line");
|
|
3941
|
+
const column = numberLikeField(value, "column") || numberLikeField(value, "col");
|
|
3942
|
+
const location = formatJsonRecordLocation(file, line, column);
|
|
3943
|
+
const messages = jsonRecordMessages(value);
|
|
3944
|
+
const marker = state ? `[${state}]` : "";
|
|
3945
|
+
const lead = [marker, id].filter(Boolean).join(" ");
|
|
3946
|
+
const details = [direction, location].filter(Boolean);
|
|
3947
|
+
const meta = id
|
|
3948
|
+
? [lead, ...details].filter(Boolean).join(" · ")
|
|
3949
|
+
: [lead, ...details].filter(Boolean).join(" ");
|
|
2895
3950
|
return {
|
|
2896
3951
|
meta: meta || "json",
|
|
2897
|
-
|
|
3952
|
+
messages: messages.length > 0 ? messages : [JSON.stringify(value)]
|
|
2898
3953
|
};
|
|
2899
3954
|
}
|
|
2900
3955
|
function stringField(value, key) {
|
|
2901
3956
|
const field = value[key];
|
|
2902
3957
|
return typeof field === "string" ? field.trim() : "";
|
|
2903
3958
|
}
|
|
3959
|
+
function jsonRecordMessages(value) {
|
|
3960
|
+
const primary = stringField(value, "message") || stringField(value, "summary");
|
|
3961
|
+
if (primary) {
|
|
3962
|
+
return [primary];
|
|
3963
|
+
}
|
|
3964
|
+
return [
|
|
3965
|
+
...textFragmentsField(value, "title"),
|
|
3966
|
+
...textFragmentsField(value, "detail").map((text) => `detail · ${text}`),
|
|
3967
|
+
...textFragmentsField(value, "details").map((text) => `detail · ${text}`),
|
|
3968
|
+
...textFragmentsField(value, "recommendation").map((text) => `fix · ${text}`)
|
|
3969
|
+
].filter(uniqueTextFragmentFilter());
|
|
3970
|
+
}
|
|
3971
|
+
function textFragmentsField(value, key) {
|
|
3972
|
+
return textFragmentsValue(value[key]);
|
|
3973
|
+
}
|
|
3974
|
+
function textFragmentsValue(value) {
|
|
3975
|
+
if (typeof value === "string") {
|
|
3976
|
+
return value.trim() ? [value.trim()] : [];
|
|
3977
|
+
}
|
|
3978
|
+
if (Array.isArray(value)) {
|
|
3979
|
+
return value.flatMap((item) => textFragmentsValue(item));
|
|
3980
|
+
}
|
|
3981
|
+
return [];
|
|
3982
|
+
}
|
|
3983
|
+
function uniqueTextFragmentFilter() {
|
|
3984
|
+
const seen = new Set();
|
|
3985
|
+
return (value) => {
|
|
3986
|
+
const normalized = value.toLowerCase();
|
|
3987
|
+
if (!value || seen.has(normalized)) {
|
|
3988
|
+
return false;
|
|
3989
|
+
}
|
|
3990
|
+
seen.add(normalized);
|
|
3991
|
+
return true;
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
function numberLikeField(value, key) {
|
|
3995
|
+
const field = value[key];
|
|
3996
|
+
if (typeof field === "number" && Number.isFinite(field)) {
|
|
3997
|
+
return String(field);
|
|
3998
|
+
}
|
|
3999
|
+
if (typeof field !== "string") {
|
|
4000
|
+
return "";
|
|
4001
|
+
}
|
|
4002
|
+
const trimmed = field.trim();
|
|
4003
|
+
return /^\d+$/.test(trimmed) ? trimmed : "";
|
|
4004
|
+
}
|
|
4005
|
+
function formatJsonRecordLocation(file, line, column) {
|
|
4006
|
+
if (!file) {
|
|
4007
|
+
return "";
|
|
4008
|
+
}
|
|
4009
|
+
if (!line) {
|
|
4010
|
+
return file;
|
|
4011
|
+
}
|
|
4012
|
+
return column ? `${file}:${line}:${column}` : `${file}:${line}`;
|
|
4013
|
+
}
|
|
2904
4014
|
function stripAnsi(value) {
|
|
2905
4015
|
return value.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "");
|
|
2906
4016
|
}
|
|
@@ -2918,72 +4028,91 @@ function groupTitle(group) {
|
|
|
2918
4028
|
}
|
|
2919
4029
|
export function workerOutputLineTheme(kind) {
|
|
2920
4030
|
if (kind === "group") {
|
|
2921
|
-
return { backgroundColor:
|
|
4031
|
+
return { backgroundColor: TUI_THEME.chrome, bold: true, color: TUI_THEME.text };
|
|
2922
4032
|
}
|
|
2923
4033
|
if (kind === "section") {
|
|
2924
|
-
return { backgroundColor:
|
|
4034
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning };
|
|
4035
|
+
}
|
|
4036
|
+
if (kind === "content" || kind === "list" || kind === "list-detail" || kind === "ordered-list" || kind === "task") {
|
|
4037
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text };
|
|
4038
|
+
}
|
|
4039
|
+
if (kind === "placeholder") {
|
|
4040
|
+
return workerOutputEmptyFallbackTheme();
|
|
2925
4041
|
}
|
|
2926
4042
|
if (kind === "heading") {
|
|
2927
|
-
return { backgroundColor:
|
|
4043
|
+
return { backgroundColor: TUI_THEME.surface, bold: true, color: TUI_THEME.text };
|
|
2928
4044
|
}
|
|
2929
4045
|
if (kind === "quote") {
|
|
2930
|
-
return { backgroundColor:
|
|
4046
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted };
|
|
2931
4047
|
}
|
|
2932
4048
|
if (kind === "table") {
|
|
2933
|
-
return { backgroundColor:
|
|
4049
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text };
|
|
2934
4050
|
}
|
|
2935
4051
|
if (kind === "rule") {
|
|
2936
|
-
return {
|
|
4052
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted };
|
|
4053
|
+
}
|
|
4054
|
+
if (kind === "blank") {
|
|
4055
|
+
return { backgroundColor: TUI_THEME.surface };
|
|
2937
4056
|
}
|
|
2938
4057
|
if (kind === "code") {
|
|
2939
|
-
return { backgroundColor:
|
|
4058
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted };
|
|
2940
4059
|
}
|
|
2941
4060
|
if (kind === "source-line") {
|
|
2942
|
-
return { backgroundColor:
|
|
4061
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text };
|
|
2943
4062
|
}
|
|
2944
4063
|
if (kind === "summary") {
|
|
2945
|
-
return { backgroundColor:
|
|
4064
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted };
|
|
2946
4065
|
}
|
|
2947
4066
|
if (kind === "json") {
|
|
2948
|
-
return { backgroundColor:
|
|
4067
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent };
|
|
2949
4068
|
}
|
|
2950
4069
|
if (kind === "json-message") {
|
|
2951
|
-
return { backgroundColor:
|
|
4070
|
+
return { backgroundColor: TUI_THEME.surface };
|
|
2952
4071
|
}
|
|
2953
4072
|
if (kind === "command") {
|
|
2954
|
-
return { backgroundColor:
|
|
4073
|
+
return { backgroundColor: TUI_THEME.chrome, color: TUI_THEME.accent };
|
|
2955
4074
|
}
|
|
2956
4075
|
if (kind === "success") {
|
|
2957
|
-
return { backgroundColor:
|
|
4076
|
+
return { backgroundColor: TUI_THEME.successSurface, color: TUI_THEME.success };
|
|
2958
4077
|
}
|
|
2959
4078
|
if (kind === "error") {
|
|
2960
|
-
return { backgroundColor:
|
|
4079
|
+
return { backgroundColor: TUI_THEME.dangerSurface, color: TUI_THEME.danger };
|
|
2961
4080
|
}
|
|
2962
4081
|
if (kind === "diff-file") {
|
|
2963
|
-
return { bold: true, color:
|
|
4082
|
+
return { backgroundColor: TUI_THEME.surface, bold: true, color: TUI_THEME.accent };
|
|
2964
4083
|
}
|
|
2965
4084
|
if (kind === "diff-summary") {
|
|
2966
|
-
return { color:
|
|
4085
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text };
|
|
2967
4086
|
}
|
|
2968
4087
|
if (kind === "diff-hunk" || kind === "diff-meta") {
|
|
2969
|
-
return { backgroundColor:
|
|
4088
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted };
|
|
2970
4089
|
}
|
|
2971
4090
|
if (kind === "diff-add") {
|
|
2972
|
-
return { backgroundColor:
|
|
4091
|
+
return { backgroundColor: TUI_THEME.successSurface, color: TUI_THEME.success };
|
|
2973
4092
|
}
|
|
2974
4093
|
if (kind === "diff-remove") {
|
|
2975
|
-
return { backgroundColor:
|
|
4094
|
+
return { backgroundColor: TUI_THEME.dangerSurface, color: TUI_THEME.danger };
|
|
2976
4095
|
}
|
|
2977
4096
|
if (kind === "diff-context") {
|
|
2978
|
-
return { color:
|
|
4097
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted };
|
|
2979
4098
|
}
|
|
2980
4099
|
return {};
|
|
2981
4100
|
}
|
|
4101
|
+
export function workerOutputEmptyFallbackTheme() {
|
|
4102
|
+
return {
|
|
4103
|
+
backgroundColor: TUI_THEME.surface,
|
|
4104
|
+
color: TUI_THEME.muted
|
|
4105
|
+
};
|
|
4106
|
+
}
|
|
4107
|
+
export function workerOutputLineFillTheme(kind) {
|
|
4108
|
+
const backgroundColor = workerOutputLineTheme(kind).backgroundColor;
|
|
4109
|
+
return backgroundColor ?? null;
|
|
4110
|
+
}
|
|
2982
4111
|
export function workerOutputLineLayout(kind, text) {
|
|
2983
4112
|
if (kind === "section") {
|
|
2984
|
-
return { gutter: "", body:
|
|
4113
|
+
return { gutter: "", body: joinWorkerOutputChromeParts([workerSectionLabel(text), formatWorkerSectionTitle(text)]) };
|
|
2985
4114
|
}
|
|
2986
|
-
if (kind === "content") {
|
|
4115
|
+
if (kind === "content" || kind === "placeholder") {
|
|
2987
4116
|
return { gutter: "", body: formatPlainDisplayText(text) };
|
|
2988
4117
|
}
|
|
2989
4118
|
if (kind === "heading") {
|
|
@@ -2999,7 +4128,7 @@ export function workerOutputLineLayout(kind, text) {
|
|
|
2999
4128
|
return { gutter: "", body: text };
|
|
3000
4129
|
}
|
|
3001
4130
|
if (kind === "quote") {
|
|
3002
|
-
return { gutter: "", body:
|
|
4131
|
+
return { gutter: "", body: `│ ${text}` };
|
|
3003
4132
|
}
|
|
3004
4133
|
if (kind === "table" || kind === "rule") {
|
|
3005
4134
|
return { gutter: "", body: text };
|
|
@@ -3035,7 +4164,7 @@ export function workerOutputLineLayout(kind, text) {
|
|
|
3035
4164
|
return { gutter: "", body: `└ ${text}` };
|
|
3036
4165
|
}
|
|
3037
4166
|
if (kind === "diff-hunk") {
|
|
3038
|
-
return { gutter: "", body:
|
|
4167
|
+
return { gutter: "", body: text };
|
|
3039
4168
|
}
|
|
3040
4169
|
if (kind === "diff-context") {
|
|
3041
4170
|
return { gutter: "", body: text };
|
|
@@ -3052,9 +4181,66 @@ export function workerOutputLineLayout(kind, text) {
|
|
|
3052
4181
|
return { gutter: "", body: text };
|
|
3053
4182
|
}
|
|
3054
4183
|
function formatWorkerSectionTitle(title) {
|
|
4184
|
+
const featureMatch = title.match(/^features\/([^/]+)\/([^/]+)$/);
|
|
4185
|
+
const turn = featureMatch?.[1] ?? "";
|
|
4186
|
+
const file = (featureMatch?.[2] ?? basename(title)).toLowerCase();
|
|
4187
|
+
if (turn && isTurnScopedWorkerArtifact(file)) {
|
|
4188
|
+
return turn;
|
|
4189
|
+
}
|
|
4190
|
+
if (file === "requirements.md" || file === "plan.md" || file === "acceptance.md" || file === "worklog.md") {
|
|
4191
|
+
return "";
|
|
4192
|
+
}
|
|
4193
|
+
if (file === "patch.diff") {
|
|
4194
|
+
return "patch";
|
|
4195
|
+
}
|
|
3055
4196
|
return title.replace(/^features\//, "");
|
|
3056
4197
|
}
|
|
4198
|
+
function workerSectionLabel(title) {
|
|
4199
|
+
const file = basename(title).toLowerCase();
|
|
4200
|
+
if (file === "requirements.md") {
|
|
4201
|
+
return "requirements";
|
|
4202
|
+
}
|
|
4203
|
+
if (file === "plan.md") {
|
|
4204
|
+
return "plan";
|
|
4205
|
+
}
|
|
4206
|
+
if (file === "acceptance.md") {
|
|
4207
|
+
return "acceptance";
|
|
4208
|
+
}
|
|
4209
|
+
if (file === "actor-worklog.md" || file === "worklog.md") {
|
|
4210
|
+
return "worklog";
|
|
4211
|
+
}
|
|
4212
|
+
if (file === "decisions.md") {
|
|
4213
|
+
return "decision";
|
|
4214
|
+
}
|
|
4215
|
+
if (file.endsWith(".diff")) {
|
|
4216
|
+
return "diff";
|
|
4217
|
+
}
|
|
4218
|
+
if (file === "actor-replies.jsonl") {
|
|
4219
|
+
return "mail";
|
|
4220
|
+
}
|
|
4221
|
+
if (file === "critic-findings.jsonl") {
|
|
4222
|
+
return "findings";
|
|
4223
|
+
}
|
|
4224
|
+
if (file.endsWith(".jsonl")) {
|
|
4225
|
+
return "jsonl";
|
|
4226
|
+
}
|
|
4227
|
+
return "file";
|
|
4228
|
+
}
|
|
4229
|
+
function isTurnScopedWorkerArtifact(file) {
|
|
4230
|
+
return file === "actor-worklog.md" ||
|
|
4231
|
+
file === "decisions.md" ||
|
|
4232
|
+
file === "actor-replies.jsonl" ||
|
|
4233
|
+
file === "critic-findings.jsonl";
|
|
4234
|
+
}
|
|
3057
4235
|
function formatSummaryDisplayText(text) {
|
|
4236
|
+
const networkRecovery = networkRecoverySummaryParts(text);
|
|
4237
|
+
if (networkRecovery) {
|
|
4238
|
+
return [
|
|
4239
|
+
"network recovered",
|
|
4240
|
+
networkRecovery.reconnect,
|
|
4241
|
+
networkRecovery.fallback
|
|
4242
|
+
].filter(Boolean).join(" · ");
|
|
4243
|
+
}
|
|
3058
4244
|
const readRun = text.match(/^Collapsed read summaries: (\d+) chunks, (\d+) lines(?: \((.+)\))?$/i);
|
|
3059
4245
|
if (readRun) {
|
|
3060
4246
|
return [
|
|
@@ -3275,41 +4461,76 @@ function formatFileUrlDisplay(url) {
|
|
|
3275
4461
|
const segments = path.split("/").filter(Boolean);
|
|
3276
4462
|
return segments.slice(-2).join("/") || url;
|
|
3277
4463
|
}
|
|
3278
|
-
function WorkerOutputLine({ line }) {
|
|
4464
|
+
function WorkerOutputLine({ line, selected, width }) {
|
|
3279
4465
|
const theme = workerOutputLineTheme(line.kind);
|
|
4466
|
+
const fillBackground = workerOutputLineFillTheme(line.kind);
|
|
3280
4467
|
if (line.kind === "blank") {
|
|
3281
|
-
return _jsx(
|
|
4468
|
+
return _jsx(WorkerOutputBlankLine, { width: width });
|
|
3282
4469
|
}
|
|
3283
4470
|
if (line.kind === "group") {
|
|
3284
|
-
|
|
4471
|
+
const body = `${selected ? ">" : " "}${line.text} `;
|
|
4472
|
+
return (_jsxs(Box, { children: [_jsx(Text, { ...theme, children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: displayWidth(body) })] }));
|
|
3285
4473
|
}
|
|
3286
4474
|
if (line.preformatted) {
|
|
3287
|
-
|
|
4475
|
+
const body = line.text || " ";
|
|
4476
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), _jsx(Text, { ...theme, wrap: "truncate-end", children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: 2 + displayWidth(body) })] }));
|
|
3288
4477
|
}
|
|
3289
4478
|
const layout = workerOutputLineLayout(line.kind, line.text);
|
|
3290
4479
|
if (line.kind === "code") {
|
|
3291
|
-
return (_jsxs(Box, { children: [_jsx(
|
|
4480
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), _jsx(Text, { ...theme, wrap: "wrap", children: line.text || " " }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: 2 + displayWidth(line.text || " ") })] }));
|
|
3292
4481
|
}
|
|
3293
4482
|
if (line.kind === "source-line") {
|
|
3294
4483
|
const sourceParts = sourceDisplayLineParts(line.text);
|
|
3295
4484
|
if (sourceParts) {
|
|
3296
|
-
|
|
4485
|
+
const usedWidth = 2 + displayWidth(sourceParts.gutter) + displayWidth(sourceParts.code || " ");
|
|
4486
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), _jsx(Text, { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted, children: sourceParts.gutter }), _jsx(Text, { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text, wrap: "truncate-end", children: sourceParts.code }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: usedWidth })] }));
|
|
3297
4487
|
}
|
|
3298
|
-
|
|
4488
|
+
const body = line.text || " ";
|
|
4489
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), _jsx(Text, { ...theme, children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: 2 + displayWidth(body) })] }));
|
|
3299
4490
|
}
|
|
3300
4491
|
if (line.continuation && isDiffCodeKind(line.kind)) {
|
|
3301
|
-
|
|
4492
|
+
const body = line.text || " ";
|
|
4493
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), _jsx(Text, { ...theme, wrap: "truncate-end", children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: 2 + displayWidth(body) })] }));
|
|
3302
4494
|
}
|
|
3303
4495
|
const diffCodeLine = diffCodeLineParts(line);
|
|
3304
4496
|
if (diffCodeLine) {
|
|
3305
|
-
|
|
4497
|
+
const prefix = `${diffCodeLine.lineNumber} ${diffCodeLine.sign} `;
|
|
4498
|
+
const code = diffCodeLine.code || " ";
|
|
4499
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), _jsx(Text, { ...theme, children: prefix }), _jsx(Text, { ...theme, wrap: "wrap", children: code }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: 2 + displayWidth(prefix) + displayWidth(code) })] }));
|
|
4500
|
+
}
|
|
4501
|
+
const gutter = layout.gutter ? formatGutter(layout.gutter) : "";
|
|
4502
|
+
const body = layout.body || " ";
|
|
4503
|
+
return (_jsxs(Box, { children: [_jsx(WorkerOutputIndent, { backgroundColor: fillBackground, selected: selected }), gutter ? _jsx(Text, { ...theme, children: gutter }) : null, _jsx(Text, { ...theme, wrap: "truncate-end", children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: 2 + displayWidth(gutter) + displayWidth(body) })] }));
|
|
4504
|
+
}
|
|
4505
|
+
function WorkerOutputIndent({ backgroundColor, selected }) {
|
|
4506
|
+
if (selected) {
|
|
4507
|
+
return _jsx(Text, { backgroundColor: backgroundColor ?? TUI_THEME.surface, color: TUI_THEME.accent, bold: true, children: "> " });
|
|
4508
|
+
}
|
|
4509
|
+
return backgroundColor
|
|
4510
|
+
? _jsx(Text, { backgroundColor: backgroundColor, children: " " })
|
|
4511
|
+
: _jsx(Text, { color: TUI_THEME.muted, children: " " });
|
|
4512
|
+
}
|
|
4513
|
+
function WorkerOutputBlankLine({ width }) {
|
|
4514
|
+
const theme = workerOutputLineTheme("blank");
|
|
4515
|
+
const fillWidth = shouldRenderWorkerOutputRailFill() ? Math.max(1, width) : 1;
|
|
4516
|
+
return _jsx(Text, { ...theme, children: " ".repeat(fillWidth) });
|
|
4517
|
+
}
|
|
4518
|
+
function WorkerOutputTrailingFill({ backgroundColor, usedWidth, width }) {
|
|
4519
|
+
if (!backgroundColor || !shouldRenderWorkerOutputRailFill()) {
|
|
4520
|
+
return null;
|
|
3306
4521
|
}
|
|
3307
|
-
|
|
4522
|
+
const fillWidth = Math.max(0, width - usedWidth);
|
|
4523
|
+
return fillWidth > 0 ? _jsx(Text, { backgroundColor: backgroundColor, children: " ".repeat(fillWidth) }) : null;
|
|
4524
|
+
}
|
|
4525
|
+
function shouldRenderWorkerOutputRailFill() {
|
|
4526
|
+
return process.stdout.isTTY === true && typeof process.stdout.columns === "number";
|
|
3308
4527
|
}
|
|
3309
|
-
function WorkerOutputNanoLine({ line, width }) {
|
|
4528
|
+
function WorkerOutputNanoLine({ fillWidth, line, selected, width }) {
|
|
3310
4529
|
const theme = workerOutputLineTheme(line.kind);
|
|
3311
|
-
const
|
|
3312
|
-
|
|
4530
|
+
const fillBackground = workerOutputLineFillTheme(line.kind);
|
|
4531
|
+
const text = compactEndByDisplayWidth(`${selected ? "> " : ""}${workerOutputNanoLineText(line)}`, Math.max(1, width));
|
|
4532
|
+
const body = text || " ";
|
|
4533
|
+
return (_jsxs(Box, { children: [_jsx(Text, { ...theme, wrap: "truncate-end", children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: fillWidth, usedWidth: displayWidth(body) })] }));
|
|
3313
4534
|
}
|
|
3314
4535
|
function workerOutputNanoLineText(line) {
|
|
3315
4536
|
if (line.kind === "blank") {
|
|
@@ -3367,9 +4588,9 @@ export function workerOutputDiffColumns(text) {
|
|
|
3367
4588
|
code: match[3] ?? ""
|
|
3368
4589
|
};
|
|
3369
4590
|
}
|
|
3370
|
-
function renderDisplayLines(lines, width = Number(process.stdout.columns) || 120) {
|
|
3371
|
-
const contentWidth = Math.max(1, width - 4);
|
|
3372
|
-
if (isNanoWorkerOutputWidth(width)) {
|
|
4591
|
+
function renderDisplayLines(lines, width = Number(process.stdout.columns) || 120, options = {}) {
|
|
4592
|
+
const contentWidth = options.contentWidth ?? Math.max(1, width - 4);
|
|
4593
|
+
if (options.nano ?? isNanoWorkerOutputWidth(width)) {
|
|
3373
4594
|
return renderNanoDisplayLines(lines, contentWidth);
|
|
3374
4595
|
}
|
|
3375
4596
|
return lines.flatMap((line) => {
|
|
@@ -3442,6 +4663,9 @@ export function workerOutputBodyDisplayLines(kind, text, width) {
|
|
|
3442
4663
|
return wrapBodyWithContinuation(body, Math.max(1, width), continuationPrefix);
|
|
3443
4664
|
}
|
|
3444
4665
|
function compactWorkerBodyForWidth(kind, body, width) {
|
|
4666
|
+
if (kind === "diff-hunk") {
|
|
4667
|
+
return compactDiffHunkBodyForWidth(body, width);
|
|
4668
|
+
}
|
|
3445
4669
|
if (kind === "summary") {
|
|
3446
4670
|
return compactSummaryBodyForWidth(body, width);
|
|
3447
4671
|
}
|
|
@@ -3480,6 +4704,25 @@ function compactWorkerBodyForWidth(kind, body, width) {
|
|
|
3480
4704
|
}
|
|
3481
4705
|
return body;
|
|
3482
4706
|
}
|
|
4707
|
+
function compactDiffHunkBodyForWidth(body, width) {
|
|
4708
|
+
if (displayWidth(body) <= width) {
|
|
4709
|
+
return body;
|
|
4710
|
+
}
|
|
4711
|
+
const match = body.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@(?:\s+.*)?$/);
|
|
4712
|
+
if (!match) {
|
|
4713
|
+
return compactEndByDisplayWidth(body, width);
|
|
4714
|
+
}
|
|
4715
|
+
const oldStart = match[1] ?? "0";
|
|
4716
|
+
const newStart = match[2] ?? "0";
|
|
4717
|
+
const candidates = [
|
|
4718
|
+
`@@ -${oldStart} +${newStart} @@`,
|
|
4719
|
+
`-${oldStart} +${newStart}`,
|
|
4720
|
+
`${oldStart}>${newStart}`,
|
|
4721
|
+
`+${newStart}`
|
|
4722
|
+
];
|
|
4723
|
+
return candidates.find((candidate) => displayWidth(candidate) <= width)
|
|
4724
|
+
?? compactEndByDisplayWidth(body, width);
|
|
4725
|
+
}
|
|
3483
4726
|
function compactContextWindowErrorForWidth(body, width) {
|
|
3484
4727
|
if (width < 7) {
|
|
3485
4728
|
return "ctx";
|
|
@@ -3558,9 +4801,9 @@ function compactNarrowWorkerBody(body, width) {
|
|
|
3558
4801
|
if (/^Findings:\s*none$/i.test(body)) {
|
|
3559
4802
|
return width < 24 ? compactFindingsNoneForWidth(width) : body;
|
|
3560
4803
|
}
|
|
3561
|
-
const findingsLabel = width < 24 ? "findings" : "findings.jsonl";
|
|
3562
|
-
const repliesLabel = width < 24 ? "replies" : "replies.jsonl";
|
|
3563
|
-
const worklogLabel = width < 24 ? "worklog" : "worklog.md";
|
|
4804
|
+
const findingsLabel = width < 24 ? "findings" : width < 32 ? "findings.jsonl" : "critic-findings.jsonl";
|
|
4805
|
+
const repliesLabel = width < 24 ? "replies" : width < 32 ? "replies.jsonl" : "actor-replies.jsonl";
|
|
4806
|
+
const worklogLabel = width < 24 ? "worklog" : width < 32 ? "worklog.md" : "actor-worklog.md";
|
|
3564
4807
|
return body
|
|
3565
4808
|
.replace(/^(•\s+)?npm run dev could not bind\b.*$/i, (_match, marker) => `${marker ?? ""}dev fallback.`)
|
|
3566
4809
|
.replace(/\bcritic-findings\.jsonl\b/g, findingsLabel)
|