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

308 lines
9.7 KiB
TypeScript

import type { AgentActivityMessage } from "@tutti-os/agent-activity-core";
import { buildWorkspaceAgentActivityListViewModel } from "../../../shared/workspaceAgentActivityListViewModel";
import {
buildWorkspaceAgentSessionDetailViewModel,
type WorkspaceAgentSessionDetailToolCall
} from "../../../shared/workspaceAgentSessionDetailViewModel";
import {
firstAgentGUIUserMessageTitle,
resolveAgentGUIProviderIdentity
} from "../../../shared/agentConversationTitleProjection.ts";
import { type WorkspaceAgentActivityTimelineItem } from "../../../shared/workspaceAgentTimelineTypes";
import {
normalizeAgentActivitySession,
type AgentActivitySession
} from "@tutti-os/agent-activity-core";
import {
type AgentGUIConversationProjectionSource,
type AgentGUITimelineRow
} from "./agentGuiConversationTypes";
import {
dedupeTimelineRowsByID,
latestTimelineItemByCallId,
latestTimelineTime,
normalizeToolCallID,
stableTimelineRowID,
timelineRowStatus,
timelineRowStatusByCallId,
timelineRowTime,
timelineRowTimeByCallId
} from "./agentGuiInteractiveProjection";
export function timelineRowsFromActivityTimelineItems(
timelineItems: WorkspaceAgentActivityTimelineItem[]
): AgentGUITimelineRow[] {
if (timelineItems.length === 0) {
return [];
}
const session = timelineSessionFromItems(timelineItems);
const activity =
buildWorkspaceAgentActivityListViewModel(
{
presences: [],
sessions: [session]
},
{
sessionMessagesById: {
[session.agentSessionId]:
workspaceAgentMessagesFromTimelineItems(timelineItems)
}
}
).activities[0] ?? null;
if (!activity) {
return [];
}
const detail = buildWorkspaceAgentSessionDetailViewModel({
activity,
session,
timelineItems
});
const rows: AgentGUITimelineRow[] = [];
for (const turn of detail.turns) {
for (const message of turn.userMessages) {
const rowID = stableTimelineRowID(timelineItems, message.id);
rows.push({
id: rowID,
turnId: turn.id,
role: "user",
content: message.body,
eventType: "message.user",
status: timelineRowStatus(timelineItems, message.id),
occurredAtUnixMs: timelineRowTime(timelineItems, message.id)
});
}
for (const item of turn.agentItems) {
if (item.kind === "message") {
const rowID = stableTimelineRowID(timelineItems, item.message.id);
rows.push({
id: rowID,
turnId: turn.id,
role: "assistant",
content: item.message.body,
eventType: "message.assistant",
status: timelineRowStatus(timelineItems, item.message.id),
occurredAtUnixMs: timelineRowTime(timelineItems, item.message.id)
});
} else if (item.kind === "thinking") {
const rowID = stableTimelineRowID(timelineItems, item.thinking.id);
rows.push({
id: rowID,
turnId: turn.id,
role: "assistant_thinking",
content: item.thinking.body,
eventType: "message.assistant_thinking",
status: timelineRowStatus(timelineItems, item.thinking.id),
occurredAtUnixMs: timelineRowTime(timelineItems, item.thinking.id)
});
} else {
for (const call of item.toolCalls) {
const callID = normalizeToolCallID(call.id);
const latestCallItem = latestTimelineItemByCallId(
timelineItems,
callID
);
const approvalTitle = latestCallItem
? historicalApprovalTitle(latestCallItem)
: null;
rows.push({
id: `call:${turn.id}:${callID}`,
turnId: turn.id,
role: "tool",
content: toolRowContent(call, approvalTitle),
eventType: "call",
status:
timelineRowStatusByCallId(timelineItems, callID) ?? call.status,
callType: latestCallItem?.callType?.trim() || undefined,
occurredAtUnixMs: timelineRowTimeByCallId(timelineItems, callID)
});
}
}
}
}
return dedupeTimelineRowsByID(rows);
}
function historicalApprovalTitle(
item: WorkspaceAgentActivityTimelineItem
): string | null {
const payload = item.payload ?? {};
const callType =
item.callType?.trim().toLowerCase() ||
(typeof payload.callType === "string"
? payload.callType.trim().toLowerCase()
: "");
if (callType !== "approval") {
return null;
}
return (
item.name?.trim() ||
(typeof payload.name === "string" ? payload.name.trim() : "") ||
item.content?.trim() ||
item.callId?.trim() ||
null
);
}
export function toolRowContent(
call: WorkspaceAgentSessionDetailToolCall,
approvalTitle: string | null
): string {
if (approvalTitle?.trim()) {
return approvalTitle.trim();
}
const summary = call.summary.trim();
if (summary && !looksLikeOpaqueFunctionCallSummary(summary)) {
return summary;
}
return call.name;
}
export function looksLikeOpaqueFunctionCallSummary(value: string): boolean {
return /^call function [a-z0-9]+(?: \d+)?$/i.test(value.trim());
}
export function timelineSessionFromItems(
timelineItems: readonly WorkspaceAgentActivityTimelineItem[],
conversation?: AgentGUIConversationProjectionSource
): AgentActivitySession {
const first = timelineItems[0];
const fallbackAgentSessionId =
first?.agentSessionId?.trim() ||
conversation?.id?.trim() ||
"agent-gui-session";
const provider = resolveAgentGUIProviderIdentity({
conversationProvider: conversation?.provider,
timelineItems
});
const workspaceId = first?.workspaceId?.trim() || "";
return normalizeAgentActivitySession({
workspaceId,
agentSessionId: fallbackAgentSessionId,
userId: conversation?.userId?.trim() ?? "",
provider: provider ?? "",
providerSessionId: fallbackAgentSessionId,
cwd: conversation?.cwd?.trim() ?? "",
activeTurn: conversation?.activeTurn ?? null,
activeTurnId: conversation?.activeTurn?.turnId ?? null,
latestTurnInteractions: [],
pendingInteractions: [],
title: conversation?.title ?? "",
createdAtUnixMs: first?.createdAtUnixMs ?? first?.occurredAtUnixMs ?? 0,
updatedAtUnixMs:
latestTimelineTime(timelineItems) || conversation?.updatedAtUnixMs || 0,
pinnedAtUnixMs: conversation?.pinnedAtUnixMs ?? null
});
}
export function firstUserMessageTitleFromTimelineItems(
timelineItems: readonly WorkspaceAgentActivityTimelineItem[]
): string {
const userMessage = [...timelineItems]
.filter(
(item) =>
timelineItemRole(item) === "user" && timelineItemText(item).length > 0
)
.sort(compareTimelineItemsAscending)[0];
return userMessage ? timelineItemText(userMessage) : "";
}
export function firstUserMessageTitleFromMessages(
messages: readonly AgentActivityMessage[]
): string {
return firstAgentGUIUserMessageTitle(messages);
}
export function workspaceAgentMessagesFromTimelineItems(
timelineItems: readonly WorkspaceAgentActivityTimelineItem[]
): AgentActivityMessage[] {
return timelineItems.map((item, index) => {
const messageId =
item.eventId || `${item.agentSessionId}:${item.id}:${index}`;
const version = item.seq ?? item.id;
return {
id: item.id,
workspaceId: item.workspaceId,
agentSessionId: item.agentSessionId,
messageId,
version,
turnId: item.turnId || `timeline:${messageId}`,
role: item.role ?? timelineItemRole(item) ?? item.actorType,
kind: item.itemType === "call" ? "tool_call" : item.itemType,
...(item.status ? { status: item.status } : {}),
payload: {
...item.payload,
content: item.payload?.content ?? item.content,
text: item.payload?.text ?? item.content,
callType: item.callType,
callId: item.callId,
name: item.name
},
occurredAtUnixMs:
item.occurredAtUnixMs ?? item.createdAtUnixMs ?? version,
startedAtUnixMs: item.createdAtUnixMs,
completedAtUnixMs: item.occurredAtUnixMs
};
});
}
export function timelineItemRole(
item: WorkspaceAgentActivityTimelineItem
): "user" | "agent" | null {
const role = item.role?.trim().toLowerCase();
if (role === "user") {
return "user";
}
if (role === "assistant" || role === "agent") {
return "agent";
}
const itemType = item.itemType.trim().toLowerCase();
if (itemType === "message.user") {
return "user";
}
if (itemType === "message.assistant" || itemType === "message.agent") {
return "agent";
}
return null;
}
export function timelineItemText(
item: WorkspaceAgentActivityTimelineItem
): string {
const payloadDisplayPrompt =
typeof item.payload?.displayPrompt === "string"
? item.payload.displayPrompt
: "";
const payloadContent =
typeof item.payload?.content === "string" ? item.payload.content : "";
const payloadText =
typeof item.payload?.text === "string" ? item.payload.text : "";
return (payloadDisplayPrompt || payloadText || item.content || payloadContent)
.replace(/\s+/g, " ")
.trim();
}
export function compareTimelineItemsAscending(
left: WorkspaceAgentActivityTimelineItem,
right: WorkspaceAgentActivityTimelineItem
): number {
const leftTime = left.occurredAtUnixMs ?? left.createdAtUnixMs ?? 0;
const rightTime = right.occurredAtUnixMs ?? right.createdAtUnixMs ?? 0;
const timeDiff = leftTime - rightTime;
if (timeDiff !== 0) {
return timeDiff;
}
return left.id - right.id || left.eventId.localeCompare(right.eventId);
}
export function sessionLifecycleStatus(status: string): string {
switch (status.trim().toLowerCase()) {
case "completed":
case "canceled":
return "ended";
case "failed":
return "failed";
default:
return "active";
}
}