fix(task): only point follow-up hints at transcripts that exist

The aborted-task follow-up hint always references history://<agentId>,
including when no transcript can actually be served. Following that link
then fails.

Add hasResolvableTranscript beside sessionFilesFromDisk, mirroring the
availability half of HistoryProtocolHandler's resolution semantics: a
registered ref's live session, a retained session file verified on disk,
or a disk-scanned .jsonl under a known artifacts dir (which still serves
hard-aborted children whose refs were unregistered). Probing never
throws; a stale path or unreadable artifacts subtree reads as unavailable
instead of failing delivery of the settled result. Render the transcript
clause from that check, independently of the resume affordance, so a
still-resumable idle/parked agent keeps its hub resume hint and a
disk-backed transcript keeps its link. Idle-completion hints are
unchanged.

Signed-off-by: Christian Stewart <christian@aperture.us>
This commit is contained in:
Christian Stewart
2026-07-22 18:12:18 -07:00
parent 50e388035a
commit 0ff5312744
3 changed files with 55 additions and 7 deletions
+11 -7
View File
@@ -41,6 +41,7 @@ import {
// Import review tools for side effects (registers subagent tool handlers)
import "../tools/review";
import type { AsyncJobManager } from "../async";
import { hasResolvableTranscript } from "../internal-urls/registry-helpers";
import { AgentRegistry } from "../registry/agent-registry";
import { type DiscoveryResult, discoverAgents } from "./discovery";
import { generateTaskName } from "./name-generator";
@@ -1043,14 +1044,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
}): string {
const { manager, toolCallId, spawnParams, agentId, progress, ircEnabled, buildDetails, onUpdate, onSettled } =
options;
const buildFollowUpHint = (aborted: boolean): string => {
const buildFollowUpHint = async (aborted: boolean): Promise<string> => {
if (aborted) {
const status = AgentRegistry.global().get(agentId)?.status;
if (status === "idle" || status === "parked") {
const ref = AgentRegistry.global().get(agentId);
const transcript = (await hasResolvableTranscript(agentId))
? `transcript at history://${agentId}`
: "transcript unavailable";
if (ref?.status === "idle" || ref?.status === "parked") {
const followUp = ircEnabled ? "message it via `hub` to resume; " : "";
return `\n\n${agentId} was stopped but is still resumable — ${followUp}transcript at history://${agentId}`;
return `\n\n${agentId} was stopped but is still resumable — ${followUp}${transcript}`;
}
return `\n\n${agentId} was aborted — transcript at history://${agentId}`;
return `\n\n${agentId} was aborted — ${transcript}`;
}
const followUp = ircEnabled ? "message it via `hub` to follow up; " : "";
return `\n\n${agentId} is now idle — ${followUp}transcript at history://${agentId}`;
@@ -1157,7 +1161,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
? `Background task ${agentId} failed.`
: `Background task ${agentId} complete.`;
await reportProgress(statusText, buildDetails() as unknown as Record<string, unknown>);
const deliveryText = `${finalText}${buildFollowUpHint(singleResult?.aborted === true)}`;
const deliveryText = `${finalText}${await buildFollowUpHint(singleResult?.aborted === true)}`;
if (resultFailed) {
// Mark the job itself failed; the failed agent stays interrogable.
throw new TaskJobError(deliveryText);
@@ -1173,7 +1177,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
const statusText = `Background task ${agentId} failed.`;
await reportProgress(statusText, buildDetails() as unknown as Record<string, unknown>);
const message = error instanceof Error ? error.message : String(error);
const hint = AgentRegistry.global().get(agentId) ? buildFollowUpHint(false) : "";
const hint = AgentRegistry.global().get(agentId) ? await buildFollowUpHint(false) : "";
throw new TaskJobError(`${message}${hint}`);
} finally {
releasePermit();