diff --git a/packages/provider-core/src/server/notification.test.ts b/packages/provider-core/src/server/notification.test.ts index 272cd007632a2..f8d0b1d113d46 100644 --- a/packages/provider-core/src/server/notification.test.ts +++ b/packages/provider-core/src/server/notification.test.ts @@ -60,6 +60,16 @@ describe("backgroundWorkNotification", () => { outcome: "cancelled", summary: 'Subagent "Agent B" and command "Sleep 60 seconds" were stopped', }); + assert.deepInclude( + backgroundWorkNotification([ + { kind: "command", label: "npm test", outcome: "completed", exitCode: 0 }, + { kind: "command", label: "npm run lint", outcome: "failed", exitCode: 1 }, + ]), + { + outcome: "failed", + summary: 'Command "npm test" finished (exit 0) and command "npm run lint" failed (exit 1)', + }, + ); }); it("counts work it cannot list", () => { @@ -73,6 +83,18 @@ describe("backgroundWorkNotification", () => { outcome: "completed", summary: "4 subagents finished", }); + assert.deepEqual( + backgroundWorkNotification( + reports.map((report, index) => + index === 0 ? { ...report, outcome: "failed" as const } : report, + ), + ), + { + source: { kind: "subagent" }, + outcome: "failed", + summary: "1 subagent failed and 3 finished", + }, + ); }); }); diff --git a/packages/provider-core/src/server/notification.ts b/packages/provider-core/src/server/notification.ts index 55b1125fd9448..9ccb425faf2d1 100644 --- a/packages/provider-core/src/server/notification.ts +++ b/packages/provider-core/src/server/notification.ts @@ -71,11 +71,26 @@ function exitSuffix(report: BackgroundWorkReport): string { : ""; } -function namedReport(report: BackgroundWorkReport, capitalize: boolean): string { +function namedReport(report: BackgroundWorkReport, capitalize: boolean, verb?: string): string { const noun = KIND_NOUN[report.kind][0]; const label = reportLabel(report.label); const named = `${capitalize ? noun : noun.toLowerCase()}${label === undefined ? "" : ` "${label}"`}`; - return `${named}${exitSuffix(report)}`; + return `${named}${verb === undefined ? "" : ` ${verb}`}${exitSuffix(report)}`; +} + +const OUTCOME_GROUPS = ["failed", "cancelled", "updated", "completed"] as const; + +function groupVerb(group: (typeof OUTCOME_GROUPS)[number], count: number): string { + switch (group) { + case "failed": + return "failed"; + case "cancelled": + return count === 1 ? "was stopped" : "were stopped"; + case "updated": + return "updated"; + case "completed": + return "finished"; + } } function joinNames(names: ReadonlyArray): string { @@ -85,7 +100,6 @@ function joinNames(names: ReadonlyArray): string { function reportsSummary( reports: readonly [BackgroundWorkReport, ...ReadonlyArray], - outcome: NotificationOutcome, ): string { const [first] = reports; if (reports.length === 1) { @@ -93,20 +107,30 @@ function reportsSummary( const noun = KIND_NOUN[first.kind][0]; return `${noun}${label === undefined ? "" : ` "${label}"`} ${outcomeVerb(first.kind, first.outcome)}${exitSuffix(first)}`; } - const verb = - outcome === "failed" - ? "failed" - : outcome === "cancelled" - ? "were stopped" - : outcome === "updated" - ? "updated" - : "finished"; + const groups = OUTCOME_GROUPS.flatMap((group) => { + const count = reports.filter( + (report) => (report.outcome === "unknown" ? "completed" : report.outcome) === group, + ).length; + return count === 0 ? [] : [{ group, count }]; + }); if (reports.length > 3 || reports.every((report) => reportLabel(report.label) === undefined)) { const kinds = new Set(reports.map((report) => report.kind)); - const noun = kinds.size === 1 ? KIND_NOUN[first.kind][1] : "background tasks"; - return `${reports.length} ${noun} ${verb}`; + const [noun, nouns] = kinds.size === 1 ? KIND_NOUN[first.kind] : KIND_NOUN.background_task; + return joinNames( + groups.map( + ({ group, count }, index) => + `${count}${index === 0 ? ` ${count === 1 ? noun.toLowerCase() : nouns}` : ""} ${groupVerb(group, count)}`, + ), + ); + } + if (groups.length > 1) { + return joinNames( + reports.map((report, index) => + namedReport(report, index === 0, outcomeVerb(report.kind, report.outcome)), + ), + ); } - return `${joinNames(reports.map((report, index) => namedReport(report, index === 0)))} ${verb}`; + return `${joinNames(reports.map((report, index) => namedReport(report, index === 0)))} ${groupVerb(groups[0]!.group, reports.length)}`; } function reportsSource( @@ -141,7 +165,7 @@ export function backgroundWorkNotification( return { source: reportsSource([first, ...rest]), outcome, - summary: reportsSummary([first, ...rest], outcome), + summary: reportsSummary([first, ...rest]), }; }