fix(pi): remove running rows for refused subagent resumes (#7369)

A text-only refusal finishes successfully without identifying a child. Remove its provisional subagent row when the tool completes, while retaining the refusal in the tool timeline.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-19 14:11:41 +02:00
committed by GitHub
parent 1c8c68f93e
commit dd244c076d
3 changed files with 95 additions and 1 deletions
@@ -0,0 +1,68 @@
{
"provenance": {
"package": "@gotgenes/pi-subagents",
"version": "23.4.0",
"sourceCommit": "63476ff869efd8ea339040f496fa734048b89e70",
"piVersion": "0.84.4",
"captureDate": "2026-10-19"
},
"events": [
{
"type": "tool_execution_start",
"toolCallId": "resume-call",
"toolName": "subagent",
"args": {
"subagent_type": "general-purpose",
"description": "resume",
"prompt": "Continue",
"resume": "expired-child",
"run_in_background": true
}
},
{
"type": "tool_execution_end",
"toolCallId": "resume-call",
"toolName": "subagent",
"result": {
"content": [
{
"type": "text",
"text": "Agent \"expired-child\" had its session released after its retention window; resume is unavailable, but its result is still retrievable via get_subagent_result."
}
]
},
"isError": false
}
],
"messages": [
{
"role": "assistant",
"content": [
{
"type": "toolCall",
"id": "resume-call",
"name": "subagent",
"arguments": {
"subagent_type": "general-purpose",
"description": "resume",
"prompt": "Continue",
"resume": "expired-child",
"run_in_background": true
}
}
]
},
{
"role": "toolResult",
"toolCallId": "resume-call",
"toolName": "subagent",
"content": [
{
"type": "text",
"text": "Agent \"expired-child\" had its session released after its retention window; resume is unavailable, but its result is still retrievable via get_subagent_result."
}
],
"isError": false
}
]
}
@@ -1,5 +1,6 @@
import { describe, expect, test } from "vitest";
import { fileURLToPath } from "node:url";
import { ProviderSubagentStore } from "../../../../provider-subagents/store.js";
import { createPiExtensionHost } from "../index.js";
import { readSubagentFixture, verifySubagentFixture } from "../subagent-fixture-test.js";
import { streamPiHistory } from "../../history-mapper.js";
@@ -25,6 +26,31 @@ function publishService(agents: Array<{ id: string; outputFile?: string }>): voi
}
describe("@gotgenes/pi-subagents adapter", () => {
test.each([true, false])(
"removes a refused resume from live and replayed subagents (background=%s)",
async (background) => {
// The result came from 23.4.0's real AgentTool and SubagentManager after releaseSession().
// The child session was a fake; releaseSession replaced waiting for the retention sweep.
const fixture = readSubagentFixture(
new URL("./fixtures/refused-resume.json", import.meta.url),
);
const start = fixture.events[0]!;
if (start.type !== "tool_execution_start") throw new Error("Expected a tool start");
start.args = { ...(start.args as object), run_in_background: background };
const assistant = fixture.messages[0]!;
if (assistant.role !== "assistant") throw new Error("Expected an assistant tool call");
for (const block of assistant.content) {
if (block.type === "toolCall") block.arguments = start.args;
}
const events = await verifySubagentFixture(fixture);
const store = new ProviderSubagentStore();
store.apply("parent", "pi", { type: "upsert", id: "previous-child", status: "completed" });
for (const event of events) store.apply("parent", "pi", event.event);
expect(events.map(({ event }) => event.type)).toEqual(["upsert", "remove"]);
expect(store.list("parent")).toMatchObject([{ id: "previous-child", status: "completed" }]);
},
);
test("reports a child's file after the child republishes the gotgenes service", async () => {
const notifications: string[] = [];
const emit = loadRuntimeBridge();
@@ -86,7 +86,7 @@ export const gotgenesPiSubagents: PiExtension = {
detail,
subagents: [{ type: "upsert" as const, id: call.callId, status: "failed" as const }],
}
: { detail };
: { detail, subagents: [{ type: "remove" as const, id: call.callId }] };
callsByAgent.set(details.data.agentId, call.callId);
const file =
outputFileFromToolResult(call.result) ?? pendingFilesByAgent.get(details.data.agentId);