parallel-codex-tui 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.parallel-codex/config.example.toml +136 -3
  2. package/README.md +299 -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-preflight.js +18 -0
  7. package/dist/cli-startup-recovery.js +82 -0
  8. package/dist/cli-workspace-picker.js +330 -0
  9. package/dist/cli-workspace-transition.js +33 -0
  10. package/dist/cli-workspace.js +7 -71
  11. package/dist/cli.js +234 -24
  12. package/dist/core/collaboration-timeline.js +268 -0
  13. package/dist/core/config-errors.js +14 -0
  14. package/dist/core/config.js +297 -109
  15. package/dist/core/file-store.js +119 -6
  16. package/dist/core/lease-finalization.js +22 -0
  17. package/dist/core/paths.js +7 -0
  18. package/dist/core/process-identity.js +48 -0
  19. package/dist/core/process-mutation-turn.js +128 -0
  20. package/dist/core/process-ownership.js +276 -0
  21. package/dist/core/process-tree.js +90 -0
  22. package/dist/core/router-audit.js +155 -0
  23. package/dist/core/router-redaction.js +31 -0
  24. package/dist/core/router.js +462 -35
  25. package/dist/core/session-index.js +412 -88
  26. package/dist/core/session-manager.js +1110 -40
  27. package/dist/core/task-session-details.js +175 -0
  28. package/dist/core/task-state-machine.js +18 -0
  29. package/dist/core/workspace-commit-recovery.js +118 -0
  30. package/dist/core/workspace.js +19 -11
  31. package/dist/doctor.js +373 -34
  32. package/dist/domain/schemas.js +142 -7
  33. package/dist/orchestrator/collaboration-channel.js +289 -6
  34. package/dist/orchestrator/feature-plan.js +70 -0
  35. package/dist/orchestrator/final-acceptance.js +86 -0
  36. package/dist/orchestrator/judge-artifacts.js +236 -0
  37. package/dist/orchestrator/orchestrator.js +2086 -203
  38. package/dist/orchestrator/prompts.js +168 -5
  39. package/dist/orchestrator/supervisor-summary.js +56 -2
  40. package/dist/orchestrator/workspace-sandbox.js +927 -0
  41. package/dist/tui/App.js +3187 -161
  42. package/dist/tui/AppShell.js +196 -25
  43. package/dist/tui/CollaborationTimelineView.js +327 -0
  44. package/dist/tui/FeatureBoardView.js +232 -0
  45. package/dist/tui/InputBar.js +581 -57
  46. package/dist/tui/RouterDiagnosticsView.js +469 -0
  47. package/dist/tui/StatusBar.js +610 -57
  48. package/dist/tui/StatusDetailView.js +164 -0
  49. package/dist/tui/TaskSessionDetailView.js +222 -0
  50. package/dist/tui/TaskSessionsView.js +210 -0
  51. package/dist/tui/TerminalOutput.js +53 -9
  52. package/dist/tui/WorkerOutputView.js +1404 -161
  53. package/dist/tui/WorkerOverviewView.js +269 -0
  54. package/dist/tui/chat-history.js +25 -0
  55. package/dist/tui/chat-input.js +67 -19
  56. package/dist/tui/chat-paste.js +76 -0
  57. package/dist/tui/display-width.js +41 -3
  58. package/dist/tui/incremental-text-file.js +101 -0
  59. package/dist/tui/keyboard.js +49 -0
  60. package/dist/tui/markdown-text.js +14 -0
  61. package/dist/tui/raw-input-decoder.js +3 -0
  62. package/dist/tui/scrolling.js +2 -1
  63. package/dist/tui/status-line.js +360 -11
  64. package/dist/tui/task-memory.js +13 -1
  65. package/dist/tui/task-result.js +105 -0
  66. package/dist/tui/terminal-screen.js +13 -1
  67. package/dist/tui/theme-contrast.js +144 -0
  68. package/dist/tui/theme-preview.js +109 -0
  69. package/dist/tui/theme.js +158 -0
  70. package/dist/version.js +1 -1
  71. package/dist/workers/capabilities.js +213 -0
  72. package/dist/workers/live-probe.js +177 -0
  73. package/dist/workers/mock-adapter.js +73 -8
  74. package/dist/workers/native-attach.js +106 -16
  75. package/dist/workers/process-adapter.js +572 -77
  76. package/dist/workers/provider.js +26 -0
  77. package/dist/workers/registry.js +12 -20
  78. package/package.json +11 -2
@@ -0,0 +1,469 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo } from "react";
3
+ import { basename, resolve } from "node:path";
4
+ import { Box, Text } from "ink";
5
+ import { classifyRouterFailure, diagnoseRouterFailure } from "../core/router-audit.js";
6
+ import { routerCommandLabel, routerProxyContext } from "../core/router.js";
7
+ import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
8
+ import { formatRouteStatus } from "./status-line.js";
9
+ import { TUI_THEME } from "./theme.js";
10
+ export function routerDiagnosticsPolicy(router, env = process.env) {
11
+ const proxy = routerProxyContext(router.codex.env, env);
12
+ return {
13
+ mode: router.defaultMode,
14
+ command: routerCommandLabel(router.codex.command),
15
+ timeoutMs: router.codex.timeoutMs,
16
+ firstOutputTimeoutMs: router.codex.firstOutputTimeoutMs,
17
+ idleTimeoutMs: router.codex.idleTimeoutMs,
18
+ maxOutputBytes: router.codex.maxOutputBytes,
19
+ maxAttempts: router.codex.maxAttempts,
20
+ retryDelayMs: router.codex.retryDelayMs,
21
+ followUpTimeoutMs: router.codex.followUpTimeoutMs,
22
+ fallback: router.codex.fallback,
23
+ proxyConfigured: proxy.configured,
24
+ proxySource: proxy.configured ? proxy.source : null,
25
+ proxyVariable: proxy.configured ? proxy.variable : null,
26
+ proxyEndpoint: proxy.configured ? proxy.endpoint : null
27
+ };
28
+ }
29
+ export function RouterDiagnosticsView({ records, policy, loading = false, error = null, currentWorkspace = "", scope = "all", scrollOffset = 0, height = 20, terminalWidth = process.stdout.columns || 120, onViewportChange }) {
30
+ const lines = useMemo(() => routerDiagnosticsDisplayLines(records, policy, terminalWidth, {
31
+ loading,
32
+ error,
33
+ currentWorkspace,
34
+ scope
35
+ }), [currentWorkspace, error, loading, policy, records, scope, terminalWidth]);
36
+ const viewportHeight = Math.max(1, height);
37
+ const maxOffset = Math.max(0, lines.length - viewportHeight);
38
+ const clampedOffset = Math.min(maxOffset, Math.max(0, Math.trunc(scrollOffset)));
39
+ const visible = lines.slice(clampedOffset, clampedOffset + viewportHeight);
40
+ const blankRows = Math.max(0, viewportHeight - visible.length);
41
+ const contentWidth = routerDiagnosticsContentWidth(terminalWidth);
42
+ useEffect(() => {
43
+ onViewportChange?.({ offset: clampedOffset, maxOffset });
44
+ }, [clampedOffset, maxOffset, onViewportChange]);
45
+ return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [visible.map((line, index) => (_jsx(RouterDiagnosticRow, { line: line, width: contentWidth }, `${clampedOffset + index}-${line.tone}`))), Array.from({ length: blankRows }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(contentWidth) }, `router-diagnostic-fill-${index}`)))] }));
46
+ }
47
+ export function routerDiagnosticsDisplayLines(records, policy, terminalWidth, state = {}) {
48
+ const width = routerDiagnosticsContentWidth(terminalWidth);
49
+ const scope = state.scope ?? "all";
50
+ const currentWorkspace = state.currentWorkspace ?? "";
51
+ const visibleRecords = filterRouterAuditRecords(records, currentWorkspace, scope);
52
+ const codexCount = visibleRecords.filter((record) => record.source === "codex").length;
53
+ const recoveredCount = visibleRecords.filter((record) => (record.source === "codex" && Boolean(record.router_recovered_from))).length;
54
+ const retryCount = visibleRecords.filter((record) => (record.source === "fallback" && record.router_fallback_resolution === "auto-retry")).length;
55
+ const fallbackCount = visibleRecords.filter((record) => (record.source === "fallback" && record.router_fallback_resolution !== "auto-retry")).length;
56
+ const forcedCount = visibleRecords.filter((record) => record.source === "forced").length;
57
+ const timeoutCount = visibleRecords.filter((record) => routerAuditFailureKind(record) === "timeout").length;
58
+ const workspaceCount = new Set(records.map((record) => normalizedWorkspace(record.workspace))).size;
59
+ const proxyRecordCount = visibleRecords.filter(routerAuditHasProxyContext).length;
60
+ const budget = routerDiagnosticsBudget(visibleRecords, policy);
61
+ const health = [
62
+ `health · codex ${codexCount}`,
63
+ ...(recoveredCount > 0 ? [`recovered ${recoveredCount}`] : []),
64
+ `fallback ${fallbackCount}`,
65
+ ...(retryCount > 0 ? [`retry ${retryCount}`] : []),
66
+ ...(forcedCount > 0 ? [`forced ${forcedCount}`] : []),
67
+ ...(timeoutCount > 0 ? [`timeout ${timeoutCount}`] : [])
68
+ ].join(" · ");
69
+ const logical = [
70
+ { text: "Router diagnostics", tone: "heading" },
71
+ {
72
+ text: routerDiagnosticsScopeText(scope, currentWorkspace, visibleRecords.length, records.length, workspaceCount),
73
+ tone: "text"
74
+ },
75
+ { text: health, tone: fallbackCount > 0 || retryCount > 0 ? "warning" : "success" },
76
+ { text: routerDiagnosticsLatencyText(visibleRecords), tone: "muted" },
77
+ budget,
78
+ {
79
+ text: [
80
+ `policy · ${policy.mode}`,
81
+ ...(policy.command !== "codex" ? [`runner ${policy.command}`] : []),
82
+ `total ${formatDiagnosticDuration(policy.timeoutMs)} / ${formatDiagnosticDuration(policy.followUpTimeoutMs)}`,
83
+ `first ${formatDiagnosticDuration(policy.firstOutputTimeoutMs)}`,
84
+ `idle ${formatDiagnosticDuration(policy.idleTimeoutMs)}`,
85
+ `retry ${policy.maxAttempts}x / ${formatDiagnosticDuration(policy.retryDelayMs)}`,
86
+ `fallback ${policy.fallback}`
87
+ ].join(" · "),
88
+ tone: "muted"
89
+ },
90
+ {
91
+ text: `guard · output ${formatDiagnosticBytes(policy.maxOutputBytes)} · ${routerDiagnosticsProxyPolicy(policy, proxyRecordCount)}`,
92
+ tone: policy.proxyConfigured || proxyRecordCount > 0 ? "warning" : "muted"
93
+ },
94
+ { text: "Recent routes", tone: "heading" }
95
+ ];
96
+ if (state.loading) {
97
+ logical.push({ text: "loading route audit", tone: "muted" });
98
+ }
99
+ else if (state.error) {
100
+ logical.push({ text: `error · ${safeDiagnosticText(state.error)}`, tone: "danger" });
101
+ }
102
+ else if (visibleRecords.length === 0) {
103
+ logical.push({
104
+ text: scope === "workspace" ? "no route records for current workspace" : "no route records",
105
+ tone: "muted"
106
+ });
107
+ }
108
+ else {
109
+ for (const record of [...visibleRecords].reverse()) {
110
+ const workspace = basename(record.workspace) || record.workspace;
111
+ logical.push({
112
+ text: routerAuditHeading(record, workspace, width),
113
+ tone: record.source === "fallback" ? "warning" : record.source === "codex" ? "success" : "muted"
114
+ });
115
+ logical.push({ text: `request · ${boundedDiagnosticText(record.request)}`, tone: "text" });
116
+ const evidence = routerAuditEvidence(record);
117
+ if (evidence) {
118
+ logical.push({ text: evidence, tone: "warning" });
119
+ }
120
+ const trace = routerAuditTraceLines(record);
121
+ if (record.source === "fallback") {
122
+ const diagnosis = diagnoseRouterFailure(record);
123
+ logical.push({ text: `diagnosis · ${diagnosis.summary}`, tone: "danger" });
124
+ logical.push({ text: `next · ${diagnosis.action}`, tone: "warning" });
125
+ }
126
+ for (const line of trace) {
127
+ logical.push({ text: line, tone: "muted" });
128
+ }
129
+ logical.push({ text: `reason · ${boundedDiagnosticText(record.reason)}`, tone: "muted" });
130
+ }
131
+ }
132
+ return logical.flatMap((line) => wrapDiagnosticLine(line, width));
133
+ }
134
+ export function filterRouterAuditRecords(records, currentWorkspace, scope) {
135
+ if (scope === "all" || !currentWorkspace.trim()) {
136
+ return records;
137
+ }
138
+ const current = normalizedWorkspace(currentWorkspace);
139
+ return records.filter((record) => normalizedWorkspace(record.workspace) === current);
140
+ }
141
+ function RouterDiagnosticRow({ line, width }) {
142
+ const fill = Math.max(0, width - displayWidth(line.text));
143
+ return (_jsxs(Text, { children: [_jsx(Text, { ...routerDiagnosticLineTheme(line.tone), children: line.text }), fill > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(fill) }) : null] }));
144
+ }
145
+ function routerDiagnosticLineTheme(tone) {
146
+ return {
147
+ backgroundColor: TUI_THEME.surface,
148
+ color: tone === "heading"
149
+ ? TUI_THEME.accent
150
+ : tone === "success"
151
+ ? TUI_THEME.success
152
+ : tone === "warning"
153
+ ? TUI_THEME.warning
154
+ : tone === "danger"
155
+ ? TUI_THEME.danger
156
+ : tone === "muted"
157
+ ? TUI_THEME.muted
158
+ : TUI_THEME.text,
159
+ ...(tone === "heading" || tone === "danger" ? { bold: true } : {})
160
+ };
161
+ }
162
+ function routerAuditStatus(record) {
163
+ const route = record.router_failure_kind || !record.failure_kind
164
+ ? record
165
+ : { ...record, router_failure_kind: record.failure_kind };
166
+ const formatted = formatRouteStatus(route).replace(/^route\s+/, "");
167
+ const parts = formatted.split(/\s+·\s+/);
168
+ if (record.router_command && record.router_command !== "codex") {
169
+ const sourceIndex = parts.findIndex((part) => part === "codex" || part === "fallback" || part === "forced");
170
+ parts.splice(Math.max(1, sourceIndex + 1), 0, `runner ${safeDiagnosticText(record.router_command)}`);
171
+ }
172
+ if (record.source === "codex") {
173
+ parts.splice(1, 0, "codex");
174
+ }
175
+ return parts.join(" · ");
176
+ }
177
+ function routerAuditHeading(record, workspace, width) {
178
+ const prefix = `${record.time.slice(11, 19)} · `;
179
+ const suffix = ` · ${record.scope} · ${routerAuditStatus(record)}`;
180
+ const safeWorkspace = safeDiagnosticText(workspace);
181
+ const minimumWorkspaceWidth = Math.min(12, displayWidth(safeWorkspace));
182
+ const workspaceWidth = Math.max(minimumWorkspaceWidth, width - displayWidth(prefix) - displayWidth(suffix));
183
+ return `${prefix}${compactEndByDisplayWidth(safeWorkspace, workspaceWidth)}${suffix}`;
184
+ }
185
+ function routerDiagnosticsScopeText(scope, currentWorkspace, visibleCount, totalCount, workspaceCount) {
186
+ if (scope === "workspace") {
187
+ const workspace = basename(currentWorkspace) || currentWorkspace || "unknown";
188
+ return `scope · current · ${safeDiagnosticText(workspace)} · ${visibleCount}/${totalCount} routes`;
189
+ }
190
+ return `scope · all · ${visibleCount}/${totalCount} routes · ${workspaceCount} ${workspaceCount === 1 ? "workspace" : "workspaces"}`;
191
+ }
192
+ function routerDiagnosticsLatencyText(records) {
193
+ const durations = successfulRouterDurations(records);
194
+ if (durations.length === 0) {
195
+ return "latency · no successful Codex routes";
196
+ }
197
+ return [
198
+ `latency · successful attempts p50 ${formatDiagnosticDuration(routerDurationPercentile(durations, 0.5))}`,
199
+ `p95 ${formatDiagnosticDuration(routerDurationPercentile(durations, 0.95))}`,
200
+ `max ${formatDiagnosticDuration(durations.at(-1) ?? 0)}`,
201
+ `n ${durations.length}`
202
+ ].join(" · ");
203
+ }
204
+ export function routerDiagnosticsBudget(records, policy) {
205
+ const initial = routerBudgetSegment("initial", policy.timeoutMs, successfulRouterDurations(records, "initial"), 20000, 60000);
206
+ const followUp = routerBudgetSegment("follow-up", policy.followUpTimeoutMs, successfulRouterDurations(records, "follow-up"), 15000, 45000);
207
+ const states = [initial.state, followUp.state];
208
+ return {
209
+ text: `budget · ${initial.text} · ${followUp.text}`,
210
+ tone: states.some((state) => state === "tight" || state === "high")
211
+ ? "warning"
212
+ : states.every((state) => state === "no-data" || state === "learning")
213
+ ? "muted"
214
+ : "success"
215
+ };
216
+ }
217
+ function successfulRouterDurations(records, scope) {
218
+ return records
219
+ .filter((record) => record.source === "codex" && (!scope || record.scope === scope))
220
+ .map((record) => record.duration_ms)
221
+ .filter((duration) => (typeof duration === "number" && Number.isFinite(duration) && duration > 0))
222
+ .sort((left, right) => left - right);
223
+ }
224
+ function routerBudgetSegment(label, configuredMs, durations, minimumMs, maximumMs) {
225
+ if (durations.length === 0) {
226
+ return {
227
+ text: `${label} no data · ${formatDiagnosticDuration(configuredMs)}`,
228
+ state: "no-data"
229
+ };
230
+ }
231
+ const p95 = routerDurationPercentile(durations, 0.95);
232
+ const recommendedMs = Math.min(maximumMs, Math.max(minimumMs, Math.ceil((p95 * 2) / 1000) * 1000));
233
+ const state = durations.length < 3
234
+ ? "learning"
235
+ : configuredMs < p95 * 1.5
236
+ ? "tight"
237
+ : configuredMs > recommendedMs * 2
238
+ ? "high"
239
+ : "healthy";
240
+ return {
241
+ text: [
242
+ `${label} ${state}`,
243
+ `${formatDiagnosticDuration(configuredMs)} / p95 ${formatDiagnosticDuration(p95)}`,
244
+ `n ${durations.length}`,
245
+ ...(state === "tight" || state === "high" ? [`consider ${formatDiagnosticDuration(recommendedMs)}`] : [])
246
+ ].join(" · "),
247
+ state
248
+ };
249
+ }
250
+ function routerDurationPercentile(sortedDurations, percentile) {
251
+ const index = Math.max(0, Math.ceil(sortedDurations.length * percentile) - 1);
252
+ return sortedDurations[Math.min(sortedDurations.length - 1, index)] ?? 0;
253
+ }
254
+ function routerAuditEvidence(record) {
255
+ if (record.source !== "fallback") {
256
+ return null;
257
+ }
258
+ const kind = routerAuditFailureKind(record) ?? "unknown";
259
+ const parts = [`evidence · ${routerFailureKindLabel(kind)}`];
260
+ if (record.router_command && record.router_command !== "codex") {
261
+ parts.push(`runner ${safeDiagnosticText(record.router_command)}`);
262
+ }
263
+ if (kind === "timeout" && record.router_timeout_kind) {
264
+ parts.push(record.router_timeout_kind.replaceAll("-", " "));
265
+ }
266
+ const stage = routerFailureStageLabel(record);
267
+ if (stage) {
268
+ parts.push(stage);
269
+ }
270
+ if (kind === "timeout") {
271
+ const timeoutMs = routerAuditTimeoutLimit(record);
272
+ if (typeof timeoutMs === "number") {
273
+ parts.push(`limit ${formatDiagnosticDuration(timeoutMs)}`);
274
+ }
275
+ }
276
+ if (routerAuditHasProxyContext(record)) {
277
+ parts.push(record.proxy_endpoint ? `via ${record.proxy_endpoint}` : "proxy configured");
278
+ if (record.proxy_source || record.proxy_variable) {
279
+ parts.push([
280
+ record.proxy_source ? routerProxySourceLabel(record.proxy_source) : "proxy",
281
+ record.proxy_variable
282
+ ].filter(Boolean).join(" "));
283
+ }
284
+ parts.push("cause unproven");
285
+ }
286
+ else if (record.proxy_configured === false) {
287
+ parts.push("direct path");
288
+ }
289
+ const resolution = routerFallbackResolutionLabel(record.router_fallback_resolution);
290
+ if (resolution) {
291
+ parts.push(resolution);
292
+ }
293
+ parts.push(`fallback ${record.mode}`);
294
+ return parts.join(" · ");
295
+ }
296
+ function routerAuditTimeoutLimit(record) {
297
+ if (record.router_timeout_kind === "first-output") {
298
+ return record.router_first_output_timeout_ms ?? record.duration_ms;
299
+ }
300
+ if (record.router_timeout_kind === "idle") {
301
+ return record.router_idle_timeout_ms ?? record.duration_ms;
302
+ }
303
+ return record.router_timeout_ms ?? record.duration_ms;
304
+ }
305
+ function routerAuditTraceLines(record) {
306
+ const stages = [
307
+ ...(typeof record.router_dispatch_ms === "number"
308
+ ? [`dispatch ${formatDiagnosticDuration(record.router_dispatch_ms)}`]
309
+ : []),
310
+ ...(typeof record.router_spawn_ms === "number"
311
+ ? [`spawn ${formatDiagnosticDuration(record.router_spawn_ms)}`]
312
+ : []),
313
+ ...routerFirstOutputTraceParts(record),
314
+ ...(typeof record.router_process_ms === "number"
315
+ ? [`process ${formatDiagnosticDuration(record.router_process_ms)}`]
316
+ : []),
317
+ ...(typeof record.router_parse_ms === "number"
318
+ ? [`parse ${formatDiagnosticDuration(record.router_parse_ms)}`]
319
+ : [])
320
+ ];
321
+ const io = [
322
+ ...(typeof record.router_stdout_bytes === "number"
323
+ ? [`stdout ${formatDiagnosticBytes(record.router_stdout_bytes)}`]
324
+ : []),
325
+ ...(typeof record.router_stderr_bytes === "number"
326
+ ? [`stderr ${formatDiagnosticBytes(record.router_stderr_bytes)}`]
327
+ : [])
328
+ ];
329
+ if (stages.length === 0 && io.length === 0) {
330
+ return [];
331
+ }
332
+ if (typeof record.duration_ms === "number") {
333
+ stages.push(`attempt ${formatDiagnosticDuration(record.duration_ms)}`);
334
+ }
335
+ if (typeof record.router_total_duration_ms === "number"
336
+ && typeof record.router_attempt === "number"
337
+ && record.router_attempt > 1) {
338
+ stages.push(`journey ${formatDiagnosticDuration(record.router_total_duration_ms)}`);
339
+ }
340
+ return [
341
+ ...(stages.length > 0 ? [`trace · ${stages.join(" · ")}`] : []),
342
+ ...(io.length > 0 ? [`io · ${io.join(" · ")}`] : [])
343
+ ];
344
+ }
345
+ function routerFirstOutputTraceParts(record) {
346
+ const streams = [
347
+ ...(typeof record.router_first_stdout_ms === "number"
348
+ ? [{ at: record.router_first_stdout_ms, text: `first stdout ${formatDiagnosticDuration(record.router_first_stdout_ms)}` }]
349
+ : []),
350
+ ...(typeof record.router_first_stderr_ms === "number"
351
+ ? [{ at: record.router_first_stderr_ms, text: `first stderr ${formatDiagnosticDuration(record.router_first_stderr_ms)}` }]
352
+ : [])
353
+ ];
354
+ if (streams.length > 0) {
355
+ return streams.sort((left, right) => left.at - right.at).map((stream) => stream.text);
356
+ }
357
+ if (typeof record.router_first_output_ms === "number") {
358
+ return [`first output ${formatDiagnosticDuration(record.router_first_output_ms)}`];
359
+ }
360
+ return record.router_failure_stage === "waiting-output" ? ["first output none"] : [];
361
+ }
362
+ function routerAuditFailureKind(record) {
363
+ return record.failure_kind ?? classifyRouterFailure(record.reason);
364
+ }
365
+ function routerAuditHasProxyContext(record) {
366
+ if (typeof record.proxy_configured === "boolean") {
367
+ return record.proxy_configured;
368
+ }
369
+ return /\bproxy\b|代理/i.test(record.reason);
370
+ }
371
+ function routerDiagnosticsProxyPolicy(policy, recorded) {
372
+ if (!policy.proxyConfigured) {
373
+ return `proxy · direct now · ${recorded} recorded · context only`;
374
+ }
375
+ return [
376
+ "proxy",
377
+ policy.proxySource ? routerProxySourceLabel(policy.proxySource) : "configured now",
378
+ policy.proxyVariable,
379
+ policy.proxyEndpoint,
380
+ `${recorded} recorded`,
381
+ "context only"
382
+ ].filter(Boolean).join(" · ");
383
+ }
384
+ function routerProxySourceLabel(source) {
385
+ return source === "router-config" ? "router config" : "environment";
386
+ }
387
+ function routerFailureKindLabel(kind) {
388
+ return kind.replaceAll("-", " ");
389
+ }
390
+ function routerFailureStageLabel(record) {
391
+ const stage = record.router_failure_stage;
392
+ if (stage === "waiting-output") {
393
+ return "waiting output";
394
+ }
395
+ if (stage === "streaming") {
396
+ if (record.router_stdout_bytes && record.router_stdout_bytes > 0) {
397
+ return "after stdout";
398
+ }
399
+ if (record.router_stderr_bytes && record.router_stderr_bytes > 0) {
400
+ return "after stderr";
401
+ }
402
+ return "after output";
403
+ }
404
+ if (stage === "response") {
405
+ return "response parse";
406
+ }
407
+ return stage ?? null;
408
+ }
409
+ function routerFallbackResolutionLabel(resolution) {
410
+ if (resolution === "main") {
411
+ return "resolved Main";
412
+ }
413
+ if (resolution === "parallel") {
414
+ return "resolved Parallel";
415
+ }
416
+ if (resolution === "retry") {
417
+ return "Router retry requested";
418
+ }
419
+ if (resolution === "auto-retry") {
420
+ return "automatic retry";
421
+ }
422
+ if (resolution === "cancelled") {
423
+ return "cancelled by user";
424
+ }
425
+ if (resolution === "configured") {
426
+ return "configured fallback";
427
+ }
428
+ return null;
429
+ }
430
+ function normalizedWorkspace(workspace) {
431
+ return resolve(workspace.trim());
432
+ }
433
+ function wrapDiagnosticLine(line, width) {
434
+ if (!line.text) {
435
+ return [line];
436
+ }
437
+ return wrapByDisplayWidth(line.text, width).map((text) => ({ ...line, text }));
438
+ }
439
+ function boundedDiagnosticText(value) {
440
+ const safe = safeDiagnosticText(value).replace(/\s+/g, " ").trim();
441
+ const characters = Array.from(safe);
442
+ return characters.length <= 320
443
+ ? safe
444
+ : `${characters.slice(0, 308).join("")} [truncated]`;
445
+ }
446
+ function safeDiagnosticText(value) {
447
+ return value
448
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
449
+ .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ")
450
+ .replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^@\s/]+)@/gi, "$1***@");
451
+ }
452
+ function formatDiagnosticDuration(durationMs) {
453
+ if (durationMs < 1000) {
454
+ return `${Math.round(durationMs)}ms`;
455
+ }
456
+ return `${(durationMs / 1000).toFixed(durationMs % 1000 === 0 ? 0 : 1)}s`;
457
+ }
458
+ function formatDiagnosticBytes(bytes) {
459
+ if (bytes < 1024) {
460
+ return `${Math.round(bytes)}B`;
461
+ }
462
+ if (bytes >= 1024 * 1024) {
463
+ return `${(bytes / (1024 * 1024)).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0).replace(/\.0$/, "")}MB`;
464
+ }
465
+ return `${(bytes / 1024).toFixed(bytes < 10 * 1024 ? 1 : 0)}KB`;
466
+ }
467
+ function routerDiagnosticsContentWidth(terminalWidth) {
468
+ return Math.max(1, terminalWidth - 2);
469
+ }