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
|
@@ -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
|
+
}
|