parallel-codex-tui 0.1.3 → 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.
Files changed (72) hide show
  1. package/.parallel-codex/config.example.toml +90 -3
  2. package/README.md +240 -21
  3. package/dist/bootstrap.js +37 -17
  4. package/dist/cli-args.js +34 -3
  5. package/dist/cli-help.js +20 -0
  6. package/dist/cli-startup-recovery.js +70 -0
  7. package/dist/cli-workspace-picker.js +330 -0
  8. package/dist/cli-workspace-transition.js +33 -0
  9. package/dist/cli-workspace.js +7 -71
  10. package/dist/cli.js +221 -23
  11. package/dist/core/collaboration-timeline.js +261 -0
  12. package/dist/core/config-errors.js +14 -0
  13. package/dist/core/config.js +154 -24
  14. package/dist/core/file-store.js +119 -6
  15. package/dist/core/lease-finalization.js +22 -0
  16. package/dist/core/paths.js +7 -0
  17. package/dist/core/process-identity.js +48 -0
  18. package/dist/core/process-mutation-turn.js +128 -0
  19. package/dist/core/process-ownership.js +276 -0
  20. package/dist/core/process-tree.js +90 -0
  21. package/dist/core/router-audit.js +155 -0
  22. package/dist/core/router-redaction.js +31 -0
  23. package/dist/core/router.js +462 -35
  24. package/dist/core/session-index.js +188 -37
  25. package/dist/core/session-manager.js +1086 -40
  26. package/dist/core/task-state-machine.js +17 -0
  27. package/dist/core/workspace-commit-recovery.js +118 -0
  28. package/dist/core/workspace.js +19 -11
  29. package/dist/doctor.js +343 -23
  30. package/dist/domain/schemas.js +127 -6
  31. package/dist/orchestrator/collaboration-channel.js +255 -4
  32. package/dist/orchestrator/feature-plan.js +70 -0
  33. package/dist/orchestrator/judge-artifacts.js +236 -0
  34. package/dist/orchestrator/orchestrator.js +1749 -202
  35. package/dist/orchestrator/prompts.js +126 -2
  36. package/dist/orchestrator/supervisor-summary.js +56 -2
  37. package/dist/orchestrator/workspace-sandbox.js +911 -0
  38. package/dist/tui/App.js +2830 -153
  39. package/dist/tui/AppShell.js +188 -23
  40. package/dist/tui/CollaborationTimelineView.js +327 -0
  41. package/dist/tui/FeatureBoardView.js +227 -0
  42. package/dist/tui/InputBar.js +514 -57
  43. package/dist/tui/RouterDiagnosticsView.js +469 -0
  44. package/dist/tui/StatusBar.js +610 -57
  45. package/dist/tui/TaskSessionsView.js +207 -0
  46. package/dist/tui/TerminalOutput.js +53 -9
  47. package/dist/tui/WorkerOutputView.js +1403 -161
  48. package/dist/tui/WorkerOverviewView.js +250 -0
  49. package/dist/tui/chat-history.js +25 -0
  50. package/dist/tui/chat-input.js +67 -19
  51. package/dist/tui/chat-paste.js +76 -0
  52. package/dist/tui/display-width.js +41 -3
  53. package/dist/tui/incremental-text-file.js +101 -0
  54. package/dist/tui/keyboard.js +46 -0
  55. package/dist/tui/markdown-text.js +14 -0
  56. package/dist/tui/raw-input-decoder.js +3 -0
  57. package/dist/tui/scrolling.js +2 -1
  58. package/dist/tui/status-line.js +318 -11
  59. package/dist/tui/task-memory.js +13 -1
  60. package/dist/tui/task-result.js +105 -0
  61. package/dist/tui/terminal-screen.js +13 -1
  62. package/dist/tui/theme-contrast.js +144 -0
  63. package/dist/tui/theme-preview.js +109 -0
  64. package/dist/tui/theme.js +158 -0
  65. package/dist/version.js +1 -1
  66. package/dist/workers/capabilities.js +212 -0
  67. package/dist/workers/live-probe.js +176 -0
  68. package/dist/workers/mock-adapter.js +39 -6
  69. package/dist/workers/native-attach.js +78 -3
  70. package/dist/workers/process-adapter.js +570 -77
  71. package/dist/workers/registry.js +4 -2
  72. package/package.json +5 -2
@@ -1,32 +1,70 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
4
- const STATUS_BAR_BACKGROUND = "ansi256(236)";
5
- export function StatusBar({ text, terminalWidth = process.stdout.columns || 120, showTask = false }) {
4
+ import { TUI_THEME } from "./theme.js";
5
+ export function StatusBar({ text, terminalWidth: providedTerminalWidth, showTask = false, fillRail: providedFillRail }) {
6
+ const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
7
+ const fillRail = providedFillRail ?? (providedTerminalWidth !== undefined || typeof process.stdout.columns === "number");
8
+ const { segments: fittedSegments, compact } = resolveStatusBar(text, terminalWidth, showTask);
9
+ if (isIdleStatus(fittedSegments)) {
10
+ return _jsx(IdleStatusRail, { terminalWidth: terminalWidth, fill: fillRail });
11
+ }
12
+ const { leadingWidth, trailingWidth } = statusRailLayout(terminalWidth, statusSegmentsWidth(fittedSegments, compact), { fill: fillRail });
13
+ return (_jsxs(Box, { children: [leadingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(leadingWidth) }) : null, fittedSegments.map((segment, index) => (_jsx(StatusSegment, { segment: segment, compact: compact, isLast: index === fittedSegments.length - 1 }, `${segment.label}-${index}`))), trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(trailingWidth) }) : null] }));
14
+ }
15
+ export function statusBarDisplayText(text, terminalWidth, showTask = false) {
16
+ const resolved = resolveStatusBar(text, terminalWidth, showTask);
17
+ if (isIdleStatus(resolved.segments)) {
18
+ return "";
19
+ }
20
+ return resolved.segments.map((segment, index) => {
21
+ const display = statusSegmentDisplay(segment, resolved.compact);
22
+ return `${display.label}${display.separator}${display.value}${index < resolved.segments.length - 1 ? statusSegmentSeparator(resolved.compact) : ""}`;
23
+ }).join("");
24
+ }
25
+ function resolveStatusBar(text, terminalWidth, showTask) {
6
26
  const parsedSegments = parseStatusText(text, { hideTask: !showTask || terminalWidth < 40 });
7
27
  if (isIdleStatus(parsedSegments)) {
8
- return (_jsx(Box, { children: _jsx(Text, { children: " " }) }));
28
+ return { segments: parsedSegments, compact: false };
9
29
  }
10
- const segments = omitTinyCurrentSegment(parsedSegments, terminalWidth);
30
+ const baseSegments = omitTinyCurrentSegment(readableCompletedSegments(parsedSegments, terminalWidth), terminalWidth);
31
+ const segments = fitCompletedMainIdentityBesideRoute(baseSegments, terminalWidth);
11
32
  const compact = shouldUseCompactStatus(segments, terminalWidth);
12
33
  const fittedSegments = fitStatusSegments(segments, terminalWidth, compact);
13
- if (isIdleStatus(fittedSegments)) {
14
- return (_jsx(Box, { children: _jsx(Text, { children: " " }) }));
15
- }
34
+ return { segments: fittedSegments, compact };
35
+ }
36
+ function IdleStatusRail({ terminalWidth, fill }) {
37
+ const { leadingWidth, trailingWidth } = statusRailLayout(terminalWidth, 0, { fill });
38
+ return (_jsx(Box, { children: _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(leadingWidth + trailingWidth) }) }));
39
+ }
40
+ export function statusRailLayout(terminalWidth, contentWidth, options = {}) {
16
41
  const leadingWidth = terminalWidth > 1 ? 1 : 0;
42
+ if (options.fill === false) {
43
+ return { leadingWidth, trailingWidth: 0 };
44
+ }
17
45
  const renderWidth = typeof process.stdout.columns === "number"
18
46
  ? Math.max(1, Math.min(terminalWidth, process.stdout.columns))
19
- : null;
20
- const barWidth = renderWidth === null ? null : Math.max(1, renderWidth - 1);
21
- const trailingWidth = barWidth === null
22
- ? 0
23
- : Math.max(0, barWidth - leadingWidth - statusSegmentsWidth(fittedSegments, compact));
24
- return (_jsxs(Box, { children: [leadingWidth > 0 ? _jsx(Text, { backgroundColor: STATUS_BAR_BACKGROUND, children: " ".repeat(leadingWidth) }) : null, fittedSegments.map((segment, index) => (_jsx(StatusSegment, { segment: segment, compact: compact, isLast: index === fittedSegments.length - 1 }, `${segment.label}-${index}`))), trailingWidth > 0 ? _jsx(Text, { backgroundColor: STATUS_BAR_BACKGROUND, children: " ".repeat(trailingWidth) }) : null] }));
47
+ : Math.max(1, terminalWidth);
48
+ const barWidth = Math.max(1, renderWidth - 1);
49
+ return {
50
+ leadingWidth,
51
+ trailingWidth: Math.max(0, barWidth - leadingWidth - Math.max(0, contentWidth))
52
+ };
25
53
  }
26
54
  function omitTinyCurrentSegment(segments, terminalWidth) {
27
55
  if (terminalWidth >= 24 || segments.length <= 1) {
28
56
  return segments;
29
57
  }
58
+ const current = segments.find((segment) => segment.label.toLowerCase() === "current");
59
+ const currentRole = current ? workerIdentityStatus(current.value)?.role : undefined;
60
+ if (current
61
+ && (currentRole === "main"
62
+ || current.tone === "run"
63
+ || current.tone === "wait"
64
+ || current.tone === "fail"
65
+ || current.tone === "stop")) {
66
+ return segments;
67
+ }
30
68
  const compacted = segments.filter((segment) => segment.label.toLowerCase() !== "current");
31
69
  return compacted.length > 0 ? compacted : segments;
32
70
  }
@@ -37,36 +75,243 @@ function shouldUseCompactStatus(segments, terminalWidth) {
37
75
  const roomyDisplays = segments.map((segment) => statusSegmentDisplay(segment, false));
38
76
  return statusSegmentsDisplayWidth(roomyDisplays, false) > Math.max(1, terminalWidth - 2);
39
77
  }
78
+ function readableCompletedSegments(segments, terminalWidth) {
79
+ const workersIndex = segments.findIndex((segment) => segment.label.toLowerCase() === "workers");
80
+ const doneIndex = segments.findIndex((segment) => (segment.label.toLowerCase() === "done" && segment.tone === "done"));
81
+ const routeIndex = segments.findIndex((segment) => (segment.label.toLowerCase() === "route"
82
+ && !segment.tone
83
+ && /^(?:simple|complex)(?:\s+·\s+|$)/i.test(segment.value)));
84
+ const completed = workersIndex >= 0
85
+ && doneIndex >= 0
86
+ && segments[workersIndex]?.value === segments[doneIndex]?.value;
87
+ if (!completed) {
88
+ return segments;
89
+ }
90
+ const readable = segments.map((segment, index) => (index === doneIndex
91
+ ? { ...segment, value: "done", hideLabel: true }
92
+ : index === routeIndex && terminalWidth < 56
93
+ ? { ...segment, hideLabel: true }
94
+ : segment));
95
+ return readable;
96
+ }
97
+ function fitCompletedMainIdentityBesideRoute(segments, terminalWidth) {
98
+ if (terminalWidth < 56 || segments.length !== 2) {
99
+ return segments;
100
+ }
101
+ const currentIndex = segments.findIndex((segment) => segment.label.toLowerCase() === "current");
102
+ const routeIndex = segments.findIndex((segment) => segment.label.toLowerCase() === "route");
103
+ if (currentIndex < 0 || routeIndex < 0 || statusSegmentsWidth(segments, false) <= terminalWidth - 2) {
104
+ return segments;
105
+ }
106
+ const current = segments[currentIndex];
107
+ const identity = current ? workerIdentityStatus(current.value) : null;
108
+ if (!current
109
+ || identity?.role !== "main"
110
+ || current.tone === "run"
111
+ || current.tone === "wait"
112
+ || current.tone === "fail") {
113
+ return segments;
114
+ }
115
+ const route = segments[routeIndex];
116
+ if (!route) {
117
+ return segments;
118
+ }
119
+ const contentWidth = Math.max(1, terminalWidth - 2);
120
+ const currentWidth = Math.max(1, contentWidth
121
+ - statusSegmentsWidth([route], false)
122
+ - displayWidth(statusSegmentSeparator(false)));
123
+ const fittedCurrent = fitCurrentStatusSegment(current, currentWidth, false);
124
+ if (!fittedCurrent) {
125
+ return segments;
126
+ }
127
+ const fitted = segments.map((segment, index) => (index === currentIndex ? fittedCurrent : segment));
128
+ return statusSegmentsWidth(fitted, false) <= contentWidth ? fitted : segments;
129
+ }
40
130
  function isIdleStatus(segments) {
41
131
  return segments.length === 1 && segments[0]?.tone === "idle" && segments[0]?.value === "idle";
42
132
  }
43
133
  function StatusSegment({ segment, compact, isLast }) {
44
134
  const display = statusSegmentDisplay(segment, compact);
45
- return (_jsxs(Box, { flexShrink: 0, children: [display.label ? (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: STATUS_BAR_BACKGROUND, color: colorForTone(segment.tone), dimColor: segment.tone === undefined || segment.tone === "idle", children: display.label }), _jsx(Text, { backgroundColor: STATUS_BAR_BACKGROUND, dimColor: true, children: display.separator })] })) : null, _jsx(Text, { backgroundColor: STATUS_BAR_BACKGROUND, color: valueColorForTone(segment.tone), bold: segment.tone !== undefined && segment.tone !== "idle", wrap: "truncate-end", children: display.value }), !isLast ? _jsx(Text, { backgroundColor: STATUS_BAR_BACKGROUND, dimColor: true, children: statusSegmentSeparator(compact) }) : null] }));
135
+ return (_jsxs(Box, { flexShrink: 0, children: [display.label ? (_jsxs(_Fragment, { children: [_jsx(Text, { ...statusSegmentLabelTheme(segment.tone), children: display.label }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: display.separator })] })) : null, _jsx(Text, { ...statusSegmentValueTheme(segment.tone), wrap: "truncate-end", children: display.value }), !isLast ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: statusSegmentSeparator(compact) }) : null] }));
46
136
  }
47
137
  function fitStatusSegments(segments, terminalWidth, compact) {
48
138
  const contentWidth = Math.max(1, terminalWidth - 2);
49
- if (statusSegmentsWidth(segments, compact) <= contentWidth) {
50
- return segments;
139
+ const semanticSegments = segments.map((segment) => fitAtomicStatusSegment(segment, contentWidth, compact));
140
+ if (statusSegmentsWidth(semanticSegments, compact) <= contentWidth) {
141
+ return semanticSegments;
51
142
  }
52
- const displays = segments.map((segment) => statusSegmentDisplay(segment, compact));
53
- const totalWidth = statusSegmentsDisplayWidth(displays, compact);
54
- const currentIndex = segments.map((segment) => segment.label.toLowerCase()).lastIndexOf("current");
143
+ const activeMainPair = fitActiveMainWithCompletedRoute(semanticSegments, contentWidth, compact);
144
+ if (activeMainPair) {
145
+ return activeMainPair;
146
+ }
147
+ const currentIndex = semanticSegments.map((segment) => segment.label.toLowerCase()).lastIndexOf("current");
55
148
  if (currentIndex >= 0) {
56
- const currentDisplay = displays[currentIndex];
57
- if (currentDisplay) {
58
- const overflow = totalWidth - contentWidth;
59
- const nextValueWidth = Math.max(1, displayWidth(currentDisplay.value) - overflow);
60
- const fitted = segments.map((segment, index) => index === currentIndex
61
- ? { ...segment, value: compactStatusTextEnd(segment.value, nextValueWidth) }
62
- : segment);
63
- if (statusSegmentsWidth(fitted, compact) <= contentWidth) {
64
- return fitted;
149
+ const current = semanticSegments[currentIndex];
150
+ if (current) {
151
+ const others = semanticSegments.filter((_, index) => index !== currentIndex);
152
+ const besideWidth = contentWidth
153
+ - statusSegmentsWidth(others, compact)
154
+ - (others.length > 0 ? displayWidth(statusSegmentSeparator(compact)) : 0);
155
+ const fittedBeside = fitCurrentStatusSegment(current, besideWidth, compact, true);
156
+ if (fittedBeside) {
157
+ const fitted = semanticSegments.map((segment, index) => (index === currentIndex ? fittedBeside : segment));
158
+ if (statusSegmentsWidth(fitted, compact) <= contentWidth) {
159
+ return fitted;
160
+ }
65
161
  }
162
+ const fittedCurrent = fitCurrentStatusSegment(current, contentWidth, compact);
163
+ const fitted = fittedCurrent
164
+ ? semanticSegments.map((segment, index) => index === currentIndex ? fittedCurrent : segment)
165
+ : semanticSegments.filter((_, index) => index !== currentIndex);
66
166
  return selectStatusSegmentsThatFit(fitted, contentWidth, compact);
67
167
  }
68
168
  }
69
- return selectStatusSegmentsThatFit(segments, contentWidth, compact);
169
+ return selectStatusSegmentsThatFit(semanticSegments, contentWidth, compact);
170
+ }
171
+ function fitActiveMainWithCompletedRoute(segments, contentWidth, compact) {
172
+ if (segments.length !== 2) {
173
+ return null;
174
+ }
175
+ const currentIndex = segments.findIndex((segment) => segment.label.toLowerCase() === "current");
176
+ const routeIndex = segments.findIndex((segment) => segment.label.toLowerCase() === "route");
177
+ const current = segments[currentIndex];
178
+ const route = segments[routeIndex];
179
+ if (currentIndex < 0
180
+ || routeIndex < 0
181
+ || !current
182
+ || !route
183
+ || (current.tone !== "run" && current.tone !== "wait" && current.tone !== "fail")
184
+ || !/^(?:simple|complex)(?:\s+·\s+|$)/i.test(route.value)
185
+ || workerIdentityStatus(current.value)?.role !== "main") {
186
+ return null;
187
+ }
188
+ for (const value of currentStatusValueCandidates(current.value)) {
189
+ const fittedCurrent = { ...current, value };
190
+ if (!currentStatusDisplay(value, compact).value) {
191
+ continue;
192
+ }
193
+ const remaining = contentWidth
194
+ - statusSegmentsWidth([fittedCurrent], compact)
195
+ - displayWidth(statusSegmentSeparator(compact));
196
+ const fittedRoute = fitCompletedRouteSegment(route, remaining, compact);
197
+ if (!fittedRoute) {
198
+ continue;
199
+ }
200
+ const pair = segments.map((segment, index) => (index === currentIndex ? fittedCurrent : fittedRoute));
201
+ if (statusSegmentsWidth(pair, compact) <= contentWidth) {
202
+ return pair;
203
+ }
204
+ }
205
+ const fittedCurrent = fitCurrentStatusSegment(current, contentWidth, compact);
206
+ return fittedCurrent ? [fittedCurrent] : null;
207
+ }
208
+ function fitCompletedRouteSegment(segment, maxWidth, compact) {
209
+ if (maxWidth <= 0) {
210
+ return null;
211
+ }
212
+ const display = statusSegmentDisplay(segment, compact);
213
+ const valueWidth = maxWidth - displayWidth(`${display.label}${display.separator}`);
214
+ if (valueWidth < 1) {
215
+ return null;
216
+ }
217
+ const value = compactCompletedRouteStatusValue(segment.value, valueWidth);
218
+ return value ? { ...segment, value } : null;
219
+ }
220
+ function compactCompletedRouteStatusValue(value, maxWidth) {
221
+ const parts = value.split(/\s+·\s+/).map((part) => part.trim()).filter(Boolean);
222
+ const mode = /^(?:simple|complex)$/i.test(parts[0] ?? "") ? parts[0] : null;
223
+ const duration = parts.find((part) => /^\d+(?:\.\d+)?(?:ms|s|m)(?:\s+total)?$/i.test(part));
224
+ const compactFailure = compactRouteStatusValue(value, true);
225
+ const exceptional = /(?:^|\s·\s)(?:fallback|forced)(?:\s·\s|$)/i.test(value);
226
+ const candidates = [
227
+ value,
228
+ ...(mode && exceptional && compactFailure !== value
229
+ ? [`${mode} · ${compactFailure}${duration ? ` · ${duration}` : ""}`]
230
+ : []),
231
+ ...(mode && duration ? [`${mode} · ${duration}`] : []),
232
+ ...(mode ? [mode] : []),
233
+ ...(compactFailure !== value ? [compactFailure] : []),
234
+ ...(duration ? [duration] : [])
235
+ ];
236
+ return candidates.find((candidate) => displayWidth(candidate) <= maxWidth) ?? null;
237
+ }
238
+ function fitCurrentStatusSegment(segment, maxWidth, compact, requireDetail = false) {
239
+ if (maxWidth <= 0) {
240
+ return null;
241
+ }
242
+ for (const value of currentStatusValueCandidates(segment.value)) {
243
+ const candidate = { ...segment, value };
244
+ if (requireDetail && !currentStatusDisplay(value, compact).value) {
245
+ continue;
246
+ }
247
+ if (statusSegmentsWidth([candidate], compact) <= maxWidth) {
248
+ return candidate;
249
+ }
250
+ }
251
+ return null;
252
+ }
253
+ function currentStatusValueCandidates(text) {
254
+ const identity = workerIdentityStatus(text);
255
+ const roleMatch = identity ? null : text.match(/^(main|judge|actor|critic)(?:\s+(.+))?$/i);
256
+ const role = identity?.role ?? roleMatch?.[1]?.toLowerCase();
257
+ if (!role) {
258
+ return [text];
259
+ }
260
+ const fullIdentity = identity ? `${identity.role}/${identity.engine}` : role;
261
+ const detail = identity
262
+ ? text.slice(fullIdentity.length).trim()
263
+ : roleMatch?.[2]?.trim() ?? "";
264
+ const compactDetail = compactCurrentStatusDetail(detail);
265
+ const compactState = compactDetail.split(/\s+/, 1)[0] ?? "";
266
+ return Array.from(new Set([
267
+ text,
268
+ ...(compactDetail ? [`${fullIdentity} ${compactDetail}`] : []),
269
+ ...(compactDetail ? [`${role} ${compactDetail}`] : []),
270
+ ...(compactState ? [`${fullIdentity} ${compactState}`] : []),
271
+ ...(compactState ? [`${role} ${compactState}`] : []),
272
+ fullIdentity,
273
+ role,
274
+ compactRoleStatusLabel(role)
275
+ ].filter(Boolean)));
276
+ }
277
+ function compactCurrentStatusDetail(detail) {
278
+ const progress = detail.match(/^(waiting output|responding)\s+·\s+(\d+(?:\.\d+)?s)\s*\/\s*(\d+(?:\.\d+)?(?:ms|s|m))\s+(?:first|idle)$/i);
279
+ if (progress) {
280
+ const state = progress[1]?.toLowerCase() === "responding" ? "reply" : "wait";
281
+ return `${state} ${progress[2]}/${progress[3]}`;
282
+ }
283
+ const state = detail.match(/^(starting|running|run|done|failed|fail|waiting|wait|queued|stopping|stop|idle|cancelled|canceled)\b/i)?.[1]?.toLowerCase();
284
+ if (state === "starting") {
285
+ return "start";
286
+ }
287
+ if (state === "running") {
288
+ return "run";
289
+ }
290
+ if (state === "failed") {
291
+ return "fail";
292
+ }
293
+ if (state === "waiting" || state === "queued") {
294
+ return "wait";
295
+ }
296
+ if (state === "stopping" || state === "cancelled" || state === "canceled") {
297
+ return "stop";
298
+ }
299
+ return state ?? "";
300
+ }
301
+ function fitAtomicStatusSegment(segment, contentWidth, compact) {
302
+ if (segment.label.toLowerCase() !== "route"
303
+ || (segment.tone !== "wait" && segment.tone !== "fail")) {
304
+ return segment;
305
+ }
306
+ const display = statusSegmentDisplay(segment, compact);
307
+ const valueWidth = Math.max(1, contentWidth - displayWidth(`${display.label}${display.separator}`));
308
+ if (displayWidth(display.value) <= valueWidth) {
309
+ return segment;
310
+ }
311
+ return {
312
+ ...segment,
313
+ value: compactRouteStatusValueToWidth(segment.value, valueWidth)
314
+ };
70
315
  }
71
316
  function statusSegmentsDisplayWidth(displays, compact) {
72
317
  const segmentWidths = displays.map((display) => displayWidth(`${display.label}${display.separator}${display.value}`));
@@ -93,32 +338,51 @@ function selectStatusSegmentsThatFit(segments, contentWidth, compact) {
93
338
  }
94
339
  function statusSegmentKeepPriority(segment) {
95
340
  const label = segment.label.toLowerCase();
341
+ if (label === "route" && segment.tone === "fail") {
342
+ return -1;
343
+ }
96
344
  if (segment.tone === "fail" || label === "fail") {
97
345
  return 0;
98
346
  }
99
- if (segment.tone === "run" || label === "run") {
347
+ if (segment.tone === "stop" || label === "stop") {
100
348
  return 1;
101
349
  }
102
- if (segment.tone === "wait" || label === "wait") {
350
+ if (segment.tone === "run" || label === "run") {
103
351
  return 2;
104
352
  }
105
- if (label === "workers") {
353
+ if (segment.tone === "wait" || label === "wait") {
106
354
  return 3;
107
355
  }
108
- if (segment.tone === "done" || label === "done") {
356
+ if (label === "workers") {
109
357
  return 4;
110
358
  }
111
- if (label === "current") {
359
+ if (segment.tone === "done" || label === "done") {
112
360
  return 5;
113
361
  }
114
- return 6;
362
+ if (label === "current") {
363
+ return 6;
364
+ }
365
+ if (label === "route") {
366
+ return 8;
367
+ }
368
+ return 7;
115
369
  }
116
370
  function compactSingleStatusSegment(segment, contentWidth, compact) {
117
371
  const display = statusSegmentDisplay(segment, compact);
118
372
  const labelWidth = displayWidth(`${display.label}${display.separator}`);
373
+ const valueWidth = Math.max(1, contentWidth - labelWidth);
374
+ if (segment.label.toLowerCase() === "route") {
375
+ return {
376
+ ...segment,
377
+ value: compactRouteStatusValueToWidth(segment.value, valueWidth)
378
+ };
379
+ }
380
+ if (segment.label.toLowerCase() === "current") {
381
+ return fitCurrentStatusSegment(segment, contentWidth, compact) ?? segment;
382
+ }
119
383
  return {
120
384
  ...segment,
121
- value: compactStatusTextEnd(display.value, Math.max(1, contentWidth - labelWidth))
385
+ value: compactStatusTextEnd(display.value, valueWidth)
122
386
  };
123
387
  }
124
388
  function statusSegmentSeparator(compact) {
@@ -127,7 +391,20 @@ function statusSegmentSeparator(compact) {
127
391
  function compactStatusTextEnd(text, maxLength) {
128
392
  return compactEndByDisplayWidth(text, maxLength);
129
393
  }
394
+ function workerIdentityStatus(text) {
395
+ const match = text.trim().match(/^(main|judge|actor|critic)\/([^\s]+)(?:\s+.*)?$/i);
396
+ if (!match) {
397
+ return null;
398
+ }
399
+ return {
400
+ role: (match[1] ?? "").toLowerCase(),
401
+ engine: match[2] ?? ""
402
+ };
403
+ }
130
404
  function statusSegmentDisplay(segment, compact) {
405
+ if (segment.hideLabel) {
406
+ return { label: "", separator: "", value: segment.value };
407
+ }
131
408
  const label = segment.label.toLowerCase();
132
409
  if (label === "task") {
133
410
  return { label: "", separator: "", value: segment.value };
@@ -135,13 +412,33 @@ function statusSegmentDisplay(segment, compact) {
135
412
  if (isRoleStatusLabel(label)) {
136
413
  return {
137
414
  label: compact ? compactRoleStatusLabel(label) : displayRoleStatusLabel(label),
138
- separator: compact ? ":" : " ",
415
+ separator: compact ? ":" : " · ",
139
416
  value: segment.value
140
417
  };
141
418
  }
419
+ if (label === "route") {
420
+ return {
421
+ label: compact ? "r" : "route",
422
+ separator: compact ? ":" : " ",
423
+ value: compactRouteStatusValue(segment.value, compact)
424
+ };
425
+ }
426
+ if (label === "wave") {
427
+ return {
428
+ label: "wave",
429
+ separator: " ",
430
+ value: compact ? compactWaveStatusValue(segment.value) : segment.value
431
+ };
432
+ }
142
433
  if (!compact) {
143
434
  if (label === "current") {
144
- return { label: "@", separator: " ", value: segment.value };
435
+ return currentStatusDisplay(segment.value, false);
436
+ }
437
+ if (label === "workers") {
438
+ return { label: "workers", separator: " ", value: segment.value };
439
+ }
440
+ if (segment.tone && segment.tone !== "idle" && label === segment.tone) {
441
+ return { label: segment.tone, separator: " ", value: segment.value };
145
442
  }
146
443
  if (segment.tone && segment.tone !== "idle") {
147
444
  return { label: segment.tone, separator: " ", value: segment.value };
@@ -149,16 +446,177 @@ function statusSegmentDisplay(segment, compact) {
149
446
  return { label, separator: " ", value: segment.value };
150
447
  }
151
448
  if (label === "workers") {
152
- return { label: "w", separator: "", value: segment.value };
449
+ return { label: "wk", separator: "", value: segment.value };
153
450
  }
154
451
  if (label === "current") {
155
- return { label: "@", separator: " ", value: segment.value };
452
+ return currentStatusDisplay(segment.value, true);
156
453
  }
157
454
  if (segment.tone && segment.tone !== "idle") {
158
455
  return { label: compactToneLabel(segment.tone), separator: "", value: segment.value };
159
456
  }
160
457
  return { label, separator: " ", value: segment.value };
161
458
  }
459
+ function currentStatusDisplay(text, compact) {
460
+ const identity = workerIdentityStatus(text);
461
+ if (identity) {
462
+ const fullIdentity = `${identity.role}/${identity.engine}`;
463
+ const detail = text.slice(fullIdentity.length).trim();
464
+ return {
465
+ label: fullIdentity,
466
+ separator: detail ? (compact ? ":" : " · ") : "",
467
+ value: detail
468
+ };
469
+ }
470
+ const role = text.match(/^(main|judge|actor|critic)(?:\s+(.+))?$/i);
471
+ if (role) {
472
+ const detail = role[2]?.trim() ?? "";
473
+ return {
474
+ label: displayRoleStatusLabel(role[1] ?? ""),
475
+ separator: detail ? (compact ? ":" : " · ") : "",
476
+ value: detail
477
+ };
478
+ }
479
+ return { label: text, separator: "", value: "" };
480
+ }
481
+ function compactRouteStatusValue(value, compact) {
482
+ if (compact && /(?:^|\s·\s)fallback(?:\s·\s|$)/i.test(value)) {
483
+ const cause = value
484
+ .split(/\s+·\s+/)
485
+ .find((part) => /^(?:(?:idle|total) timeout(?: waiting output| after (?:stdout|stderr|output))?|first output timeout|timeout(?: via proxy| waiting output| after (?:stdout|stderr|output))?|proxy|auth|rate limit|network|unavailable|invalid output|exit)$/i.test(part));
486
+ if (cause) {
487
+ return cause.toLowerCase() === "invalid output" ? "invalid" : cause.toLowerCase();
488
+ }
489
+ return "fallback";
490
+ }
491
+ return value;
492
+ }
493
+ function compactRouteStatusValueToWidth(value, maxWidth) {
494
+ const compact = compactRouteStatusValue(value, true);
495
+ const parts = value.split(/\s+·\s+/).map((part) => part.trim()).filter(Boolean);
496
+ const duration = parts.find((part) => /^\d+(?:\.\d+)?(?:ms|s|m)(?:\s+(?:max|total|first|idle))?$/i.test(part))
497
+ ?.replace(/\s+(?:max|total|first|idle)$/i, "");
498
+ const progress = parts
499
+ .map((part) => part.match(/^(\d+(?:\.\d+)?s)\s*\/\s*(\d+(?:\.\d+)?(?:ms|s|m))(?:\s+(first|total))?$/i))
500
+ .find((match) => match !== null);
501
+ const elapsed = progress?.[1];
502
+ const compactProgress = progress
503
+ ? `${elapsed?.replace(/s$/i, "")}/${progress[2]}`
504
+ : undefined;
505
+ const progressCandidates = compactProgress
506
+ ? [compactProgress, elapsed].filter((item) => Boolean(item))
507
+ : duration ? [duration] : [];
508
+ const first = parts[0]?.toLowerCase() ?? "";
509
+ const retry = first.match(/^retry\s+(\d+\/\d+)$/);
510
+ let candidates;
511
+ if (retry) {
512
+ candidates = [compact, `retry ${retry[1]}`, retry[1] ?? "retry", "retry"];
513
+ }
514
+ else if (first === "checking") {
515
+ candidates = [compact, progressCandidates[0] ? `check ${progressCandidates[0]}` : "check", ...progressCandidates, "wait"];
516
+ }
517
+ else if (first === "follow-up") {
518
+ candidates = [compact, progressCandidates[0] ? `follow ${progressCandidates[0]}` : "follow", ...progressCandidates, "wait"];
519
+ }
520
+ else if (routeProgressStatusAlias(first)) {
521
+ const alias = routeProgressStatusAlias(first);
522
+ const firstOutputProgress = progress?.[3]?.toLowerCase() === "first";
523
+ candidates = [
524
+ compact,
525
+ ...(compactProgress ? [`${alias} ${compactProgress}`] : []),
526
+ ...(firstOutputProgress && compactProgress ? [compactProgress] : []),
527
+ ...(elapsed ? [`${alias} ${elapsed}`] : []),
528
+ alias,
529
+ ...progressCandidates,
530
+ "wait"
531
+ ];
532
+ }
533
+ else if (/(?:^|\s·\s)fallback(?:\s·\s|$)/i.test(value)) {
534
+ candidates = [compact, ...compactRouteFailureAliases(compact), "wait"];
535
+ }
536
+ else {
537
+ candidates = [compact, first, "route"];
538
+ }
539
+ return candidates.find((candidate) => displayWidth(candidate) <= maxWidth)
540
+ ?? candidates.at(-1)
541
+ ?? "route";
542
+ }
543
+ function compactRouteFailureAliases(value) {
544
+ if (value === "timeout via proxy") {
545
+ return ["proxy", "p:to"];
546
+ }
547
+ if (value === "proxy") {
548
+ return ["pxy"];
549
+ }
550
+ if (value === "rate limit") {
551
+ return ["rate"];
552
+ }
553
+ if (value === "timeout") {
554
+ return ["time"];
555
+ }
556
+ if (value === "timeout waiting output") {
557
+ return ["wait:to", "w:to", "time"];
558
+ }
559
+ if (value === "timeout after stderr") {
560
+ return ["err:to", "e:to", "time"];
561
+ }
562
+ if (value === "timeout after stdout") {
563
+ return ["out:to", "o:to", "time"];
564
+ }
565
+ if (value === "timeout after output") {
566
+ return ["out:to", "o:to", "time"];
567
+ }
568
+ if (value === "first output timeout") {
569
+ return ["first:to", "f:to", "time"];
570
+ }
571
+ if (value.startsWith("idle timeout")) {
572
+ return ["idle:to", "i:to", "time"];
573
+ }
574
+ if (value.startsWith("total timeout")) {
575
+ return ["total:to", "t:to", "time"];
576
+ }
577
+ if (value === "network") {
578
+ return ["net"];
579
+ }
580
+ if (value === "unavailable") {
581
+ return ["down"];
582
+ }
583
+ if (value === "invalid") {
584
+ return ["bad"];
585
+ }
586
+ if (value === "fallback") {
587
+ return ["fall"];
588
+ }
589
+ return [value];
590
+ }
591
+ function routeProgressStatusAlias(value) {
592
+ if (value === "starting") {
593
+ return "start";
594
+ }
595
+ if (value === "waiting output") {
596
+ return "wait";
597
+ }
598
+ if (value === "diagnostics") {
599
+ return "diag";
600
+ }
601
+ if (value === "receiving") {
602
+ return "recv";
603
+ }
604
+ if (value === "parsing") {
605
+ return "parse";
606
+ }
607
+ if (value === "stopping") {
608
+ return "stop";
609
+ }
610
+ return null;
611
+ }
612
+ function compactWaveStatusValue(value) {
613
+ return value
614
+ .replace(/\s+·\s+actor\s+/i, " a")
615
+ .replace(/\s+·\s+critic\s+/i, " c")
616
+ .replace(/\s+·\s+revision\s+/i, " r")
617
+ .replace(/\s+·\s+integration\s+/i, " i")
618
+ .replace(/\s+·\s+verification\s+/i, " v");
619
+ }
162
620
  function compactToneLabel(tone) {
163
621
  if (tone === "run") {
164
622
  return "r";
@@ -169,18 +627,18 @@ function compactToneLabel(tone) {
169
627
  if (tone === "fail") {
170
628
  return "f";
171
629
  }
630
+ if (tone === "stop") {
631
+ return "s";
632
+ }
172
633
  return "w";
173
634
  }
174
635
  function isRoleStatusLabel(label) {
175
636
  return label === "main" || label === "judge" || label === "actor" || label === "critic";
176
637
  }
177
638
  function displayRoleStatusLabel(label) {
178
- return label === "main" ? "chat" : label;
639
+ return label;
179
640
  }
180
641
  function compactRoleStatusLabel(label) {
181
- if (label === "main") {
182
- return "chat";
183
- }
184
642
  return label.slice(0, 1);
185
643
  }
186
644
  function parseStatusText(text, options = {}) {
@@ -201,11 +659,30 @@ function parseStatusText(text, options = {}) {
201
659
  segments.push({ label: "TASK", value: first, tone: first === "idle" ? "idle" : undefined });
202
660
  }
203
661
  for (const part of statusParts) {
662
+ const wave = parseWaveProgress(part);
663
+ if (wave) {
664
+ segments.push(wave);
665
+ continue;
666
+ }
204
667
  const workerMatch = part.match(/^workers\s+(\d+)$/i);
205
668
  if (workerMatch) {
206
669
  segments.push({ label: "WORKERS", value: workerMatch[1] ?? "0" });
207
670
  continue;
208
671
  }
672
+ const routeMatch = part.match(/^route\s+(.+)$/i);
673
+ if (routeMatch) {
674
+ const value = routeMatch[1]?.trim() || "unknown";
675
+ segments.push({
676
+ label: "ROUTE",
677
+ value,
678
+ tone: /(?:^|\s·\s)fallback(?:\s·\s|$)/i.test(value)
679
+ ? "fail"
680
+ : /^(?:checking|follow-up|starting|retry|waiting output|diagnostics|receiving|parsing|stopping)\b/i.test(value)
681
+ ? "wait"
682
+ : undefined
683
+ });
684
+ continue;
685
+ }
209
686
  const counts = parseStateCounts(part);
210
687
  if (counts.length > 0) {
211
688
  segments.push(...counts);
@@ -216,10 +693,26 @@ function parseStatusText(text, options = {}) {
216
693
  return segments.length > 0 ? segments : [{ label: "STATUS", value: "idle", tone: "idle" }];
217
694
  }
218
695
  function isStatusPart(part) {
219
- return /^workers\s+\d+$/i.test(part) || parseStateCounts(part).length > 0;
696
+ return parseWaveProgress(part) !== null
697
+ || /^workers\s+\d+$/i.test(part)
698
+ || /^route\s+\S+/i.test(part)
699
+ || parseStateCounts(part).length > 0
700
+ || parseRoleStatus(part) !== null
701
+ || workerIdentityStatus(part) !== null;
702
+ }
703
+ function parseWaveProgress(part) {
704
+ const match = part.match(/^wave\s+(\d+\/\d+)\s+·\s+(actor|critic|revision|integration|verification)\s+(\d+\/\d+)$/i);
705
+ if (!match) {
706
+ return null;
707
+ }
708
+ return {
709
+ label: "WAVE",
710
+ value: `${match[1]} · ${(match[2] ?? "actor").toLowerCase()} ${match[3]}`,
711
+ tone: "run"
712
+ };
220
713
  }
221
714
  function parseStateCounts(part) {
222
- const matches = Array.from(part.matchAll(/\b(run|done|fail|wait|idle)\s+(\d+)\b/gi));
715
+ const matches = Array.from(part.matchAll(/\b(run|done|fail|wait|stop|idle)\s+(\d+)\b/gi));
223
716
  if (matches.length === 0) {
224
717
  return [];
225
718
  }
@@ -233,12 +726,23 @@ function parseStateCounts(part) {
233
726
  });
234
727
  }
235
728
  function parseCurrentStatus(part) {
729
+ const mainEngineStatus = parseMainEngineStatus(part);
730
+ if (mainEngineStatus) {
731
+ return mainEngineStatus;
732
+ }
236
733
  const roleStatus = parseRoleStatus(part);
237
734
  if (roleStatus) {
238
735
  return roleStatus;
239
736
  }
240
- const toneMatch = part.match(/\b(run|done|fail|wait|idle)\b/i);
737
+ const toneMatch = part.match(/\b(run|done|fail|wait|stop|idle)\b/i);
241
738
  const tone = toneMatch ? normalizeTone(toneMatch[1] ?? "idle") : undefined;
739
+ if (workerIdentityStatus(part)) {
740
+ return {
741
+ label: "CURRENT",
742
+ value: part,
743
+ tone
744
+ };
745
+ }
242
746
  const value = toneMatch ? part.replace(toneMatch[0], "").trim() || part : part;
243
747
  return {
244
748
  label: "CURRENT",
@@ -246,8 +750,41 @@ function parseCurrentStatus(part) {
246
750
  tone
247
751
  };
248
752
  }
753
+ function parseMainEngineStatus(part) {
754
+ const match = part.match(/^main\/([^\s]+)\s+([^\s]+)\b/i);
755
+ if (!match) {
756
+ return null;
757
+ }
758
+ const status = (match[2] ?? "idle").toLowerCase();
759
+ return {
760
+ label: "CURRENT",
761
+ value: part,
762
+ tone: runtimeStatusTone(status)
763
+ };
764
+ }
765
+ function runtimeStatusTone(status) {
766
+ if (status === "run" || status === "running" || status === "starting" || status === "responding") {
767
+ return "run";
768
+ }
769
+ if (status === "done") {
770
+ return "done";
771
+ }
772
+ if (status === "fail" || status === "failed" || status === "error") {
773
+ return "fail";
774
+ }
775
+ if (status === "wait" || status === "waiting" || status === "queued" || status === "stopping") {
776
+ return "wait";
777
+ }
778
+ if (status === "stop" || status === "cancelled" || status === "canceled") {
779
+ return "stop";
780
+ }
781
+ if (status === "idle") {
782
+ return "idle";
783
+ }
784
+ return undefined;
785
+ }
249
786
  function parseRoleStatus(part) {
250
- const match = part.match(/^(main|judge|actor|critic)\s+(run|done|fail|wait|idle)\b/i);
787
+ const match = part.match(/^(main|judge|actor|critic)\s+(run|done|fail|wait|stop|idle)\b/i);
251
788
  if (!match) {
252
789
  return null;
253
790
  }
@@ -260,29 +797,45 @@ function parseRoleStatus(part) {
260
797
  }
261
798
  function normalizeTone(value) {
262
799
  const normalized = value.toLowerCase();
263
- if (normalized === "run" || normalized === "done" || normalized === "fail" || normalized === "wait") {
800
+ if (normalized === "run" || normalized === "done" || normalized === "fail" || normalized === "wait" || normalized === "stop") {
264
801
  return normalized;
265
802
  }
266
803
  return "idle";
267
804
  }
805
+ export function statusSegmentLabelTheme(_tone) {
806
+ return {
807
+ backgroundColor: TUI_THEME.rail,
808
+ color: TUI_THEME.muted
809
+ };
810
+ }
811
+ export function statusSegmentValueTheme(tone) {
812
+ return {
813
+ backgroundColor: TUI_THEME.rail,
814
+ color: valueColorForTone(tone),
815
+ ...(tone === "run" || tone === "fail" ? { bold: true } : {})
816
+ };
817
+ }
268
818
  function colorForTone(tone) {
269
819
  if (tone === "run") {
270
- return "cyan";
820
+ return TUI_THEME.accent;
271
821
  }
272
822
  if (tone === "done") {
273
- return "green";
823
+ return TUI_THEME.success;
274
824
  }
275
825
  if (tone === "fail") {
276
- return "red";
826
+ return TUI_THEME.danger;
277
827
  }
278
828
  if (tone === "wait") {
279
- return "yellow";
829
+ return TUI_THEME.warning;
830
+ }
831
+ if (tone === "stop") {
832
+ return TUI_THEME.warning;
280
833
  }
281
- return "gray";
834
+ return TUI_THEME.muted;
282
835
  }
283
836
  function valueColorForTone(tone) {
284
- if (tone === "fail") {
285
- return "red";
837
+ if (tone === "run" || tone === "done" || tone === "fail" || tone === "wait" || tone === "stop") {
838
+ return colorForTone(tone);
286
839
  }
287
- return undefined;
840
+ return TUI_THEME.text;
288
841
  }