diff --git a/.github/workflows/ai-triage-campaign.lock.yml b/.github/workflows/ai-triage-campaign.lock.yml
index a64d293d696..5e32289f5da 100644
--- a/.github/workflows/ai-triage-campaign.lock.yml
+++ b/.github/workflows/ai-triage-campaign.lock.yml
@@ -2949,81 +2949,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3596,6 +3524,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3637,7 +3736,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3689,10 +3788,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml
index c5948c89a86..f9426f55005 100644
--- a/.github/workflows/archie.lock.yml
+++ b/.github/workflows/archie.lock.yml
@@ -4590,81 +4590,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5237,6 +5165,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5278,7 +5377,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5330,10 +5429,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml
index 50ce81b8411..d9fa053d95e 100644
--- a/.github/workflows/artifacts-summary.lock.yml
+++ b/.github/workflows/artifacts-summary.lock.yml
@@ -3119,81 +3119,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3766,6 +3694,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3807,7 +3906,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3859,10 +3958,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml
index 5092a7d8fd1..94a74799cdb 100644
--- a/.github/workflows/audit-workflows.lock.yml
+++ b/.github/workflows/audit-workflows.lock.yml
@@ -4592,81 +4592,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5239,6 +5167,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5254,6 +5353,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5307,13 +5407,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml
index 3dcf7d4caa6..973ec47575c 100644
--- a/.github/workflows/blog-auditor.lock.yml
+++ b/.github/workflows/blog-auditor.lock.yml
@@ -3716,81 +3716,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4363,6 +4291,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4378,6 +4477,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4431,13 +4531,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml
index b3c67f2d7e8..d39d884b767 100644
--- a/.github/workflows/brave.lock.yml
+++ b/.github/workflows/brave.lock.yml
@@ -4381,81 +4381,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5028,6 +4956,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5069,7 +5168,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5121,10 +5220,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml
index af2e3d0e498..72002519dc5 100644
--- a/.github/workflows/breaking-change-checker.lock.yml
+++ b/.github/workflows/breaking-change-checker.lock.yml
@@ -3185,81 +3185,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3832,6 +3760,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3873,7 +3972,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3925,10 +4024,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml
index 0a861a68383..0a3756c7dd6 100644
--- a/.github/workflows/changeset.lock.yml
+++ b/.github/workflows/changeset.lock.yml
@@ -4089,81 +4089,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4736,6 +4664,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml
index 05b65a1d5b9..99ff4bb9333 100644
--- a/.github/workflows/ci-doctor.lock.yml
+++ b/.github/workflows/ci-doctor.lock.yml
@@ -3855,81 +3855,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4502,6 +4430,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4543,7 +4642,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4595,10 +4694,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml
index 8136db1348f..592d08d4855 100644
--- a/.github/workflows/cli-consistency-checker.lock.yml
+++ b/.github/workflows/cli-consistency-checker.lock.yml
@@ -3199,81 +3199,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3846,6 +3774,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3887,7 +3986,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3939,10 +4038,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml
index 75d09afea0d..1ac4f82bb53 100644
--- a/.github/workflows/cli-version-checker.lock.yml
+++ b/.github/workflows/cli-version-checker.lock.yml
@@ -3528,81 +3528,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4175,6 +4103,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4216,7 +4315,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4268,10 +4367,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml
index 5c0dce4cc7c..d05ef57fc0a 100644
--- a/.github/workflows/cloclo.lock.yml
+++ b/.github/workflows/cloclo.lock.yml
@@ -5108,81 +5108,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5755,6 +5683,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5770,6 +5869,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5823,13 +5923,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/close-old-discussions.lock.yml b/.github/workflows/close-old-discussions.lock.yml
index e4fb79db0f6..3d2965f026d 100644
--- a/.github/workflows/close-old-discussions.lock.yml
+++ b/.github/workflows/close-old-discussions.lock.yml
@@ -3358,81 +3358,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4005,6 +3933,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml
index ada41b0e860..131d4707e4d 100644
--- a/.github/workflows/commit-changes-analyzer.lock.yml
+++ b/.github/workflows/commit-changes-analyzer.lock.yml
@@ -3598,81 +3598,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4245,6 +4173,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4260,6 +4359,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4313,13 +4413,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml
index 2a5c45a798d..e4a882ed1ef 100644
--- a/.github/workflows/copilot-agent-analysis.lock.yml
+++ b/.github/workflows/copilot-agent-analysis.lock.yml
@@ -4279,81 +4279,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4926,6 +4854,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4941,6 +5040,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4994,13 +5094,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml
index 5f7c1423dce..a66f44393f6 100644
--- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml
+++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml
@@ -4670,81 +4670,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5317,6 +5245,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5358,7 +5457,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5410,10 +5509,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml
index fc528386358..579bc9b41fb 100644
--- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml
+++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml
@@ -3759,81 +3759,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4406,6 +4334,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4447,7 +4546,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4499,10 +4598,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml
index 7a3cc680677..6b8c92fe393 100644
--- a/.github/workflows/copilot-session-insights.lock.yml
+++ b/.github/workflows/copilot-session-insights.lock.yml
@@ -6219,81 +6219,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -6866,6 +6794,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -6881,6 +6980,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -6934,13 +7034,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml
index a06420c9232..c48eda1da89 100644
--- a/.github/workflows/craft.lock.yml
+++ b/.github/workflows/craft.lock.yml
@@ -4727,81 +4727,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5374,6 +5302,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5415,7 +5514,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5467,10 +5566,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml
index e21afb6a7cb..e6601e7f4ce 100644
--- a/.github/workflows/daily-code-metrics.lock.yml
+++ b/.github/workflows/daily-code-metrics.lock.yml
@@ -4729,81 +4729,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5376,6 +5304,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5391,6 +5490,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5444,13 +5544,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml
index 3d2cc1a979e..ae78fc1f9f2 100644
--- a/.github/workflows/daily-doc-updater.lock.yml
+++ b/.github/workflows/daily-doc-updater.lock.yml
@@ -3396,81 +3396,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4043,6 +3971,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4058,6 +4157,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4111,13 +4211,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml
index 3af34599f1f..e9ba12121ec 100644
--- a/.github/workflows/daily-fact.lock.yml
+++ b/.github/workflows/daily-fact.lock.yml
@@ -3474,81 +3474,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4121,6 +4049,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml
index 6041fde5b67..1b9fb4a7f2b 100644
--- a/.github/workflows/daily-file-diet.lock.yml
+++ b/.github/workflows/daily-file-diet.lock.yml
@@ -3439,81 +3439,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4086,6 +4014,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml
index f9e3e9cc46e..602b957832f 100644
--- a/.github/workflows/daily-firewall-report.lock.yml
+++ b/.github/workflows/daily-firewall-report.lock.yml
@@ -4172,81 +4172,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4819,6 +4747,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4860,7 +4959,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4912,10 +5011,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml
index 0844eafb31f..220e79fc3bb 100644
--- a/.github/workflows/daily-issues-report.lock.yml
+++ b/.github/workflows/daily-issues-report.lock.yml
@@ -4952,81 +4952,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5599,6 +5527,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/daily-malicious-code-scan.lock.yml b/.github/workflows/daily-malicious-code-scan.lock.yml
index cc2c5af3f37..82fb1580d97 100644
--- a/.github/workflows/daily-malicious-code-scan.lock.yml
+++ b/.github/workflows/daily-malicious-code-scan.lock.yml
@@ -3424,81 +3424,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4071,6 +3999,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4112,7 +4211,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4164,10 +4263,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml
index 56f5171cdb6..dcb551c8009 100644
--- a/.github/workflows/daily-multi-device-docs-tester.lock.yml
+++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml
@@ -3298,81 +3298,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3945,6 +3873,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -3960,6 +4059,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4013,13 +4113,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml
index 8970372b478..9b36394aa25 100644
--- a/.github/workflows/daily-news.lock.yml
+++ b/.github/workflows/daily-news.lock.yml
@@ -4727,81 +4727,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5374,6 +5302,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5415,7 +5514,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5467,10 +5566,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml
index 191aec79dec..1100b4d9883 100644
--- a/.github/workflows/daily-performance-summary.lock.yml
+++ b/.github/workflows/daily-performance-summary.lock.yml
@@ -2363,6 +2363,9 @@ jobs:
registerTool("github-pr-query", "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", {"properties":{"jq":{"description":"jq filter expression to apply to output. If not provided, returns schema info instead of full data.","type":"string"},"limit":{"description":"Maximum number of PRs to fetch (default: 30)","type":"number"},"repo":{"description":"Repository in owner/repo format (defaults to current repository)","type":"string"},"state":{"description":"PR state: open, closed, merged, all (default: open)","type":"string"}},"type":"object"}, async (args) => {
try {
const env = { ...process.env };
+ if (args && args["jq"] !== undefined) {
+ env["INPUT_JQ"] = typeof args["jq"] === "object" ? JSON.stringify(args["jq"]) : String(args["jq"]);
+ }
if (args && args["limit"] !== undefined) {
env["INPUT_LIMIT"] = typeof args["limit"] === "object" ? JSON.stringify(args["limit"]) : String(args["limit"]);
}
@@ -2386,6 +2389,9 @@ jobs:
registerTool("github-discussion-query", "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", {"properties":{"jq":{"description":"jq filter expression to apply to output. If not provided, returns schema info instead of full data.","type":"string"},"limit":{"description":"Maximum number of discussions to fetch (default: 30)","type":"number"},"repo":{"description":"Repository in owner/repo format (defaults to current repository)","type":"string"}},"type":"object"}, async (args) => {
try {
const env = { ...process.env };
+ if (args && args["state"] !== undefined) {
+ env["INPUT_STATE"] = typeof args["state"] === "object" ? JSON.stringify(args["state"]) : String(args["state"]);
+ }
if (args && args["jq"] !== undefined) {
env["INPUT_JQ"] = typeof args["jq"] === "object" ? JSON.stringify(args["jq"]) : String(args["jq"]);
}
@@ -4967,81 +4973,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5614,6 +5548,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml
index 0a3640e222e..f0dc31ef94a 100644
--- a/.github/workflows/daily-repo-chronicle.lock.yml
+++ b/.github/workflows/daily-repo-chronicle.lock.yml
@@ -4272,81 +4272,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4919,6 +4847,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4960,7 +5059,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5012,10 +5111,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml
index 422d7349572..19ec38f0fc1 100644
--- a/.github/workflows/daily-team-status.lock.yml
+++ b/.github/workflows/daily-team-status.lock.yml
@@ -3012,81 +3012,8 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3659,6 +3586,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "x" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, 40);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3700,7 +3798,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3752,10 +3850,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml
index bcaa2df126b..45dbcdbb472 100644
--- a/.github/workflows/deep-report.lock.yml
+++ b/.github/workflows/deep-report.lock.yml
@@ -3893,81 +3893,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4540,6 +4468,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml
index cd9c48a3ce9..20eaf907016 100644
--- a/.github/workflows/dependabot-go-checker.lock.yml
+++ b/.github/workflows/dependabot-go-checker.lock.yml
@@ -3719,81 +3719,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4366,6 +4294,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4407,7 +4506,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4459,10 +4558,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml
index 09631dc698b..6dbd3c81df0 100644
--- a/.github/workflows/dev-hawk.lock.yml
+++ b/.github/workflows/dev-hawk.lock.yml
@@ -3625,81 +3625,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4272,6 +4200,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4313,7 +4412,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4365,10 +4464,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml
index e398ff0bde6..bc6ae78ef7b 100644
--- a/.github/workflows/dev.lock.yml
+++ b/.github/workflows/dev.lock.yml
@@ -3099,81 +3099,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3746,6 +3674,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml
index 57c04169bf7..5555466e9b3 100644
--- a/.github/workflows/developer-docs-consolidator.lock.yml
+++ b/.github/workflows/developer-docs-consolidator.lock.yml
@@ -4478,81 +4478,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5125,6 +5053,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5140,6 +5239,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5193,13 +5293,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml
index f93fcb07d63..f0d91044e11 100644
--- a/.github/workflows/dictation-prompt.lock.yml
+++ b/.github/workflows/dictation-prompt.lock.yml
@@ -3128,81 +3128,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3775,6 +3703,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3816,7 +3915,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3868,10 +3967,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml
index d10607465d3..c11fd3befc8 100644
--- a/.github/workflows/docs-noob-tester.lock.yml
+++ b/.github/workflows/docs-noob-tester.lock.yml
@@ -3209,81 +3209,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3856,6 +3784,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3897,7 +3996,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3949,10 +4048,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml
index b504cf87b4d..baf699db448 100644
--- a/.github/workflows/duplicate-code-detector.lock.yml
+++ b/.github/workflows/duplicate-code-detector.lock.yml
@@ -3270,81 +3270,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3917,6 +3845,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/example-permissions-warning.lock.yml b/.github/workflows/example-permissions-warning.lock.yml
index 260028be3e8..7463d90d723 100644
--- a/.github/workflows/example-permissions-warning.lock.yml
+++ b/.github/workflows/example-permissions-warning.lock.yml
@@ -750,81 +750,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -1397,6 +1325,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -1438,7 +1537,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -1490,10 +1589,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml
index a6888679ab3..3e22a2caa8b 100644
--- a/.github/workflows/example-workflow-analyzer.lock.yml
+++ b/.github/workflows/example-workflow-analyzer.lock.yml
@@ -3173,81 +3173,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3820,6 +3748,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -3835,6 +3934,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -3888,13 +3988,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/firewall.lock.yml b/.github/workflows/firewall.lock.yml
index 05c088f2c7d..ecf37502e47 100644
--- a/.github/workflows/firewall.lock.yml
+++ b/.github/workflows/firewall.lock.yml
@@ -813,81 +813,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -1460,6 +1388,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -1501,7 +1600,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -1553,10 +1652,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml
index 1aa555e53fa..bb13fcf54dc 100644
--- a/.github/workflows/github-mcp-structural-analysis.lock.yml
+++ b/.github/workflows/github-mcp-structural-analysis.lock.yml
@@ -4404,81 +4404,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5051,6 +4979,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5066,6 +5165,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5119,13 +5219,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml
index bc8eb88fc33..1c09706c9b2 100644
--- a/.github/workflows/github-mcp-tools-report.lock.yml
+++ b/.github/workflows/github-mcp-tools-report.lock.yml
@@ -4184,81 +4184,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4831,6 +4759,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4846,6 +4945,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4899,13 +4999,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml
index 36da5942d03..b16a984ed49 100644
--- a/.github/workflows/glossary-maintainer.lock.yml
+++ b/.github/workflows/glossary-maintainer.lock.yml
@@ -4179,81 +4179,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4826,6 +4754,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4867,7 +4966,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4919,10 +5018,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml
index f74b0aa73ca..816bad1192b 100644
--- a/.github/workflows/go-fan.lock.yml
+++ b/.github/workflows/go-fan.lock.yml
@@ -3787,81 +3787,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4434,6 +4362,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4449,6 +4548,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4502,13 +4602,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml
index 7028fdbd99d..317207651d9 100644
--- a/.github/workflows/go-logger.lock.yml
+++ b/.github/workflows/go-logger.lock.yml
@@ -3624,81 +3624,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4271,6 +4199,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4286,6 +4385,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4339,13 +4439,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml
index 2718a9b6b58..3799d68d6a0 100644
--- a/.github/workflows/go-pattern-detector.lock.yml
+++ b/.github/workflows/go-pattern-detector.lock.yml
@@ -3312,81 +3312,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3959,6 +3887,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -3974,6 +4073,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4027,13 +4127,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml
index b14bc4f4660..af86a3dd577 100644
--- a/.github/workflows/grumpy-reviewer.lock.yml
+++ b/.github/workflows/grumpy-reviewer.lock.yml
@@ -4532,81 +4532,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5179,6 +5107,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5220,7 +5319,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5272,10 +5371,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml
index 6592ad8593c..ec2ed6d8d05 100644
--- a/.github/workflows/instructions-janitor.lock.yml
+++ b/.github/workflows/instructions-janitor.lock.yml
@@ -3390,81 +3390,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4037,6 +3965,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4052,6 +4151,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4105,13 +4205,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml
index 04de0e8ed1f..2a9464458b4 100644
--- a/.github/workflows/issue-arborist.lock.yml
+++ b/.github/workflows/issue-arborist.lock.yml
@@ -3284,81 +3284,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3931,6 +3859,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml
index 11122adbc31..4c2edeff4a4 100644
--- a/.github/workflows/issue-monster.lock.yml
+++ b/.github/workflows/issue-monster.lock.yml
@@ -3641,81 +3641,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4288,6 +4216,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4329,7 +4428,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4381,10 +4480,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml
index 7a830aa6022..0a0a938c2cd 100644
--- a/.github/workflows/issue-triage-agent.lock.yml
+++ b/.github/workflows/issue-triage-agent.lock.yml
@@ -3295,81 +3295,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3942,6 +3870,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3983,7 +4082,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4035,10 +4134,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml
index e7fb8516399..edc477cec6f 100644
--- a/.github/workflows/lockfile-stats.lock.yml
+++ b/.github/workflows/lockfile-stats.lock.yml
@@ -3832,81 +3832,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4479,6 +4407,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4494,6 +4593,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4547,13 +4647,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml
index 82ed7c521cf..d1f232d28a5 100644
--- a/.github/workflows/mcp-inspector.lock.yml
+++ b/.github/workflows/mcp-inspector.lock.yml
@@ -3803,81 +3803,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4450,6 +4378,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4491,7 +4590,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4543,10 +4642,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml
index c6b65b0f837..0b3a6d875b7 100644
--- a/.github/workflows/mergefest.lock.yml
+++ b/.github/workflows/mergefest.lock.yml
@@ -3894,81 +3894,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4541,6 +4469,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4582,7 +4681,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4634,10 +4733,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml
index 05610474899..f742c5460d4 100644
--- a/.github/workflows/notion-issue-summary.lock.yml
+++ b/.github/workflows/notion-issue-summary.lock.yml
@@ -2797,81 +2797,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3444,6 +3372,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3485,7 +3584,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3537,10 +3636,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml
index c8379de4a4b..02e6db38f41 100644
--- a/.github/workflows/pdf-summary.lock.yml
+++ b/.github/workflows/pdf-summary.lock.yml
@@ -4551,81 +4551,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5198,6 +5126,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5239,7 +5338,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5291,10 +5390,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml
index dbf96bb9bca..0b5796ce38c 100644
--- a/.github/workflows/plan.lock.yml
+++ b/.github/workflows/plan.lock.yml
@@ -3902,81 +3902,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4549,6 +4477,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4590,7 +4689,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4642,10 +4741,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml
index b1cd00a964b..5543ada043e 100644
--- a/.github/workflows/poem-bot.lock.yml
+++ b/.github/workflows/poem-bot.lock.yml
@@ -5601,81 +5601,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -6248,6 +6176,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -6289,7 +6388,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -6341,10 +6440,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml
index 4ae7129a244..f712c5d717b 100644
--- a/.github/workflows/pr-nitpick-reviewer.lock.yml
+++ b/.github/workflows/pr-nitpick-reviewer.lock.yml
@@ -4871,81 +4871,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5518,6 +5446,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5559,7 +5658,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5611,10 +5710,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml
index 0cd30bd1ef8..96be224bc30 100644
--- a/.github/workflows/prompt-clustering-analysis.lock.yml
+++ b/.github/workflows/prompt-clustering-analysis.lock.yml
@@ -5041,81 +5041,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5688,6 +5616,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5703,6 +5802,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5756,13 +5856,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml
index 35b3a33bbd7..da5131bb8eb 100644
--- a/.github/workflows/python-data-charts.lock.yml
+++ b/.github/workflows/python-data-charts.lock.yml
@@ -4901,81 +4901,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5548,6 +5476,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5589,7 +5688,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5641,10 +5740,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml
index fd359250144..5136d5f47cb 100644
--- a/.github/workflows/q.lock.yml
+++ b/.github/workflows/q.lock.yml
@@ -5132,81 +5132,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5779,6 +5707,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5820,7 +5919,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5872,10 +5971,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml
index d7bd79fe032..3d039770874 100644
--- a/.github/workflows/release.lock.yml
+++ b/.github/workflows/release.lock.yml
@@ -3204,81 +3204,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3851,6 +3779,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3892,7 +3991,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3944,10 +4043,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml
index b451db72c9e..7570c05386a 100644
--- a/.github/workflows/repo-tree-map.lock.yml
+++ b/.github/workflows/repo-tree-map.lock.yml
@@ -3196,81 +3196,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3843,6 +3771,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3884,7 +3983,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3936,10 +4035,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml
index 7105329cd4a..94426cccd60 100644
--- a/.github/workflows/repository-quality-improver.lock.yml
+++ b/.github/workflows/repository-quality-improver.lock.yml
@@ -4105,81 +4105,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4752,6 +4680,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4793,7 +4892,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4845,10 +4944,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml
index 39a8cbac5bc..107d9b8e37b 100644
--- a/.github/workflows/research.lock.yml
+++ b/.github/workflows/research.lock.yml
@@ -3058,81 +3058,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3705,6 +3633,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3746,7 +3845,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3798,10 +3897,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml
index be176969be8..3c222d7f64b 100644
--- a/.github/workflows/safe-output-health.lock.yml
+++ b/.github/workflows/safe-output-health.lock.yml
@@ -4064,81 +4064,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4711,6 +4639,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4726,6 +4825,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4779,13 +4879,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml
index 7fd05822e16..e7843262b5d 100644
--- a/.github/workflows/schema-consistency-checker.lock.yml
+++ b/.github/workflows/schema-consistency-checker.lock.yml
@@ -3842,81 +3842,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4489,6 +4417,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4504,6 +4603,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4557,13 +4657,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml
index 3c9a1f0bbe2..ec5898d43d2 100644
--- a/.github/workflows/scout.lock.yml
+++ b/.github/workflows/scout.lock.yml
@@ -5165,81 +5165,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5812,6 +5740,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5827,6 +5926,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5880,13 +5980,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml
index d01d84e57aa..76e8e69307c 100644
--- a/.github/workflows/security-fix-pr.lock.yml
+++ b/.github/workflows/security-fix-pr.lock.yml
@@ -3328,81 +3328,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3975,6 +3903,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -3990,6 +4089,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4043,13 +4143,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml
index 4b7fc416fb1..a901dcaa261 100644
--- a/.github/workflows/semantic-function-refactor.lock.yml
+++ b/.github/workflows/semantic-function-refactor.lock.yml
@@ -4098,81 +4098,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4745,6 +4673,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4760,6 +4859,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4813,13 +4913,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml
index e70f21321fa..33b483010ea 100644
--- a/.github/workflows/smoke-claude.lock.yml
+++ b/.github/workflows/smoke-claude.lock.yml
@@ -4993,81 +4993,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5640,6 +5568,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5655,6 +5754,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5708,13 +5808,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml
index f83e00255f8..de97ec076cc 100644
--- a/.github/workflows/smoke-codex.lock.yml
+++ b/.github/workflows/smoke-codex.lock.yml
@@ -4559,81 +4559,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5206,6 +5134,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCodexLog,
diff --git a/.github/workflows/smoke-copilot-no-firewall.lock.yml b/.github/workflows/smoke-copilot-no-firewall.lock.yml
index b2185c964f7..03ea691047a 100644
--- a/.github/workflows/smoke-copilot-no-firewall.lock.yml
+++ b/.github/workflows/smoke-copilot-no-firewall.lock.yml
@@ -4643,81 +4643,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5290,6 +5218,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5331,7 +5430,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5383,10 +5482,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml
index 7fcb470a6d8..a52b81ab6d8 100644
--- a/.github/workflows/smoke-copilot.lock.yml
+++ b/.github/workflows/smoke-copilot.lock.yml
@@ -4647,81 +4647,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5294,6 +5222,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5335,7 +5434,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5387,10 +5486,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml
index 76f7cbd8979..c2f6ff42b75 100644
--- a/.github/workflows/smoke-detector.lock.yml
+++ b/.github/workflows/smoke-detector.lock.yml
@@ -4781,81 +4781,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5428,6 +5356,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5443,6 +5542,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5496,13 +5596,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/smoke-srt-custom-config.lock.yml b/.github/workflows/smoke-srt-custom-config.lock.yml
index 1e6389c47ad..cb9750a96ee 100644
--- a/.github/workflows/smoke-srt-custom-config.lock.yml
+++ b/.github/workflows/smoke-srt-custom-config.lock.yml
@@ -1063,81 +1063,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -1710,6 +1638,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -1751,7 +1850,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -1803,10 +1902,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/smoke-srt.lock.yml b/.github/workflows/smoke-srt.lock.yml
index 8c6ab6453bc..2f0fde2ea40 100644
--- a/.github/workflows/smoke-srt.lock.yml
+++ b/.github/workflows/smoke-srt.lock.yml
@@ -3081,81 +3081,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3728,6 +3656,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -3769,7 +3868,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -3821,10 +3920,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml
index 03df89a041a..1d3e6592a3b 100644
--- a/.github/workflows/static-analysis-report.lock.yml
+++ b/.github/workflows/static-analysis-report.lock.yml
@@ -3868,81 +3868,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4515,6 +4443,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4530,6 +4629,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4583,13 +4683,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml
index e5c2d7bab6a..0078943fd62 100644
--- a/.github/workflows/super-linter.lock.yml
+++ b/.github/workflows/super-linter.lock.yml
@@ -3342,81 +3342,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -3989,6 +3917,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4030,7 +4129,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4082,10 +4181,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml
index 0ebfc52fee2..665707ad58c 100644
--- a/.github/workflows/technical-doc-writer.lock.yml
+++ b/.github/workflows/technical-doc-writer.lock.yml
@@ -4413,81 +4413,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5060,6 +4988,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -5101,7 +5200,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -5153,10 +5252,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/test-firewall-escape.lock.yml b/.github/workflows/test-firewall-escape.lock.yml
index 424141236ea..c13781a643c 100644
--- a/.github/workflows/test-firewall-escape.lock.yml
+++ b/.github/workflows/test-firewall-escape.lock.yml
@@ -1138,81 +1138,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -1785,6 +1713,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -1826,7 +1925,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -1878,10 +1977,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml
index 56a13ff89f1..5b2f34f46b8 100644
--- a/.github/workflows/tidy.lock.yml
+++ b/.github/workflows/tidy.lock.yml
@@ -3536,81 +3536,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4183,6 +4111,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4224,7 +4323,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4276,10 +4375,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml
index 6714cab0fda..8f08eff0475 100644
--- a/.github/workflows/typist.lock.yml
+++ b/.github/workflows/typist.lock.yml
@@ -4129,81 +4129,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4776,6 +4704,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -4791,6 +4890,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -4844,13 +4944,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml
index 6f6b128c46e..efcc791a527 100644
--- a/.github/workflows/unbloat-docs.lock.yml
+++ b/.github/workflows/unbloat-docs.lock.yml
@@ -4914,81 +4914,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -5561,6 +5489,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseClaudeLog,
@@ -5576,6 +5675,7 @@ jobs:
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
const mcpFailures = [];
@@ -5629,13 +5729,14 @@ jobs:
maxTurnsHit = true;
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml
index 67551490d90..8b7bd5f3138 100644
--- a/.github/workflows/video-analyzer.lock.yml
+++ b/.github/workflows/video-analyzer.lock.yml
@@ -3385,81 +3385,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4032,6 +3960,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4073,7 +4172,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4125,10 +4224,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml
index b1b109314e0..52156abf241 100644
--- a/.github/workflows/weekly-issue-summary.lock.yml
+++ b/.github/workflows/weekly-issue-summary.lock.yml
@@ -4129,81 +4129,9 @@ jobs:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
with:
script: |
- function runLogParser(options) {
- const fs = require("fs");
- const path = require("path");
- const { parseLog, parserName, supportsDirectories = false } = options;
- try {
- const logPath = process.env.GH_AW_AGENT_OUTPUT;
- if (!logPath) {
- core.info("No agent log file specified");
- return;
- }
- if (!fs.existsSync(logPath)) {
- core.info(`Log path not found: ${logPath}`);
- return;
- }
- let content = "";
- const stat = fs.statSync(logPath);
- if (stat.isDirectory()) {
- if (!supportsDirectories) {
- core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
- return;
- }
- const files = fs.readdirSync(logPath);
- const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
- if (logFiles.length === 0) {
- core.info(`No log files found in directory: ${logPath}`);
- return;
- }
- logFiles.sort();
- for (const file of logFiles) {
- const filePath = path.join(logPath, file);
- const fileContent = fs.readFileSync(filePath, "utf8");
- if (content.length > 0 && !content.endsWith("\n")) {
- content += "\n";
- }
- content += fileContent;
- }
- } else {
- content = fs.readFileSync(logPath, "utf8");
- }
- const result = parseLog(content);
- let markdown = "";
- let mcpFailures = [];
- let maxTurnsHit = false;
- if (typeof result === "string") {
- markdown = result;
- } else if (result && typeof result === "object") {
- markdown = result.markdown || "";
- mcpFailures = result.mcpFailures || [];
- maxTurnsHit = result.maxTurnsHit || false;
- }
- if (markdown) {
- core.info(markdown);
- core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
- } else {
- core.error(`Failed to parse ${parserName} log`);
- }
- if (mcpFailures && mcpFailures.length > 0) {
- const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
- }
- if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
- }
- } catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
- }
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- runLogParser,
- };
- }
- const MAX_TOOL_OUTPUT_LENGTH = 500;
- const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+ const MAX_TOOL_OUTPUT_LENGTH = 256;
+ const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+ const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
const SIZE_LIMIT_WARNING = "\n\n⚠️ *Step summary size limit reached. Additional content truncated.*\n\n";
class StepSummaryTracker {
constructor(maxSize = MAX_STEP_SUMMARY_SIZE) {
@@ -4776,6 +4704,177 @@ jobs:
detailsContent = detailsContent.trimEnd();
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+ function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+ const statusIcon = isError ? "✗" : "✓";
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+ return lines.join("\n");
+ }
+ function runLogParser(options) {
+ const fs = require("fs");
+ const path = require("path");
+ const { parseLog, parserName, supportsDirectories = false } = options;
+ try {
+ const logPath = process.env.GH_AW_AGENT_OUTPUT;
+ if (!logPath) {
+ core.info("No agent log file specified");
+ return;
+ }
+ if (!fs.existsSync(logPath)) {
+ core.info(`Log path not found: ${logPath}`);
+ return;
+ }
+ let content = "";
+ const stat = fs.statSync(logPath);
+ if (stat.isDirectory()) {
+ if (!supportsDirectories) {
+ core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`);
+ return;
+ }
+ const files = fs.readdirSync(logPath);
+ const logFiles = files.filter(file => file.endsWith(".log") || file.endsWith(".txt"));
+ if (logFiles.length === 0) {
+ core.info(`No log files found in directory: ${logPath}`);
+ return;
+ }
+ logFiles.sort();
+ for (const file of logFiles) {
+ const filePath = path.join(logPath, file);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ if (content.length > 0 && !content.endsWith("\n")) {
+ content += "\n";
+ }
+ content += fileContent;
+ }
+ } else {
+ content = fs.readFileSync(logPath, "utf8");
+ }
+ const result = parseLog(content);
+ let markdown = "";
+ let mcpFailures = [];
+ let maxTurnsHit = false;
+ let logEntries = null;
+ if (typeof result === "string") {
+ markdown = result;
+ } else if (result && typeof result === "object") {
+ markdown = result.markdown || "";
+ mcpFailures = result.mcpFailures || [];
+ maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
+ }
+ if (markdown) {
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ core.info(`${parserName} log parsed successfully`);
+ }
+ core.summary.addRaw(markdown).write();
+ } else {
+ core.error(`Failed to parse ${parserName} log`);
+ }
+ if (mcpFailures && mcpFailures.length > 0) {
+ const failedServers = mcpFailures.join(", ");
+ core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ }
+ if (maxTurnsHit) {
+ core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ }
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = {
+ runLogParser,
+ };
+ }
function main() {
runLogParser({
parseLog: parseCopilotLog,
@@ -4817,7 +4916,7 @@ jobs:
}
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
const conversationResult = generateConversationMarkdown(logEntries, {
formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
@@ -4869,10 +4968,13 @@ jobs:
return "";
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
function scanForToolErrors(logContent) {
diff --git a/docs/src/content/docs/labs.mdx b/docs/src/content/docs/labs.mdx
index 607839b1daa..c21942c0620 100644
--- a/docs/src/content/docs/labs.mdx
+++ b/docs/src/content/docs/labs.mdx
@@ -37,11 +37,11 @@ These are experimental agentic workflows used by the GitHub Next team to learn,
| [Daily Issues Report Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/daily-issues-report.md) | codex | [](https://github.com/githubnext/gh-aw/actions/workflows/daily-issues-report.lock.yml) | `0 6 * * *` | - |
| [Daily Malicious Code Scan Agent](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/daily-malicious-code-scan.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/daily-malicious-code-scan.lock.yml) | `0 9 * * *` | - |
| [Daily News](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/daily-news.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/daily-news.lock.yml) | `0 9 * * 1-5` | - |
-| [Daily Project Performance Summary Generator (Powered by Skillz)](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/daily-performance-summary.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/daily-performance-summary.lock.yml) | `0 8 * * *` | - |
+| [Daily Project Performance Summary Generator (Using Safe Inputs)](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/daily-performance-summary.md) | codex | [](https://github.com/githubnext/gh-aw/actions/workflows/daily-performance-summary.lock.yml) | `0 8 * * *` | - |
| [Daily Team Status](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/daily-team-status.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/daily-team-status.lock.yml) | - | - |
| [DeepReport - Intelligence Gathering Agent](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/deep-report.md) | codex | [](https://github.com/githubnext/gh-aw/actions/workflows/deep-report.lock.yml) | `0 15 * * 1-5` | - |
| [Dependabot Dependency Checker](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/dependabot-go-checker.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/dependabot-go-checker.lock.yml) | `0 9 * * 1,3,5` | - |
-| [Dev](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/dev.md) | claude | [](https://github.com/githubnext/gh-aw/actions/workflows/dev.lock.yml) | - | - |
+| [Dev](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/dev.md) | codex | [](https://github.com/githubnext/gh-aw/actions/workflows/dev.lock.yml) | - | - |
| [Dev Hawk](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/dev-hawk.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/dev-hawk.lock.yml) | - | - |
| [Developer Documentation Consolidator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/developer-docs-consolidator.md) | claude | [](https://github.com/githubnext/gh-aw/actions/workflows/developer-docs-consolidator.lock.yml) | `17 3 * * *` | - |
| [Dictation Prompt Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/dictation-prompt.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/dictation-prompt.lock.yml) | `0 6 * * 0` | - |
diff --git a/pkg/workflow/js/log_parser_bootstrap.cjs b/pkg/workflow/js/log_parser_bootstrap.cjs
index a4d3dafa79a..188a084edb6 100644
--- a/pkg/workflow/js/log_parser_bootstrap.cjs
+++ b/pkg/workflow/js/log_parser_bootstrap.cjs
@@ -1,13 +1,15 @@
// @ts-check
///
+const { generatePlainTextSummary } = require("./log_parser_shared.cjs");
+
/**
* Bootstrap helper for log parser entry points.
* Handles common logic for environment variable lookup, file existence checks,
* content reading (file or directory), and summary emission.
*
* @param {Object} options - Configuration options
- * @param {function(string): string|{markdown: string, mcpFailures?: string[], maxTurnsHit?: boolean}} options.parseLog - Parser function that takes log content and returns markdown or result object
+ * @param {function(string): string|{markdown: string, mcpFailures?: string[], maxTurnsHit?: boolean, logEntries?: Array}} options.parseLog - Parser function that takes log content and returns markdown or result object
* @param {string} options.parserName - Name of the parser (e.g., "Codex", "Claude", "Copilot")
* @param {boolean} [options.supportsDirectories=false] - Whether the parser supports reading from directories
* @returns {void}
@@ -74,6 +76,7 @@ function runLogParser(options) {
let markdown = "";
let mcpFailures = [];
let maxTurnsHit = false;
+ let logEntries = null;
if (typeof result === "string") {
markdown = result;
@@ -81,12 +84,28 @@ function runLogParser(options) {
markdown = result.markdown || "";
mcpFailures = result.mcpFailures || [];
maxTurnsHit = result.maxTurnsHit || false;
+ logEntries = result.logEntries || null;
}
if (markdown) {
- core.info(markdown);
+ // Generate lightweight plain text summary for core.info instead of full markdown
+ if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
+ // Extract model from init entry if available
+ const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
+ const model = initEntry?.model || null;
+
+ const plainTextSummary = generatePlainTextSummary(logEntries, {
+ model,
+ parserName,
+ });
+ core.info(plainTextSummary);
+ } else {
+ // Fallback: just log success message for parsers without log entries
+ core.info(`${parserName} log parsed successfully`);
+ }
+
+ // Write full markdown to step summary
core.summary.addRaw(markdown).write();
- core.info(`${parserName} log parsed successfully`);
} else {
core.error(`Failed to parse ${parserName} log`);
}
diff --git a/pkg/workflow/js/log_parser_bootstrap.test.cjs b/pkg/workflow/js/log_parser_bootstrap.test.cjs
index 775ae7a1ef3..8fabab010d1 100644
--- a/pkg/workflow/js/log_parser_bootstrap.test.cjs
+++ b/pkg/workflow/js/log_parser_bootstrap.test.cjs
@@ -88,10 +88,10 @@ describe("log_parser_bootstrap.cjs", () => {
});
expect(mockParseLog).toHaveBeenCalledWith(logContent);
- expect(mockCore.info).toHaveBeenCalledWith("## Parsed Log\n\nSuccess!");
+ // When no logEntries are returned (string result), we log success message
+ expect(mockCore.info).toHaveBeenCalledWith("TestParser log parsed successfully");
expect(mockCore.summary.addRaw).toHaveBeenCalledWith("## Parsed Log\n\nSuccess!");
expect(mockCore.summary.write).toHaveBeenCalled();
- expect(mockCore.info).toHaveBeenCalledWith("TestParser log parsed successfully");
// Cleanup
fs.unlinkSync(logFile);
@@ -116,7 +116,8 @@ describe("log_parser_bootstrap.cjs", () => {
parserName: "TestParser",
});
- expect(mockCore.info).toHaveBeenCalledWith("## Result\n");
+ // When no logEntries are returned, we log success message
+ expect(mockCore.info).toHaveBeenCalledWith("TestParser log parsed successfully");
expect(mockCore.summary.addRaw).toHaveBeenCalledWith("## Result\n");
expect(mockCore.setFailed).not.toHaveBeenCalled();
@@ -124,6 +125,42 @@ describe("log_parser_bootstrap.cjs", () => {
fs.rmdirSync(tmpDir);
});
+ it("should generate plain text summary when logEntries are available", () => {
+ const tmpDir = fs.mkdtempSync(path.join(__dirname, "test-"));
+ const logFile = path.join(tmpDir, "test.log");
+ fs.writeFileSync(logFile, "content");
+
+ process.env.GH_AW_AGENT_OUTPUT = logFile;
+
+ const mockLogEntries = [
+ { type: "system", subtype: "init", model: "gpt-5" },
+ { type: "assistant", message: { content: [{ type: "text", text: "Hello" }] } },
+ { type: "result", num_turns: 2, duration_ms: 5000 },
+ ];
+
+ const mockParseLog = vi.fn().mockReturnValue({
+ markdown: "## Result\n",
+ mcpFailures: [],
+ maxTurnsHit: false,
+ logEntries: mockLogEntries,
+ });
+
+ runLogParser({
+ parseLog: mockParseLog,
+ parserName: "TestParser",
+ });
+
+ // Should generate plain text summary instead of success message
+ const infoCall = mockCore.info.mock.calls.find(call => call[0].includes("=== TestParser Execution Summary ==="));
+ expect(infoCall).toBeDefined();
+ expect(infoCall[0]).toContain("Model: gpt-5");
+ expect(infoCall[0]).toContain("Turns: 2");
+ expect(mockCore.summary.addRaw).toHaveBeenCalledWith("## Result\n");
+
+ fs.unlinkSync(logFile);
+ fs.rmdirSync(tmpDir);
+ });
+
it("should handle MCP failures", () => {
const tmpDir = fs.mkdtempSync(path.join(__dirname, "test-"));
const logFile = path.join(tmpDir, "test.log");
diff --git a/pkg/workflow/js/log_parser_shared.cjs b/pkg/workflow/js/log_parser_shared.cjs
index f439e57f110..2f91df82d87 100644
--- a/pkg/workflow/js/log_parser_shared.cjs
+++ b/pkg/workflow/js/log_parser_shared.cjs
@@ -9,15 +9,22 @@
/**
* Maximum length for tool output content in characters.
* Tool output/response sections are truncated to this length to keep step summaries readable.
+ * Reduced from 500 to 256 for more compact output.
*/
-const MAX_TOOL_OUTPUT_LENGTH = 500;
+const MAX_TOOL_OUTPUT_LENGTH = 256;
/**
- * Maximum step summary size in bytes (8MB).
- * GitHub Actions step summaries have a size limit. We stop rendering additional content
- * when approaching this limit to prevent workflow failures.
+ * Maximum step summary size in bytes (1000KB).
+ * GitHub Actions step summaries have a limit of 1024KB. We use 1000KB to leave buffer space.
+ * We stop rendering additional content when approaching this limit to prevent workflow failures.
*/
-const MAX_STEP_SUMMARY_SIZE = 8 * 1024 * 1024;
+const MAX_STEP_SUMMARY_SIZE = 1000 * 1024;
+
+/**
+ * Maximum length for bash command display in plain text summaries.
+ * Commands are truncated to this length for compact display.
+ */
+const MAX_BASH_COMMAND_DISPLAY_LENGTH = 40;
/**
* Warning message shown when step summary size limit is reached.
@@ -854,7 +861,7 @@ function parseLogEntries(logContent) {
* Generic helper to format a tool call as an HTML details section.
* This is a reusable helper for all code engines (Claude, Copilot, Codex).
*
- * Tool output/response content is automatically truncated to MAX_TOOL_OUTPUT_LENGTH (500 chars)
+ * Tool output/response content is automatically truncated to MAX_TOOL_OUTPUT_LENGTH (256 chars)
* to keep step summaries readable and prevent size limit issues.
*
* @param {Object} options - Configuration options
@@ -935,6 +942,130 @@ function formatToolCallAsDetails(options) {
return `\n${fullSummary}
\n\n${detailsContent}\n \n\n`;
}
+/**
+ * Generates a lightweight plain text summary optimized for raw text rendering.
+ * This is designed for console output (core.info) instead of markdown step summaries.
+ *
+ * The output includes:
+ * - A compact header with model info
+ * - Tool/command summary list (status icon + name only)
+ * - Basic execution statistics
+ *
+ * @param {Array} logEntries - Array of log entries with type, message, etc.
+ * @param {Object} options - Configuration options
+ * @param {string} [options.model] - Model name to include in the header
+ * @param {string} [options.parserName] - Name of the parser (e.g., "Copilot", "Claude")
+ * @returns {string} Plain text summary for console output
+ */
+function generatePlainTextSummary(logEntries, options = {}) {
+ const { model, parserName = "Agent" } = options;
+ const lines = [];
+
+ // Header
+ lines.push(`=== ${parserName} Execution Summary ===`);
+ if (model) {
+ lines.push(`Model: ${model}`);
+ }
+ lines.push("");
+
+ // Collect tool usage summary
+ const toolUsePairs = new Map();
+ for (const entry of logEntries) {
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_result" && content.tool_use_id) {
+ toolUsePairs.set(content.tool_use_id, content);
+ }
+ }
+ }
+ }
+
+ // Count tools and gather summary
+ const toolCounts = { total: 0, success: 0, error: 0 };
+ const toolSummary = [];
+
+ for (const entry of logEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "tool_use") {
+ const toolName = content.name;
+ const input = content.input || {};
+
+ // Skip internal tools
+ if (["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"].includes(toolName)) {
+ continue;
+ }
+
+ toolCounts.total++;
+ const toolResult = toolUsePairs.get(content.id);
+ const isError = toolResult?.is_error === true;
+
+ if (isError) {
+ toolCounts.error++;
+ } else {
+ toolCounts.success++;
+ }
+
+ const statusIcon = isError ? "✗" : "✓";
+
+ // Format tool name compactly
+ let displayName;
+ if (toolName === "Bash") {
+ const cmd = formatBashCommand(input.command || "").slice(0, MAX_BASH_COMMAND_DISPLAY_LENGTH);
+ displayName = `bash: ${cmd}`;
+ } else if (toolName.startsWith("mcp__")) {
+ displayName = formatMcpName(toolName);
+ } else {
+ displayName = toolName;
+ }
+
+ // Limit to 20 tool summaries
+ if (toolSummary.length < 20) {
+ toolSummary.push(` [${statusIcon}] ${displayName}`);
+ }
+ }
+ }
+ }
+ }
+
+ // Tool summary section
+ if (toolSummary.length > 0) {
+ lines.push("Tools/Commands:");
+ lines.push(...toolSummary);
+ if (toolCounts.total > 20) {
+ lines.push(` ... and ${toolCounts.total - 20} more`);
+ }
+ lines.push("");
+ }
+
+ // Statistics
+ const lastEntry = logEntries[logEntries.length - 1];
+ lines.push("Statistics:");
+ if (lastEntry?.num_turns) {
+ lines.push(` Turns: ${lastEntry.num_turns}`);
+ }
+ if (lastEntry?.duration_ms) {
+ const duration = formatDuration(lastEntry.duration_ms);
+ if (duration) {
+ lines.push(` Duration: ${duration}`);
+ }
+ }
+ if (toolCounts.total > 0) {
+ lines.push(` Tools: ${toolCounts.success}/${toolCounts.total} succeeded`);
+ }
+ if (lastEntry?.usage) {
+ const usage = lastEntry.usage;
+ if (usage.input_tokens && usage.output_tokens) {
+ lines.push(` Tokens: ${usage.input_tokens.toLocaleString()} in / ${usage.output_tokens.toLocaleString()} out`);
+ }
+ }
+ if (lastEntry?.total_cost_usd) {
+ lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`);
+ }
+
+ return lines.join("\n");
+}
+
// Export functions and constants
module.exports = {
// Constants
@@ -956,4 +1087,5 @@ module.exports = {
formatToolUse,
parseLogEntries,
formatToolCallAsDetails,
+ generatePlainTextSummary,
};
diff --git a/pkg/workflow/js/log_parser_shared.test.cjs b/pkg/workflow/js/log_parser_shared.test.cjs
index 401e8579a36..f9576bf22fb 100644
--- a/pkg/workflow/js/log_parser_shared.test.cjs
+++ b/pkg/workflow/js/log_parser_shared.test.cjs
@@ -1170,12 +1170,12 @@ describe("log_parser_shared.cjs", () => {
sections: [{ label: "Response", content: longContent }],
});
- // Should contain truncated content (500 chars) plus "... (truncated)"
+ // Should contain truncated content (256 chars) plus "... (truncated)"
expect(result).toContain("... (truncated)");
expect(result.length).toBeLessThan(longContent.length + 200);
- // Verify truncation happens at MAX_TOOL_OUTPUT_LENGTH (500)
- expect(MAX_TOOL_OUTPUT_LENGTH).toBe(500);
+ // Verify truncation happens at MAX_TOOL_OUTPUT_LENGTH (256)
+ expect(MAX_TOOL_OUTPUT_LENGTH).toBe(256);
});
it("should allow custom maxContentLength", async () => {
@@ -1270,9 +1270,9 @@ describe("log_parser_shared.cjs", () => {
const tracker = new StepSummaryTracker();
- // Default is 8MB
- expect(MAX_STEP_SUMMARY_SIZE).toBe(8 * 1024 * 1024);
- expect(tracker.maxSize).toBe(8 * 1024 * 1024);
+ // Default is 1000KB
+ expect(MAX_STEP_SUMMARY_SIZE).toBe(1000 * 1024);
+ expect(tracker.maxSize).toBe(1000 * 1024);
});
});
@@ -1327,14 +1327,147 @@ describe("log_parser_shared.cjs", () => {
});
describe("constants", () => {
- it("should export MAX_TOOL_OUTPUT_LENGTH as 500", async () => {
+ it("should export MAX_TOOL_OUTPUT_LENGTH as 256", async () => {
const { MAX_TOOL_OUTPUT_LENGTH } = await import("./log_parser_shared.cjs");
- expect(MAX_TOOL_OUTPUT_LENGTH).toBe(500);
+ expect(MAX_TOOL_OUTPUT_LENGTH).toBe(256);
});
- it("should export MAX_STEP_SUMMARY_SIZE as 8MB", async () => {
+ it("should export MAX_STEP_SUMMARY_SIZE as 1000KB", async () => {
const { MAX_STEP_SUMMARY_SIZE } = await import("./log_parser_shared.cjs");
- expect(MAX_STEP_SUMMARY_SIZE).toBe(8 * 1024 * 1024);
+ expect(MAX_STEP_SUMMARY_SIZE).toBe(1000 * 1024);
+ });
+ });
+
+ describe("generatePlainTextSummary", () => {
+ it("should generate plain text summary with model info", async () => {
+ const { generatePlainTextSummary } = await import("./log_parser_shared.cjs");
+
+ const logEntries = [
+ { type: "system", subtype: "init", model: "gpt-5" },
+ { type: "result", num_turns: 3, duration_ms: 60000 },
+ ];
+
+ const result = generatePlainTextSummary(logEntries, {
+ model: "gpt-5",
+ parserName: "TestParser",
+ });
+
+ expect(result).toContain("=== TestParser Execution Summary ===");
+ expect(result).toContain("Model: gpt-5");
+ expect(result).toContain("Turns: 3");
+ expect(result).toContain("Duration: 1m");
+ });
+
+ it("should include tool usage statistics", async () => {
+ const { generatePlainTextSummary } = await import("./log_parser_shared.cjs");
+
+ const logEntries = [
+ { type: "system", subtype: "init", model: "test-model" },
+ {
+ type: "assistant",
+ message: {
+ content: [
+ { type: "tool_use", id: "1", name: "Bash", input: { command: "echo test" } },
+ { type: "tool_use", id: "2", name: "mcp__github__create_issue", input: {} },
+ ],
+ },
+ },
+ {
+ type: "user",
+ message: {
+ content: [
+ { type: "tool_result", tool_use_id: "1", is_error: false },
+ { type: "tool_result", tool_use_id: "2", is_error: true },
+ ],
+ },
+ },
+ { type: "result", num_turns: 1 },
+ ];
+
+ const result = generatePlainTextSummary(logEntries, { parserName: "Agent" });
+
+ expect(result).toContain("Tools/Commands:");
+ expect(result).toContain("[✓] bash: echo test");
+ expect(result).toContain("[✗] github::create_issue");
+ expect(result).toContain("Tools: 1/2 succeeded");
+ });
+
+ it("should limit tool summaries to 20 items", async () => {
+ const { generatePlainTextSummary } = await import("./log_parser_shared.cjs");
+
+ // Create 25 tool uses
+ const toolUses = [];
+ const toolResults = [];
+ for (let i = 0; i < 25; i++) {
+ toolUses.push({ type: "tool_use", id: `${i}`, name: "Bash", input: { command: `cmd${i}` } });
+ toolResults.push({ type: "tool_result", tool_use_id: `${i}`, is_error: false });
+ }
+
+ const logEntries = [
+ { type: "system", subtype: "init" },
+ { type: "assistant", message: { content: toolUses } },
+ { type: "user", message: { content: toolResults } },
+ { type: "result", num_turns: 1 },
+ ];
+
+ const result = generatePlainTextSummary(logEntries, { parserName: "Agent" });
+
+ expect(result).toContain("... and 5 more");
+ });
+
+ it("should include token usage and cost", async () => {
+ const { generatePlainTextSummary } = await import("./log_parser_shared.cjs");
+
+ const logEntries = [
+ { type: "system", subtype: "init" },
+ {
+ type: "result",
+ num_turns: 2,
+ usage: { input_tokens: 1000, output_tokens: 500 },
+ total_cost_usd: 0.0025,
+ },
+ ];
+
+ const result = generatePlainTextSummary(logEntries, { parserName: "Agent" });
+
+ expect(result).toContain("Tokens: 1,000 in / 500 out");
+ expect(result).toContain("Cost: $0.0025");
+ });
+
+ it("should skip internal tools in summary", async () => {
+ const { generatePlainTextSummary } = await import("./log_parser_shared.cjs");
+
+ const logEntries = [
+ { type: "system", subtype: "init" },
+ {
+ type: "assistant",
+ message: {
+ content: [
+ { type: "tool_use", id: "1", name: "Read", input: {} },
+ { type: "tool_use", id: "2", name: "Write", input: {} },
+ { type: "tool_use", id: "3", name: "Bash", input: { command: "test" } },
+ ],
+ },
+ },
+ {
+ type: "user",
+ message: {
+ content: [
+ { type: "tool_result", tool_use_id: "1", is_error: false },
+ { type: "tool_result", tool_use_id: "2", is_error: false },
+ { type: "tool_result", tool_use_id: "3", is_error: false },
+ ],
+ },
+ },
+ { type: "result" },
+ ];
+
+ const result = generatePlainTextSummary(logEntries, { parserName: "Agent" });
+
+ // Should only show Bash, not Read or Write
+ expect(result).toContain("bash: test");
+ expect(result).not.toContain("Read");
+ expect(result).not.toContain("Write");
});
});
});
diff --git a/pkg/workflow/js/parse_claude_log.cjs b/pkg/workflow/js/parse_claude_log.cjs
index 583cc423016..33d467153b0 100644
--- a/pkg/workflow/js/parse_claude_log.cjs
+++ b/pkg/workflow/js/parse_claude_log.cjs
@@ -21,7 +21,7 @@ function main() {
/**
* Parses Claude log content and converts it to markdown format
* @param {string} logContent - The raw log content as a string
- * @returns {{markdown: string, mcpFailures: string[], maxTurnsHit: boolean}} Result with formatted markdown content, MCP failure list, and max-turns status
+ * @returns {{markdown: string, mcpFailures: string[], maxTurnsHit: boolean, logEntries: Array}} Result with formatted markdown content, MCP failure list, max-turns status, and parsed log entries
*/
function parseClaudeLog(logContent) {
try {
@@ -33,6 +33,7 @@ function parseClaudeLog(logContent) {
markdown: "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n",
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
@@ -107,13 +108,14 @@ function parseClaudeLog(logContent) {
}
}
- return { markdown, mcpFailures, maxTurnsHit };
+ return { markdown, mcpFailures, maxTurnsHit, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
mcpFailures: [],
maxTurnsHit: false,
+ logEntries: [],
};
}
}
diff --git a/pkg/workflow/js/parse_claude_log.test.cjs b/pkg/workflow/js/parse_claude_log.test.cjs
index e989017b9d8..3392dcf423e 100644
--- a/pkg/workflow/js/parse_claude_log.test.cjs
+++ b/pkg/workflow/js/parse_claude_log.test.cjs
@@ -571,11 +571,11 @@ npm warn exec The following package was not found
expect(markdownCall[0]).toContain("🚀 Initialization");
expect(markdownCall[0]).toContain("integration-test");
- // Verify that core.info was also called with the same content (via write helper)
+ // Verify that core.info was called with plain text summary (contains parser name and model info)
expect(mockCore.info).toHaveBeenCalled();
- const infoCall = mockCore.info.mock.calls.find(call => call[0].includes("🚀 Initialization"));
+ const infoCall = mockCore.info.mock.calls.find(call => call[0].includes("=== Claude Execution Summary ==="));
expect(infoCall).toBeDefined();
- expect(infoCall[0]).toContain("integration-test");
+ expect(infoCall[0]).toContain("Model: claude-sonnet-4-20250514");
});
it("should handle log with MCP failures", async () => {
diff --git a/pkg/workflow/js/parse_copilot_log.cjs b/pkg/workflow/js/parse_copilot_log.cjs
index ebf517c6d5d..207f454ae14 100644
--- a/pkg/workflow/js/parse_copilot_log.cjs
+++ b/pkg/workflow/js/parse_copilot_log.cjs
@@ -73,7 +73,7 @@ function parseCopilotLog(logContent) {
}
if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
+ return { markdown: "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n", logEntries: [] };
}
// Generate conversation markdown using shared function
@@ -141,10 +141,13 @@ function parseCopilotLog(logContent) {
},
});
- return markdown;
+ return { markdown, logEntries };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ return {
+ markdown: `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`,
+ logEntries: [],
+ };
}
}
diff --git a/pkg/workflow/js/parse_copilot_log.test.cjs b/pkg/workflow/js/parse_copilot_log.test.cjs
index 85872294574..bb3a14cb459 100644
--- a/pkg/workflow/js/parse_copilot_log.test.cjs
+++ b/pkg/workflow/js/parse_copilot_log.test.cjs
@@ -183,13 +183,13 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(jsonArrayLog);
- expect(result).toContain("🚀 Initialization");
- expect(result).toContain("🤖 Commands and Tools");
- expect(result).toContain("copilot-test-123");
- expect(result).toContain("echo 'Hello World'");
- expect(result).toContain("Total Cost");
- expect(result).toContain("");
- expect(result).toContain("");
+ expect(result.markdown).toContain("🚀 Initialization");
+ expect(result.markdown).toContain("🤖 Commands and Tools");
+ expect(result.markdown).toContain("copilot-test-123");
+ expect(result.markdown).toContain("echo 'Hello World'");
+ expect(result.markdown).toContain("Total Cost");
+ expect(result.markdown).toContain("");
+ expect(result.markdown).toContain("");
});
it("should parse mixed format with debug logs and JSON array", () => {
@@ -200,11 +200,11 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(mixedFormatLog);
- expect(result).toContain("🚀 Initialization");
- expect(result).toContain("🤖 Commands and Tools");
- expect(result).toContain("copilot-456");
- expect(result).toContain("safe_outputs::missing-tool");
- expect(result).toContain("Total Cost");
+ expect(result.markdown).toContain("🚀 Initialization");
+ expect(result.markdown).toContain("🤖 Commands and Tools");
+ expect(result.markdown).toContain("copilot-456");
+ expect(result.markdown).toContain("safe_outputs::missing-tool");
+ expect(result.markdown).toContain("Total Cost");
});
it("should parse mixed format with individual JSON lines (JSONL)", () => {
@@ -218,11 +218,11 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(jsonlFormatLog);
- expect(result).toContain("🚀 Initialization");
- expect(result).toContain("🤖 Commands and Tools");
- expect(result).toContain("copilot-789");
- expect(result).toContain("ls -la");
- expect(result).toContain("Total Cost");
+ expect(result.markdown).toContain("🚀 Initialization");
+ expect(result.markdown).toContain("🤖 Commands and Tools");
+ expect(result.markdown).toContain("copilot-789");
+ expect(result.markdown).toContain("ls -la");
+ expect(result.markdown).toContain("Total Cost");
});
it("should handle tool calls with details in HTML format", () => {
@@ -264,30 +264,30 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(logWithToolOutput);
// Should contain HTML details tag
- expect(result).toContain("");
- expect(result).toContain("");
- expect(result).toContain("
");
- expect(result).toContain(" ");
+ expect(result.markdown).toContain("");
+ expect(result.markdown).toContain("");
+ expect(result.markdown).toContain("
");
+ expect(result.markdown).toContain(" ");
// Summary should contain the command
- expect(result).toContain("cat README.md");
+ expect(result.markdown).toContain("cat README.md");
// Details should contain the output
- expect(result).toContain("Project Title");
+ expect(result.markdown).toContain("Project Title");
// Should use 6 backticks (not 5) for code blocks
- expect(result).toContain("``````json");
- expect(result).toMatch(/``````\n#/); // Response content should start after 6 backticks
+ expect(result.markdown).toContain("``````json");
+ expect(result.markdown).toMatch(/``````\n#/); // Response content should start after 6 backticks
// Should have Parameters and Response sections
- expect(result).toContain("**Parameters:**");
- expect(result).toContain("**Response:**");
+ expect(result.markdown).toContain("**Parameters:**");
+ expect(result.markdown).toContain("**Response:**");
// Parameters should be formatted as JSON
- expect(result).toContain("``````json");
+ expect(result.markdown).toContain("``````json");
// Verify the structure contains both parameter and response sections
- const detailsMatch = result.match(/[\s\S]*?<\/details>/);
+ const detailsMatch = result.markdown.match(/[\s\S]*?<\/details>/);
expect(detailsMatch).toBeDefined();
const detailsContent = detailsMatch[0];
expect(detailsContent).toContain("**Parameters:**");
@@ -328,8 +328,8 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(logWithMcpTools);
- expect(result).toContain("github::create_issue");
- expect(result).toContain("safe_outputs::missing-tool");
+ expect(result.markdown).toContain("github::create_issue");
+ expect(result.markdown).toContain("safe_outputs::missing-tool");
});
it("should handle unrecognized log format", () => {
@@ -337,13 +337,13 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(invalidLog);
- expect(result).toContain("Log format not recognized");
+ expect(result.markdown).toContain("Log format not recognized");
});
it("should handle empty log content", () => {
const result = parseCopilotLog("");
- expect(result).toContain("Log format not recognized");
+ expect(result.markdown).toContain("Log format not recognized");
});
it("should skip internal file operations in summary", () => {
@@ -378,8 +378,8 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(logWithInternalTools);
// Commands and Tools section should only show Bash
- expect(result).toContain("🤖 Commands and Tools");
- const commandsSection = result.split("📊 Information")[0];
+ expect(result.markdown).toContain("🤖 Commands and Tools");
+ const commandsSection = result.markdown.split("📊 Information")[0];
expect(commandsSection).toContain("echo test");
// Read and Write should not be in the summary
expect(commandsSection.split("🤖 Reasoning")[0]).not.toContain("Read");
@@ -404,10 +404,10 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(logWithTextMessage);
// Text should be rendered directly in the Reasoning section
- expect(result).toContain("🤖 Reasoning");
- expect(result).toContain("Let me analyze the code");
- expect(result).toContain("## Analysis");
- expect(result).toContain("could use some improvements");
+ expect(result.markdown).toContain("🤖 Reasoning");
+ expect(result.markdown).toContain("Let me analyze the code");
+ expect(result.markdown).toContain("## Analysis");
+ expect(result.markdown).toContain("could use some improvements");
});
it("should parse debug log format with tool calls and mark them as successful", () => {
@@ -459,18 +459,18 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(debugLogFormat);
// Should successfully parse the debug log format
- expect(result).toContain("🤖 Commands and Tools");
- expect(result).toContain("echo 'Hello World'");
- expect(result).toContain("github::search_issues");
+ expect(result.markdown).toContain("🤖 Commands and Tools");
+ expect(result.markdown).toContain("echo 'Hello World'");
+ expect(result.markdown).toContain("github::search_issues");
// CRITICAL: Tools should be marked as successful (✅) not unknown (❓)
// This is the fix for the issue - parseDebugLogFormat now creates tool_result entries
- expect(result).toContain("✅");
- expect(result).not.toContain("❓ `echo");
- expect(result).not.toContain("❓ `github::search_issues");
+ expect(result.markdown).toContain("✅");
+ expect(result.markdown).not.toContain("❓ `echo");
+ expect(result.markdown).not.toContain("❓ `github::search_issues");
// Check that the tool calls are in the Commands and Tools section with success icon
- const commandsSection = result.split("📊 Information")[0];
+ const commandsSection = result.markdown.split("📊 Information")[0];
expect(commandsSection).toContain("✅ `echo 'Hello World'`");
expect(commandsSection).toContain("✅ `github::search_issues(...)`");
});
@@ -530,10 +530,10 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(debugLogWithModelInfo);
// Should successfully parse and display premium model information
- expect(result).toContain("🚀 Initialization");
- expect(result).toContain("**Model Name:** Claude Sonnet 4 (Anthropic)");
- expect(result).toContain("**Premium Model:** Yes");
- expect(result).toContain("**Required Plans:** pro, pro_plus, max, business, enterprise");
+ expect(result.markdown).toContain("🚀 Initialization");
+ expect(result.markdown).toContain("**Model Name:** Claude Sonnet 4 (Anthropic)");
+ expect(result.markdown).toContain("**Premium Model:** Yes");
+ expect(result.markdown).toContain("**Required Plans:** pro, pro_plus, max, business, enterprise");
});
it("should handle non-premium models in debug logs", () => {
@@ -560,8 +560,8 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(debugLogNonPremium);
// Should display non-premium model information
- expect(result).toContain("**Model Name:** GPT-4o (OpenAI)");
- expect(result).toContain("**Premium Model:** No");
+ expect(result.markdown).toContain("**Model Name:** GPT-4o (OpenAI)");
+ expect(result.markdown).toContain("**Premium Model:** No");
});
it("should handle model info with cost multiplier", () => {
@@ -589,8 +589,8 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(debugLogWithMultiplier);
// Should display cost multiplier
- expect(result).toContain("**Premium Model:** Yes (2.5x cost multiplier)");
- expect(result).toContain("**Required Plans:** enterprise");
+ expect(result.markdown).toContain("**Premium Model:** Yes (2.5x cost multiplier)");
+ expect(result.markdown).toContain("**Required Plans:** enterprise");
});
it("should display premium requests consumed for premium models", () => {
@@ -653,11 +653,11 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(structuredLog);
// Should display premium requests consumed (always 1 per workflow run, regardless of num_turns)
- expect(result).toContain("**Premium Requests Consumed:** 1");
- expect(result).toContain("**Turns:** 5");
- expect(result).toContain("**Token Usage:**");
- expect(result).toContain("- Input: 1,000");
- expect(result).toContain("- Output: 250");
+ expect(result.markdown).toContain("**Premium Requests Consumed:** 1");
+ expect(result.markdown).toContain("**Turns:** 5");
+ expect(result.markdown).toContain("**Token Usage:**");
+ expect(result.markdown).toContain("- Input: 1,000");
+ expect(result.markdown).toContain("- Output: 250");
});
it("should not display premium requests for non-premium models", () => {
@@ -692,9 +692,9 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(structuredLog);
// Should NOT display premium requests consumed
- expect(result).not.toContain("Premium Requests Consumed");
- expect(result).toContain("**Turns:** 3");
- expect(result).toContain("**Token Usage:**");
+ expect(result.markdown).not.toContain("Premium Requests Consumed");
+ expect(result.markdown).toContain("**Turns:** 3");
+ expect(result.markdown).toContain("**Token Usage:**");
});
it("should display 1 premium request consumed regardless of number of turns", () => {
@@ -743,9 +743,9 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(structuredLog);
// Should display 1 premium request consumed (default when not specified in log)
- expect(result).toContain("**Premium Requests Consumed:** 1");
- expect(result).toContain("**Turns:** 17");
- expect(result).toContain("**Token Usage:**");
+ expect(result.markdown).toContain("**Premium Requests Consumed:** 1");
+ expect(result.markdown).toContain("**Turns:** 17");
+ expect(result.markdown).toContain("**Token Usage:**");
});
it("should accumulate token usage across multiple API responses in debug logs", () => {
@@ -791,12 +791,12 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(debugLogWith2Responses);
// Should show accumulated tokens: 100+200=300 input, 50+10=60 output
- expect(result).toContain("**Token Usage:**");
- expect(result).toContain("- Input: 300");
- expect(result).toContain("- Output: 60");
+ expect(result.markdown).toContain("**Token Usage:**");
+ expect(result.markdown).toContain("- Input: 300");
+ expect(result.markdown).toContain("- Output: 60");
// Should have 2 turns
- expect(result).toContain("**Turns:** 2");
+ expect(result.markdown).toContain("**Turns:** 2");
});
it("should extract premium request count from log content using regex", () => {
@@ -836,8 +836,8 @@ More log content
const result = parseCopilotLog(logWithPremiumInfo);
// Should extract 3 from the log content
- expect(result).toContain("**Premium Requests Consumed:** 3");
- expect(result).toContain("**Turns:** 10");
+ expect(result.markdown).toContain("**Premium Requests Consumed:** 3");
+ expect(result.markdown).toContain("**Turns:** 10");
});
});
@@ -910,11 +910,11 @@ More log content
expect(markdownCall[0]).toContain("🚀 Initialization");
expect(markdownCall[0]).toContain("integration-test");
- // Verify that core.info was also called with the same content
+ // Verify that core.info was called with plain text summary (contains parser name and model info)
expect(mockCore.info).toHaveBeenCalled();
- const infoCall = mockCore.info.mock.calls.find(call => call[0].includes("🚀 Initialization"));
+ const infoCall = mockCore.info.mock.calls.find(call => call[0].includes("=== Copilot Execution Summary ==="));
expect(infoCall).toBeDefined();
- expect(infoCall[0]).toContain("integration-test");
+ expect(infoCall[0]).toContain("Model: gpt-5");
});
it("should handle missing log file", async () => {
@@ -967,7 +967,7 @@ More log content
const result = parseCopilotLog(logWithCommand);
// Check that multi-line commands are normalized to single line
- expect(result).toContain("echo 'hello world' && ls -la && pwd");
+ expect(result.markdown).toContain("echo 'hello world' && ls -la && pwd");
});
it("should truncate long strings appropriately", () => {
@@ -993,7 +993,7 @@ More log content
const result = parseCopilotLog(logWithLongCommand);
// Should truncate and add ellipsis
- expect(result).toContain("...");
+ expect(result.markdown).toContain("...");
});
it("should format MCP tool names correctly", () => {
@@ -1017,7 +1017,7 @@ More log content
const result = parseCopilotLog(logWithMcpTool);
- expect(result).toContain("github::create_pull_request");
+ expect(result.markdown).toContain("github::create_pull_request");
});
it("should extract tools from debug log format", () => {
@@ -1096,24 +1096,24 @@ More log content
const result = parseCopilotLog(debugLogWithTools);
// Check that tools were extracted and displayed
- expect(result).toContain("**Available Tools:**");
- expect(result).toContain("bash");
+ expect(result.markdown).toContain("**Available Tools:**");
+ expect(result.markdown).toContain("bash");
// Tools are displayed in formatted form (github::create_issue) not internal form (mcp__github__create_issue)
- expect(result).toContain("github::create_issue");
+ expect(result.markdown).toContain("github::create_issue");
// safe_outputs tools are shown without prefix in Safe Outputs category
- expect(result).toContain("**Safe Outputs:**");
- expect(result).toContain("create_issue");
+ expect(result.markdown).toContain("**Safe Outputs:**");
+ expect(result.markdown).toContain("create_issue");
// Verify tool categories are shown
- expect(result).toContain("**Git/GitHub:**");
- expect(result).toContain("**Builtin:**");
+ expect(result.markdown).toContain("**Git/GitHub:**");
+ expect(result.markdown).toContain("**Builtin:**");
// Check that the model info was extracted
- expect(result).toContain("Claude Sonnet 4.5");
- expect(result).toContain("**Premium Model:** Yes");
+ expect(result.markdown).toContain("Claude Sonnet 4.5");
+ expect(result.markdown).toContain("**Premium Model:** Yes");
// Check that tool calls are displayed
- expect(result).toContain("echo test");
+ expect(result.markdown).toContain("echo test");
});
it("should detect permission denied errors in tool calls from debug logs", () => {
@@ -1171,10 +1171,10 @@ More log content
const result = parseCopilotLog(debugLogWithPermissionError);
// Should detect the permission error and mark the tool call as failed
- expect(result).toContain("github::create_issue");
+ expect(result.markdown).toContain("github::create_issue");
// The tool should be marked with ❌ (failed) instead of ✅ (success)
- const commandsSection = result.split("📊 Information")[0];
+ const commandsSection = result.markdown.split("📊 Information")[0];
expect(commandsSection).toContain("❌");
expect(commandsSection).toContain("❌ `github::create_issue(...)`");
@@ -1216,32 +1216,32 @@ More log content
const result = parseCopilotLog(logWithManyTools);
// Verify all GitHub tools are shown (not just first 3 with "and X more")
- expect(result).toContain("github::create_issue");
- expect(result).toContain("github::list_issues");
- expect(result).toContain("github::get_issue");
- expect(result).toContain("github::create_pull_request");
- expect(result).toContain("github::list_pull_requests");
- expect(result).toContain("github::get_pull_request");
- expect(result).toContain("github::create_discussion");
- expect(result).toContain("github::list_discussions");
+ expect(result.markdown).toContain("github::create_issue");
+ expect(result.markdown).toContain("github::list_issues");
+ expect(result.markdown).toContain("github::get_issue");
+ expect(result.markdown).toContain("github::create_pull_request");
+ expect(result.markdown).toContain("github::list_pull_requests");
+ expect(result.markdown).toContain("github::get_pull_request");
+ expect(result.markdown).toContain("github::create_discussion");
+ expect(result.markdown).toContain("github::list_discussions");
// Verify safe_outputs tools are shown (without prefix, in Safe Outputs category)
- expect(result).toContain("**Safe Outputs:**");
- expect(result).toContain("create_issue");
- expect(result).toContain("add-comment");
+ expect(result.markdown).toContain("**Safe Outputs:**");
+ expect(result.markdown).toContain("create_issue");
+ expect(result.markdown).toContain("add-comment");
// Verify file operations are shown
- expect(result).toContain("Read");
- expect(result).toContain("Write");
- expect(result).toContain("Edit");
- expect(result).toContain("LS");
- expect(result).toContain("Grep");
+ expect(result.markdown).toContain("Read");
+ expect(result.markdown).toContain("Write");
+ expect(result.markdown).toContain("Edit");
+ expect(result.markdown).toContain("LS");
+ expect(result.markdown).toContain("Grep");
// Verify Bash is shown
- expect(result).toContain("Bash");
+ expect(result.markdown).toContain("Bash");
// Ensure we don't have "and X more" text in the tools list (the pattern used to truncate tool lists)
- const toolsSection = result.split("## 🤖 Reasoning")[0];
+ const toolsSection = result.markdown.split("## 🤖 Reasoning")[0];
expect(toolsSection).not.toMatch(/and \d+ more/);
});
});
diff --git a/pkg/workflow/neutral_tools_integration_test.go b/pkg/workflow/neutral_tools_integration_test.go
index b2e37fde74e..686db4c5b4b 100644
--- a/pkg/workflow/neutral_tools_integration_test.go
+++ b/pkg/workflow/neutral_tools_integration_test.go
@@ -97,9 +97,14 @@ Test workflow with neutral tools format.
}
}
- // Verify that the old format is not present in the compiled output (excluding comments)
- if strings.Contains(yamlContentNoComments, "bash:") || strings.Contains(yamlContentNoComments, "web-fetch:") {
- t.Error("Compiled YAML should not contain neutral tool names directly")
+ // Verify that the old format is not present as YAML keys in the compiled output (excluding comments)
+ // The check is for YAML keys specifically, not string literals in bundled JavaScript code
+ // YAML keys will have format like " bash:" or "\nbash:" at the start of a line
+ if strings.Contains(yamlContentNoComments, "\n bash:") ||
+ strings.Contains(yamlContentNoComments, "\nbash:") ||
+ strings.Contains(yamlContentNoComments, "\n web-fetch:") ||
+ strings.Contains(yamlContentNoComments, "\nweb-fetch:") {
+ t.Error("Compiled YAML should not contain neutral tool names as YAML keys")
}
}
diff --git a/pkg/workflow/test_data/expected_claude_baseline.md b/pkg/workflow/test_data/expected_claude_baseline.md
index d8389baf600..6ed1e40d2b8 100644
--- a/pkg/workflow/test_data/expected_claude_baseline.md
+++ b/pkg/workflow/test_data/expected_claude_baseline.md
@@ -44,13 +44,7 @@ I'll help you try to call the `draw_pelican` tool. Let me first check if there's
5→
6→## Steps to Complete
7→1. ✓ Check for existing plan (found previous attempt)
- 8→2. ✓ Read existing plan
- 9→3. ✓ Confirm tool is missing
- 10→4. ✓ Report missing tool using safe-outputs
- 11→
- 12→## Discoveries
- 13→- Previous attempt confirmed `draw_pelican` tool does not exist
- 14→- Need to report missing functionali... (truncated)
+ 8→2. ✓ Read e... (truncated)
``````
@@ -80,13 +74,7 @@ The file /tmp/gh-aw/cache-memory/plan.md has been updated. Here's the result of
1→# Execution Plan: Draw Pelican Tool
2→
3→## Objective
- 4→Try to call a tool called `draw_pelican` that draws a pelican.
- 5→
- 6→## Steps to Complete
- 7→1. ✓ Check for existing plan (found previous attempt)
- 8→2. ✓ Read existing plan
- 9→3. ✓ Confirm tool is missing (attempted call - tool does not exist)
- 10→4. ✓ Report missing... (truncated)
+ 4→Try to call a tool called `draw_pelican` that dr... (truncated)
``````