diff --git a/actions/setup/js/log_parser_bootstrap.cjs b/actions/setup/js/log_parser_bootstrap.cjs
index 2cd007a9de5..93b07d9c61f 100644
--- a/actions/setup/js/log_parser_bootstrap.cjs
+++ b/actions/setup/js/log_parser_bootstrap.cjs
@@ -280,10 +280,10 @@ async function runLogParser(options) {
parserName,
});
- // Wrap the agent log in a details/summary section (open by default)
+ // Wrap rendered summary in a closed details/summary section to reduce initial visual clutter.
const wrappedAgentLog = wrapAgentLogInSection(copilotCliStyleMarkdown, {
parserName,
- open: true,
+ open: false,
});
// Add safe outputs preview to step summary
@@ -313,10 +313,10 @@ async function runLogParser(options) {
}
}
- // Wrap the original markdown in a details/summary section (open by default)
+ // Wrap fallback markdown in a closed details/summary section to reduce initial visual clutter.
const wrappedAgentLog = wrapAgentLogInSection(markdown, {
parserName,
- open: true,
+ open: false,
});
// Write wrapped markdown to step summary if available
diff --git a/actions/setup/js/log_parser_bootstrap.test.cjs b/actions/setup/js/log_parser_bootstrap.test.cjs
index f8e703f1b59..651773e358f 100644
--- a/actions/setup/js/log_parser_bootstrap.test.cjs
+++ b/actions/setup/js/log_parser_bootstrap.test.cjs
@@ -46,7 +46,7 @@ describe("log_parser_bootstrap.cjs", () => {
(runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }),
expect(mockParseLog).toHaveBeenCalledWith("Test log content"),
expect(mockCore.info).toHaveBeenCalledWith("TestParser log parsed successfully"),
- expect(mockCore.summary.addRaw).toHaveBeenCalledWith("\nAgentic Conversation
\n\n## Parsed Log\n\nSuccess!\n "),
+ expect(mockCore.summary.addRaw).toHaveBeenCalledWith("\nAgentic Conversation
\n\n## Parsed Log\n\nSuccess!\n "),
expect(mockCore.summary.write).toHaveBeenCalled(),
fs.unlinkSync(logFile),
fs.rmdirSync(tmpDir));
@@ -58,7 +58,7 @@ describe("log_parser_bootstrap.cjs", () => {
const mockParseLog = vi.fn().mockReturnValue({ markdown: "## Result\n", mcpFailures: [], maxTurnsHit: !1 });
(runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }),
expect(mockCore.info).toHaveBeenCalledWith("TestParser log parsed successfully"),
- expect(mockCore.summary.addRaw).toHaveBeenCalledWith("\nAgentic Conversation
\n\n## Result\n\n "),
+ expect(mockCore.summary.addRaw).toHaveBeenCalledWith("\nAgentic Conversation
\n\n## Result\n\n "),
expect(mockCore.setFailed).not.toHaveBeenCalled(),
fs.unlinkSync(logFile),
fs.rmdirSync(tmpDir));
diff --git a/actions/setup/js/log_parser_format.cjs b/actions/setup/js/log_parser_format.cjs
index 2ae3fa29438..ed0aaa02e18 100644
--- a/actions/setup/js/log_parser_format.cjs
+++ b/actions/setup/js/log_parser_format.cjs
@@ -57,6 +57,9 @@ function createLogParserFormatters(deps) {
} = deps;
const INTERNAL_TOOLS = ["Read", "Write", "Edit", "MultiEdit", "LS", "Grep", "Glob", "TodoWrite"];
+ const AWF_TOKEN_WARNING_RE = /\[AWF TOKEN WARNING\][^\n\r]+/g;
+ const AWF_STEERING_MESSAGE_RE = /Agent is still running\.[^\n\r]*A completion notification will arrive as a new turn[^.\n\r]*\.?/g;
+ const AWF_WAITING_GUIDANCE_RE = /Consider telling the user you're waiting[^.\n\r]*\.?/g;
/**
* Selects an outer markdown code fence that is longer than any backtick run
@@ -500,6 +503,69 @@ function createLogParserFormatters(deps) {
appendConversationLine(lines, "", state);
}
+ function collectAwfSteeringMessages(renderEntries) {
+ /** @type {string[]} */
+ const messages = [];
+ const seen = new Set();
+
+ const addMatches = (value, pattern) => {
+ if (typeof value !== "string") return;
+ const matches = value.match(pattern);
+ if (!matches) return;
+ for (const match of matches) {
+ const normalized = match.trim();
+ if (!normalized || seen.has(normalized)) continue;
+ seen.add(normalized);
+ messages.push(normalized);
+ }
+ };
+
+ const addFromValue = value => {
+ if (typeof value !== "string") return;
+ addMatches(value, AWF_TOKEN_WARNING_RE);
+ addMatches(value, AWF_STEERING_MESSAGE_RE);
+ addMatches(value, AWF_WAITING_GUIDANCE_RE);
+ };
+
+ for (const entry of renderEntries) {
+ if (entry.type === "assistant" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type === "text") addFromValue(content.text);
+ if (content.type === "thinking") addFromValue(content.thinking);
+ }
+ }
+ if (entry.type === "user" && entry.message?.content) {
+ for (const content of entry.message.content) {
+ if (content.type !== "tool_result") continue;
+ if (typeof content.content === "string") {
+ addFromValue(content.content);
+ continue;
+ }
+ if (content.content && typeof content.content === "object") {
+ try {
+ addFromValue(JSON.stringify(content.content));
+ } catch {
+ // ignore non-serializable content
+ }
+ }
+ }
+ }
+ }
+
+ return messages;
+ }
+
+ function appendAwfSteering(lines, messages) {
+ if (!Array.isArray(messages) || messages.length === 0) {
+ return;
+ }
+ lines.push("AWF Steering:");
+ for (const message of messages) {
+ lines.push(` - ${message}`);
+ }
+ lines.push("");
+ }
+
function appendStatistics(lines, logEntries, toolUsePairs) {
const lastEntry = logEntries[logEntries.length - 1];
lines.push("Statistics:");
@@ -559,6 +625,7 @@ function createLogParserFormatters(deps) {
const renderEntries = normalizeEntriesForRendering(logEntries);
const lines = [];
const toolUsePairs = collectToolUsePairs(renderEntries);
+ const awfSteeringMessages = collectAwfSteeringMessages(renderEntries);
const state = {
conversationLineCount: 0,
@@ -604,6 +671,7 @@ function createLogParserFormatters(deps) {
lines.push("");
}
+ appendAwfSteering(lines, awfSteeringMessages);
appendStatistics(lines, renderEntries, toolUsePairs);
return lines;
diff --git a/actions/setup/js/parse_copilot_log.cjs b/actions/setup/js/parse_copilot_log.cjs
index c94b994e0d5..a1759fdfa44 100644
--- a/actions/setup/js/parse_copilot_log.cjs
+++ b/actions/setup/js/parse_copilot_log.cjs
@@ -22,6 +22,8 @@ const main = createEngineLogParser({
});
const AWF_TOKEN_WARNING_RE = /\[AWF TOKEN WARNING\][^\n\r]+/g;
+const AWF_STEERING_MESSAGE_RE = /Agent is still running\.[^\n\r]*A completion notification will arrive as a new turn[^.\n\r]*\.?/g;
+const AWF_WAITING_GUIDANCE_RE = /Consider telling the user you're waiting[^.\n\r]*\.?/g;
/**
* Extracts AWF token steering warnings from parsed Copilot log entries.
@@ -35,9 +37,9 @@ function extractAwfTokenWarnings(logEntries) {
const warnings = [];
const seen = new Set();
- const addMatches = value => {
+ const addMatches = (value, pattern) => {
if (typeof value !== "string") return;
- const matches = value.match(AWF_TOKEN_WARNING_RE);
+ const matches = value.match(pattern);
if (!matches) return;
for (const match of matches) {
const normalized = match.trim();
@@ -50,7 +52,9 @@ function extractAwfTokenWarnings(logEntries) {
const visit = value => {
if (!value) return;
if (typeof value === "string") {
- addMatches(value);
+ addMatches(value, AWF_TOKEN_WARNING_RE);
+ addMatches(value, AWF_STEERING_MESSAGE_RE);
+ addMatches(value, AWF_WAITING_GUIDANCE_RE);
return;
}
if (Array.isArray(value)) {
diff --git a/actions/setup/js/parse_copilot_log.test.cjs b/actions/setup/js/parse_copilot_log.test.cjs
index 28acbddbd8a..1cc2998bf88 100644
--- a/actions/setup/js/parse_copilot_log.test.cjs
+++ b/actions/setup/js/parse_copilot_log.test.cjs
@@ -153,6 +153,20 @@ describe("parse_copilot_log.cjs", () => {
expect(result.markdown).toContain("fileB.txt");
});
+ it("renders AWF steering guidance from Copilot SDK events.jsonl tool results", () => {
+ const eventsLog = [
+ '{"type":"user.message","timestamp":"2026-06-05T00:44:01.367Z","data":{}}',
+ '{"type":"tool.execution_start","timestamp":"2026-06-05T00:44:04.520Z","data":{"toolName":"read_agent","mcpServerName":""}}',
+ '{"type":"tool.execution_complete","timestamp":"2026-06-05T00:44:04.700Z","data":{"toolName":"read_agent","mcpServerName":"","success":true,"result":{"content":"Agent is still running. agent_id: helper. Consider telling the user you\'re waiting, then end your response with no further tool calls. A completion notification will arrive as a new turn; no need to poll or redo its work."}}}',
+ '{"type":"assistant.message","timestamp":"2026-06-05T00:44:59.769Z","data":{"content":"Waiting for sub-agent completion."}}',
+ ].join("\n");
+
+ const result = parseCopilotLog(eventsLog);
+
+ expect(result.markdown).toContain("Agent is still running.");
+ expect(result.markdown).toContain("A completion notification will arrive as a new turn");
+ });
+
it("should handle tool calls with details in HTML format", () => {
const logWithHtmlDetails = JSON.stringify([
{ type: "system", subtype: "init", session_id: "html-test", tools: ["Bash"], model: "gpt-5" },
@@ -449,7 +463,6 @@ describe("parse_copilot_log.cjs", () => {
const result = parseCopilotLog(structuredLog);
- expect(result.markdown).toContain("Firewall Steering");
expect(result.markdown).toContain("[AWF TOKEN WARNING] You have used 90% of your effective token budget.");
});
});
@@ -477,6 +490,34 @@ describe("parse_copilot_log.cjs", () => {
}
});
+ it("includes AWF steering guidance in plain logs and step summary", async () => {
+ const steeringEventsLog = [
+ '{"type":"user.message","timestamp":"2026-06-05T00:44:01.367Z","data":{}}',
+ '{"type":"tool.execution_start","timestamp":"2026-06-05T00:44:04.520Z","data":{"toolName":"read_agent","mcpServerName":""}}',
+ '{"type":"tool.execution_complete","timestamp":"2026-06-05T00:44:04.700Z","data":{"toolName":"read_agent","mcpServerName":"","success":true,"result":{"content":"Agent is still running. agent_id: helper. Consider telling the user you\'re waiting, then end your response with no further tool calls. A completion notification will arrive as a new turn; no need to poll or redo its work."}}}',
+ '{"type":"assistant.message","timestamp":"2026-06-05T00:44:59.769Z","data":{"content":"Waiting for sub-agent completion."}}',
+ ].join("\n");
+
+ const tempFile = path.join(process.cwd(), `test_log_${Date.now()}.jsonl`);
+ fs.writeFileSync(tempFile, steeringEventsLog);
+ process.env.GH_AW_AGENT_OUTPUT = tempFile;
+
+ try {
+ await main();
+
+ const summaryText = String(mockCore.summary.addRaw.mock.calls[0]?.[0] || "");
+ expect(summaryText).toContain("AWF Steering");
+ expect(summaryText).toContain("Agent is still running.");
+
+ const hasSteeringInInfo = mockCore.info.mock.calls.some(([message]) => String(message).includes("AWF Steering"));
+ expect(hasSteeringInInfo).toBe(true);
+ } finally {
+ if (fs.existsSync(tempFile)) {
+ fs.unlinkSync(tempFile);
+ }
+ }
+ });
+
it("should handle missing log file", async () => {
process.env.GH_AW_AGENT_OUTPUT = "/nonexistent/file.log";
await main();