diff --git a/actions/setup/js/copilot_session_bootstrap.test.cjs b/actions/setup/js/copilot_session_bootstrap.test.cjs index 432c0cce8a9..73b1f0e6e4c 100644 --- a/actions/setup/js/copilot_session_bootstrap.test.cjs +++ b/actions/setup/js/copilot_session_bootstrap.test.cjs @@ -70,6 +70,34 @@ describe("Copilot retry session bootstrap", () => { const run = () => runLogParser({ parseLog: parseCopilotLog, parserName: "Copilot", supportsDirectories: true, artifactDir: root }); + it.each([false, true])("publishes Unified session as a top-level disclosure with safe outputs enabled: %s", async safeOutputs => { + writeSession("session", session("session", "2026-10-03T18:23:15Z", { tool: true })); + if (safeOutputs) { + const outputs = path.join(root, "safe-outputs.jsonl"); + fs.writeFileSync(outputs, serialize([{ type: "create_issue", title: "Test issue", body: "Test body" }])); + process.env.GH_AW_SAFE_OUTPUTS = outputs; + } + + await run(); + + expect(core.setFailed).not.toHaveBeenCalled(); + expect(core.summary.addRaw).toHaveBeenCalledOnce(); + const markdown = core.summary.addRaw.mock.calls[0][0]; + expect(markdown).toMatch(/^
Unified session<\/summary>\n\n/); + expect(markdown.match(/Unified session<\/summary>/g)).toHaveLength(1); + expect(markdown).not.toContain("Agentic Conversation"); + expect(markdown).toContain("retained result"); + let depth = 0; + for (const match of markdown.matchAll(/|<\/details>|(Unified session|Safe Outputs)<\/summary>/g)) { + if (match[0].startsWith("") depth--; + else expect(depth).toBe(1); + expect(depth).toBeGreaterThanOrEqual(0); + } + expect(depth).toBe(0); + if (safeOutputs) expect(markdown).toContain("Safe Outputs"); + }); + it("persists every retry conversation with source provenance but accounts only the chronological final attempt", async () => { writeSession("z-first", session("first", "2026-10-03T18:23:15Z", { inputTokens: 100, tool: true })); writeSession("a-last", session("last", "2026-10-03T18:23:23Z")); diff --git a/actions/setup/js/log_parser_bootstrap.cjs b/actions/setup/js/log_parser_bootstrap.cjs index 75a5b80f8a2..4b1633e65a3 100644 --- a/actions/setup/js/log_parser_bootstrap.cjs +++ b/actions/setup/js/log_parser_bootstrap.cjs @@ -12,6 +12,7 @@ const { writeSessionArtifact } = require("./session_artifact.cjs"); const { collectCodexJSONRecords } = require("./codex_log_framing.cjs"); const { collectAgentExecution, parseAgentExitCode, isAgentExecutionEvent } = require("./agent_execution.cjs"); const { hasCopilotConversation, hasMalformedJsonl, deduplicateCopilotFallback } = require("./copilot_session.cjs"); +const { isUnifiedSessionTrace } = require("./unified_session_render.cjs"); const INFERENCE_ACCESS_ERROR_PATTERN = /Access denied by policy settings|invalid access to inference/i; const CLAUDE_RATE_LIMIT_PATTERN = /rate_limit_error|429 Too Many Requests|"api_error_status"\s*:\s*429|request rejected \(429\)|rate limit/i; const CLAUDE_OVERLOAD_PATTERN = /overloaded_error|"overloaded"/i; @@ -532,11 +533,12 @@ async function runLogParser(options) { parserName, }); - // Wrap the agent log in a details/summary section (open by default) - const wrappedAgentLog = wrapAgentLogInSection(copilotCliStyleMarkdown, { - parserName, - open: true, - }); + const wrappedAgentLog = isUnifiedSessionTrace(publicationEntries) + ? copilotCliStyleMarkdown + : wrapAgentLogInSection(copilotCliStyleMarkdown, { + parserName, + open: true, + }); // Add safe outputs preview to step summary let fullMarkdown = wrappedAgentLog; diff --git a/actions/setup/js/session_cli.test.cjs b/actions/setup/js/session_cli.test.cjs index 4557b0afe2e..2a537aedd5d 100644 --- a/actions/setup/js/session_cli.test.cjs +++ b/actions/setup/js/session_cli.test.cjs @@ -46,7 +46,7 @@ describe("Session CLI adapter", () => { ]); const file = write("usage/aw_session.jsonl", sessionCLI(["reconstruct", root])); const markdown = sessionCLI(["markdown", file]); - expect(markdown).toContain("Unified session"); + expect(markdown).toMatch(/^
Unified session<\/summary>\n\n/); expect(markdown).toContain("Visible assistant response"); expect(markdown).toContain("tool.execution_start"); expect(markdown).not.toContain("private user prompt"); diff --git a/actions/setup/js/unified_session_render.test.cjs b/actions/setup/js/unified_session_render.test.cjs index 301cf1fcc9f..3a30f43f125 100644 --- a/actions/setup/js/unified_session_render.test.cjs +++ b/actions/setup/js/unified_session_render.test.cjs @@ -341,7 +341,7 @@ describe("unified session publication views", () => { expect(trace).toEqual(original); const markdown = generateCopilotCliStyleSummary(trace); expect(markdown).toContain("Done
"); - expect(markdown).toContain("
Unified session"); + expect(markdown).toMatch(/^
Unified session<\/summary>\n\n/); expect(markdown).not.toContain("### Unified session"); expect(markdown).not.toContain("=== Unified session ==="); expect(markdown).not.toContain("| Time |"); diff --git a/docs/src/content/docs/specs/unified-agent-session-specification.md b/docs/src/content/docs/specs/unified-agent-session-specification.md index 6b47dbf3a57..fa2db5a29b7 100644 --- a/docs/src/content/docs/specs/unified-agent-session-specification.md +++ b/docs/src/content/docs/specs/unified-agent-session-specification.md @@ -1419,13 +1419,14 @@ envelope's target `agentId` does not identify the emitting caller. Readers must not invent parent tool IDs to distinguish these scopes; a view containing only one nested scope can select that scope's initialization and accounting explicitly. -Actions publishes this view once in a plain-titled `Unified session` disclosure, +Actions publishes this view once in a top-level `Unified session` disclosure, using a fenced CLI-style transcript like the Claude conversation summary rather than a Markdown table. Opening the disclosure shows source-local conversations with paired tool outcomes; per-source statistics and the full chronological -timeline remain in separate, collapsed disclosures. Gateway diagnostics do not -append a second unified event timeline, and the disclosure does not repeat its -title as a standalone heading. +timeline remain in separate, collapsed disclosures. The log parser does not wrap +this view in an additional `Agentic Conversation` disclosure. Gateway diagnostics +do not append a second unified event timeline, and the disclosure does not repeat +its title as a standalone heading. | Observation | Unified display projection | | --- | --- |