Files
tutti/packages/agent/gui/shared/workspaceAgentLatestActivitySummary.ts
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

303 lines
8.1 KiB
TypeScript

import type { WorkspaceAgentActivityTimelineItem } from "./workspaceAgentTimelineTypes";
import { translate, translateInUiLanguage } from "../i18n/index";
import { isWorkspaceAgentSyntheticControlMessage } from "./workspaceAgentSyntheticMessages";
export type WorkspaceAgentLatestActivityStatus =
| "working"
| "waiting"
| "idle"
| "completed"
| "canceled"
| "failed";
export interface WorkspaceAgentLatestActivityActors {
agentName: string;
userName: string;
}
export interface WorkspaceAgentLatestActivitySummary {
actorName: string;
summary: string;
}
export interface WorkspaceAgentConversationPreviewLine {
actorName: string;
summary: string;
}
export function activityTitleFromTimeline(
timelineItems: WorkspaceAgentActivityTimelineItem[],
fallbackTitle = ""
): string {
return (
compactText(fallbackTitle) ||
latestUserMessageText(timelineItems) ||
workspaceAgentUntitledConversationLabel()
);
}
export function resolveLatestActivitySummary(
timelineItems: WorkspaceAgentActivityTimelineItem[],
status: WorkspaceAgentLatestActivityStatus,
actors: WorkspaceAgentLatestActivityActors
): WorkspaceAgentLatestActivitySummary {
for (const item of [...timelineItems].sort(compareTimelineItemsDescending)) {
const summary = displayableActivitySummary(item);
if (summary) {
return {
actorName: activityActorName(item, actors),
summary
};
}
}
return {
actorName: actors.agentName,
summary: fallbackSummary(status)
};
}
export function fallbackSummary(
status: WorkspaceAgentLatestActivityStatus
): string {
switch (status) {
case "working":
return translate("agentHost.workspaceAgentActivityStatusWorking");
case "waiting":
return translate("agentHost.workspaceAgentActivityStatusWaiting");
case "idle":
return translate("agentHost.workspaceAgentActivityStatusIdle");
case "canceled":
case "completed":
return translate("agentHost.workspaceAgentActivityStatusEnd");
case "failed":
return translate("agentHost.workspaceAgentActivityStatusFailed");
}
}
export function isWorkspaceAgentIdleSummary(summary: string): boolean {
return localizedWorkspaceAgentLabelSet(
"agentHost.workspaceAgentActivityStatusIdle"
).has(compactText(summary));
}
export function isWorkspaceAgentUntitledConversation(title: string): boolean {
return localizedWorkspaceAgentLabelSet(
"agentHost.workspaceAgentsUntitledConversation"
).has(compactText(title));
}
export function latestUserMessageFromTimeline(
timelineItems: WorkspaceAgentActivityTimelineItem[]
): string {
return latestUserMessageText(timelineItems);
}
export function latestAgentMessageFromTimeline(
timelineItems: WorkspaceAgentActivityTimelineItem[]
): string {
return latestAgentMessageText(timelineItems);
}
export function buildUserAndAgentConversationPreview(input: {
timelineItems: WorkspaceAgentActivityTimelineItem[];
userName: string;
agentName: string;
userPromptFallback?: string;
status: WorkspaceAgentLatestActivityStatus;
}): WorkspaceAgentConversationPreviewLine[] {
const timeline = input.timelineItems ?? [];
const lines: WorkspaceAgentConversationPreviewLine[] = [];
const userMessage =
compactText(input.userPromptFallback ?? "") ||
latestUserMessageFromTimeline(timeline);
if (userMessage) {
lines.push({ actorName: input.userName, summary: userMessage });
}
const agentMessage = latestAgentMessageFromTimeline(timeline);
if (agentMessage) {
lines.push({ actorName: input.agentName, summary: agentMessage });
} else if (userMessage || timeline.length > 0) {
const latestActivity = resolveLatestActivitySummary(
timeline,
input.status,
{
userName: input.userName,
agentName: input.agentName
}
);
if (
latestActivity.actorName === input.agentName &&
latestActivity.summary
) {
lines.push({
actorName: input.agentName,
summary: latestActivity.summary
});
} else {
lines.push({
actorName: input.agentName,
summary: fallbackSummary(input.status)
});
}
}
return lines;
}
function latestUserMessageText(
timelineItems: WorkspaceAgentActivityTimelineItem[]
): string {
const latestUserMessage = [...timelineItems]
.filter(isUserMessageItem)
.sort(compareTimelineItemsDescending)[0];
if (!latestUserMessage) {
return "";
}
return firstPresentText(
stringPayloadValue(latestUserMessage, "displayPrompt"),
stringPayloadValue(latestUserMessage, "text"),
stringPayloadValue(latestUserMessage, "content"),
latestUserMessage.content
);
}
function displayableActivitySummary(
item: WorkspaceAgentActivityTimelineItem
): string {
if (messageRole(item)) {
return messageContent(item);
}
return "";
}
function activityActorName(
item: WorkspaceAgentActivityTimelineItem,
actors: WorkspaceAgentLatestActivityActors
): string {
return messageRole(item) === "user" ? actors.userName : actors.agentName;
}
function isUserMessageItem(item: WorkspaceAgentActivityTimelineItem): boolean {
return (
isMessageItem(item) &&
messageRole(item) === "user" &&
messageContent(item).length > 0
);
}
function latestAgentMessageText(
timelineItems: WorkspaceAgentActivityTimelineItem[]
): string {
const latestAgentMessage = [...timelineItems]
.filter(isAgentMessageItem)
.sort(compareTimelineItemsDescending)[0];
if (!latestAgentMessage) {
return "";
}
return messageContent(latestAgentMessage);
}
function isAgentMessageItem(item: WorkspaceAgentActivityTimelineItem): boolean {
return (
isMessageItem(item) &&
messageRole(item) === "agent" &&
messageContent(item).length > 0
);
}
function isMessageItem(item: WorkspaceAgentActivityTimelineItem): boolean {
return item.itemType === "message" || item.itemType.startsWith("message.");
}
function messageRole(
item: WorkspaceAgentActivityTimelineItem
): "user" | "agent" | null {
const explicitRole = item.role?.trim().toLowerCase();
if (explicitRole === "user") {
return "user";
}
if (explicitRole === "assistant" || explicitRole === "agent") {
return "agent";
}
const itemType = item.itemType.trim().toLowerCase();
if (itemType === "message.user") {
return "user";
}
if (itemType === "message.agent" || itemType === "message.assistant") {
return "agent";
}
return null;
}
function messageContent(item: WorkspaceAgentActivityTimelineItem): string {
const content = firstPresentText(
stringPayloadValue(item, "displayPrompt"),
stringPayloadValue(item, "text"),
stringPayloadValue(item, "content"),
item.content
);
return isWorkspaceAgentSyntheticControlMessage(content) ? "" : content;
}
function stringPayloadValue(
item: WorkspaceAgentActivityTimelineItem,
key: "content" | "displayPrompt" | "summary" | "text"
): string {
const value = item.payload?.[key];
return typeof value === "string" ? value : "";
}
function compareTimelineItemsDescending(
left: WorkspaceAgentActivityTimelineItem,
right: WorkspaceAgentActivityTimelineItem
): number {
const leftTime = timelineTime(left);
const rightTime = timelineTime(right);
const timeDiff = (rightTime ?? 0) - (leftTime ?? 0);
if (timeDiff !== 0) {
return timeDiff;
}
return right.id - left.id || right.eventId.localeCompare(left.eventId);
}
function timelineTime(
item: WorkspaceAgentActivityTimelineItem | null | undefined
): number | null {
if (!item) {
return null;
}
return item.occurredAtUnixMs ?? item.createdAtUnixMs ?? null;
}
function firstPresentText(...values: Array<string | undefined>): string {
for (const value of values) {
const text = compactText(value ?? "");
if (text) {
return text;
}
}
return "";
}
function compactText(value: string): string {
return value.trim().replace(/\s+/g, " ");
}
function workspaceAgentUntitledConversationLabel(): string {
return translate("agentHost.workspaceAgentsUntitledConversation");
}
function localizedWorkspaceAgentLabelSet(key: string): Set<string> {
return new Set(
(["en", "zh-CN"] as const)
.map((language) => compactText(translateInUiLanguage(language, key)))
.filter(Boolean)
);
}