Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

201 lines
6.2 KiB
TypeScript

import { memo, useState, type JSX } from "react";
import { ChevronDown, ChevronRight } from "lucide-react";
import { ToolActivityKindIcon } from "../../toolActivityKindIcons";
import type { AgentToolCallVM } from "../contracts/agentToolCallVM";
import type { AgentToolGroupRowVM } from "../contracts/agentToolGroupRowVM";
import { AgentAskUserQuestionCard } from "./AgentAskUserQuestionCard";
import { CollapsibleReveal } from "./CollapsibleReveal";
import { AgentEnterPlanModeCard } from "./AgentEnterPlanModeCard";
import { AgentExitPlanModeCard } from "./AgentExitPlanModeCard";
import { AgentThinkingDisclosure } from "./AgentThinkingDisclosure";
import { AgentTaskCallCard } from "./AgentTaskCallCard";
import { AgentSubAgentCards } from "./AgentSubAgentCards";
import { AgentToolCallCard } from "./AgentToolCallCard";
import { RawTimelineJsonDisclosure } from "./RawTimelineJsonDisclosure";
interface AgentToolGroupRowProps {
row: AgentToolGroupRowVM;
label: (count: number) => string;
thinkingLabel: string;
onLinkClick?: (href: string) => void;
expanded?: boolean;
expansionKey?: string;
onExpandedChange?: (key: string, expanded: boolean) => void;
showRawTimelineJson?: boolean;
rawTimelineJsonLabel?: string;
}
export const AgentToolGroupRow = memo(function AgentToolGroupRow({
row,
label,
thinkingLabel,
onLinkClick,
expanded,
expansionKey,
onExpandedChange,
showRawTimelineJson = false,
rawTimelineJsonLabel = ""
}: AgentToolGroupRowProps): JSX.Element {
"use memo";
const [localExpanded, setLocalExpanded] = useState(false);
const hasDetail = row.entries.length > 0;
const isExpanded = hasDetail && (expanded ?? localExpanded);
const setNextExpanded = (nextExpanded: boolean) => {
if (expanded === undefined) {
setLocalExpanded(nextExpanded);
}
if (expansionKey) {
onExpandedChange?.(expansionKey, nextExpanded);
}
};
const groupLabel = label(row.calls.length);
const singleCall = row.calls[0];
if (!row.grouped && singleCall) {
return renderToolCard(
singleCall,
onLinkClick,
showRawTimelineJson,
rawTimelineJsonLabel
);
}
return (
<div
className="workspace-agents-status-panel__detail-tool-section"
data-tool-group-expanded={isExpanded ? "true" : undefined}
>
{hasDetail ? (
<button
type="button"
className="workspace-agents-status-panel__detail-tool-count"
aria-expanded={isExpanded}
aria-label={groupLabel}
onClick={() => setNextExpanded(!isExpanded)}
>
<ToolGroupLabel label={groupLabel} />
{isExpanded ? (
<ChevronDown
size={12}
strokeWidth={2.2}
aria-hidden="true"
className="workspace-agents-status-panel__detail-tool-count-chevron"
/>
) : (
<ChevronRight
size={12}
strokeWidth={2.2}
aria-hidden="true"
className="workspace-agents-status-panel__detail-tool-count-chevron"
/>
)}
</button>
) : (
<div className="workspace-agents-status-panel__detail-tool-count">
<ToolGroupLabel label={groupLabel} />
</div>
)}
{hasDetail ? (
<CollapsibleReveal expanded={isExpanded}>
<div className="workspace-agents-status-panel__detail-tool-list">
{row.entries.map((entry) =>
entry.kind === "thinking" ? (
<div
key={entry.thinking.id}
className="workspace-agents-status-panel__detail-tool-list-item workspace-agents-status-panel__detail-tool-row"
>
<AgentThinkingDisclosure
thinking={entry.thinking}
label={thinkingLabel}
onLinkClick={onLinkClick}
showRawTimelineJson={showRawTimelineJson}
rawTimelineJsonLabel={rawTimelineJsonLabel}
/>
</div>
) : (
<div
key={entry.call.id}
className="workspace-agents-status-panel__detail-tool-list-item"
>
{renderToolCard(
entry.call,
onLinkClick,
showRawTimelineJson,
rawTimelineJsonLabel
)}
</div>
)
)}
</div>
</CollapsibleReveal>
) : null}
</div>
);
});
function ToolGroupLabel({ label }: { label: string }): JSX.Element {
"use memo";
return (
<>
<ToolActivityKindIcon
kind="update_todos"
width={16}
height={16}
className="workspace-agents-status-panel__detail-tool-count-icon"
/>
<span className="workspace-agents-status-panel__detail-tool-count-label">
{renderToolCountLabel(label)}
</span>
</>
);
}
function renderToolCountLabel(label: string): JSX.Element {
return (
<span className="workspace-agents-status-panel__detail-tool-count-primary">
{label}
</span>
);
}
function renderToolCard(
call: AgentToolCallVM,
onLinkClick?: (href: string) => void,
showRawTimelineJson = false,
rawTimelineJsonLabel = ""
): JSX.Element {
const props = { call, onLinkClick };
// A delegated sub-agent renders as its own first-class card - no tool-row
// chrome, no prompt echo; identity/status belong to the sub-agent itself.
if (call.task?.subAgents?.length) {
return <AgentSubAgentCards call={call} onLinkClick={onLinkClick} />;
}
let card: JSX.Element;
switch (call.rendererKind) {
case "plan-enter":
card = <AgentEnterPlanModeCard {...props} />;
break;
case "plan-exit":
card = <AgentExitPlanModeCard {...props} />;
break;
case "ask-user":
card = <AgentAskUserQuestionCard {...props} />;
break;
case "task":
card = <AgentTaskCallCard {...props} />;
break;
default:
card = <AgentToolCallCard {...props} />;
}
return (
<>
{card}
{showRawTimelineJson && rawTimelineJsonLabel ? (
<RawTimelineJsonDisclosure
items={call.sourceTimelineItems}
label={rawTimelineJsonLabel}
/>
) : null}
</>
);
}