fix: summarize AgentRun command output by signal

This commit is contained in:
Codex
2026-06-11 11:00:23 +00:00
parent 9bf56ca5a3
commit fde8499378
2 changed files with 278 additions and 5 deletions
+26
View File
@@ -133,6 +133,30 @@ assertCondition(
"AgentRun logs/events must render payload error, backend phase, and command id summaries by default",
);
assertCondition(
agentRunSource.includes("function commandOutputSummary(payload: Record<string, unknown>): string | null")
&& agentRunSource.includes("function summarizeStructuredCliOutput(value: Record<string, unknown>): string | null")
&& agentRunSource.includes("function parseJsonRecordFromText(raw: string): Record<string, unknown> | null")
&& agentRunSource.includes('if (type === "command_output")')
&& agentRunSource.includes("push(commandOutputSummary(payload))")
&& agentRunSource.includes('if (type !== "command_output")'),
"AgentRun command_output summaries must use a dedicated low-noise renderer instead of raw payload JSON",
);
assertCondition(
agentRunSource.includes("isLowSignalJsonField")
&& agentRunSource.includes('key === "generatedAt" || key === "cli" || key === "version" || key === "valuesRedacted" || key === "secretMaterialStored"')
&& agentRunSource.includes('firstPathText(value, ["action", "operation", "command", "kind"])')
&& agentRunSource.includes('"traceId", "lastTraceId", "trace.traceId", "trace.id", "body.traceId", "body.trace.id", "providerTrace.traceId", "runnerTrace.traceId"')
&& agentRunSource.includes('"sessionId", "session.sessionId", "body.sessionId", "workspace.selectedAgentSessionId", "workspace.selectedConversation.sessionId"')
&& agentRunSource.includes('"conversationId", "session.conversationId", "body.conversationId", "workspace.selectedConversationId", "workspace.selectedConversation.conversationId"')
&& agentRunSource.includes('"providerProfile", "backendProfile", "profile", "session.providerProfile", "workspace.providerProfile"')
&& agentRunSource.includes('"pipelineRun", "pipelineRunName", "pipelineRun.name", "pipeline.runName", "pipeline.name"')
&& agentRunSource.includes("function summarizePartialJsonCommandOutput(raw: string): string | null")
&& agentRunSource.includes("if (failure !== null || isFailureLikeStatus(status))"),
"AgentRun command_output summaries must prefer business fields and suppress metadata-only JSON headers",
);
assertCondition(
agentRunSource.includes("function rerunWithoutDryRun(command: string): string")
&& agentRunSource.includes("options.dryRun ? [rerunWithoutDryRun(command)] : undefined"),
@@ -157,6 +181,8 @@ console.log(JSON.stringify({
"AgentRun resource failure output is visible in human mode",
"AgentRun logs tail is enforced by the render-only client",
"AgentRun logs/events expose payload error and backend phase summaries",
"AgentRun command_output summaries use a dedicated low-noise renderer",
"AgentRun command_output summaries prefer business fields over metadata headers",
"AgentRun dry-run mutations keep resource-command follow-up",
],
}));
+252 -5
View File
@@ -950,8 +950,7 @@ function agentRunEventSummary(item: Record<string, unknown>, payload: Record<str
}
if (type === "command_output") {
push(payload.summary);
push(payload.text);
push(commandOutputSummary(payload));
}
if (type === "assistant_message") {
@@ -962,15 +961,263 @@ function agentRunEventSummary(item: Record<string, unknown>, payload: Record<str
push(item.summary);
push(item.outputSummary);
push(item.text);
push(payload.summary);
push(payload.outputSummary);
push(payload.text);
if (type !== "command_output") {
push(payload.summary);
push(payload.outputSummary);
push(payload.text);
}
push(payload.message);
push(payload.reason);
return parts.join("; ");
}
function commandOutputSummary(payload: Record<string, unknown>): string | null {
const raw = commandOutputText(payload);
if (raw === null) return null;
const structured = parseJsonRecordFromText(raw);
if (structured !== null) {
const summary = summarizeStructuredCliOutput(structured);
if (summary !== null) return summary;
}
return summarizePartialJsonCommandOutput(raw) ?? summarizeTextCommandOutput(raw);
}
function commandOutputText(payload: Record<string, unknown>): string | null {
const summary = record(payload.summary);
const summaryText = stringOrNull(summary.text);
const payloadText = stringOrNull(payload.text);
if (summary.textTruncated === true && payloadText !== null) return payloadText;
return summaryText
?? payloadText
?? stringOrNull(payload.outputSummary)
?? eventText(payload.summary);
}
function parseJsonRecordFromText(raw: string): Record<string, unknown> | null {
const trimmed = raw.trim();
const direct = tryParseJsonRecord(trimmed);
if (direct !== null) return direct;
const firstBrace = trimmed.indexOf("{");
const lastBrace = trimmed.lastIndexOf("}");
if (firstBrace >= 0 && lastBrace > firstBrace) {
const sliced = tryParseJsonRecord(trimmed.slice(firstBrace, lastBrace + 1));
if (sliced !== null) return sliced;
}
const line = trimmed
.split(/\r?\n/u)
.map((item) => item.trim())
.reverse()
.find((item) => item.startsWith("{") && item.endsWith("}"));
return line === undefined ? null : tryParseJsonRecord(line);
}
function tryParseJsonRecord(raw: string): Record<string, unknown> | null {
try {
const parsed = JSON.parse(raw) as unknown;
return isRecord(parsed) ? parsed : null;
} catch {
return null;
}
}
function summarizeStructuredCliOutput(value: Record<string, unknown>): string | null {
const parts: string[] = [];
const action = firstPathText(value, ["action", "operation", "command", "kind"]);
const status = firstPathText(value, ["status", "state", "phase", "body.status"]);
const failure = firstPathText(value, ["failureKind", "error.failureKind", "body.failureKind"]);
const message = firstPathText(value, ["error.message", "error.additionalDetails", "message", "reason", "body.message", "body.error", "result.message"]);
const primary = [
action,
status,
labeledSummaryValue("http", firstPathText(value, ["httpStatus", "request.httpStatus"])),
].filter((item): item is string => item !== null);
if (primary.length > 0) pushSummaryPart(parts, primary.join(" "));
pushLabeledSummaryPart(parts, "failure", failure);
if (failure !== null || isFailureLikeStatus(status)) pushLabeledSummaryPart(parts, "message", message);
pushLabeledSummaryPart(parts, "trace", firstPathText(value, ["traceId", "lastTraceId", "trace.traceId", "trace.id", "body.traceId", "body.trace.id", "providerTrace.traceId", "runnerTrace.traceId"]), true);
pushLabeledSummaryPart(parts, "session", firstPathText(value, ["sessionId", "session.sessionId", "body.sessionId", "workspace.selectedAgentSessionId", "workspace.selectedConversation.sessionId"]), true);
pushLabeledSummaryPart(parts, "conversation", firstPathText(value, ["conversationId", "session.conversationId", "body.conversationId", "workspace.selectedConversationId", "workspace.selectedConversation.conversationId"]), true);
pushLabeledSummaryPart(parts, "thread", firstPathText(value, ["threadId", "session.threadId", "body.threadId", "workspace.selectedConversation.threadId"]), true);
pushLabeledSummaryPart(parts, "provider", firstPathText(value, ["providerProfile", "backendProfile", "profile", "session.providerProfile", "workspace.providerProfile"]));
pushLabeledSummaryPart(parts, "route", commandOutputRouteSummary(value));
pushLabeledSummaryPart(parts, "pipeline", firstPathText(value, ["pipelineRun", "pipelineRunName", "pipelineRun.name", "pipeline.runName", "pipeline.name"]), true);
pushLabeledSummaryPart(parts, "run", firstPathText(value, ["runId", "agentRun.runId"]), true);
pushLabeledSummaryPart(parts, "cmd", firstPathText(value, ["commandId", "agentRun.commandId", "providerTrace.commandId", "runnerTrace.commandId"]), true);
pushLabeledSummaryPart(parts, "job", firstPathText(value, ["jobName", "agentRun.jobName", "runnerJobId"]), true);
pushLabeledSummaryPart(parts, "lane", firstPathText(value, ["runtimeEndpoint.lane", "lane"]));
pushLabeledSummaryPart(parts, "ns", firstPathText(value, ["runtimeEndpoint.namespace", "namespace"]));
pushLabeledSummaryPart(parts, "actor", firstPathText(value, ["actor.username", "body.actor.username", "actor.displayName", "body.actor.displayName"]));
pushLabeledSummaryPart(parts, "auth", firstPathText(value, ["authMethod", "auth.authMethod", "body.authMethod"]));
pushLabeledSummaryPart(parts, "reply", firstPathText(value, ["reply.content", "result.reply.content", "body.reply.content", "answer", "content", "body.content"]));
if (failure === null && !isFailureLikeStatus(status)) pushLabeledSummaryPart(parts, "message", message);
const itemsCount = arrayRecords(value.items).length;
if (itemsCount > 0) pushSummaryPart(parts, `items=${itemsCount}`);
const eventsCount = arrayRecords(value.events).length;
if (eventsCount > 0) pushSummaryPart(parts, `events=${eventsCount}`);
if (value.sessionUsable === false) pushSummaryPart(parts, "sessionUsable=false");
if (record(value.summary).textTruncated === true || value.textTruncated === true || value.outputTruncated === true) pushSummaryPart(parts, "truncated=true");
return parts.length > 0 ? parts.slice(0, 10).join("; ") : null;
}
function commandOutputRouteSummary(value: Record<string, unknown>): string | null {
const route = record(value.route);
const request = record(value.request);
const method = stringOrNull(route.method) ?? stringOrNull(request.method);
const path = stringOrNull(route.path) ?? stringOrNull(request.path);
if (method !== null && path !== null) return `${method} ${path}`;
return stringOrNull(request.url) ?? stringOrNull(value.baseUrl);
}
function firstPathText(value: Record<string, unknown>, paths: string[]): string | null {
for (const path of paths) {
const text = summaryScalar(pathValue(value, path));
if (text !== null) return text;
}
return null;
}
function pathValue(value: Record<string, unknown>, path: string): unknown {
let current: unknown = value;
for (const key of path.split(".")) {
if (!isRecord(current)) return undefined;
current = current[key];
}
return current;
}
function summaryScalar(value: unknown): string | null {
if (typeof value === "string") {
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
if (typeof value === "number" && Number.isFinite(value)) return String(value);
if (typeof value === "boolean") return String(value);
return null;
}
function isFailureLikeStatus(value: string | null): boolean {
return value !== null && /(?:fail|error|timeout|cancel|interrupt|denied|rejected)/iu.test(value);
}
function pushLabeledSummaryPart(parts: string[], label: string, value: string | null, shorten = false): void {
const text = labeledSummaryValue(label, value, shorten);
if (text !== null) pushSummaryPart(parts, text);
}
function labeledSummaryValue(label: string, value: string | null, shorten = false): string | null {
if (value === null) return null;
const rendered = shorten ? shortId(value) : truncateOneLine(value, 80);
return `${label}=${rendered}`;
}
function pushSummaryPart(parts: string[], value: string): void {
const text = truncateOneLine(value, 160);
if (text.length > 0 && !parts.includes(text)) parts.push(text);
}
function summarizeTextCommandOutput(raw: string): string | null {
const lines = raw
.split(/\r?\n/u)
.map((line) => line.trim())
.filter((line) => line.length > 0 && !isLowSignalCommandOutputLine(line));
const signal = lines.find((line) => /(?:trace|session|conversation|thread|status|failure|error|route|http|pipeline|provider|commit|succeeded|failed|created)/iu.test(line))
?? lines[0]
?? null;
if (signal === null) return null;
return truncateOneLine(cleanCommandOutputLine(signal), 220);
}
function summarizePartialJsonCommandOutput(raw: string): string | null {
const fields = new Map<string, string>();
for (const line of raw.split(/\r?\n/u)) {
const field = jsonLineField(line);
if (field === null || isLowSignalJsonField(field.key)) continue;
if (!fields.has(field.key) && field.value.length > 0) fields.set(field.key, field.value);
}
const parts: string[] = [];
const action = fields.get("action") ?? fields.get("operation") ?? fields.get("command") ?? fields.get("kind") ?? null;
const status = fields.get("status") ?? fields.get("state") ?? fields.get("phase") ?? null;
const http = fields.get("httpStatus") ?? null;
const failure = fields.get("failureKind") ?? null;
const message = fields.get("message") ?? fields.get("reason") ?? fields.get("error") ?? null;
const primary = [action, status, labeledSummaryValue("http", http)].filter((item): item is string => item !== null);
if (primary.length > 0) pushSummaryPart(parts, primary.join(" "));
pushLabeledSummaryPart(parts, "failure", failure);
if (failure !== null || isFailureLikeStatus(status)) pushLabeledSummaryPart(parts, "message", message);
pushLabeledSummaryPart(parts, "trace", fields.get("traceId") ?? fields.get("lastTraceId") ?? null, true);
pushLabeledSummaryPart(parts, "session", fields.get("sessionId") ?? fields.get("selectedAgentSessionId") ?? null, true);
pushLabeledSummaryPart(parts, "conversation", fields.get("conversationId") ?? fields.get("selectedConversationId") ?? null, true);
pushLabeledSummaryPart(parts, "thread", fields.get("threadId") ?? null, true);
pushLabeledSummaryPart(parts, "provider", fields.get("providerProfile") ?? fields.get("backendProfile") ?? fields.get("profile") ?? null);
pushLabeledSummaryPart(parts, "route", partialRouteSummary(fields));
pushLabeledSummaryPart(parts, "pipeline", fields.get("pipelineRun") ?? fields.get("pipelineRunName") ?? null, true);
pushLabeledSummaryPart(parts, "run", fields.get("runId") ?? null, true);
pushLabeledSummaryPart(parts, "cmd", fields.get("commandId") ?? null, true);
pushLabeledSummaryPart(parts, "job", fields.get("jobName") ?? fields.get("runnerJobId") ?? null, true);
pushLabeledSummaryPart(parts, "lane", fields.get("lane") ?? null);
pushLabeledSummaryPart(parts, "ns", fields.get("namespace") ?? null);
pushLabeledSummaryPart(parts, "actor", fields.get("username") ?? fields.get("displayName") ?? null);
pushLabeledSummaryPart(parts, "auth", fields.get("authMethod") ?? null);
if (failure === null && !isFailureLikeStatus(status)) pushLabeledSummaryPart(parts, "message", message);
return parts.length > 0 ? parts.slice(0, 10).join("; ") : null;
}
function partialRouteSummary(fields: Map<string, string>): string | null {
const method = fields.get("method") ?? null;
const path = fields.get("path") ?? null;
if (method !== null && path !== null) return `${method} ${path}`;
return fields.get("url") ?? fields.get("baseUrl") ?? null;
}
function jsonLineField(line: string): { key: string; value: string } | null {
const match = line.trim().replace(/,$/u, "").match(/^"([^"]+)":\s*(.+)$/u);
if (match === null) return null;
return { key: match[1], value: cleanJsonScalar(match[2]) };
}
function cleanJsonScalar(raw: string): string {
const trimmed = raw.trim();
if (trimmed === "null" || trimmed === "{" || trimmed === "[" || trimmed === "}" || trimmed === "]") return "";
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
try {
const parsed = JSON.parse(trimmed) as unknown;
return typeof parsed === "string" ? parsed.trim() : String(parsed);
} catch {
return trimmed.slice(1, -1).trim();
}
}
return trimmed;
}
function cleanCommandOutputLine(line: string): string {
const trimmed = line.replace(/,$/u, "").trim();
const jsonField = trimmed.match(/^"([^"]+)":\s*(.+)$/u);
if (jsonField === null) return trimmed;
const key = jsonField[1];
const value = jsonField[2].replace(/^"|"$/gu, "").trim();
return `${key}=${value}`;
}
function isLowSignalCommandOutputLine(line: string): boolean {
const trimmed = line.replace(/,$/u, "").trim();
if (trimmed === "{" || trimmed === "}" || trimmed === "}," || trimmed === "];" || trimmed === "[") return true;
const field = jsonLineField(trimmed);
return field !== null && isLowSignalJsonField(field.key);
}
function isLowSignalJsonField(key: string): boolean {
return key === "generatedAt" || key === "cli" || key === "version" || key === "valuesRedacted" || key === "secretMaterialStored";
}
function eventText(value: unknown): string | null {
const direct = stringOrNull(value);
if (direct !== null && direct.trim().length > 0) return direct.trim();