mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 13:13:25 +00:00
fix(server): put a tintinweb foreground answer in the child timeline
A foreground @tintinweb/pi-subagents child has no session file, so its answer only exists in the Agent tool result. Emit that text as the child's assistant message when the child finishes. Tag every sub_agent producer's `log: ""` with COMPAT(subagent-log) so the shim removal finds them.
This commit is contained in:
@@ -314,7 +314,8 @@ export type ToolCallDetail =
|
||||
description?: string;
|
||||
childSessionId?: string;
|
||||
// COMPAT(subagent-log): added in v0.14.1, remove after 2027-04-30. Apps before v0.14.1
|
||||
// require `log`; every producer sends it empty. Drop the field and those producers' `log: ""`.
|
||||
// require `log`; every producer sends it empty and carries the same tag. Optional here because
|
||||
// protocol timeline items, where `log` is optional, flow into this type.
|
||||
log?: string;
|
||||
actions?: Array<{
|
||||
index: number;
|
||||
|
||||
@@ -6276,6 +6276,7 @@ function buildClaudeSubagentCardDetail(
|
||||
type: "sub_agent",
|
||||
...(facts.title ? { subAgentType: facts.title } : {}),
|
||||
...(facts.description ? { description: facts.description } : {}),
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
actions: [],
|
||||
};
|
||||
|
||||
@@ -137,6 +137,7 @@ export class ClaudeSidechainTracker {
|
||||
type: "sub_agent",
|
||||
...(state.subAgentType ? { subAgentType: state.subAgentType } : {}),
|
||||
...(state.description ? { description: state.description } : {}),
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
actions: [],
|
||||
};
|
||||
|
||||
@@ -950,6 +950,7 @@ function mapCollabAgentToolCallItem(
|
||||
type: "sub_agent",
|
||||
subAgentType: "Sub-agent",
|
||||
...(item.prompt ? { description: item.prompt } : {}),
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
actions: [],
|
||||
};
|
||||
@@ -1006,6 +1007,7 @@ function mapSubAgentActivityItem(
|
||||
type: "sub_agent",
|
||||
subAgentType: nativeName,
|
||||
description,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
actions: [],
|
||||
},
|
||||
|
||||
@@ -199,6 +199,7 @@ function mapOmpTaskDetail(args: unknown, result: OmpToolResult): ToolCallDetail
|
||||
argRecord.assignment,
|
||||
),
|
||||
...(childSessionId ? { childSessionId } : {}),
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ function deriveOpencodeTaskDetail(
|
||||
...(subAgentType ? { subAgentType } : {}),
|
||||
...(description ? { description } : {}),
|
||||
...(childSessionId ? { childSessionId } : {}),
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
actions: [],
|
||||
};
|
||||
|
||||
@@ -98,6 +98,7 @@ export const edxethPiSubagents: PiExtension = {
|
||||
detail: {
|
||||
type: "sub_agent",
|
||||
description,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
},
|
||||
subagents,
|
||||
|
||||
+2
@@ -63,6 +63,7 @@ export const gotgenesPiSubagents: PiExtension = {
|
||||
type: "sub_agent" as const,
|
||||
subAgentType: title,
|
||||
description,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
};
|
||||
if (call.status === "running")
|
||||
@@ -117,6 +118,7 @@ export const gotgenesPiSubagents: PiExtension = {
|
||||
const detail = {
|
||||
type: "sub_agent" as const,
|
||||
description: details.success ? details.data.description : undefined,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
};
|
||||
if (call.status === "running" || !details.success) return { detail };
|
||||
|
||||
@@ -131,6 +131,7 @@ export const piSubagents: PiExtension = {
|
||||
type: "sub_agent" as const,
|
||||
subAgentType: args.data.agent,
|
||||
description: args.data.task,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
};
|
||||
const base = {
|
||||
|
||||
@@ -54,8 +54,6 @@ export async function verifySubagentFixture(
|
||||
try {
|
||||
await session.startTurn("Delegate work");
|
||||
for (const event of fixture.events) pi.latestSession().emit(event);
|
||||
const immediate = live.filter((event) => event.type === "provider_subagent");
|
||||
expect(immediate.some((event) => event.event.type === "timeline")).toBe(false);
|
||||
pi.latestSession().finishTurn();
|
||||
const replay: AgentStreamEvent[] = [];
|
||||
for await (const event of streamPiHistory("pi", fixture.messages)) replay.push(event);
|
||||
|
||||
+17
@@ -36,6 +36,11 @@ describe("@tintinweb/pi-subagents adapter", () => {
|
||||
});
|
||||
expect(mapping?.subagents).toEqual([
|
||||
expect.objectContaining({ id: "call-1", status: "completed" }),
|
||||
{
|
||||
type: "timeline",
|
||||
id: "call-1",
|
||||
item: { type: "assistant_message", text: "Wrapped up (turn limit)" },
|
||||
},
|
||||
]);
|
||||
expect(mapping?.detail).toEqual({
|
||||
type: "sub_agent",
|
||||
@@ -53,6 +58,18 @@ describe("@tintinweb/pi-subagents adapter", () => {
|
||||
.filter((event) => event.event.type === "upsert")
|
||||
.map((event) => (event.event.type === "upsert" ? event.event.status : null)),
|
||||
).toEqual(["running", "completed"]);
|
||||
expect(
|
||||
events.flatMap((event) => (event.event.type === "timeline" ? [event.event] : [])),
|
||||
).toEqual([
|
||||
{
|
||||
type: "timeline",
|
||||
id: "call_458926",
|
||||
item: {
|
||||
type: "assistant_message",
|
||||
text: "Agent completed in 6.6s (1 tool uses, 18.8k token).\n\nThe workspace directory `/tmp/paseo-pi-extension-phase3/workspace` is empty (no files or hidden dotfiles).",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
test("uses a structured notification to complete a background child", async () => {
|
||||
const source = readSubagentFixture(new URL("./fixtures/background.json", import.meta.url));
|
||||
|
||||
+17
-1
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { extractTextFromToolResult } from "../../tool-call-mapper.js";
|
||||
import { outputFileFromToolResult } from "../child-session.js";
|
||||
import type { PiExtension, PiExtensionToolCall } from "../contract.js";
|
||||
|
||||
@@ -56,6 +57,7 @@ export const tintinwebPiSubagents: PiExtension = {
|
||||
type: "sub_agent" as const,
|
||||
subAgentType: title,
|
||||
description,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
};
|
||||
if (call.status === "running")
|
||||
@@ -83,6 +85,10 @@ export const tintinwebPiSubagents: PiExtension = {
|
||||
const file = outputFileFromToolResult(call.result);
|
||||
const childSessions = file && !readSessions.has(file) ? [{ id: call.callId, file }] : [];
|
||||
if (file) readSessions.add(file);
|
||||
const childStatus = status(details.data.status);
|
||||
// A foreground child has no session file; its answer is the tool result.
|
||||
const answer =
|
||||
!file && childStatus !== "running" ? extractTextFromToolResult(call.result)?.trim() : "";
|
||||
return {
|
||||
detail,
|
||||
subagents: [
|
||||
@@ -92,8 +98,17 @@ export const tintinwebPiSubagents: PiExtension = {
|
||||
title,
|
||||
description,
|
||||
toolCallId: call.callId,
|
||||
status: status(details.data.status),
|
||||
status: childStatus,
|
||||
},
|
||||
...(answer
|
||||
? [
|
||||
{
|
||||
type: "timeline" as const,
|
||||
id: call.callId,
|
||||
item: { type: "assistant_message" as const, text: answer },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
childSessions,
|
||||
};
|
||||
@@ -109,6 +124,7 @@ export const tintinwebPiSubagents: PiExtension = {
|
||||
const detail = {
|
||||
type: "sub_agent" as const,
|
||||
description: details.success ? details.data.description : undefined,
|
||||
// COMPAT(subagent-log): remove after 2027-04-30.
|
||||
log: "",
|
||||
};
|
||||
if (call.status === "running" || !details.success) return { detail };
|
||||
|
||||
Reference in New Issue
Block a user