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

324 lines
8.9 KiB
TypeScript

import type { WorkspaceAgentSessionDetailToolCall } from "../../workspaceAgentSessionDetailViewModel";
import type {
AgentTaskItemVM,
AgentTaskStepVM
} from "../contracts/agentTaskItemVM";
import type { AgentToolCallVM } from "../contracts/agentToolCallVM";
import { projectAgentApprovalItem } from "./agentApprovalProjection";
import {
projectAgentAskUserQuestionItem,
projectAgentPlanModeItem
} from "./agentInteractiveProjection";
import {
inferToolCallType,
isApprovalToolCall,
normalizeToolName,
resolveAgentToolRendererKind
} from "./agentToolRendererKind";
export function projectAgentTaskItem(
call: WorkspaceAgentSessionDetailToolCall,
input: Record<string, unknown> | null,
output: Record<string, unknown> | null,
metadata: Record<string, unknown> | null
): AgentTaskItemVM | null {
const toolName = normalizeToolName(call.toolName);
if (
!["task", "subagent", "delegatetask", "delegateagent", "agent"].includes(
toolName
)
) {
return null;
}
const steps = normalizeTaskSteps(
arrayValue(metadata?.steps) ??
arrayValue(output?.steps) ??
arrayValue(call.payload?.steps) ??
[],
call.turnId ?? "turn:unknown"
);
return {
kind: "task",
id: call.id,
turnId: call.turnId ?? "turn:unknown",
title: call.summary.trim() || call.name,
status:
stringValue(metadata?.taskStatus) ??
stringValue(metadata?.subagentStatus) ??
call.status,
prompt:
stringValue(input?.prompt) ??
stringValue(input?.description) ??
stringValue(call.payload?.description),
delegateSessionId:
stringValue(metadata?.childSessionID) ??
stringValue(metadata?.child_session_id) ??
stringValue(metadata?.subagentSessionID) ??
stringValue(metadata?.subagent_session_id) ??
stringValue(metadata?.subagentAgentId) ??
stringValue(metadata?.agentId),
steps,
result: firstNonEmptyText(output),
resultMarkdown: firstNonEmptyText(output),
durationMs:
numberValue(metadata?.durationMs) ?? numberValue(output?.durationMs),
occurredAtUnixMs: call.occurredAtUnixMs ?? null
};
}
function normalizeTaskSteps(
values: readonly unknown[],
turnId: string
): AgentTaskStepVM[] {
return values.flatMap((value, index) => {
const step = objectValue(value);
if (!step) {
return [];
}
const toolName =
stringValue(step.toolName) ??
stringValue(step.tool_name) ??
stringValue(step.name);
const callType = stringValue(step.callType) ?? stringValue(step.call_type);
if (isApprovalToolCall({ toolName, callType })) {
return [];
}
const inputPayload =
objectValue(step.toolInput) ?? objectValue(step.tool_input);
const outputPayload =
objectValue(step.toolResult) ?? objectValue(step.tool_result);
const errorPayload =
objectValue(step.toolError) ?? objectValue(step.tool_error);
const metadata = objectValue(step.metadata);
const locations = arrayValue(step.locations);
return [
{
id:
stringValue(step.toolUseId) ??
stringValue(step.id) ??
`step-${index + 1}`,
turnId,
name: toolName ? humanizeToolLabel(toolName) : `Step ${index + 1}`,
toolName: toolName ?? null,
status:
stringValue(step.status) ??
stringValue(objectValue(step.toolResult)?.status) ??
stringValue(objectValue(step.tool_result)?.status),
summary: firstNonEmptyText(outputPayload, inputPayload) ?? "",
payload: {
input: inputPayload,
output: outputPayload,
error: errorPayload
},
tool: projectAgentTaskStepTool({
id:
stringValue(step.toolUseId) ??
stringValue(step.id) ??
`step-${index + 1}`,
turnId,
toolName,
name: stringValue(step.name),
callType,
status:
stringValue(step.status) ?? stringValue(outputPayload?.status),
summary: firstNonEmptyText(outputPayload, inputPayload) ?? "",
payload: {
input: inputPayload,
output: outputPayload,
error: errorPayload,
metadata,
...(step.fileChanges !== undefined
? { fileChanges: step.fileChanges }
: {})
},
metadata,
input: inputPayload,
output: outputPayload,
error: errorPayload,
locations,
occurredAtUnixMs: numberValue(step.occurredAtUnixMs)
}),
occurredAtUnixMs: numberValue(step.occurredAtUnixMs) ?? null
}
];
});
}
export function projectAgentTaskStepTool(step: {
id: string;
turnId: string;
toolName: string | null;
name: string | null;
callType: string | null;
status: string | null;
summary: string;
payload: Record<string, unknown> | null;
metadata: Record<string, unknown> | null;
input: Record<string, unknown> | null;
output: Record<string, unknown> | null;
error: Record<string, unknown> | null;
locations: unknown[] | null;
occurredAtUnixMs: number | null;
}): AgentToolCallVM {
const call: WorkspaceAgentSessionDetailToolCall = {
id: `call:${step.id}`,
turnId: step.turnId,
name:
step.name ?? (step.toolName ? humanizeToolLabel(step.toolName) : "Tool"),
toolName: step.toolName,
callType: step.callType ?? inferToolCallType(step.toolName),
status: step.status,
statusKind: normalizeTaskStepStatus(step.status),
summary: step.summary,
compactSummary: step.summary || null,
payload: step.payload,
occurredAtUnixMs: step.occurredAtUnixMs
};
const approval = projectAgentApprovalItem(call, step.input, step.output);
const askUserQuestion = projectAgentAskUserQuestionItem(
call,
step.input,
step.output
);
const planMode = projectAgentPlanModeItem(call, step.input);
const task = projectAgentTaskItem(
call,
step.input,
step.output,
step.metadata
);
const rendererKind = resolveAgentToolRendererKind(
{
toolName: call.toolName,
displayName: call.name,
callType: call.callType,
input: step.input,
output: step.output,
metadata: step.metadata
},
approval,
askUserQuestion,
planMode,
task
);
return {
kind: "tool-call",
...call,
turnId: step.turnId,
compactSummary: call.compactSummary ?? null,
payload: step.payload,
input: step.input,
output: step.output,
error: step.error,
metadata: step.metadata,
locations: step.locations,
occurredAtUnixMs: call.occurredAtUnixMs ?? null,
rendererKind,
approval,
planMode,
askUserQuestion,
task
};
}
function humanizeToolLabel(value: string): string {
return value
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.replace(/^\w/, (match) => match.toUpperCase());
}
function stringValue(value: unknown): string | null {
return typeof value === "string" && value.trim() ? value.trim() : null;
}
function numberValue(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function normalizeTaskStepStatus(
value: string | null
): AgentToolCallVM["statusKind"] {
switch ((value ?? "").trim().toLowerCase()) {
case "completed":
case "done":
case "success":
case "succeeded":
return "completed";
case "failed":
case "error":
case "errored":
return "failed";
case "waiting":
case "waiting_input":
case "waiting_approval":
return "waiting";
case "running":
case "working":
case "active":
return "working";
default:
return null;
}
}
function objectValue(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: null;
}
function arrayValue(value: unknown): unknown[] | null {
return Array.isArray(value) ? value : null;
}
function firstNonEmptyText(
...values: Array<Record<string, unknown> | string | null | undefined>
): string | null {
for (const value of values) {
const text = extractText(value);
if (text) {
return text;
}
}
return null;
}
function extractText(value: unknown, depth = 0): string | null {
if (depth > 4) {
return null;
}
if (typeof value === "string" && value.trim()) {
return value.trim();
}
if (Array.isArray(value)) {
for (const item of value) {
const text = extractText(item, depth + 1);
if (text) {
return text;
}
}
return null;
}
if (value && typeof value === "object") {
const record = value as Record<string, unknown>;
for (const key of [
"text",
"output",
"summary",
"message",
"patch",
"result"
]) {
const text = extractText(record[key], depth + 1);
if (text) {
return text;
}
}
return null;
}
return null;
}