fix(status): full-width tables + better diagnosis
This commit is contained in:
@@ -13,6 +13,177 @@ export async function readFileTailLines(
|
||||
.filter((line) => line.trim().length > 0);
|
||||
}
|
||||
|
||||
function countMatches(haystack: string, needle: string): number {
|
||||
if (!haystack || !needle) return 0;
|
||||
return haystack.split(needle).length - 1;
|
||||
}
|
||||
|
||||
function shorten(message: string, maxLen: number): string {
|
||||
const cleaned = message.replace(/\s+/g, " ").trim();
|
||||
if (cleaned.length <= maxLen) return cleaned;
|
||||
return `${cleaned.slice(0, Math.max(0, maxLen - 1))}…`;
|
||||
}
|
||||
|
||||
function normalizeGwsLine(line: string): string {
|
||||
return line
|
||||
.replace(/\s+runId=[^\s]+/g, "")
|
||||
.replace(/\s+conn=[^\s]+/g, "")
|
||||
.replace(/\s+id=[^\s]+/g, "")
|
||||
.replace(/\s+error=Error:.*$/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function consumeJsonBlock(
|
||||
lines: string[],
|
||||
startIndex: number,
|
||||
): { json: string; endIndex: number } | null {
|
||||
const startLine = lines[startIndex] ?? "";
|
||||
const braceAt = startLine.indexOf("{");
|
||||
if (braceAt < 0) return null;
|
||||
|
||||
const parts: string[] = [startLine.slice(braceAt)];
|
||||
let depth =
|
||||
countMatches(parts[0] ?? "", "{") - countMatches(parts[0] ?? "", "}");
|
||||
let i = startIndex;
|
||||
while (depth > 0 && i + 1 < lines.length) {
|
||||
i += 1;
|
||||
const next = lines[i] ?? "";
|
||||
parts.push(next);
|
||||
depth += countMatches(next, "{") - countMatches(next, "}");
|
||||
}
|
||||
return { json: parts.join("\n"), endIndex: i };
|
||||
}
|
||||
|
||||
export function summarizeLogTail(
|
||||
rawLines: string[],
|
||||
opts?: { maxLines?: number },
|
||||
): string[] {
|
||||
const maxLines = Math.max(6, opts?.maxLines ?? 26);
|
||||
|
||||
const out: string[] = [];
|
||||
const groups = new Map<
|
||||
string,
|
||||
{ count: number; index: number; base: string }
|
||||
>();
|
||||
|
||||
const addGroup = (key: string, base: string) => {
|
||||
const existing = groups.get(key);
|
||||
if (existing) {
|
||||
existing.count += 1;
|
||||
return;
|
||||
}
|
||||
groups.set(key, { count: 1, index: out.length, base });
|
||||
out.push(base);
|
||||
};
|
||||
|
||||
const addLine = (line: string) => {
|
||||
const trimmed = line.trimEnd();
|
||||
if (!trimmed) return;
|
||||
out.push(trimmed);
|
||||
};
|
||||
|
||||
const lines = rawLines.map((line) => line.trimEnd()).filter(Boolean);
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
const line = lines[i] ?? "";
|
||||
const trimmedStart = line.trimStart();
|
||||
if (
|
||||
(trimmedStart.startsWith('"') ||
|
||||
trimmedStart === "}" ||
|
||||
trimmedStart === "{" ||
|
||||
trimmedStart.startsWith("}") ||
|
||||
trimmedStart.startsWith("{")) &&
|
||||
!trimmedStart.startsWith("[") &&
|
||||
!trimmedStart.startsWith("#")
|
||||
) {
|
||||
// Tail can cut in the middle of a JSON blob; drop orphaned JSON fragments.
|
||||
continue;
|
||||
}
|
||||
|
||||
// "[openai-codex] Token refresh failed: 401 { ...json... }"
|
||||
const tokenRefresh = line.match(
|
||||
/^\[([^\]]+)\]\s+Token refresh failed:\s*(\d+)\s*(\{)?\s*$/,
|
||||
);
|
||||
if (tokenRefresh) {
|
||||
const tag = tokenRefresh[1] ?? "unknown";
|
||||
const status = tokenRefresh[2] ?? "unknown";
|
||||
const block = consumeJsonBlock(lines, i);
|
||||
if (block) {
|
||||
i = block.endIndex;
|
||||
const parsed = (() => {
|
||||
try {
|
||||
return JSON.parse(block.json) as {
|
||||
error?: { code?: string; message?: string };
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const code = parsed?.error?.code?.trim() || null;
|
||||
const msg = parsed?.error?.message?.trim() || null;
|
||||
const msgShort = msg
|
||||
? msg.toLowerCase().includes("signing in again")
|
||||
? "re-auth required"
|
||||
: shorten(msg, 52)
|
||||
: null;
|
||||
const base = `[${tag}] token refresh ${status}${code ? ` ${code}` : ""}${msgShort ? ` · ${msgShort}` : ""}`;
|
||||
addGroup(
|
||||
`token:${tag}:${status}:${code ?? ""}:${msgShort ?? ""}`,
|
||||
base,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// "Embedded agent failed before reply: OAuth token refresh failed for openai-codex: ..."
|
||||
const embedded = line.match(
|
||||
/^Embedded agent failed before reply:\s+OAuth token refresh failed for ([^:]+):/,
|
||||
);
|
||||
if (embedded) {
|
||||
const provider = embedded[1]?.trim() || "unknown";
|
||||
addGroup(
|
||||
`embedded:${provider}`,
|
||||
`Embedded agent: OAuth token refresh failed (${provider})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// "[gws] ⇄ res ✗ agent ... errorCode=UNAVAILABLE errorMessage=Error: OAuth token refresh failed ... runId=..."
|
||||
if (
|
||||
line.startsWith("[gws]") &&
|
||||
line.includes("errorCode=UNAVAILABLE") &&
|
||||
line.includes("OAuth token refresh failed")
|
||||
) {
|
||||
const normalized = normalizeGwsLine(line);
|
||||
addGroup(`gws:${normalized}`, normalized);
|
||||
continue;
|
||||
}
|
||||
|
||||
addLine(line);
|
||||
}
|
||||
|
||||
for (const g of groups.values()) {
|
||||
if (g.count <= 1) continue;
|
||||
out[g.index] = `${g.base} ×${g.count}`;
|
||||
}
|
||||
|
||||
const deduped: string[] = [];
|
||||
for (const line of out) {
|
||||
if (deduped[deduped.length - 1] === line) continue;
|
||||
deduped.push(line);
|
||||
}
|
||||
|
||||
if (deduped.length <= maxLines) return deduped;
|
||||
|
||||
const head = Math.min(6, Math.floor(maxLines / 3));
|
||||
const tail = Math.max(1, maxLines - head - 1);
|
||||
const kept = [
|
||||
...deduped.slice(0, head),
|
||||
`… ${deduped.length - head - tail} lines omitted …`,
|
||||
...deduped.slice(-tail),
|
||||
];
|
||||
return kept;
|
||||
}
|
||||
|
||||
export function pickGatewaySelfPresence(presence: unknown): {
|
||||
host?: string;
|
||||
ip?: string;
|
||||
|
||||
Reference in New Issue
Block a user