feat(coding-agent): filtered non-partial poll previews to show only settled jobs
- Added a list-mode flag to job rendering and used it to keep background job listing behavior distinct. - Filtered non-partial poll call results to exclude running jobs and return no output when only running jobs remain. - Added tests covering partial rendering, poll-based filtering, and list/cancel paths for the updated preview behavior.
This commit is contained in:
@@ -372,6 +372,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
||||
interface JobRenderArgs {
|
||||
poll?: string[];
|
||||
cancel?: string[];
|
||||
list?: boolean;
|
||||
}
|
||||
|
||||
const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
||||
@@ -433,6 +434,7 @@ function flattenStructuredPreview(text: string): string {
|
||||
}
|
||||
|
||||
function describeTarget(args: JobRenderArgs | undefined): string {
|
||||
if (args?.list) return "background jobs";
|
||||
const poll = args?.poll ?? [];
|
||||
const cancel = args?.cancel ?? [];
|
||||
const parts: string[] = [];
|
||||
@@ -460,7 +462,7 @@ export const jobToolRenderer = {
|
||||
uiTheme: Theme,
|
||||
args?: JobRenderArgs,
|
||||
): Component {
|
||||
const jobs = result.details?.jobs ?? [];
|
||||
let jobs = result.details?.jobs ?? [];
|
||||
|
||||
if (jobs.length === 0) {
|
||||
const fallback = result.content?.find(c => c.type === "text")?.text || "No jobs to process";
|
||||
@@ -468,6 +470,17 @@ export const jobToolRenderer = {
|
||||
return new Text([header, formatEmptyMessage(fallback, uiTheme)].join("\n"), 0, 0);
|
||||
}
|
||||
|
||||
const isPollCall = args
|
||||
? !args.list && (!args.cancel || args.cancel.length === 0 || args.poll !== undefined)
|
||||
: true;
|
||||
|
||||
if (!options.isPartial && isPollCall) {
|
||||
jobs = jobs.filter(job => job.status !== "running");
|
||||
if (jobs.length === 0) {
|
||||
return new Text("", 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const counts = { completed: 0, failed: 0, cancelled: 0, running: 0 };
|
||||
for (const job of jobs) counts[job.status]++;
|
||||
|
||||
|
||||
@@ -110,4 +110,127 @@ describe("job renderer task-result preview", () => {
|
||||
expect(header).toBeDefined();
|
||||
expect(header!.match(/SpawnProbe/g)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("collapse and filter when turned into a result", () => {
|
||||
const jobsData = [
|
||||
{
|
||||
id: "Job1",
|
||||
type: "task" as const,
|
||||
status: "running" as const,
|
||||
label: "Job1 running",
|
||||
durationMs: 1200,
|
||||
},
|
||||
{
|
||||
id: "Job2",
|
||||
type: "task" as const,
|
||||
status: "completed" as const,
|
||||
label: "Job2 completed",
|
||||
durationMs: 3400,
|
||||
resultText: "Job2 result",
|
||||
},
|
||||
{
|
||||
id: "Job3",
|
||||
type: "task" as const,
|
||||
status: "running" as const,
|
||||
label: "Job3 running",
|
||||
durationMs: 500,
|
||||
},
|
||||
];
|
||||
|
||||
it("shows all jobs when isPartial is true", () => {
|
||||
const result = {
|
||||
content: [{ type: "text" as const, text: "" }],
|
||||
details: { jobs: jobsData },
|
||||
};
|
||||
const component = jobToolRenderer.renderResult(
|
||||
result,
|
||||
{ expanded: true, isPartial: true } as Parameters<typeof jobToolRenderer.renderResult>[1],
|
||||
theme,
|
||||
{ poll: [] },
|
||||
);
|
||||
const output = Bun.stripANSI((component.render(120) as readonly string[]).join("\n"));
|
||||
expect(output).toContain("Job1 running");
|
||||
expect(output).toContain("Job2 completed");
|
||||
expect(output).toContain("Job3 running");
|
||||
expect(output).toContain("waiting on 2 of 3 jobs");
|
||||
});
|
||||
|
||||
it("shows only finished jobs when isPartial is false and it is a poll call", () => {
|
||||
const result = {
|
||||
content: [{ type: "text" as const, text: "" }],
|
||||
details: { jobs: jobsData },
|
||||
};
|
||||
const component = jobToolRenderer.renderResult(
|
||||
result,
|
||||
{ expanded: true, isPartial: false } as Parameters<typeof jobToolRenderer.renderResult>[1],
|
||||
theme,
|
||||
{ poll: [] },
|
||||
);
|
||||
const output = Bun.stripANSI((component.render(120) as readonly string[]).join("\n"));
|
||||
expect(output).not.toContain("Job1 running");
|
||||
expect(output).toContain("Job2 completed");
|
||||
expect(output).not.toContain("Job3 running");
|
||||
expect(output).toContain("1 job settled");
|
||||
});
|
||||
|
||||
it("shows nothing when isPartial is false and all jobs are running and it is a poll call", () => {
|
||||
const runningJobsOnly = [
|
||||
{
|
||||
id: "Job1",
|
||||
type: "task" as const,
|
||||
status: "running" as const,
|
||||
label: "Job1 running",
|
||||
durationMs: 1200,
|
||||
},
|
||||
];
|
||||
const result = {
|
||||
content: [{ type: "text" as const, text: "" }],
|
||||
details: { jobs: runningJobsOnly },
|
||||
};
|
||||
const component = jobToolRenderer.renderResult(
|
||||
result,
|
||||
{ expanded: true, isPartial: false } as Parameters<typeof jobToolRenderer.renderResult>[1],
|
||||
theme,
|
||||
{ poll: [] },
|
||||
);
|
||||
const lines = component.render(120) as readonly string[];
|
||||
expect(lines).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not collapse running jobs when isPartial is false and list is true", () => {
|
||||
const result = {
|
||||
content: [{ type: "text" as const, text: "" }],
|
||||
details: { jobs: jobsData },
|
||||
};
|
||||
const component = jobToolRenderer.renderResult(
|
||||
result,
|
||||
{ expanded: true, isPartial: false } as Parameters<typeof jobToolRenderer.renderResult>[1],
|
||||
theme,
|
||||
{ list: true },
|
||||
);
|
||||
const output = Bun.stripANSI((component.render(120) as readonly string[]).join("\n"));
|
||||
expect(output).toContain("Job1 running");
|
||||
expect(output).toContain("Job2 completed");
|
||||
expect(output).toContain("Job3 running");
|
||||
expect(output).toContain("waiting on 2 of 3 jobs");
|
||||
});
|
||||
|
||||
it("does not collapse running jobs when isPartial is false and cancel-only is true", () => {
|
||||
const result = {
|
||||
content: [{ type: "text" as const, text: "" }],
|
||||
details: { jobs: jobsData },
|
||||
};
|
||||
const component = jobToolRenderer.renderResult(
|
||||
result,
|
||||
{ expanded: true, isPartial: false } as Parameters<typeof jobToolRenderer.renderResult>[1],
|
||||
theme,
|
||||
{ cancel: ["Job1"] },
|
||||
);
|
||||
const output = Bun.stripANSI((component.render(120) as readonly string[]).join("\n"));
|
||||
expect(output).toContain("Job1 running");
|
||||
expect(output).toContain("Job2 completed");
|
||||
expect(output).toContain("Job3 running");
|
||||
expect(output).toContain("waiting on 2 of 3 jobs");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user