mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(pi): render delegated tasks with lifecycle status (#2891)
Pi left task and direct subagent tool calls in the unknown detail shape, so clients rendered raw request and result payloads. Normalize execution calls at the provider boundary so the existing task card owns their running and terminal states. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -553,6 +553,61 @@ describe("PiRpcAgentSession", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("streams Pi task calls as sub-agent cards with lifecycle status", async () => {
|
||||
const { pi, session, events } = await createSession();
|
||||
const fakeSession = pi.latestSession();
|
||||
|
||||
await session.startTurn("delegate this");
|
||||
fakeSession.emit({
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "task-1",
|
||||
toolName: "task",
|
||||
args: {
|
||||
agent: "explore",
|
||||
task: "Trace the Pi provider tool mapper",
|
||||
},
|
||||
});
|
||||
fakeSession.emit({
|
||||
type: "tool_execution_end",
|
||||
toolCallId: "task-1",
|
||||
toolName: "task",
|
||||
result: { content: [{ type: "text", text: "Found the mapper." }] },
|
||||
isError: false,
|
||||
});
|
||||
fakeSession.finishTurn();
|
||||
|
||||
await events.nextTurnCompletion();
|
||||
|
||||
expect(events.timelineItems()).toEqual([
|
||||
{
|
||||
type: "tool_call",
|
||||
callId: "task-1",
|
||||
name: "task",
|
||||
status: "running",
|
||||
detail: {
|
||||
type: "sub_agent",
|
||||
subAgentType: "explore",
|
||||
description: "Trace the Pi provider tool mapper",
|
||||
log: "",
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
type: "tool_call",
|
||||
callId: "task-1",
|
||||
name: "task",
|
||||
status: "completed",
|
||||
detail: {
|
||||
type: "sub_agent",
|
||||
subAgentType: "explore",
|
||||
description: "Trace the Pi provider tool mapper",
|
||||
log: "Found the mapper.",
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps one generated message id when Pi omits message start and response id", async () => {
|
||||
const { pi, session, events } = await createSession();
|
||||
const fakeSession = pi.latestSession();
|
||||
|
||||
@@ -136,6 +136,37 @@ describe("Pi tool call mapper", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("maps task calls to sub-agent detail while running", () => {
|
||||
const toolCall = parseToolArgs("task", {
|
||||
agent: "explore",
|
||||
task: "Trace the Pi provider tool mapper",
|
||||
});
|
||||
|
||||
expect(mapToolDetail(toolCall, null)).toEqual({
|
||||
type: "sub_agent",
|
||||
subAgentType: "explore",
|
||||
description: "Trace the Pi provider tool mapper",
|
||||
log: "",
|
||||
});
|
||||
});
|
||||
|
||||
test("maps completed subagent calls with task input to sub-agent detail", () => {
|
||||
const toolCall = parseToolArgs("subagent", {
|
||||
agent: "reviewer",
|
||||
task: "Review the Pi mapper change",
|
||||
});
|
||||
const result = parseToolResult({
|
||||
content: [{ type: "text", text: "The mapper change preserves provider status." }],
|
||||
});
|
||||
|
||||
expect(mapToolDetail(toolCall, result)).toEqual({
|
||||
type: "sub_agent",
|
||||
subAgentType: "reviewer",
|
||||
description: "Review the Pi mapper change",
|
||||
log: "The mapper change preserves provider status.",
|
||||
});
|
||||
});
|
||||
|
||||
test("normalizes Pi MCP proxy calls from requested tool args while running", () => {
|
||||
const toolCall = parseToolArgs("mcp", {
|
||||
tool: "paseo_list_models",
|
||||
|
||||
@@ -340,6 +340,10 @@ export function resolveToolCallName(toolCall: PiTrackedToolCall, result?: PiTool
|
||||
export function mapToolDetail(toolCall: PiTrackedToolCall, result?: PiToolResult): ToolCallDetail {
|
||||
const parsedResult = result ?? null;
|
||||
|
||||
if (isTaskToolCall(toolCall)) {
|
||||
return mapTaskToolDetail(toolCall.args, parsedResult);
|
||||
}
|
||||
|
||||
switch (toolCall.kind) {
|
||||
case "bash": {
|
||||
const summary = resolveToolCallOutput(parsedResult);
|
||||
@@ -388,6 +392,30 @@ export function mapToolDetail(toolCall: PiTrackedToolCall, result?: PiToolResult
|
||||
}
|
||||
}
|
||||
|
||||
function isTaskToolCall(toolCall: PiTrackedToolCall): boolean {
|
||||
if (toolCall.toolName === "task") {
|
||||
return true;
|
||||
}
|
||||
if (toolCall.toolName !== "subagent" || !isRecord(toolCall.args)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
toolCall.args.action === undefined &&
|
||||
(readNonEmptyString(toolCall.args.agent) !== undefined ||
|
||||
readNonEmptyString(toolCall.args.task) !== undefined)
|
||||
);
|
||||
}
|
||||
|
||||
function mapTaskToolDetail(args: unknown, result: PiToolResult): ToolCallDetail {
|
||||
const argRecord = isRecord(args) ? args : {};
|
||||
return {
|
||||
type: "sub_agent",
|
||||
subAgentType: readNonEmptyString(argRecord.agent),
|
||||
description: readNonEmptyString(argRecord.task),
|
||||
log: extractTextFromToolResult(result)?.trim() ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
function mapWriteToolDetail(args: WriteToolInput, result: PiToolResult): ToolCallDetail {
|
||||
if (result && typeof result !== "string" && result.details && "xdev" in result.details) {
|
||||
const xdev = XdevExecuteDetailsSchema.safeParse(result.details.xdev);
|
||||
|
||||
Reference in New Issue
Block a user