parallel-codex-tui 0.1.0 → 0.1.4
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 +90 -3
- package/README.md +269 -12
- package/dist/bootstrap.js +50 -18
- package/dist/cli-args.js +96 -14
- package/dist/cli-help.js +20 -0
- package/dist/cli-startup-recovery.js +70 -0
- package/dist/cli-workspace-picker.js +330 -0
- package/dist/cli-workspace-transition.js +33 -0
- package/dist/cli-workspace.js +40 -0
- package/dist/cli.js +291 -35
- package/dist/core/app-root.js +8 -0
- package/dist/core/collaboration-timeline.js +261 -0
- package/dist/core/config-errors.js +14 -0
- package/dist/core/config.js +191 -23
- package/dist/core/file-store.js +130 -6
- package/dist/core/lease-finalization.js +22 -0
- package/dist/core/paths.js +10 -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 +473 -42
- package/dist/core/session-index.js +225 -30
- package/dist/core/session-manager.js +1182 -44
- package/dist/core/task-state-machine.js +17 -0
- package/dist/core/workspace-commit-recovery.js +118 -0
- package/dist/core/workspace.js +126 -0
- package/dist/doctor.js +384 -30
- package/dist/domain/schemas.js +127 -6
- package/dist/orchestrator/collaboration-channel.js +255 -4
- package/dist/orchestrator/feature-plan.js +70 -0
- package/dist/orchestrator/judge-artifacts.js +236 -0
- package/dist/orchestrator/orchestrator.js +1777 -212
- package/dist/orchestrator/prompts.js +126 -2
- package/dist/orchestrator/supervisor-summary.js +56 -2
- package/dist/orchestrator/workspace-sandbox.js +911 -0
- package/dist/tui/App.js +2838 -159
- package/dist/tui/AppShell.js +188 -23
- package/dist/tui/CollaborationTimelineView.js +327 -0
- package/dist/tui/FeatureBoardView.js +227 -0
- package/dist/tui/InputBar.js +514 -57
- package/dist/tui/RouterDiagnosticsView.js +469 -0
- package/dist/tui/StatusBar.js +610 -57
- package/dist/tui/TaskSessionsView.js +207 -0
- package/dist/tui/TerminalOutput.js +53 -9
- package/dist/tui/WorkerOutputView.js +1403 -161
- package/dist/tui/WorkerOverviewView.js +250 -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 +46 -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 +318 -11
- package/dist/tui/task-memory.js +15 -0
- 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 +212 -0
- package/dist/workers/live-probe.js +176 -0
- package/dist/workers/mock-adapter.js +39 -6
- package/dist/workers/native-attach.js +147 -8
- package/dist/workers/native-session-detection.js +17 -0
- package/dist/workers/process-adapter.js +580 -81
- package/dist/workers/registry.js +4 -2
- package/package.json +17 -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,6 +2872,10 @@ 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\"") ||
|
|
@@ -2070,6 +2885,12 @@ function shouldDropNoisyProcessLine(trimmed, line) {
|
|
|
2070
2885
|
/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]$/.test(trimmed) ||
|
|
2071
2886
|
line === "--------");
|
|
2072
2887
|
}
|
|
2888
|
+
function isCodexPluginManifestNoise(trimmed) {
|
|
2889
|
+
return /codex_?core_?plugins::manifest: ignoring interface\.defaultPrompt/.test(trimmed);
|
|
2890
|
+
}
|
|
2891
|
+
function isCodexTelemetryNoise(trimmed) {
|
|
2892
|
+
return trimmed.includes("codex_otel::events::session_telemetry: metrics counter");
|
|
2893
|
+
}
|
|
2073
2894
|
function isNpmLifecycleEchoStart(trimmed) {
|
|
2074
2895
|
return /^>\s+(?:@[^/\s]+\/)?[^@\s]+@\S+\s+\S+/.test(trimmed);
|
|
2075
2896
|
}
|
|
@@ -2240,6 +3061,7 @@ function collectEmbeddedDiffBlock(rawLines, startIndex) {
|
|
|
2240
3061
|
return { lines, nextIndex: index };
|
|
2241
3062
|
}
|
|
2242
3063
|
function collectBareDiffHunkBlock(rawLines, startIndex) {
|
|
3064
|
+
const lines = [];
|
|
2243
3065
|
let index = startIndex;
|
|
2244
3066
|
let insideHunk = false;
|
|
2245
3067
|
let hunkCounts = null;
|
|
@@ -2249,6 +3071,7 @@ function collectBareDiffHunkBlock(rawLines, startIndex) {
|
|
|
2249
3071
|
if (isDiffHunkLine(trimmedStart)) {
|
|
2250
3072
|
insideHunk = true;
|
|
2251
3073
|
hunkCounts = parseHunkCounts(trimmedStart);
|
|
3074
|
+
lines.push(trimmedStart);
|
|
2252
3075
|
index += 1;
|
|
2253
3076
|
continue;
|
|
2254
3077
|
}
|
|
@@ -2256,17 +3079,63 @@ function collectBareDiffHunkBlock(rawLines, startIndex) {
|
|
|
2256
3079
|
if (hunkCounts && hunkCounts.oldRemaining <= 0 && hunkCounts.newRemaining <= 0) {
|
|
2257
3080
|
break;
|
|
2258
3081
|
}
|
|
3082
|
+
lines.push(line);
|
|
2259
3083
|
hunkCounts = consumeHunkLine(line, hunkCounts);
|
|
2260
3084
|
index += 1;
|
|
2261
3085
|
continue;
|
|
2262
3086
|
}
|
|
2263
3087
|
if (insideHunk && isDiffMetaLine(trimmedStart)) {
|
|
3088
|
+
lines.push(trimmedStart);
|
|
2264
3089
|
index += 1;
|
|
2265
3090
|
continue;
|
|
2266
3091
|
}
|
|
2267
3092
|
break;
|
|
2268
3093
|
}
|
|
2269
|
-
return { nextIndex: index };
|
|
3094
|
+
return { lines, nextIndex: index };
|
|
3095
|
+
}
|
|
3096
|
+
function inferBareDiffFile(rawLines, startIndex) {
|
|
3097
|
+
let newPath = null;
|
|
3098
|
+
let oldPath = null;
|
|
3099
|
+
let inspected = 0;
|
|
3100
|
+
for (let index = startIndex - 1; index >= 0 && inspected < 8; index -= 1) {
|
|
3101
|
+
const trimmed = normalizedProcessLine(rawLines[index] ?? "").trim();
|
|
3102
|
+
if (!trimmed) {
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
inspected += 1;
|
|
3106
|
+
const nextFile = unifiedDiffHeaderPath(trimmed, "+++");
|
|
3107
|
+
if (nextFile !== null) {
|
|
3108
|
+
newPath = nextFile;
|
|
3109
|
+
continue;
|
|
3110
|
+
}
|
|
3111
|
+
const previousFile = unifiedDiffHeaderPath(trimmed, "---");
|
|
3112
|
+
if (previousFile !== null) {
|
|
3113
|
+
oldPath = previousFile;
|
|
3114
|
+
const selected = newPath && newPath !== "/dev/null" ? newPath : oldPath;
|
|
3115
|
+
return selected && selected !== "/dev/null" ? formatDiffDisplayPath(selected) : null;
|
|
3116
|
+
}
|
|
3117
|
+
if (!newPath && looksLikeStandaloneDiffPath(trimmed)) {
|
|
3118
|
+
return formatDiffDisplayPath(trimmed);
|
|
3119
|
+
}
|
|
3120
|
+
if (parseHunkStart(trimmed) || isDiffStartLine(trimmed) || isHardProcessBoundary(trimmed)) {
|
|
3121
|
+
break;
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
const selected = newPath && newPath !== "/dev/null" ? newPath : oldPath;
|
|
3125
|
+
return selected && selected !== "/dev/null" ? formatDiffDisplayPath(selected) : null;
|
|
3126
|
+
}
|
|
3127
|
+
function unifiedDiffHeaderPath(line, marker) {
|
|
3128
|
+
if (!line.startsWith(`${marker} `) && !line.startsWith(`${marker}\t`)) {
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
const value = line.slice(marker.length).trimStart().split("\t", 1)[0]?.trim() ?? "";
|
|
3132
|
+
return value.replace(/^[ab]\//, "");
|
|
3133
|
+
}
|
|
3134
|
+
function looksLikeStandaloneDiffPath(line) {
|
|
3135
|
+
return !/\s/.test(line)
|
|
3136
|
+
&& /(?:^|\/)[A-Za-z0-9._@+-]+\.[A-Za-z0-9._@+-]+$/.test(line)
|
|
3137
|
+
&& !line.startsWith("+")
|
|
3138
|
+
&& !line.startsWith("-");
|
|
2270
3139
|
}
|
|
2271
3140
|
function collectBareProcessDiffBodyRun(rawLines, startIndex) {
|
|
2272
3141
|
let index = startIndex;
|
|
@@ -2314,7 +3183,7 @@ function nextSignificantProcessLineStartsDiff(rawLines, startIndex) {
|
|
|
2314
3183
|
isCollapsedOutputSummaryLine(trimmed)) {
|
|
2315
3184
|
continue;
|
|
2316
3185
|
}
|
|
2317
|
-
return isDiffStartLine(trimmed);
|
|
3186
|
+
return isDiffStartLine(trimmed) || parseHunkStart(trimmed) !== null;
|
|
2318
3187
|
}
|
|
2319
3188
|
return false;
|
|
2320
3189
|
}
|
|
@@ -2426,6 +3295,58 @@ function skipProcessDiffTailContext(rawLines, startIndex) {
|
|
|
2426
3295
|
}
|
|
2427
3296
|
return index;
|
|
2428
3297
|
}
|
|
3298
|
+
function skipBareDiffTailContext(rawLines, startIndex) {
|
|
3299
|
+
let index = startIndex;
|
|
3300
|
+
let skippedTail = false;
|
|
3301
|
+
while (index < rawLines.length) {
|
|
3302
|
+
const line = normalizedProcessLine(rawLines[index] ?? "");
|
|
3303
|
+
const trimmed = line.trim();
|
|
3304
|
+
if (!trimmed) {
|
|
3305
|
+
if (skippedTail || nextLineLooksLikeBareDiffTail(rawLines, index + 1)) {
|
|
3306
|
+
index += 1;
|
|
3307
|
+
continue;
|
|
3308
|
+
}
|
|
3309
|
+
return index;
|
|
3310
|
+
}
|
|
3311
|
+
if (isHardProcessBoundary(trimmed)
|
|
3312
|
+
|| isDiffStartLine(trimmed)
|
|
3313
|
+
|| parseHunkStart(trimmed)
|
|
3314
|
+
|| isAssistantNarrativeMarker(trimmed)
|
|
3315
|
+
|| isRolePromptTranscriptStart(trimmed)) {
|
|
3316
|
+
return index;
|
|
3317
|
+
}
|
|
3318
|
+
if (isCollapsedOutputSummaryLine(trimmed)
|
|
3319
|
+
|| isProcessCodeOutputLine(trimmed)
|
|
3320
|
+
|| isBareProcessDiffBodyLine(line)
|
|
3321
|
+
|| isIndentedBareDiffTailLine(line)) {
|
|
3322
|
+
skippedTail = true;
|
|
3323
|
+
index += 1;
|
|
3324
|
+
continue;
|
|
3325
|
+
}
|
|
3326
|
+
return index;
|
|
3327
|
+
}
|
|
3328
|
+
return index;
|
|
3329
|
+
}
|
|
3330
|
+
function nextLineLooksLikeBareDiffTail(rawLines, startIndex) {
|
|
3331
|
+
for (let index = startIndex; index < rawLines.length; index += 1) {
|
|
3332
|
+
const line = normalizedProcessLine(rawLines[index] ?? "");
|
|
3333
|
+
const trimmed = line.trim();
|
|
3334
|
+
if (!trimmed) {
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
return (isCollapsedOutputSummaryLine(trimmed)
|
|
3338
|
+
|| isProcessCodeOutputLine(trimmed)
|
|
3339
|
+
|| isBareProcessDiffBodyLine(line)
|
|
3340
|
+
|| isIndentedBareDiffTailLine(line));
|
|
3341
|
+
}
|
|
3342
|
+
return false;
|
|
3343
|
+
}
|
|
3344
|
+
function isIndentedBareDiffTailLine(line) {
|
|
3345
|
+
const trimmedStart = line.trimStart();
|
|
3346
|
+
return !trimmedStart.startsWith("+++")
|
|
3347
|
+
&& !trimmedStart.startsWith("---")
|
|
3348
|
+
&& /^[+-]\s{2,}\S/.test(trimmedStart);
|
|
3349
|
+
}
|
|
2429
3350
|
function nextLineLooksLikeProcessDiffTail(rawLines, startIndex) {
|
|
2430
3351
|
for (let index = startIndex; index < rawLines.length; index += 1) {
|
|
2431
3352
|
const line = normalizedProcessLine(rawLines[index] ?? "");
|
|
@@ -2470,7 +3391,7 @@ function renderDiffContent(text) {
|
|
|
2470
3391
|
const fileTitle = renderDiffFileTitle(trimmedStart);
|
|
2471
3392
|
if (fileTitle) {
|
|
2472
3393
|
flushCurrentFile();
|
|
2473
|
-
currentFile = { title: fileTitle, added: 0, removed: 0, lines: [] };
|
|
3394
|
+
currentFile = { title: fileTitle, added: 0, removed: 0, hunks: 0, lines: [] };
|
|
2474
3395
|
oldLineNumber = 0;
|
|
2475
3396
|
newLineNumber = 0;
|
|
2476
3397
|
insideHunk = false;
|
|
@@ -2478,6 +3399,12 @@ function renderDiffContent(text) {
|
|
|
2478
3399
|
}
|
|
2479
3400
|
const hunkStart = parseHunkStart(trimmedStart);
|
|
2480
3401
|
if (hunkStart) {
|
|
3402
|
+
if (currentFile) {
|
|
3403
|
+
if (currentFile.hunks > 0) {
|
|
3404
|
+
currentFile.lines.push({ kind: "diff-hunk", text: trimmedStart });
|
|
3405
|
+
}
|
|
3406
|
+
currentFile.hunks += 1;
|
|
3407
|
+
}
|
|
2481
3408
|
oldLineNumber = hunkStart.oldStart;
|
|
2482
3409
|
newLineNumber = hunkStart.newStart;
|
|
2483
3410
|
insideHunk = true;
|
|
@@ -2522,6 +3449,65 @@ function renderDiffContent(text) {
|
|
|
2522
3449
|
flushCurrentFile();
|
|
2523
3450
|
return rendered;
|
|
2524
3451
|
}
|
|
3452
|
+
function renderBareDiffHunkContent(lines, diffFile = null) {
|
|
3453
|
+
const hunks = [];
|
|
3454
|
+
let currentHunk = [];
|
|
3455
|
+
let oldLineNumber = 0;
|
|
3456
|
+
let newLineNumber = 0;
|
|
3457
|
+
let insideHunk = false;
|
|
3458
|
+
const flushHunk = () => {
|
|
3459
|
+
if (currentHunk.some((line) => line.kind === "diff-add" || line.kind === "diff-remove")) {
|
|
3460
|
+
hunks.push(currentHunk);
|
|
3461
|
+
}
|
|
3462
|
+
currentHunk = [];
|
|
3463
|
+
};
|
|
3464
|
+
for (const rawLine of lines) {
|
|
3465
|
+
const line = stripAnsiForDiff(rawLine);
|
|
3466
|
+
const trimmedStart = line.trimStart();
|
|
3467
|
+
const hunkStart = parseHunkStart(trimmedStart);
|
|
3468
|
+
if (hunkStart) {
|
|
3469
|
+
flushHunk();
|
|
3470
|
+
currentHunk.push({
|
|
3471
|
+
kind: "diff-hunk",
|
|
3472
|
+
text: trimmedStart,
|
|
3473
|
+
...(diffFile ? { diffFile } : {})
|
|
3474
|
+
});
|
|
3475
|
+
oldLineNumber = hunkStart.oldStart;
|
|
3476
|
+
newLineNumber = hunkStart.newStart;
|
|
3477
|
+
insideHunk = true;
|
|
3478
|
+
continue;
|
|
3479
|
+
}
|
|
3480
|
+
if (!insideHunk) {
|
|
3481
|
+
continue;
|
|
3482
|
+
}
|
|
3483
|
+
if (line.startsWith("+")) {
|
|
3484
|
+
currentHunk.push({
|
|
3485
|
+
kind: "diff-add",
|
|
3486
|
+
text: formatDiffCodeLine(newLineNumber, "+", line.slice(1))
|
|
3487
|
+
});
|
|
3488
|
+
newLineNumber += 1;
|
|
3489
|
+
continue;
|
|
3490
|
+
}
|
|
3491
|
+
if (line.startsWith("-")) {
|
|
3492
|
+
currentHunk.push({
|
|
3493
|
+
kind: "diff-remove",
|
|
3494
|
+
text: formatDiffCodeLine(oldLineNumber, "-", line.slice(1))
|
|
3495
|
+
});
|
|
3496
|
+
oldLineNumber += 1;
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
if (line.startsWith(" ")) {
|
|
3500
|
+
currentHunk.push({
|
|
3501
|
+
kind: "diff-context",
|
|
3502
|
+
text: formatDiffCodeLine(newLineNumber, " ", line.slice(1))
|
|
3503
|
+
});
|
|
3504
|
+
oldLineNumber += 1;
|
|
3505
|
+
newLineNumber += 1;
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
flushHunk();
|
|
3509
|
+
return hunks.flat();
|
|
3510
|
+
}
|
|
2525
3511
|
function renderDiffFileLine(line) {
|
|
2526
3512
|
const title = renderDiffFileTitle(line);
|
|
2527
3513
|
if (!title) {
|
|
@@ -2678,6 +3664,9 @@ function classifyRenderedLine(section, line) {
|
|
|
2678
3664
|
if (isNoMatchesSummaryLine(trimmed)) {
|
|
2679
3665
|
return { kind: "summary", text: trimmed };
|
|
2680
3666
|
}
|
|
3667
|
+
if (isNetworkRecoverySummaryLine(trimmed)) {
|
|
3668
|
+
return { kind: "summary", text: trimmed };
|
|
3669
|
+
}
|
|
2681
3670
|
if ((isProcessSection || isExplicitErrorLine(trimmed)) &&
|
|
2682
3671
|
isErrorProcessLine(trimmed) &&
|
|
2683
3672
|
!isMarkdownListLikeLine(trimmed)) {
|
|
@@ -2833,16 +3822,70 @@ function isMarkdownTableSeparator(line) {
|
|
|
2833
3822
|
function isMarkdownTableRow(line) {
|
|
2834
3823
|
return line.startsWith("|") && line.endsWith("|") && line.split("|").length > 2;
|
|
2835
3824
|
}
|
|
2836
|
-
function
|
|
3825
|
+
function collectMarkdownTableBlock(rawLines, startIndex) {
|
|
3826
|
+
const rows = [];
|
|
3827
|
+
let index = startIndex;
|
|
3828
|
+
let consumed = false;
|
|
3829
|
+
while (index < rawLines.length) {
|
|
3830
|
+
const line = stripAnsi(rawLines[index] ?? "").replace(/\r/g, "").trim();
|
|
3831
|
+
if (!line) {
|
|
3832
|
+
break;
|
|
3833
|
+
}
|
|
3834
|
+
if (isMarkdownTableSeparator(line)) {
|
|
3835
|
+
consumed = true;
|
|
3836
|
+
index += 1;
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
if (!isMarkdownTableRow(line)) {
|
|
3840
|
+
break;
|
|
3841
|
+
}
|
|
3842
|
+
const row = parseMarkdownTableRow(line);
|
|
3843
|
+
if (row.length > 0) {
|
|
3844
|
+
rows.push(row);
|
|
3845
|
+
}
|
|
3846
|
+
consumed = true;
|
|
3847
|
+
index += 1;
|
|
3848
|
+
}
|
|
3849
|
+
if (!consumed) {
|
|
3850
|
+
return null;
|
|
3851
|
+
}
|
|
3852
|
+
return {
|
|
3853
|
+
lines: renderMarkdownTableRows(rows),
|
|
3854
|
+
nextIndex: index
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
function parseMarkdownTableRow(line) {
|
|
2837
3858
|
return line
|
|
2838
3859
|
.split("|")
|
|
2839
3860
|
.map((cell) => stripInlineMarkdown(cell.trim()))
|
|
2840
|
-
.filter(Boolean)
|
|
2841
|
-
|
|
3861
|
+
.filter(Boolean);
|
|
3862
|
+
}
|
|
3863
|
+
function renderMarkdownTableRows(rows) {
|
|
3864
|
+
if (rows.length === 0) {
|
|
3865
|
+
return [];
|
|
3866
|
+
}
|
|
3867
|
+
const columnWidths = markdownTableColumnWidths(rows);
|
|
3868
|
+
return rows.map((row) => ({
|
|
3869
|
+
kind: "table",
|
|
3870
|
+
text: row
|
|
3871
|
+
.map((cell, index) => index === row.length - 1
|
|
3872
|
+
? cell
|
|
3873
|
+
: `${cell}${" ".repeat(Math.max(0, (columnWidths[index] ?? 0) - displayWidth(cell)))}`)
|
|
3874
|
+
.join(" ")
|
|
3875
|
+
}));
|
|
3876
|
+
}
|
|
3877
|
+
function markdownTableColumnWidths(rows) {
|
|
3878
|
+
const widths = [];
|
|
3879
|
+
for (const row of rows) {
|
|
3880
|
+
row.forEach((cell, index) => {
|
|
3881
|
+
widths[index] = Math.max(widths[index] ?? 0, displayWidth(cell));
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
return widths;
|
|
2842
3885
|
}
|
|
2843
3886
|
function stripInlineMarkdown(text) {
|
|
2844
3887
|
return text
|
|
2845
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
3888
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, target) => formatMarkdownLink(label, target))
|
|
2846
3889
|
.replace(/`([^`]+)`/g, "$1")
|
|
2847
3890
|
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
2848
3891
|
.replace(/__([^_]+)__/g, "$1")
|
|
@@ -2850,19 +3893,25 @@ function stripInlineMarkdown(text) {
|
|
|
2850
3893
|
.replace(/_([^_]+)_/g, "$1")
|
|
2851
3894
|
.trim();
|
|
2852
3895
|
}
|
|
2853
|
-
function
|
|
3896
|
+
function formatMarkdownLink(label, target) {
|
|
3897
|
+
const text = stripInlineMarkdownLinkText(label);
|
|
3898
|
+
const href = target.trim();
|
|
3899
|
+
if (!href) {
|
|
3900
|
+
return text;
|
|
3901
|
+
}
|
|
3902
|
+
return isExternalMarkdownLinkTarget(href) ? `${text} <${href}>` : text;
|
|
3903
|
+
}
|
|
3904
|
+
function stripInlineMarkdownLinkText(text) {
|
|
2854
3905
|
return text
|
|
2855
|
-
.replace(
|
|
2856
|
-
.replace(
|
|
2857
|
-
.replace(
|
|
2858
|
-
.replace(
|
|
2859
|
-
.replace(
|
|
2860
|
-
.
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
.replace(/"/g, "\"")
|
|
2865
|
-
.replace(/'/g, "'");
|
|
3906
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
3907
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
3908
|
+
.replace(/__([^_]+)__/g, "$1")
|
|
3909
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
3910
|
+
.replace(/_([^_]+)_/g, "$1")
|
|
3911
|
+
.trim();
|
|
3912
|
+
}
|
|
3913
|
+
function isExternalMarkdownLinkTarget(target) {
|
|
3914
|
+
return /^(?:https?:|mailto:)/i.test(target);
|
|
2866
3915
|
}
|
|
2867
3916
|
function renderJsonLine(line) {
|
|
2868
3917
|
try {
|
|
@@ -2873,7 +3922,7 @@ function renderJsonLine(line) {
|
|
|
2873
3922
|
kind: "json",
|
|
2874
3923
|
text: summary.meta
|
|
2875
3924
|
},
|
|
2876
|
-
...
|
|
3925
|
+
...summary.messages.map((message) => ({ kind: "json-message", text: message }))
|
|
2877
3926
|
];
|
|
2878
3927
|
}
|
|
2879
3928
|
catch {
|
|
@@ -2881,26 +3930,86 @@ function renderJsonLine(line) {
|
|
|
2881
3930
|
}
|
|
2882
3931
|
}
|
|
2883
3932
|
function summarizeJsonRecord(value) {
|
|
2884
|
-
const
|
|
2885
|
-
const id = stringField(value, "id");
|
|
2886
|
-
const
|
|
3933
|
+
const state = stringField(value, "status") || stringField(value, "severity");
|
|
3934
|
+
const id = stringField(value, "id") || stringField(value, "finding_id") || stringField(value, "findingId");
|
|
3935
|
+
const to = stringField(value, "to");
|
|
3936
|
+
const from = stringField(value, "from");
|
|
3937
|
+
const direction = from && to ? `${from} -> ${to}` : to ? `to ${to}` : from ? `from ${from}` : "";
|
|
2887
3938
|
const file = stringField(value, "file") || stringField(value, "path");
|
|
2888
|
-
const
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
].filter(Boolean)
|
|
3939
|
+
const line = numberLikeField(value, "line");
|
|
3940
|
+
const column = numberLikeField(value, "column") || numberLikeField(value, "col");
|
|
3941
|
+
const location = formatJsonRecordLocation(file, line, column);
|
|
3942
|
+
const messages = jsonRecordMessages(value);
|
|
3943
|
+
const marker = state ? `[${state}]` : "";
|
|
3944
|
+
const lead = [marker, id].filter(Boolean).join(" ");
|
|
3945
|
+
const details = [direction, location].filter(Boolean);
|
|
3946
|
+
const meta = id
|
|
3947
|
+
? [lead, ...details].filter(Boolean).join(" · ")
|
|
3948
|
+
: [lead, ...details].filter(Boolean).join(" ");
|
|
2895
3949
|
return {
|
|
2896
3950
|
meta: meta || "json",
|
|
2897
|
-
|
|
3951
|
+
messages: messages.length > 0 ? messages : [JSON.stringify(value)]
|
|
2898
3952
|
};
|
|
2899
3953
|
}
|
|
2900
3954
|
function stringField(value, key) {
|
|
2901
3955
|
const field = value[key];
|
|
2902
3956
|
return typeof field === "string" ? field.trim() : "";
|
|
2903
3957
|
}
|
|
3958
|
+
function jsonRecordMessages(value) {
|
|
3959
|
+
const primary = stringField(value, "message") || stringField(value, "summary");
|
|
3960
|
+
if (primary) {
|
|
3961
|
+
return [primary];
|
|
3962
|
+
}
|
|
3963
|
+
return [
|
|
3964
|
+
...textFragmentsField(value, "title"),
|
|
3965
|
+
...textFragmentsField(value, "detail").map((text) => `detail · ${text}`),
|
|
3966
|
+
...textFragmentsField(value, "details").map((text) => `detail · ${text}`),
|
|
3967
|
+
...textFragmentsField(value, "recommendation").map((text) => `fix · ${text}`)
|
|
3968
|
+
].filter(uniqueTextFragmentFilter());
|
|
3969
|
+
}
|
|
3970
|
+
function textFragmentsField(value, key) {
|
|
3971
|
+
return textFragmentsValue(value[key]);
|
|
3972
|
+
}
|
|
3973
|
+
function textFragmentsValue(value) {
|
|
3974
|
+
if (typeof value === "string") {
|
|
3975
|
+
return value.trim() ? [value.trim()] : [];
|
|
3976
|
+
}
|
|
3977
|
+
if (Array.isArray(value)) {
|
|
3978
|
+
return value.flatMap((item) => textFragmentsValue(item));
|
|
3979
|
+
}
|
|
3980
|
+
return [];
|
|
3981
|
+
}
|
|
3982
|
+
function uniqueTextFragmentFilter() {
|
|
3983
|
+
const seen = new Set();
|
|
3984
|
+
return (value) => {
|
|
3985
|
+
const normalized = value.toLowerCase();
|
|
3986
|
+
if (!value || seen.has(normalized)) {
|
|
3987
|
+
return false;
|
|
3988
|
+
}
|
|
3989
|
+
seen.add(normalized);
|
|
3990
|
+
return true;
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
function numberLikeField(value, key) {
|
|
3994
|
+
const field = value[key];
|
|
3995
|
+
if (typeof field === "number" && Number.isFinite(field)) {
|
|
3996
|
+
return String(field);
|
|
3997
|
+
}
|
|
3998
|
+
if (typeof field !== "string") {
|
|
3999
|
+
return "";
|
|
4000
|
+
}
|
|
4001
|
+
const trimmed = field.trim();
|
|
4002
|
+
return /^\d+$/.test(trimmed) ? trimmed : "";
|
|
4003
|
+
}
|
|
4004
|
+
function formatJsonRecordLocation(file, line, column) {
|
|
4005
|
+
if (!file) {
|
|
4006
|
+
return "";
|
|
4007
|
+
}
|
|
4008
|
+
if (!line) {
|
|
4009
|
+
return file;
|
|
4010
|
+
}
|
|
4011
|
+
return column ? `${file}:${line}:${column}` : `${file}:${line}`;
|
|
4012
|
+
}
|
|
2904
4013
|
function stripAnsi(value) {
|
|
2905
4014
|
return value.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "");
|
|
2906
4015
|
}
|
|
@@ -2918,72 +4027,91 @@ function groupTitle(group) {
|
|
|
2918
4027
|
}
|
|
2919
4028
|
export function workerOutputLineTheme(kind) {
|
|
2920
4029
|
if (kind === "group") {
|
|
2921
|
-
return { backgroundColor:
|
|
4030
|
+
return { backgroundColor: TUI_THEME.chrome, bold: true, color: TUI_THEME.text };
|
|
2922
4031
|
}
|
|
2923
4032
|
if (kind === "section") {
|
|
2924
|
-
return { backgroundColor:
|
|
4033
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning };
|
|
4034
|
+
}
|
|
4035
|
+
if (kind === "content" || kind === "list" || kind === "list-detail" || kind === "ordered-list" || kind === "task") {
|
|
4036
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text };
|
|
4037
|
+
}
|
|
4038
|
+
if (kind === "placeholder") {
|
|
4039
|
+
return workerOutputEmptyFallbackTheme();
|
|
2925
4040
|
}
|
|
2926
4041
|
if (kind === "heading") {
|
|
2927
|
-
return { backgroundColor:
|
|
4042
|
+
return { backgroundColor: TUI_THEME.surface, bold: true, color: TUI_THEME.text };
|
|
2928
4043
|
}
|
|
2929
4044
|
if (kind === "quote") {
|
|
2930
|
-
return { backgroundColor:
|
|
4045
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted };
|
|
2931
4046
|
}
|
|
2932
4047
|
if (kind === "table") {
|
|
2933
|
-
return { backgroundColor:
|
|
4048
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text };
|
|
2934
4049
|
}
|
|
2935
4050
|
if (kind === "rule") {
|
|
2936
|
-
return {
|
|
4051
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted };
|
|
4052
|
+
}
|
|
4053
|
+
if (kind === "blank") {
|
|
4054
|
+
return { backgroundColor: TUI_THEME.surface };
|
|
2937
4055
|
}
|
|
2938
4056
|
if (kind === "code") {
|
|
2939
|
-
return { backgroundColor:
|
|
4057
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted };
|
|
2940
4058
|
}
|
|
2941
4059
|
if (kind === "source-line") {
|
|
2942
|
-
return { backgroundColor:
|
|
4060
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text };
|
|
2943
4061
|
}
|
|
2944
4062
|
if (kind === "summary") {
|
|
2945
|
-
return { backgroundColor:
|
|
4063
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted };
|
|
2946
4064
|
}
|
|
2947
4065
|
if (kind === "json") {
|
|
2948
|
-
return { backgroundColor:
|
|
4066
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent };
|
|
2949
4067
|
}
|
|
2950
4068
|
if (kind === "json-message") {
|
|
2951
|
-
return { backgroundColor:
|
|
4069
|
+
return { backgroundColor: TUI_THEME.surface };
|
|
2952
4070
|
}
|
|
2953
4071
|
if (kind === "command") {
|
|
2954
|
-
return { backgroundColor:
|
|
4072
|
+
return { backgroundColor: TUI_THEME.chrome, color: TUI_THEME.accent };
|
|
2955
4073
|
}
|
|
2956
4074
|
if (kind === "success") {
|
|
2957
|
-
return { backgroundColor:
|
|
4075
|
+
return { backgroundColor: TUI_THEME.successSurface, color: TUI_THEME.success };
|
|
2958
4076
|
}
|
|
2959
4077
|
if (kind === "error") {
|
|
2960
|
-
return { backgroundColor:
|
|
4078
|
+
return { backgroundColor: TUI_THEME.dangerSurface, color: TUI_THEME.danger };
|
|
2961
4079
|
}
|
|
2962
4080
|
if (kind === "diff-file") {
|
|
2963
|
-
return { bold: true, color:
|
|
4081
|
+
return { backgroundColor: TUI_THEME.surface, bold: true, color: TUI_THEME.accent };
|
|
2964
4082
|
}
|
|
2965
4083
|
if (kind === "diff-summary") {
|
|
2966
|
-
return { color:
|
|
4084
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.text };
|
|
2967
4085
|
}
|
|
2968
4086
|
if (kind === "diff-hunk" || kind === "diff-meta") {
|
|
2969
|
-
return { backgroundColor:
|
|
4087
|
+
return { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted };
|
|
2970
4088
|
}
|
|
2971
4089
|
if (kind === "diff-add") {
|
|
2972
|
-
return { backgroundColor:
|
|
4090
|
+
return { backgroundColor: TUI_THEME.successSurface, color: TUI_THEME.success };
|
|
2973
4091
|
}
|
|
2974
4092
|
if (kind === "diff-remove") {
|
|
2975
|
-
return { backgroundColor:
|
|
4093
|
+
return { backgroundColor: TUI_THEME.dangerSurface, color: TUI_THEME.danger };
|
|
2976
4094
|
}
|
|
2977
4095
|
if (kind === "diff-context") {
|
|
2978
|
-
return { color:
|
|
4096
|
+
return { backgroundColor: TUI_THEME.surface, color: TUI_THEME.muted };
|
|
2979
4097
|
}
|
|
2980
4098
|
return {};
|
|
2981
4099
|
}
|
|
4100
|
+
export function workerOutputEmptyFallbackTheme() {
|
|
4101
|
+
return {
|
|
4102
|
+
backgroundColor: TUI_THEME.surface,
|
|
4103
|
+
color: TUI_THEME.muted
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
export function workerOutputLineFillTheme(kind) {
|
|
4107
|
+
const backgroundColor = workerOutputLineTheme(kind).backgroundColor;
|
|
4108
|
+
return backgroundColor ?? null;
|
|
4109
|
+
}
|
|
2982
4110
|
export function workerOutputLineLayout(kind, text) {
|
|
2983
4111
|
if (kind === "section") {
|
|
2984
|
-
return { gutter: "", body:
|
|
4112
|
+
return { gutter: "", body: joinWorkerOutputChromeParts([workerSectionLabel(text), formatWorkerSectionTitle(text)]) };
|
|
2985
4113
|
}
|
|
2986
|
-
if (kind === "content") {
|
|
4114
|
+
if (kind === "content" || kind === "placeholder") {
|
|
2987
4115
|
return { gutter: "", body: formatPlainDisplayText(text) };
|
|
2988
4116
|
}
|
|
2989
4117
|
if (kind === "heading") {
|
|
@@ -2999,7 +4127,7 @@ export function workerOutputLineLayout(kind, text) {
|
|
|
2999
4127
|
return { gutter: "", body: text };
|
|
3000
4128
|
}
|
|
3001
4129
|
if (kind === "quote") {
|
|
3002
|
-
return { gutter: "", body:
|
|
4130
|
+
return { gutter: "", body: `│ ${text}` };
|
|
3003
4131
|
}
|
|
3004
4132
|
if (kind === "table" || kind === "rule") {
|
|
3005
4133
|
return { gutter: "", body: text };
|
|
@@ -3035,7 +4163,7 @@ export function workerOutputLineLayout(kind, text) {
|
|
|
3035
4163
|
return { gutter: "", body: `└ ${text}` };
|
|
3036
4164
|
}
|
|
3037
4165
|
if (kind === "diff-hunk") {
|
|
3038
|
-
return { gutter: "", body:
|
|
4166
|
+
return { gutter: "", body: text };
|
|
3039
4167
|
}
|
|
3040
4168
|
if (kind === "diff-context") {
|
|
3041
4169
|
return { gutter: "", body: text };
|
|
@@ -3052,9 +4180,66 @@ export function workerOutputLineLayout(kind, text) {
|
|
|
3052
4180
|
return { gutter: "", body: text };
|
|
3053
4181
|
}
|
|
3054
4182
|
function formatWorkerSectionTitle(title) {
|
|
4183
|
+
const featureMatch = title.match(/^features\/([^/]+)\/([^/]+)$/);
|
|
4184
|
+
const turn = featureMatch?.[1] ?? "";
|
|
4185
|
+
const file = (featureMatch?.[2] ?? basename(title)).toLowerCase();
|
|
4186
|
+
if (turn && isTurnScopedWorkerArtifact(file)) {
|
|
4187
|
+
return turn;
|
|
4188
|
+
}
|
|
4189
|
+
if (file === "requirements.md" || file === "plan.md" || file === "acceptance.md" || file === "worklog.md") {
|
|
4190
|
+
return "";
|
|
4191
|
+
}
|
|
4192
|
+
if (file === "patch.diff") {
|
|
4193
|
+
return "patch";
|
|
4194
|
+
}
|
|
3055
4195
|
return title.replace(/^features\//, "");
|
|
3056
4196
|
}
|
|
4197
|
+
function workerSectionLabel(title) {
|
|
4198
|
+
const file = basename(title).toLowerCase();
|
|
4199
|
+
if (file === "requirements.md") {
|
|
4200
|
+
return "requirements";
|
|
4201
|
+
}
|
|
4202
|
+
if (file === "plan.md") {
|
|
4203
|
+
return "plan";
|
|
4204
|
+
}
|
|
4205
|
+
if (file === "acceptance.md") {
|
|
4206
|
+
return "acceptance";
|
|
4207
|
+
}
|
|
4208
|
+
if (file === "actor-worklog.md" || file === "worklog.md") {
|
|
4209
|
+
return "worklog";
|
|
4210
|
+
}
|
|
4211
|
+
if (file === "decisions.md") {
|
|
4212
|
+
return "decision";
|
|
4213
|
+
}
|
|
4214
|
+
if (file.endsWith(".diff")) {
|
|
4215
|
+
return "diff";
|
|
4216
|
+
}
|
|
4217
|
+
if (file === "actor-replies.jsonl") {
|
|
4218
|
+
return "mail";
|
|
4219
|
+
}
|
|
4220
|
+
if (file === "critic-findings.jsonl") {
|
|
4221
|
+
return "findings";
|
|
4222
|
+
}
|
|
4223
|
+
if (file.endsWith(".jsonl")) {
|
|
4224
|
+
return "jsonl";
|
|
4225
|
+
}
|
|
4226
|
+
return "file";
|
|
4227
|
+
}
|
|
4228
|
+
function isTurnScopedWorkerArtifact(file) {
|
|
4229
|
+
return file === "actor-worklog.md" ||
|
|
4230
|
+
file === "decisions.md" ||
|
|
4231
|
+
file === "actor-replies.jsonl" ||
|
|
4232
|
+
file === "critic-findings.jsonl";
|
|
4233
|
+
}
|
|
3057
4234
|
function formatSummaryDisplayText(text) {
|
|
4235
|
+
const networkRecovery = networkRecoverySummaryParts(text);
|
|
4236
|
+
if (networkRecovery) {
|
|
4237
|
+
return [
|
|
4238
|
+
"network recovered",
|
|
4239
|
+
networkRecovery.reconnect,
|
|
4240
|
+
networkRecovery.fallback
|
|
4241
|
+
].filter(Boolean).join(" · ");
|
|
4242
|
+
}
|
|
3058
4243
|
const readRun = text.match(/^Collapsed read summaries: (\d+) chunks, (\d+) lines(?: \((.+)\))?$/i);
|
|
3059
4244
|
if (readRun) {
|
|
3060
4245
|
return [
|
|
@@ -3275,41 +4460,76 @@ function formatFileUrlDisplay(url) {
|
|
|
3275
4460
|
const segments = path.split("/").filter(Boolean);
|
|
3276
4461
|
return segments.slice(-2).join("/") || url;
|
|
3277
4462
|
}
|
|
3278
|
-
function WorkerOutputLine({ line }) {
|
|
4463
|
+
function WorkerOutputLine({ line, selected, width }) {
|
|
3279
4464
|
const theme = workerOutputLineTheme(line.kind);
|
|
4465
|
+
const fillBackground = workerOutputLineFillTheme(line.kind);
|
|
3280
4466
|
if (line.kind === "blank") {
|
|
3281
|
-
return _jsx(
|
|
4467
|
+
return _jsx(WorkerOutputBlankLine, { width: width });
|
|
3282
4468
|
}
|
|
3283
4469
|
if (line.kind === "group") {
|
|
3284
|
-
|
|
4470
|
+
const body = `${selected ? ">" : " "}${line.text} `;
|
|
4471
|
+
return (_jsxs(Box, { children: [_jsx(Text, { ...theme, children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: width, usedWidth: displayWidth(body) })] }));
|
|
3285
4472
|
}
|
|
3286
4473
|
if (line.preformatted) {
|
|
3287
|
-
|
|
4474
|
+
const body = line.text || " ";
|
|
4475
|
+
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
4476
|
}
|
|
3289
4477
|
const layout = workerOutputLineLayout(line.kind, line.text);
|
|
3290
4478
|
if (line.kind === "code") {
|
|
3291
|
-
return (_jsxs(Box, { children: [_jsx(
|
|
4479
|
+
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
4480
|
}
|
|
3293
4481
|
if (line.kind === "source-line") {
|
|
3294
4482
|
const sourceParts = sourceDisplayLineParts(line.text);
|
|
3295
4483
|
if (sourceParts) {
|
|
3296
|
-
|
|
4484
|
+
const usedWidth = 2 + displayWidth(sourceParts.gutter) + displayWidth(sourceParts.code || " ");
|
|
4485
|
+
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
4486
|
}
|
|
3298
|
-
|
|
4487
|
+
const body = line.text || " ";
|
|
4488
|
+
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
4489
|
}
|
|
3300
4490
|
if (line.continuation && isDiffCodeKind(line.kind)) {
|
|
3301
|
-
|
|
4491
|
+
const body = line.text || " ";
|
|
4492
|
+
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
4493
|
}
|
|
3303
4494
|
const diffCodeLine = diffCodeLineParts(line);
|
|
3304
4495
|
if (diffCodeLine) {
|
|
3305
|
-
|
|
4496
|
+
const prefix = `${diffCodeLine.lineNumber} ${diffCodeLine.sign} `;
|
|
4497
|
+
const code = diffCodeLine.code || " ";
|
|
4498
|
+
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) })] }));
|
|
4499
|
+
}
|
|
4500
|
+
const gutter = layout.gutter ? formatGutter(layout.gutter) : "";
|
|
4501
|
+
const body = layout.body || " ";
|
|
4502
|
+
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) })] }));
|
|
4503
|
+
}
|
|
4504
|
+
function WorkerOutputIndent({ backgroundColor, selected }) {
|
|
4505
|
+
if (selected) {
|
|
4506
|
+
return _jsx(Text, { backgroundColor: backgroundColor ?? TUI_THEME.surface, color: TUI_THEME.accent, bold: true, children: "> " });
|
|
4507
|
+
}
|
|
4508
|
+
return backgroundColor
|
|
4509
|
+
? _jsx(Text, { backgroundColor: backgroundColor, children: " " })
|
|
4510
|
+
: _jsx(Text, { color: TUI_THEME.muted, children: " " });
|
|
4511
|
+
}
|
|
4512
|
+
function WorkerOutputBlankLine({ width }) {
|
|
4513
|
+
const theme = workerOutputLineTheme("blank");
|
|
4514
|
+
const fillWidth = shouldRenderWorkerOutputRailFill() ? Math.max(1, width) : 1;
|
|
4515
|
+
return _jsx(Text, { ...theme, children: " ".repeat(fillWidth) });
|
|
4516
|
+
}
|
|
4517
|
+
function WorkerOutputTrailingFill({ backgroundColor, usedWidth, width }) {
|
|
4518
|
+
if (!backgroundColor || !shouldRenderWorkerOutputRailFill()) {
|
|
4519
|
+
return null;
|
|
3306
4520
|
}
|
|
3307
|
-
|
|
4521
|
+
const fillWidth = Math.max(0, width - usedWidth);
|
|
4522
|
+
return fillWidth > 0 ? _jsx(Text, { backgroundColor: backgroundColor, children: " ".repeat(fillWidth) }) : null;
|
|
4523
|
+
}
|
|
4524
|
+
function shouldRenderWorkerOutputRailFill() {
|
|
4525
|
+
return process.stdout.isTTY === true && typeof process.stdout.columns === "number";
|
|
3308
4526
|
}
|
|
3309
|
-
function WorkerOutputNanoLine({ line, width }) {
|
|
4527
|
+
function WorkerOutputNanoLine({ fillWidth, line, selected, width }) {
|
|
3310
4528
|
const theme = workerOutputLineTheme(line.kind);
|
|
3311
|
-
const
|
|
3312
|
-
|
|
4529
|
+
const fillBackground = workerOutputLineFillTheme(line.kind);
|
|
4530
|
+
const text = compactEndByDisplayWidth(`${selected ? "> " : ""}${workerOutputNanoLineText(line)}`, Math.max(1, width));
|
|
4531
|
+
const body = text || " ";
|
|
4532
|
+
return (_jsxs(Box, { children: [_jsx(Text, { ...theme, wrap: "truncate-end", children: body }), _jsx(WorkerOutputTrailingFill, { backgroundColor: fillBackground, width: fillWidth, usedWidth: displayWidth(body) })] }));
|
|
3313
4533
|
}
|
|
3314
4534
|
function workerOutputNanoLineText(line) {
|
|
3315
4535
|
if (line.kind === "blank") {
|
|
@@ -3367,9 +4587,9 @@ export function workerOutputDiffColumns(text) {
|
|
|
3367
4587
|
code: match[3] ?? ""
|
|
3368
4588
|
};
|
|
3369
4589
|
}
|
|
3370
|
-
function renderDisplayLines(lines, width = Number(process.stdout.columns) || 120) {
|
|
3371
|
-
const contentWidth = Math.max(1, width - 4);
|
|
3372
|
-
if (isNanoWorkerOutputWidth(width)) {
|
|
4590
|
+
function renderDisplayLines(lines, width = Number(process.stdout.columns) || 120, options = {}) {
|
|
4591
|
+
const contentWidth = options.contentWidth ?? Math.max(1, width - 4);
|
|
4592
|
+
if (options.nano ?? isNanoWorkerOutputWidth(width)) {
|
|
3373
4593
|
return renderNanoDisplayLines(lines, contentWidth);
|
|
3374
4594
|
}
|
|
3375
4595
|
return lines.flatMap((line) => {
|
|
@@ -3442,6 +4662,9 @@ export function workerOutputBodyDisplayLines(kind, text, width) {
|
|
|
3442
4662
|
return wrapBodyWithContinuation(body, Math.max(1, width), continuationPrefix);
|
|
3443
4663
|
}
|
|
3444
4664
|
function compactWorkerBodyForWidth(kind, body, width) {
|
|
4665
|
+
if (kind === "diff-hunk") {
|
|
4666
|
+
return compactDiffHunkBodyForWidth(body, width);
|
|
4667
|
+
}
|
|
3445
4668
|
if (kind === "summary") {
|
|
3446
4669
|
return compactSummaryBodyForWidth(body, width);
|
|
3447
4670
|
}
|
|
@@ -3480,6 +4703,25 @@ function compactWorkerBodyForWidth(kind, body, width) {
|
|
|
3480
4703
|
}
|
|
3481
4704
|
return body;
|
|
3482
4705
|
}
|
|
4706
|
+
function compactDiffHunkBodyForWidth(body, width) {
|
|
4707
|
+
if (displayWidth(body) <= width) {
|
|
4708
|
+
return body;
|
|
4709
|
+
}
|
|
4710
|
+
const match = body.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@(?:\s+.*)?$/);
|
|
4711
|
+
if (!match) {
|
|
4712
|
+
return compactEndByDisplayWidth(body, width);
|
|
4713
|
+
}
|
|
4714
|
+
const oldStart = match[1] ?? "0";
|
|
4715
|
+
const newStart = match[2] ?? "0";
|
|
4716
|
+
const candidates = [
|
|
4717
|
+
`@@ -${oldStart} +${newStart} @@`,
|
|
4718
|
+
`-${oldStart} +${newStart}`,
|
|
4719
|
+
`${oldStart}>${newStart}`,
|
|
4720
|
+
`+${newStart}`
|
|
4721
|
+
];
|
|
4722
|
+
return candidates.find((candidate) => displayWidth(candidate) <= width)
|
|
4723
|
+
?? compactEndByDisplayWidth(body, width);
|
|
4724
|
+
}
|
|
3483
4725
|
function compactContextWindowErrorForWidth(body, width) {
|
|
3484
4726
|
if (width < 7) {
|
|
3485
4727
|
return "ctx";
|
|
@@ -3558,9 +4800,9 @@ function compactNarrowWorkerBody(body, width) {
|
|
|
3558
4800
|
if (/^Findings:\s*none$/i.test(body)) {
|
|
3559
4801
|
return width < 24 ? compactFindingsNoneForWidth(width) : body;
|
|
3560
4802
|
}
|
|
3561
|
-
const findingsLabel = width < 24 ? "findings" : "findings.jsonl";
|
|
3562
|
-
const repliesLabel = width < 24 ? "replies" : "replies.jsonl";
|
|
3563
|
-
const worklogLabel = width < 24 ? "worklog" : "worklog.md";
|
|
4803
|
+
const findingsLabel = width < 24 ? "findings" : width < 32 ? "findings.jsonl" : "critic-findings.jsonl";
|
|
4804
|
+
const repliesLabel = width < 24 ? "replies" : width < 32 ? "replies.jsonl" : "actor-replies.jsonl";
|
|
4805
|
+
const worklogLabel = width < 24 ? "worklog" : width < 32 ? "worklog.md" : "actor-worklog.md";
|
|
3564
4806
|
return body
|
|
3565
4807
|
.replace(/^(•\s+)?npm run dev could not bind\b.*$/i, (_match, marker) => `${marker ?? ""}dev fallback.`)
|
|
3566
4808
|
.replace(/\bcritic-findings\.jsonl\b/g, findingsLabel)
|