mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 10:57:07 +00:00
* fix: make sub-agent cards launch markers Sub-agent tool calls no longer accumulate the child's activity into `log`. Claude, Codex, OpenCode, OMP and Pi emit the card when the child starts and when its label or status changes; the child's messages and tool calls stay in the sub-agent's own timeline. The app no longer renders a sub-agent log. The wire schema makes `log` optional. Daemons still send `log: ""` so apps that require the field keep parsing the card. * 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. * fix(server): settle child compactions and keep sub-agent cards stable - Codex: a child compaction shown as loading now completes on the child's thread/compacted or turn end, once. - Claude: a streamed child tool call's empty start input is a placeholder, so the assistant message's input reaches its card. - OMP: the task card follows only its first child, so later children no longer re-send an unchanged card. * fix(server): keep a gotgenes foreground answer on replay A foreground @gotgenes/pi-subagents child finds its session file only through the live runtime marker, which history does not keep. Without a file, emit the spawn result text as the child's assistant message.
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import type { ToolCallDetail } from "@getpaseo/protocol/agent-types";
|
|
|
|
function hasMeaningfulUnknownValue(value: unknown): boolean {
|
|
if (value === null || value === undefined) {
|
|
return false;
|
|
}
|
|
if (typeof value === "string") {
|
|
return value.trim().length > 0;
|
|
}
|
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
return true;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some(hasMeaningfulUnknownValue);
|
|
}
|
|
if (typeof value === "object") {
|
|
return Object.values(value).some(hasMeaningfulUnknownValue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function hasMeaningfulSearchDetail(detail: Extract<ToolCallDetail, { type: "search" }>): boolean {
|
|
if (detail.query.trim().length > 0) return true;
|
|
if (detail.content) return true;
|
|
if (detail.filePaths && detail.filePaths.length > 0) return true;
|
|
if (detail.webResults && detail.webResults.length > 0) return true;
|
|
if (detail.annotations && detail.annotations.length > 0) return true;
|
|
return false;
|
|
}
|
|
|
|
function hasMeaningfulReadLikeDetail(detail: { filePath?: unknown; content?: unknown }): boolean {
|
|
return Boolean(detail.filePath || detail.content);
|
|
}
|
|
|
|
function hasMeaningfulEditDetail(detail: Extract<ToolCallDetail, { type: "edit" }>): boolean {
|
|
return Boolean(detail.filePath || detail.unifiedDiff || detail.oldString || detail.newString);
|
|
}
|
|
|
|
function hasMeaningfulFetchDetail(detail: Extract<ToolCallDetail, { type: "fetch" }>): boolean {
|
|
return Boolean(detail.url || detail.result || detail.codeText);
|
|
}
|
|
|
|
function hasMeaningfulPlainTextDetail(
|
|
detail: Extract<ToolCallDetail, { type: "plain_text" }>,
|
|
): boolean {
|
|
return Boolean(detail.label || detail.text);
|
|
}
|
|
|
|
function hasMeaningfulUnknownDetail(detail: Extract<ToolCallDetail, { type: "unknown" }>): boolean {
|
|
return hasMeaningfulUnknownValue(detail.input) || hasMeaningfulUnknownValue(detail.output);
|
|
}
|
|
|
|
export function hasMeaningfulToolCallDetail(detail: ToolCallDetail | undefined): boolean {
|
|
if (!detail) {
|
|
return false;
|
|
}
|
|
|
|
switch (detail.type) {
|
|
case "shell":
|
|
return true;
|
|
case "read":
|
|
return hasMeaningfulReadLikeDetail(detail);
|
|
case "edit":
|
|
return hasMeaningfulEditDetail(detail);
|
|
case "write":
|
|
return hasMeaningfulReadLikeDetail(detail);
|
|
case "search":
|
|
return hasMeaningfulSearchDetail(detail);
|
|
case "fetch":
|
|
return hasMeaningfulFetchDetail(detail);
|
|
case "worktree_setup":
|
|
// A sub-agent card is a launch marker; the child's activity lives in the sub-agent panel.
|
|
case "sub_agent":
|
|
return false;
|
|
case "plain_text":
|
|
return hasMeaningfulPlainTextDetail(detail);
|
|
case "plan":
|
|
return detail.text.trim().length > 0;
|
|
case "unknown":
|
|
return hasMeaningfulUnknownDetail(detail);
|
|
}
|
|
}
|
|
|
|
export function isPendingToolCallDetail(params: {
|
|
detail: ToolCallDetail | undefined;
|
|
status: "executing" | "running" | "completed" | "failed" | "canceled";
|
|
error: unknown;
|
|
}): boolean {
|
|
if (params.detail?.type === "worktree_setup" || params.detail?.type === "sub_agent") return false;
|
|
const isRunning = params.status === "running" || params.status === "executing";
|
|
return isRunning && params.error == null && !hasMeaningfulToolCallDetail(params.detail);
|
|
}
|