From 14634ab4e5574c5e76b44a0a653ef18e98f967cb Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 22 Feb 2026 13:06:28 +0100 Subject: [PATCH] fix(task): corrected task progress display and async execution tracking - Fixed task progress display to hide tool count and token metrics when zero, reducing visual clutter in status output. - Refactored async task execution to use job progress reporting API instead of direct update callbacks, improving progress tracking accuracy. - Implemented pre-allocation of unique IDs for batch tasks to prevent artifact collisions across concurrent executions. - Extracted async details builder into reusable function to consolidate state construction logic. --- packages/coding-agent/CHANGELOG.md | 2 + packages/coding-agent/src/task/index.ts | 81 ++++++++++++++++++------ packages/coding-agent/src/task/render.ts | 12 +++- 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index dfd9e803f..c8f3610bd 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] + ### Added - Added support for rendering markdown output from Python cells with proper formatting and theme styling @@ -38,6 +39,7 @@ ### Fixed +- Fixed task progress display to hide tool count and token metrics when zero, reducing visual clutter in status output - Fixed Lobsters scraper to correctly parse API responses where user fields are strings instead of objects, resolving undefined user display in story listings - Fixed artifact manager caching to properly invalidate when session file changes, preventing stale artifact references - Fixed truncation behavior around UTF-8 boundaries and chunked output accounting diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index 92a76acf5..7c120bbbb 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -196,6 +196,9 @@ export class TaskTool implements AgentTool { return this.#executeSync(_toolCallId, params, signal, onUpdate); } + const outputManager = + this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null)); + const uniqueIds = await outputManager.allocateBatch(taskItems.map(t => t.id)); const fallbackAgentSource = this.#discoveredAgents.find(agent => agent.name === params.agent)?.source ?? "bundled"; const renderedTasks = taskItems.map(taskItem => renderTemplate(params.context, taskItem)); @@ -229,17 +232,19 @@ export class TaskTool implements AgentTool { .map(progress => structuredClone(progress)); }; + const buildAsyncDetails = (state: "running" | "completed" | "failed", jobId: string): TaskToolDetails => ({ + projectAgentsDir: null, + results: [], + totalDurationMs: 0, + progress: getProgressSnapshot(), + async: { state, jobId, type: "task" }, + }); + const emitAsyncUpdate = (state: "running" | "completed" | "failed", text: string): void => { const primaryJobId = startedJobs[0]?.jobId ?? "task"; onUpdate?.({ content: [{ type: "text", text }], - details: { - projectAgentsDir: null, - results: [], - totalDurationMs: 0, - progress: getProgressSnapshot(), - async: { state, jobId: primaryJobId, type: "task" }, - }, + details: buildAsyncDetails(state, primaryJobId), }); }; @@ -254,21 +259,27 @@ export class TaskTool implements AgentTool { continue; } + const uniqueId = uniqueIds[i]; const singleParams: TaskParams = { ...params, tasks: [taskItem] }; - const label = `${i}-${taskItem.id}`; + const label = uniqueId; try { const jobId = manager.register( "task", label, - async ({ signal: runSignal }) => { + async ({ signal: runSignal, reportProgress }) => { const startedAt = Date.now(); const progress = progressByTaskId.get(taskItem.id); if (progress) { progress.status = "running"; } - emitAsyncUpdate("running", `Running background task ${taskItem.id}...`); + await reportProgress( + `Running background task ${taskItem.id}...`, + buildAsyncDetails("running", startedJobs[0]?.jobId ?? label) as unknown as Record, + ); try { - const result = await this.#executeSync(_toolCallId, singleParams, runSignal); + const result = await this.#executeSync(_toolCallId, singleParams, runSignal, undefined, [ + uniqueId, + ]); const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)"; const singleResult = result.details?.results[0]; if (progress) { @@ -287,12 +298,21 @@ export class TaskTool implements AgentTool { } const remaining = taskItems.length - completedJobs; const isDone = remaining === 0; - emitAsyncUpdate( - isDone ? (failedJobs > 0 || failedSchedules.length > 0 ? "failed" : "completed") : "running", + await reportProgress( isDone ? `Background task batch complete: ${completedJobs}/${taskItems.length} finished.` : `Background task batch progress: ${completedJobs}/${taskItems.length} finished (${remaining} running).`, + buildAsyncDetails( + isDone ? (failedJobs > 0 || failedSchedules.length > 0 ? "failed" : "completed") : "running", + startedJobs[0]?.jobId ?? label, + ) as unknown as Record, ); + if (isDone) { + emitAsyncUpdate( + failedJobs > 0 || failedSchedules.length > 0 ? "failed" : "completed", + `Background task batch complete: ${completedJobs}/${taskItems.length} finished.`, + ); + } return finalText; } catch (error) { if (progress) { @@ -303,16 +323,33 @@ export class TaskTool implements AgentTool { failedJobs += 1; const remaining = taskItems.length - completedJobs; const isDone = remaining === 0; - emitAsyncUpdate( - isDone ? "failed" : "running", + await reportProgress( isDone ? `Background task batch complete with failures: ${failedJobs} failed.` : `Background task batch progress: ${completedJobs}/${taskItems.length} finished (${remaining} running).`, + buildAsyncDetails( + isDone ? "failed" : "running", + startedJobs[0]?.jobId ?? label, + ) as unknown as Record, ); + if (isDone) { + emitAsyncUpdate( + "failed", + `Background task batch complete with failures: ${failedJobs} failed.`, + ); + } throw error; } }, - { id: label }, + { + id: label, + onProgress: (text, details) => { + const progressDetails = + (details as TaskToolDetails | undefined) ?? + buildAsyncDetails("running", startedJobs[0]?.jobId ?? label); + onUpdate?.({ content: [{ type: "text", text }], details: progressDetails }); + }, + }, ); startedJobs.push({ jobId, taskId: taskItem.id }); } catch (error) { @@ -365,6 +402,7 @@ export class TaskTool implements AgentTool { params: TaskParams, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, + preAllocatedIds?: string[], ): Promise> { const startTime = Date.now(); const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd); @@ -616,9 +654,14 @@ export class TaskTool implements AgentTool { // Build full prompts with context prepended // Allocate unique IDs across the session to prevent artifact collisions - const outputManager = - this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null)); - const uniqueIds = await outputManager.allocateBatch(tasks.map(t => t.id)); + let uniqueIds: string[]; + if (preAllocatedIds && preAllocatedIds.length === tasks.length) { + uniqueIds = preAllocatedIds; + } else { + const outputManager = + this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null)); + uniqueIds = await outputManager.allocateBatch(tasks.map(t => t.id)); + } const tasksWithUniqueIds = tasks.map((t, i) => ({ ...t, id: uniqueIds[i] })); // Build full prompts with context prepended diff --git a/packages/coding-agent/src/task/render.ts b/packages/coding-agent/src/task/render.ts index bee7b6448..7841f2548 100644 --- a/packages/coding-agent/src/task/render.ts +++ b/packages/coding-agent/src/task/render.ts @@ -518,13 +518,19 @@ function renderAgentProgress( const taskPreview = truncateToWidth(progress.task, 40); statusLine += ` ${theme.fg("muted", taskPreview)}`; } - statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`; + if (progress.toolCount > 0) { + statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`; + } if (progress.tokens > 0) { statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(progress.tokens)} tokens`)}`; } } else if (progress.status === "completed") { - statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`; - statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(progress.tokens)} tokens`)}`; + if (progress.toolCount > 0) { + statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`; + } + if (progress.tokens > 0) { + statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(progress.tokens)} tokens`)}`; + } } lines.push(statusLine);