Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions actions/setup/js/parse_copilot_log.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,12 @@ function parsePrettyPrintFormat(logContent) {
const MODEL_BREAKDOWN_RE = /^Breakdown by AI model:/;
const MODEL_LINE_RE = /^ +(\S+)\s+([\d.]+k?)\s+in,\s+([\d.]+k?)\s+out(?:,\s+([\d.]+k?)\s+cached)?/;
// Recognise both legacy ("Total usage est:" / "API time spent:" / …) and the
// newer Copilot CLI footer ("Changes +N -N", "Duration Ns", "Tokens ↑N ↓N (cached)").
// The newer footer omits a colon and uses arrow glyphs, so we extend the regex rather than
// relying on the legacy "Total …:" prefix alone.
const USAGE_LINES_RE = /^(?:Total usage est:|API time spent:|Total session time:|Total code changes:|Changes\s+[+-]?\d|Duration\s+\d|Tokens\s+[↑↓])/;
// newer Copilot CLI footer ("Changes +N -N", "Duration Ns", "Tokens ↑N ↓N (cached)",
// "Resume copilot --resume=…"). The newer footer omits a colon and uses arrow glyphs, so we
// extend the regex rather than relying on the legacy "Total …:" prefix alone. The "Resume"
// hint is matched against the exact CLI command syntax to avoid false-positives on prose
// that happens to start with "Resume ".
const USAGE_LINES_RE = /^(?:Total usage est:|API time spent:|Total session time:|Total code changes:|Changes\s+[+-]?\d|Duration\s+\d|Tokens\s+[↑↓]|Resume\s{2,}copilot\s+--resume=)/;

const parseTokenCount = s => {
const n = parseFloat(s);
Expand Down
22 changes: 22 additions & 0 deletions actions/setup/js/parse_copilot_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,28 @@ describe("parse_copilot_log.cjs", () => {
expect(result.markdown).toContain("375,000");
});

it("strips the columnar 'Resume' footer hint from rendered pretty-print output", () => {
// Copilot CLI footer includes a "Resume copilot --resume=<id>" line aligned in the
// same column block as Changes/Duration/Tokens. It is CLI chrome, not agent reasoning,
// and must not leak into the rendered reasoning/agent-text section.
const prettyLog = [
"● Bash",
" └ ok",
"The work is done.",
"",
"Changes +0 -0",
"Duration 1m 0s",
"Tokens ↑ 195.4k (166.2k cached) • ↓ 2.9k",
"Resume copilot --resume=d21d3356-9296-4d1b-a392-49e5069e4e3f",
].join("\n");

const result = parseCopilotLog(prettyLog);

expect(result.markdown).toContain("The work is done.");
expect(result.markdown).not.toContain("--resume=");
expect(result.markdown).not.toMatch(/^Resume\s+copilot/m);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The test suite is missing a boundary case: a line like Resume copilot --resume=... (single space) should not be stripped. Adding this would confirm the \s{2,} discriminator is doing its job and prevent a regression if the regex is ever tightened.

💡 Suggested additional assertion
it('does not strip Resume line with only one space (not CLI footer)', () => {
  const prettyLog = [
    '● Bash',
    '    └ ok',
    'Resume copilot --resume=abc',  // single space — should be kept as agent text
  ].join('\n');
  const result = parseCopilotLog(prettyLog);
  expect(result.markdown).toContain('Resume copilot');
});

This directly exercises the {2,} quantifier that distinguishes columnar chrome from prose.

@copilot please address this.

});

it("handles the new footer without a cached segment", () => {
const prettyLog = ["● Bash", " └ ok", "", "Tokens ↑ 1.2k • ↓ 50"].join("\n");

Expand Down
Loading