From 85f4111cdf6f6cfaab76e04008ad1687dc6e6c31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:38:55 +0000 Subject: [PATCH 1/2] chore: initial progress placeholder Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ea26947-eade-4fa3-82a0-8c31a58d544b --- .github/aw/actions-lock.json | 5 +++++ .github/workflows/smoke-claude.lock.yml | 4 ++-- pkg/workflow/data/action_pins.json | 11 +++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index b9e919b7cd5..81223426c5b 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -148,6 +148,11 @@ "version": "v4.34.1", "sha": "cb06a0a8527b2c6970741b3a0baa15231dc74a4c" }, + "github/gh-aw-actions/setup@v0": { + "repo": "github/gh-aw-actions/setup", + "version": "v0", + "sha": "50f4fc16883c6c6672d8879affa8fd15d5cc79a4" + }, "github/gh-aw-actions/setup@v0.62.5": { "repo": "github/gh-aw-actions/setup", "version": "v0.62.5", diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index b3380d0afd7..ff7ca62aae6 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -650,7 +650,7 @@ jobs: run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh - name: Install and pack APM dependencies id: apm_pack - uses: microsoft/apm-action@83d54a6c7941049210433b16c8dfac573665b12a # v1.3.4 + uses: microsoft/apm-action@cc84c04bc73e19e35527f1efa34ea003be9f037f # v1.4.0 env: GITHUB_TOKEN: ${{ secrets.GH_AW_PLUGINS_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: @@ -819,7 +819,7 @@ jobs: name: apm path: /tmp/gh-aw/apm-bundle - name: Restore APM dependencies - uses: microsoft/apm-action@83d54a6c7941049210433b16c8dfac573665b12a # v1.3.4 + uses: microsoft/apm-action@cc84c04bc73e19e35527f1efa34ea003be9f037f # v1.4.0 with: bundle: /tmp/gh-aw/apm-bundle/*.tar.gz apm-version: ${{ env.GH_AW_INFO_APM_VERSION }} diff --git a/pkg/workflow/data/action_pins.json b/pkg/workflow/data/action_pins.json index 9056a621b72..b9e919b7cd5 100644 --- a/pkg/workflow/data/action_pins.json +++ b/pkg/workflow/data/action_pins.json @@ -148,11 +148,6 @@ "version": "v4.34.1", "sha": "cb06a0a8527b2c6970741b3a0baa15231dc74a4c" }, - "github/gh-aw-actions/setup@v0": { - "repo": "github/gh-aw-actions/setup", - "version": "v0", - "sha": "50f4fc16883c6c6672d8879affa8fd15d5cc79a4" - }, "github/gh-aw-actions/setup@v0.62.5": { "repo": "github/gh-aw-actions/setup", "version": "v0.62.5", @@ -168,10 +163,10 @@ "version": "v2.10.3", "sha": "9cd1b7bf3f36d5a3c3b17abc3545bfb5481912ea" }, - "microsoft/apm-action@v1.3.4": { + "microsoft/apm-action@v1.4.0": { "repo": "microsoft/apm-action", - "version": "v1.3.4", - "sha": "83d54a6c7941049210433b16c8dfac573665b12a" + "version": "v1.4.0", + "sha": "cc84c04bc73e19e35527f1efa34ea003be9f037f" }, "oven-sh/setup-bun@v2.2.0": { "repo": "oven-sh/setup-bun", From e7409f6dddff0ef461a877151fca039fc9ea8c08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:48:37 +0000 Subject: [PATCH 2/2] feat: render all rpc-messages.jsonl message types in gateway preview step summary Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ea26947-eade-4fa3-82a0-8c31a58d544b --- actions/setup/js/parse_mcp_gateway_log.cjs | 142 ++++++++++++++- .../setup/js/parse_mcp_gateway_log.test.cjs | 165 +++++++++++++++++- 2 files changed, 303 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/parse_mcp_gateway_log.cjs b/actions/setup/js/parse_mcp_gateway_log.cjs index 367f5752201..24751b9c0a4 100644 --- a/actions/setup/js/parse_mcp_gateway_log.cjs +++ b/actions/setup/js/parse_mcp_gateway_log.cjs @@ -85,6 +85,119 @@ function generateDifcFilteredSummary(filteredEvents) { return lines.join("\n"); } +/** + * Parses rpc-messages.jsonl content and returns entries categorized by type. + * DIFC_FILTERED entries are excluded here because they are handled separately + * by parseGatewayJsonlForDifcFiltered. + * @param {string} jsonlContent - The rpc-messages.jsonl file content + * @returns {{requests: Array, responses: Array, other: Array}} + */ +function parseRpcMessagesJsonl(jsonlContent) { + const requests = []; + const responses = []; + const other = []; + + const lines = jsonlContent.split("\n"); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + try { + const entry = JSON.parse(trimmed); + if (!entry || typeof entry !== "object" || !entry.type) continue; + + if (entry.type === "REQUEST") { + requests.push(entry); + } else if (entry.type === "RESPONSE") { + responses.push(entry); + } else if (entry.type !== "DIFC_FILTERED") { + other.push(entry); + } + } catch { + // skip malformed lines + } + } + + return { requests, responses, other }; +} + +/** + * Extracts a human-readable label for an MCP REQUEST entry. + * For tools/call requests, returns the tool name; for other methods, returns the method name. + * @param {Object} entry - REQUEST entry from rpc-messages.jsonl + * @returns {string} Display label for the request + */ +function getRpcRequestLabel(entry) { + const payload = entry.payload; + if (!payload) return "unknown"; + const method = payload.method; + if (method === "tools/call") { + const toolName = payload.params && payload.params.name; + return toolName || method; + } + return method || "unknown"; +} + +/** + * Generates a markdown step summary for rpc-messages.jsonl entries (mcpg v0.2.0+ format). + * Shows a table of REQUEST entries (tool calls), a count of RESPONSE entries, any other + * message types, and the DIFC_FILTERED section if there are blocked events. + * @param {{requests: Array, responses: Array, other: Array}} entries + * @param {Array} difcFilteredEvents - DIFC_FILTERED events parsed separately + * @returns {string} Markdown summary, or empty string if nothing to show + */ +function generateRpcMessagesSummary(entries, difcFilteredEvents) { + const { requests, responses, other } = entries; + const blockedCount = difcFilteredEvents ? difcFilteredEvents.length : 0; + const totalMessages = requests.length + responses.length + other.length + blockedCount; + + if (totalMessages === 0) return ""; + + const parts = []; + + // Tool calls / requests table + if (requests.length > 0) { + const blockedNote = blockedCount > 0 ? `, ${blockedCount} blocked` : ""; + const callLines = []; + callLines.push("
"); + callLines.push(`MCP Gateway Activity (${requests.length} request${requests.length !== 1 ? "s" : ""}${blockedNote})\n`); + callLines.push(""); + callLines.push("| Time | Server | Tool / Method |"); + callLines.push("|------|--------|---------------|"); + + for (const req of requests) { + const time = req.timestamp ? req.timestamp.replace("T", " ").replace(/\.\d+Z$/, "Z") : "-"; + const server = req.server_id || "-"; + const label = getRpcRequestLabel(req); + callLines.push(`| ${time} | ${server} | \`${label}\` |`); + } + + callLines.push(""); + callLines.push("
\n"); + parts.push(callLines.join("\n")); + } else if (blockedCount > 0) { + // No requests, but there are DIFC_FILTERED events — add a minimal header + parts.push(`
\nMCP Gateway Activity (${blockedCount} blocked)\n\n*All tool calls were blocked by the integrity filter.*\n\n
\n`); + } + + // Other message types (not REQUEST, RESPONSE, DIFC_FILTERED) + if (other.length > 0) { + /** @type {Record} */ + const typeCounts = {}; + for (const entry of other) { + typeCounts[entry.type] = (typeCounts[entry.type] || 0) + 1; + } + const otherLines = Object.entries(typeCounts).map(([type, count]) => `- **${type}**: ${count} message${count !== 1 ? "s" : ""}`); + parts.push("
\nOther Gateway Messages\n\n" + otherLines.join("\n") + "\n\n
\n"); + } + + // DIFC_FILTERED section (re-uses existing table renderer) + if (blockedCount > 0) { + parts.push(generateDifcFilteredSummary(difcFilteredEvents)); + } + + return parts.join("\n"); +} + /** * Main function to parse and display MCP gateway logs */ @@ -102,6 +215,7 @@ async function main() { // Parse DIFC_FILTERED events from gateway.jsonl (preferred) or rpc-messages.jsonl (fallback). // Both files use the same JSONL format with DIFC_FILTERED entries interleaved. let difcFilteredEvents = []; + let rpcMessagesContent = null; if (fs.existsSync(gatewayJsonlPath)) { const jsonlContent = fs.readFileSync(gatewayJsonlPath, "utf8"); core.info(`Found gateway.jsonl (${jsonlContent.length} bytes)`); @@ -110,9 +224,9 @@ async function main() { core.info(`Found ${difcFilteredEvents.length} DIFC_FILTERED event(s) in gateway.jsonl`); } } else if (fs.existsSync(rpcMessagesPath)) { - const jsonlContent = fs.readFileSync(rpcMessagesPath, "utf8"); - core.info(`No gateway.jsonl found; scanning rpc-messages.jsonl (${jsonlContent.length} bytes) for DIFC_FILTERED events`); - difcFilteredEvents = parseGatewayJsonlForDifcFiltered(jsonlContent); + rpcMessagesContent = fs.readFileSync(rpcMessagesPath, "utf8"); + core.info(`Found rpc-messages.jsonl (${rpcMessagesContent.length} bytes)`); + difcFilteredEvents = parseGatewayJsonlForDifcFiltered(rpcMessagesContent); if (difcFilteredEvents.length > 0) { core.info(`Found ${difcFilteredEvents.length} DIFC_FILTERED event(s) in rpc-messages.jsonl`); } @@ -142,6 +256,25 @@ async function main() { core.info(`No gateway.md found at: ${gatewayMdPath}, falling back to log files`); } + // When no gateway.md exists, check if rpc-messages.jsonl is available (mcpg v0.2.0+ unified format). + // In this format, all message types (REQUEST, RESPONSE, DIFC_FILTERED, etc.) are written to a + // single rpc-messages.jsonl file instead of separate gateway.md / gateway.log streams. + if (rpcMessagesContent !== null) { + const rpcEntries = parseRpcMessagesJsonl(rpcMessagesContent); + const totalMessages = rpcEntries.requests.length + rpcEntries.responses.length + rpcEntries.other.length; + core.info(`rpc-messages.jsonl: ${rpcEntries.requests.length} request(s), ${rpcEntries.responses.length} response(s), ${rpcEntries.other.length} other, ${difcFilteredEvents.length} DIFC_FILTERED`); + + if (totalMessages > 0 || difcFilteredEvents.length > 0) { + const rpcSummary = generateRpcMessagesSummary(rpcEntries, difcFilteredEvents); + if (rpcSummary.length > 0) { + core.summary.addRaw(rpcSummary).write(); + } + } else { + core.info("rpc-messages.jsonl is present but contains no renderable messages"); + } + return; + } + // Fallback to legacy log files let gatewayLogContent = ""; let stderrLogContent = ""; @@ -298,6 +431,9 @@ if (typeof module !== "undefined" && module.exports) { generatePlainTextLegacySummary, parseGatewayJsonlForDifcFiltered, generateDifcFilteredSummary, + parseRpcMessagesJsonl, + getRpcRequestLabel, + generateRpcMessagesSummary, printAllGatewayFiles, }; } diff --git a/actions/setup/js/parse_mcp_gateway_log.test.cjs b/actions/setup/js/parse_mcp_gateway_log.test.cjs index ca9614d4f2a..76e97a00df6 100644 --- a/actions/setup/js/parse_mcp_gateway_log.test.cjs +++ b/actions/setup/js/parse_mcp_gateway_log.test.cjs @@ -1,7 +1,17 @@ // @ts-check /// -const { generateGatewayLogSummary, generatePlainTextGatewaySummary, generatePlainTextLegacySummary, parseGatewayJsonlForDifcFiltered, generateDifcFilteredSummary, printAllGatewayFiles } = require("./parse_mcp_gateway_log.cjs"); +const { + generateGatewayLogSummary, + generatePlainTextGatewaySummary, + generatePlainTextLegacySummary, + parseGatewayJsonlForDifcFiltered, + generateDifcFilteredSummary, + parseRpcMessagesJsonl, + getRpcRequestLabel, + generateRpcMessagesSummary, + printAllGatewayFiles, +} = require("./parse_mcp_gateway_log.cjs"); describe("parse_mcp_gateway_log", () => { // Note: The main() function now checks for gateway.md first before falling back to log files. @@ -723,4 +733,157 @@ Some content here.`; expect(summary).toContain("DIFC Filtered Events (3)"); }); }); + + describe("parseRpcMessagesJsonl", () => { + test("returns empty arrays for empty content", () => { + const result = parseRpcMessagesJsonl(""); + expect(result.requests).toHaveLength(0); + expect(result.responses).toHaveLength(0); + expect(result.other).toHaveLength(0); + }); + + test("categorizes REQUEST entries", () => { + const content = [ + JSON.stringify({ timestamp: "2026-01-18T11:10:49Z", direction: "OUT", type: "REQUEST", server_id: "github", payload: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "list_issues", arguments: {} } } }), + JSON.stringify({ timestamp: "2026-01-18T11:10:50Z", direction: "IN", type: "RESPONSE", server_id: "github", payload: { jsonrpc: "2.0", id: 1, result: {} } }), + ].join("\n"); + + const result = parseRpcMessagesJsonl(content); + expect(result.requests).toHaveLength(1); + expect(result.responses).toHaveLength(1); + expect(result.other).toHaveLength(0); + expect(result.requests[0].server_id).toBe("github"); + }); + + test("excludes DIFC_FILTERED entries (handled separately)", () => { + const content = [ + JSON.stringify({ type: "REQUEST", server_id: "github", payload: { method: "tools/call", params: { name: "list_issues" } } }), + JSON.stringify({ type: "DIFC_FILTERED", server_id: "github", tool_name: "get_issue", reason: "blocked" }), + ].join("\n"); + + const result = parseRpcMessagesJsonl(content); + expect(result.requests).toHaveLength(1); + expect(result.other).toHaveLength(0); + }); + + test("captures unknown message types in other array", () => { + const content = [ + JSON.stringify({ type: "SESSION_START", server_id: "github" }), + JSON.stringify({ type: "SESSION_END", server_id: "github" }), + JSON.stringify({ type: "REQUEST", server_id: "github", payload: { method: "initialize" } }), + ].join("\n"); + + const result = parseRpcMessagesJsonl(content); + expect(result.requests).toHaveLength(1); + expect(result.other).toHaveLength(2); + }); + + test("skips malformed JSON lines", () => { + const content = ["not valid json", JSON.stringify({ type: "REQUEST", server_id: "github", payload: { method: "tools/call", params: { name: "list_issues" } } }), "{broken}"].join("\n"); + + const result = parseRpcMessagesJsonl(content); + expect(result.requests).toHaveLength(1); + }); + + test("skips entries without a type field", () => { + const content = [JSON.stringify({ server_id: "github" }), JSON.stringify({ type: "REQUEST", server_id: "ok", payload: { method: "tools/list" } })].join("\n"); + + const result = parseRpcMessagesJsonl(content); + expect(result.requests).toHaveLength(1); + expect(result.other).toHaveLength(0); + }); + }); + + describe("getRpcRequestLabel", () => { + test("returns tool name for tools/call requests", () => { + const entry = { type: "REQUEST", payload: { method: "tools/call", params: { name: "list_issues" } } }; + expect(getRpcRequestLabel(entry)).toBe("list_issues"); + }); + + test("returns method name for non-tools/call requests", () => { + const entry = { type: "REQUEST", payload: { method: "tools/list" } }; + expect(getRpcRequestLabel(entry)).toBe("tools/list"); + }); + + test("returns tools/call as fallback when params.name is missing", () => { + const entry = { type: "REQUEST", payload: { method: "tools/call" } }; + expect(getRpcRequestLabel(entry)).toBe("tools/call"); + }); + + test("returns unknown when payload is missing", () => { + const entry = { type: "REQUEST" }; + expect(getRpcRequestLabel(entry)).toBe("unknown"); + }); + + test("returns unknown when method is missing", () => { + const entry = { type: "REQUEST", payload: {} }; + expect(getRpcRequestLabel(entry)).toBe("unknown"); + }); + }); + + describe("generateRpcMessagesSummary", () => { + const sampleRequests = [ + { timestamp: "2026-01-18T11:10:49Z", direction: "OUT", type: "REQUEST", server_id: "github", payload: { method: "tools/call", params: { name: "list_issues" } } }, + { timestamp: "2026-01-18T11:10:51Z", direction: "OUT", type: "REQUEST", server_id: "safeoutputs", payload: { method: "tools/call", params: { name: "add_comment" } } }, + ]; + const sampleResponses = [{ timestamp: "2026-01-18T11:10:50Z", direction: "IN", type: "RESPONSE", server_id: "github", payload: { jsonrpc: "2.0", result: {} } }]; + + test("returns empty string for no messages", () => { + expect(generateRpcMessagesSummary({ requests: [], responses: [], other: [] }, [])).toBe(""); + }); + + test("generates details/summary with request count", () => { + const summary = generateRpcMessagesSummary({ requests: sampleRequests, responses: sampleResponses, other: [] }, []); + expect(summary).toContain("
"); + expect(summary).toContain("MCP Gateway Activity (2 requests)"); + expect(summary).toContain("
"); + }); + + test("renders request table with time, server, and tool columns", () => { + const summary = generateRpcMessagesSummary({ requests: sampleRequests, responses: [], other: [] }, []); + expect(summary).toContain("| Time | Server | Tool / Method |"); + expect(summary).toContain("`list_issues`"); + expect(summary).toContain("`add_comment`"); + expect(summary).toContain("github"); + expect(summary).toContain("safeoutputs"); + }); + + test("formats ISO timestamp as readable date-time", () => { + const summary = generateRpcMessagesSummary({ requests: sampleRequests, responses: [], other: [] }, []); + expect(summary).toContain("2026-01-18 11:10:49Z"); + }); + + test("shows blocked count in summary when DIFC events present", () => { + const difcEvents = [{ type: "DIFC_FILTERED", tool_name: "get_issue", reason: "blocked" }]; + const summary = generateRpcMessagesSummary({ requests: sampleRequests, responses: [], other: [] }, difcEvents); + expect(summary).toContain("1 blocked"); + }); + + test("includes DIFC_FILTERED table when events are present", () => { + const difcEvents = [{ type: "DIFC_FILTERED", tool_name: "get_issue", server_id: "github", reason: "Integrity check failed", author_login: "user1", author_association: "MEMBER" }]; + const summary = generateRpcMessagesSummary({ requests: sampleRequests, responses: [], other: [] }, difcEvents); + expect(summary).toContain("DIFC Filtered Events"); + expect(summary).toContain("`get_issue`"); + }); + + test("renders other message types section", () => { + const other = [ + { type: "SESSION_START", server_id: "github" }, + { type: "SESSION_START", server_id: "github" }, + { type: "SESSION_END", server_id: "github" }, + ]; + const summary = generateRpcMessagesSummary({ requests: [], responses: [], other }, []); + expect(summary).toContain("Other Gateway Messages"); + expect(summary).toContain("SESSION_START"); + expect(summary).toContain("SESSION_END"); + expect(summary).toContain("2 messages"); + }); + + test("shows minimal header when only DIFC events exist (no requests)", () => { + const difcEvents = [{ type: "DIFC_FILTERED", tool_name: "list_issues", reason: "blocked" }]; + const summary = generateRpcMessagesSummary({ requests: [], responses: [], other: [] }, difcEvents); + expect(summary).toContain("1 blocked"); + expect(summary).toContain("All tool calls were blocked"); + }); + }); });