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
51 changes: 46 additions & 5 deletions src/browser/features/Tools/TaskToolCall.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -542,20 +542,61 @@ describe("TaskAwaitToolCall", () => {
expect(view.queryByText(/Investigation Complete/)).toBeNull();
});

test("keeps multi-task completion summaries count-only", () => {
test("shows each task's kind and intent for multi-task completions", () => {
const bashSpawn = createToolMessage({
toolName: "bash",
args: {
script: "./scripts/wait_pr_ready.sh 27330",
display_name: "PR ready watcher",
model_intent: "watching PR 27330 until it is ready",
timeout_secs: 3600,
run_in_background: true,
},
result: {
success: true,
output: "Started",
exitCode: 0,
wall_duration_ms: 10,
taskId: "bash:pr-ready-watcher-a1b2",
backgroundProcessId: "pr-ready-watcher-a1b2",
},
});
const taskSpawn = createToolMessage({
toolName: "task",
args: {
agentId: "explore",
prompt: "Find pagination helpers.",
title: "Pagination exploration",
run_in_background: true,
},
result: { status: "queued", taskId: "task-1" },
});

const view = renderTaskAwaitToolCall({
status: "completed",
args: { task_ids: ["task-1", "task-2"] },
args: { task_ids: ["bash:pr-ready-watcher-a1b2", "task-1"] },
result: {
results: [
{ status: "completed", taskId: "task-1", title: "First task", reportMarkdown: "a" },
{ status: "completed", taskId: "task-2", title: "Second task", reportMarkdown: "b" },
{
status: "completed",
taskId: "bash:pr-ready-watcher-a1b2",
title: "PR ready watcher",
reportMarkdown: "exit 0",
},
{
status: "completed",
taskId: "task-1",
title: "Pagination exploration",
reportMarkdown: "Report",
},
],
},
taskReportLinking: computeTaskReportLinking([bashSpawn, taskSpawn]),
});

expect(view.getByText("2 tasks completed")).toBeDefined();
expect(view.queryByText(/First task/)).toBeNull();
expect(view.getByText("bash · Watching PR 27330 until it is ready")).toBeDefined();
expect(view.getByText("explore · Pagination exploration")).toBeDefined();
});

test("uses valid legacy agentType for task_await rows when agentId is invalid", () => {
Expand Down
32 changes: 21 additions & 11 deletions src/browser/features/Tools/TaskToolCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1346,19 +1346,18 @@ export const TaskAwaitToolCall: React.FC<TaskAwaitToolCallProps> = ({
const targetCount = totalCount > 0 ? totalCount : taskIds?.length;
const formatTasks = (count: number) => `${count} ${count === 1 ? "task" : "tasks"}`;

// "1 task completed" alone says nothing about what finished; for single-task awaits,
// surface the task's kind plus its spawn intent/title in the collapsed row.
const firstResult = results[0];
let singleTaskDetail: string | undefined;
if (results.length === 1 && firstResult.status === "completed") {
const completedTaskId = firstResult.taskId;
// "N tasks completed" alone says nothing about what finished; surface each completed
// task's available kind and spawn intent/title in the collapsed row.
const completedTaskDetails: string[] = [];
for (const taskResult of results) {
if (taskResult.status !== "completed") continue;
const completedTaskId = taskResult.taskId;
const bashSpawn = taskReportLinking?.bashSpawnByTaskId.get(completedTaskId);
const kind = fromBashTaskId(completedTaskId)
? "bash"
: isWorkflowRunTaskHandleId(completedTaskId)
? "workflow"
: isWorkspaceTurnTaskHandleId(completedTaskId) ||
firstResult.handleKind === "workspace_turn"
: isWorkspaceTurnTaskHandleId(completedTaskId) || taskResult.handleKind === "workspace_turn"
? "workspace"
: taskReportLinking?.spawnAgentTypeByTaskId.get(completedTaskId);
// Spawn-side intent first (bash model_intent, task spawn title); the result's own
Expand All @@ -1368,9 +1367,9 @@ export const TaskAwaitToolCall: React.FC<TaskAwaitToolCallProps> = ({
? sanitizeDisplayableModelIntent(bashSpawn.modelIntent, bashSpawn.script)
: undefined) ??
trimToNonEmptyString(taskReportLinking?.spawnTitleByTaskId.get(completedTaskId)) ??
trimToNonEmptyString(firstResult.title);
trimToNonEmptyString(taskResult.title);
const detail = [kind, description].filter((part): part is string => part != null).join(" · ");
singleTaskDetail = detail.length > 0 ? detail : undefined;
if (detail.length > 0) completedTaskDetails.push(detail);
}

let summaryTitle: string;
Expand Down Expand Up @@ -1408,7 +1407,7 @@ export const TaskAwaitToolCall: React.FC<TaskAwaitToolCallProps> = ({
summaryTone = "waiting";
} else if (completedCount > 0) {
summaryTitle = `${formatTasks(completedCount)} completed`;
summaryDetail = singleTaskDetail;
summaryDetail = completedTaskDetails.length === 1 ? completedTaskDetails[0] : undefined;
summaryTone = "success";
} else {
summaryTitle = "Checked task status";
Expand Down Expand Up @@ -1491,6 +1490,17 @@ export const TaskAwaitToolCall: React.FC<TaskAwaitToolCallProps> = ({
</ExpandIcon>
</ToolHeader>

{/* Align collapsed details with the header text; expanded mode already lists per-task rows. */}
{!expanded && summaryTone === "success" && completedTaskDetails.length > 1 && (
<div data-component="TaskAwaitCompletedList" className="mt-0.5 pl-[42px]">
{completedTaskDetails.map((detail, idx) => (
<div key={idx} className="text-muted truncate text-[10px] leading-4">
{detail}
</div>
))}
</div>
)}

{expanded && (
<ToolDetails className="mt-1.5 border-t-0 pt-0">
<div
Expand Down
Loading