From b6d2ab390373db34dc32041ad8d746dd22e9badd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:07:07 +0000 Subject: [PATCH 1/3] Initial plan From 3c0e9ebfdcb1d8d8f904df68ff50144b3e57fb31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:16:24 +0000 Subject: [PATCH 2/3] Pretty print parsed codex logs using shared log renderer - Convert Codex log parser to return structured logEntries format - Add convertToLogEntries helper to transform parsed data - Update parseCodexLog to return object with markdown, logEntries, mcpFailures, maxTurnsHit - Update parse_custom_log.cjs to handle new return format - Update all tests to access result.markdown property - Codex logs now output to core.info via generatePlainTextSummary Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/parse_codex_log.cjs | 173 +++++++++++++++++++++- actions/setup/js/parse_codex_log.test.cjs | 90 +++++------ actions/setup/js/parse_custom_log.cjs | 16 +- 3 files changed, 222 insertions(+), 57 deletions(-) diff --git a/actions/setup/js/parse_codex_log.cjs b/actions/setup/js/parse_codex_log.cjs index 7996c2ea6a7..a96c0a0ee40 100644 --- a/actions/setup/js/parse_codex_log.cjs +++ b/actions/setup/js/parse_codex_log.cjs @@ -122,17 +122,125 @@ function extractMCPInitialization(lines) { }; } +/** + * Convert parsed Codex data to logEntries format for plain text rendering + * @param {Array<{type: string, content?: string, toolName?: string, params?: string, response?: string, statusIcon?: string}>} parsedData - Parsed Codex log data + * @returns {Array} logEntries array in the format expected by generatePlainTextSummary + */ +function convertToLogEntries(parsedData) { + const logEntries = []; + + for (const item of parsedData) { + if (item.type === "thinking") { + // Add thinking as assistant text content + logEntries.push({ + type: "assistant", + message: { + content: [ + { + type: "text", + text: item.content, + }, + ], + }, + }); + } else if (item.type === "tool") { + // Add tool use as assistant content + const toolUseId = `tool_${logEntries.length}`; + + // Parse params - it might be a plain JSON string or need parsing + let inputObj = {}; + if (item.params) { + try { + inputObj = JSON.parse(item.params); + } catch (e) { + // If parsing fails, wrap it as a string + inputObj = { params: item.params }; + } + } + + logEntries.push({ + type: "assistant", + message: { + content: [ + { + type: "tool_use", + id: toolUseId, + name: item.toolName, // Already in server__method format + input: inputObj, + }, + ], + }, + }); + + // Add tool result as user content + logEntries.push({ + type: "user", + message: { + content: [ + { + type: "tool_result", + tool_use_id: toolUseId, + content: item.response || "", + is_error: item.statusIcon === "❌", + }, + ], + }, + }); + } else if (item.type === "bash") { + // Add bash command as tool use + const toolUseId = `bash_${logEntries.length}`; + logEntries.push({ + type: "assistant", + message: { + content: [ + { + type: "tool_use", + id: toolUseId, + name: "Bash", + input: { command: item.content }, + }, + ], + }, + }); + + // Add bash result as user content + logEntries.push({ + type: "user", + message: { + content: [ + { + type: "tool_result", + tool_use_id: toolUseId, + content: item.response || "", + is_error: item.statusIcon === "❌", + }, + ], + }, + }); + } + } + + return logEntries; +} + /** * Parse codex log content and format as markdown * @param {string} logContent - The raw log content to parse - * @returns {string} Formatted markdown content + * @returns {{markdown: string, logEntries: Array, mcpFailures: Array, maxTurnsHit: boolean}} Parsed log data */ function parseCodexLog(logContent) { if (!logContent) { - return "## 🤖 Commands and Tools\n\nNo log content provided.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n"; + return { + markdown: "## 🤖 Commands and Tools\n\nNo log content provided.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n", + logEntries: [], + mcpFailures: [], + maxTurnsHit: false, + }; } const lines = logContent.split("\n"); + const parsedData = []; // Array to collect structured data for logEntries conversion // Look-ahead window size for finding tool results // New format has verbose debug logs, so requires larger window @@ -151,6 +259,7 @@ function parseCodexLog(logContent) { // Second pass: process full conversation flow with interleaved reasoning and tools let inThinkingSection = false; + let thinkingContent = []; // Collect thinking content in chunks for (let i = 0; i < lines.length; i++) { const line = lines[i]; @@ -176,6 +285,14 @@ function parseCodexLog(logContent) { // Thinking section starts with standalone "thinking" line if (line.trim() === "thinking") { + // Save previous thinking content if any + if (thinkingContent.length > 0) { + parsedData.push({ + type: "thinking", + content: thinkingContent.join("\n"), + }); + thinkingContent = []; + } inThinkingSection = true; continue; } @@ -183,7 +300,16 @@ function parseCodexLog(logContent) { // Tool call line "tool github.list_pull_requests(...)" const toolMatch = line.match(/^tool\s+(\w+)\.(\w+)\(/); if (toolMatch) { + // Save previous thinking content if any + if (inThinkingSection && thinkingContent.length > 0) { + parsedData.push({ + type: "thinking", + content: thinkingContent.join("\n"), + }); + thinkingContent = []; + } inThinkingSection = false; + const server = toolMatch[1]; const toolName = toolMatch[2]; @@ -207,10 +333,19 @@ function parseCodexLog(logContent) { // Process thinking content (filter out timestamp lines and very short lines) if (inThinkingSection && line.trim().length > 20 && !line.match(/^\d{4}-\d{2}-\d{2}T/)) { const trimmed = line.trim(); - // Add thinking content directly + thinkingContent.push(trimmed); + // Add thinking content directly to markdown markdown += `${trimmed}\n\n`; } } + + // Save any remaining thinking content + if (thinkingContent.length > 0) { + parsedData.push({ + type: "thinking", + content: thinkingContent.join("\n"), + }); + } markdown += "## 🤖 Commands and Tools\n\n"; @@ -279,6 +414,15 @@ function parseCodexLog(logContent) { } } + // Collect data for logEntries conversion + parsedData.push({ + type: "tool", + toolName: `${server}__${toolName}`, + params, + response, + statusIcon, + }); + // Format the tool call with HTML details markdown += formatCodexToolCall(server, toolName, params, response, statusIcon); } else if (bashMatch) { @@ -316,6 +460,14 @@ function parseCodexLog(logContent) { } } + // Collect data for logEntries conversion + parsedData.push({ + type: "bash", + content: command, + response, + statusIcon, + }); + // Format the bash command with HTML details markdown += formatCodexBashCall(command, response, statusIcon); } @@ -352,7 +504,20 @@ function parseCodexLog(logContent) { markdown += `**Tool Calls:** ${toolCalls}\n\n`; } - return markdown; + // Convert parsed data to logEntries format + const logEntries = convertToLogEntries(parsedData); + + // Check for MCP failures + const mcpFailures = mcpInfo.servers + .filter(server => server.status === "failed") + .map(server => server.name); + + return { + markdown, + logEntries, + mcpFailures, + maxTurnsHit: false, // Codex doesn't have max-turns concept in logs + }; } /** diff --git a/actions/setup/js/parse_codex_log.test.cjs b/actions/setup/js/parse_codex_log.test.cjs index 8abb2a3c94b..55b28aa5b71 100644 --- a/actions/setup/js/parse_codex_log.test.cjs +++ b/actions/setup/js/parse_codex_log.test.cjs @@ -48,10 +48,10 @@ github.list_pull_requests(...) success in 123ms: const result = parseCodexLog(logContent); - expect(result).toContain("## 🤖 Reasoning"); - expect(result).toContain("## 🤖 Commands and Tools"); - expect(result).toContain("github::list_pull_requests"); - expect(result).toContain("✅"); + expect(result.markdown).toContain("## 🤖 Reasoning"); + expect(result.markdown).toContain("## 🤖 Commands and Tools"); + expect(result.markdown).toContain("github::list_pull_requests"); + expect(result.markdown).toContain("✅"); }); it("should parse tool call with failure", () => { @@ -61,8 +61,8 @@ github.create_issue(...) failed in 456ms: const result = parseCodexLog(logContent); - expect(result).toContain("github::create_issue"); - expect(result).toContain("❌"); + expect(result.markdown).toContain("github::create_issue"); + expect(result.markdown).toContain("❌"); }); it("should parse thinking sections", () => { @@ -72,9 +72,9 @@ Let me start by listing the files in the root directory`; const result = parseCodexLog(logContent); - expect(result).toContain("## 🤖 Reasoning"); - expect(result).toContain("I need to analyze the repository structure"); - expect(result).toContain("Let me start by listing the files"); + expect(result.markdown).toContain("## 🤖 Reasoning"); + expect(result.markdown).toContain("I need to analyze the repository structure"); + expect(result.markdown).toContain("Let me start by listing the files"); }); it("should skip metadata lines", () => { @@ -88,10 +88,10 @@ This is actual thinking content`; const result = parseCodexLog(logContent); - expect(result).not.toContain("OpenAI Codex"); - expect(result).not.toContain("workdir"); - expect(result).not.toContain("model:"); - expect(result).toContain("This is actual thinking content"); + expect(result.markdown).not.toContain("OpenAI Codex"); + expect(result.markdown).not.toContain("workdir"); + expect(result.markdown).not.toContain("model:"); + expect(result.markdown).toContain("This is actual thinking content"); }); it("should skip debug and timestamp lines", () => { @@ -103,9 +103,9 @@ Actual thinking content that is long enough to be included`; const result = parseCodexLog(logContent); - expect(result).not.toContain("DEBUG codex"); - expect(result).not.toContain("INFO codex"); - expect(result).toContain("Actual thinking content"); + expect(result.markdown).not.toContain("DEBUG codex"); + expect(result.markdown).not.toContain("INFO codex"); + expect(result.markdown).toContain("Actual thinking content"); }); it("should parse bash commands", () => { @@ -116,8 +116,8 @@ total 8 const result = parseCodexLog(logContent); - expect(result).toContain("bash: ls -la"); - expect(result).toContain("✅"); + expect(result.markdown).toContain("bash: ls -la"); + expect(result.markdown).toContain("✅"); }); it("should extract total tokens from log", () => { @@ -128,9 +128,9 @@ tokens used const result = parseCodexLog(logContent); - expect(result).toContain("📊 Information"); - expect(result).toContain("Total Tokens Used"); - expect(result).toContain("1,500"); + expect(result.markdown).toContain("📊 Information"); + expect(result.markdown).toContain("Total Tokens Used"); + expect(result.markdown).toContain("1,500"); }); it("should count tool calls", () => { @@ -140,23 +140,23 @@ ToolCall: github__add_labels {}`; const result = parseCodexLog(logContent); - expect(result).toContain("**Tool Calls:** 3"); + expect(result.markdown).toContain("**Tool Calls:** 3"); }); it("should handle empty log content", () => { const result = parseCodexLog(""); - expect(result).toContain("## 🤖 Reasoning"); - expect(result).toContain("## 🤖 Commands and Tools"); + expect(result.markdown).toContain("## 🤖 Reasoning"); + expect(result.markdown).toContain("## 🤖 Commands and Tools"); }); it("should handle log with errors gracefully", () => { const malformedLog = null; const result = parseCodexLog(malformedLog); - expect(result).toContain("No log content provided"); - expect(result).toContain("## 🤖 Commands and Tools"); - expect(result).toContain("## 🤖 Reasoning"); + expect(result.markdown).toContain("No log content provided"); + expect(result.markdown).toContain("## 🤖 Commands and Tools"); + expect(result.markdown).toContain("## 🤖 Reasoning"); }); it("should handle tool calls without responses", () => { @@ -164,8 +164,8 @@ ToolCall: github__add_labels {}`; const result = parseCodexLog(logContent); - expect(result).toContain("github::list_issues"); - expect(result).toContain("❓"); // Unknown status + expect(result.markdown).toContain("github::list_issues"); + expect(result.markdown).toContain("❓"); // Unknown status }); it("should filter out short lines in thinking sections", () => { @@ -176,9 +176,9 @@ x`; const result = parseCodexLog(logContent); - expect(result).toContain("This is a long enough line"); - expect(result).not.toContain("Short\n\n"); - expect(result).not.toContain("x\n\n"); + expect(result.markdown).toContain("This is a long enough line"); + expect(result.markdown).not.toContain("Short\n\n"); + expect(result.markdown).not.toContain("x\n\n"); }); it("should handle ToolCall format", () => { @@ -186,8 +186,8 @@ x`; const result = parseCodexLog(logContent); - expect(result).toContain("📊 Information"); - expect(result).toContain("**Tool Calls:** 1"); + expect(result.markdown).toContain("📊 Information"); + expect(result.markdown).toContain("**Tool Calls:** 1"); }); it("should handle tokens with commas in final count", () => { @@ -196,7 +196,7 @@ x`; const result = parseCodexLog(logContent); - expect(result).toContain("12,345"); + expect(result.markdown).toContain("12,345"); }); }); @@ -467,14 +467,14 @@ I will now use the GitHub API to list issues`; const result = parseCodexLog(logContent); - expect(result).toContain("## 🚀 Initialization"); - expect(result).toContain("**MCP Servers:**"); - expect(result).toContain("Total: 2"); - expect(result).toContain("Connected: 2"); - expect(result).toContain("✅"); - expect(result).toContain("github"); - expect(result).toContain("safe_outputs"); - expect(result).toContain("## 🤖 Reasoning"); + expect(result.markdown).toContain("## 🚀 Initialization"); + expect(result.markdown).toContain("**MCP Servers:**"); + expect(result.markdown).toContain("Total: 2"); + expect(result.markdown).toContain("Connected: 2"); + expect(result.markdown).toContain("✅"); + expect(result.markdown).toContain("github"); + expect(result.markdown).toContain("safe_outputs"); + expect(result.markdown).toContain("## 🤖 Reasoning"); }); it("should skip initialization section when no MCP info present", () => { @@ -484,8 +484,8 @@ I will analyze the code`; const result = parseCodexLog(logContent); - expect(result).not.toContain("## 🚀 Initialization"); - expect(result).toContain("## 🤖 Reasoning"); + expect(result.markdown).not.toContain("## 🚀 Initialization"); + expect(result.markdown).toContain("## 🤖 Reasoning"); }); }); }); diff --git a/actions/setup/js/parse_custom_log.cjs b/actions/setup/js/parse_custom_log.cjs index 3615e8247bc..c903a8366ba 100644 --- a/actions/setup/js/parse_custom_log.cjs +++ b/actions/setup/js/parse_custom_log.cjs @@ -33,18 +33,18 @@ function parseCustomLog(logContent) { } // Try Codex parser as fallback - // Codex parser returns a string (markdown only), so wrap it in expected object format + // Codex parser now returns an object { markdown, logEntries, mcpFailures, maxTurnsHit } try { const codexModule = require("./parse_codex_log.cjs"); - const codexMarkdown = codexModule.parseCodexLog(logContent); + const codexResult = codexModule.parseCodexLog(logContent); - // Codex parser returns a string, so check if we got meaningful content - if (codexMarkdown && typeof codexMarkdown === "string" && codexMarkdown.length > 0) { + // Check if we got meaningful content + if (codexResult && codexResult.markdown && codexResult.markdown.length > 0) { return { - markdown: "## Custom Engine Log (Codex format)\n\n" + codexMarkdown, - mcpFailures: [], - maxTurnsHit: false, - logEntries: [], + markdown: "## Custom Engine Log (Codex format)\n\n" + codexResult.markdown, + mcpFailures: codexResult.mcpFailures || [], + maxTurnsHit: codexResult.maxTurnsHit || false, + logEntries: codexResult.logEntries || [], }; } } catch (error) { From 66987a35a37ba3fa95f6f58c690f28da55b0a689 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:19:27 +0000 Subject: [PATCH 3/3] Validate JavaScript changes Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/parse_codex_log.cjs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/actions/setup/js/parse_codex_log.cjs b/actions/setup/js/parse_codex_log.cjs index a96c0a0ee40..8fb82124fb5 100644 --- a/actions/setup/js/parse_codex_log.cjs +++ b/actions/setup/js/parse_codex_log.cjs @@ -129,7 +129,7 @@ function extractMCPInitialization(lines) { */ function convertToLogEntries(parsedData) { const logEntries = []; - + for (const item of parsedData) { if (item.type === "thinking") { // Add thinking as assistant text content @@ -147,7 +147,7 @@ function convertToLogEntries(parsedData) { } else if (item.type === "tool") { // Add tool use as assistant content const toolUseId = `tool_${logEntries.length}`; - + // Parse params - it might be a plain JSON string or need parsing let inputObj = {}; if (item.params) { @@ -158,7 +158,7 @@ function convertToLogEntries(parsedData) { inputObj = { params: item.params }; } } - + logEntries.push({ type: "assistant", message: { @@ -172,7 +172,7 @@ function convertToLogEntries(parsedData) { ], }, }); - + // Add tool result as user content logEntries.push({ type: "user", @@ -203,7 +203,7 @@ function convertToLogEntries(parsedData) { ], }, }); - + // Add bash result as user content logEntries.push({ type: "user", @@ -220,7 +220,7 @@ function convertToLogEntries(parsedData) { }); } } - + return logEntries; } @@ -309,7 +309,7 @@ function parseCodexLog(logContent) { thinkingContent = []; } inThinkingSection = false; - + const server = toolMatch[1]; const toolName = toolMatch[2]; @@ -338,7 +338,7 @@ function parseCodexLog(logContent) { markdown += `${trimmed}\n\n`; } } - + // Save any remaining thinking content if (thinkingContent.length > 0) { parsedData.push({ @@ -508,9 +508,7 @@ function parseCodexLog(logContent) { const logEntries = convertToLogEntries(parsedData); // Check for MCP failures - const mcpFailures = mcpInfo.servers - .filter(server => server.status === "failed") - .map(server => server.name); + const mcpFailures = mcpInfo.servers.filter(server => server.status === "failed").map(server => server.name); return { markdown,