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

224 lines
7.0 KiB
TypeScript

import type { WorkspaceAgentActivityTimelineItem } from "../../../shared/workspaceAgentTimelineTypes";
import { objectPayload, stringPayload } from "./agentGuiInteractiveProjection";
export function mergeTimelineItems(
left: readonly WorkspaceAgentActivityTimelineItem[],
right: readonly WorkspaceAgentActivityTimelineItem[]
): WorkspaceAgentActivityTimelineItem[] {
const byKey = new Map<string, WorkspaceAgentActivityTimelineItem>();
for (const item of [...left, ...right].sort(compareTimelineItemsForMerge)) {
if (shouldOmitAgentGUITimelineItem(item)) {
continue;
}
const key = timelineItemMergeKey(item);
const previous = byKey.get(key);
byKey.set(key, previous ? mergeTimelineItem(previous, item) : item);
}
return pruneOptimisticUserPrompts([...byKey.values()]).sort(
compareTimelineItemsForMerge
);
}
function shouldOmitAgentGUITimelineItem(
item: WorkspaceAgentActivityTimelineItem
): boolean {
const itemType = item.itemType?.trim().toLowerCase() ?? "";
const payload = objectPayload(item.payload);
const metadata = objectPayload(payload?.metadata);
const activityKey =
stringPayload(payload?.activityKey) || stringPayload(metadata?.activityKey);
if (activityKey.toLowerCase() === "agent.responding") {
return true;
}
const activityKind =
stringPayload(payload?.activityKind) ||
stringPayload(metadata?.activityKind);
return (
activityKind.toLowerCase() === "responding" &&
(itemType === "event" || itemType.startsWith("activity."))
);
}
function pruneOptimisticUserPrompts(
items: readonly WorkspaceAgentActivityTimelineItem[]
): WorkspaceAgentActivityTimelineItem[] {
return items.filter((item) => {
if (!isOptimisticUserPromptItem(item)) {
return true;
}
const turnId = item.turnId?.trim();
const body = normalizedTimelineItemBody(item);
return !items.some((candidate) => {
return (
candidate !== item &&
candidate.role === "user" &&
!isOptimisticUserPromptItem(candidate) &&
candidate.turnId?.trim() === turnId &&
normalizedTimelineItemBody(candidate) === body
);
});
});
}
function isOptimisticUserPromptItem(
item: WorkspaceAgentActivityTimelineItem
): boolean {
return item.payload?.__agentGuiOptimisticPrompt === true;
}
function normalizedTimelineItemBody(
item: WorkspaceAgentActivityTimelineItem
): string {
const content =
typeof item.payload?.displayPrompt === "string" &&
item.payload.displayPrompt.trim()
? item.payload.displayPrompt
: typeof item.payload?.text === "string" && item.payload.text.trim()
? item.payload.text
: typeof item.payload?.content === "string" &&
item.payload.content.trim()
? item.payload.content
: typeof item.content === "string"
? item.content
: "";
return content.trim();
}
function timelineItemMergeKey(
item: WorkspaceAgentActivityTimelineItem
): string {
const callId = item.callId?.trim();
if (callId) {
return `call:${item.turnId?.trim() ?? ""}:${callId}`;
}
const eventId = item.eventId?.trim();
if (eventId) {
return `event:${eventId}`;
}
const seq = item.seq ?? 0;
if (seq > 0) {
const turnId = item.turnId?.trim();
if (turnId) {
return `seq:${turnId}:${seq}`;
}
return `seq:${seq}`;
}
return `id:${item.id}`;
}
function mergeTimelineItem(
previous: WorkspaceAgentActivityTimelineItem,
next: WorkspaceAgentActivityTimelineItem
): WorkspaceAgentActivityTimelineItem {
const preserveLatestMessageTimestamp =
isMessageTimelineItem(previous) || isMessageTimelineItem(next);
return {
...previous,
...next,
id: durableTimelineItemId(previous.id, next.id),
payload: mergeTimelinePayload(previous.payload, next.payload),
content: next.content || previous.content,
status: next.status || previous.status,
role: next.role || previous.role,
callId: next.callId || previous.callId,
callType: next.callType || previous.callType,
name: next.name || previous.name,
seq: Math.max(previous.seq ?? 0, next.seq ?? 0),
occurredAtUnixMs: preserveLatestMessageTimestamp
? latestPositiveTimestamp(
previous.occurredAtUnixMs,
next.occurredAtUnixMs
)
: earliestPositiveTimestamp(
previous.occurredAtUnixMs,
next.occurredAtUnixMs
),
createdAtUnixMs: preserveLatestMessageTimestamp
? latestPositiveTimestamp(previous.createdAtUnixMs, next.createdAtUnixMs)
: earliestPositiveTimestamp(
previous.createdAtUnixMs,
next.createdAtUnixMs
)
};
}
function mergeTimelinePayload(
previous: WorkspaceAgentActivityTimelineItem["payload"],
next: WorkspaceAgentActivityTimelineItem["payload"]
): WorkspaceAgentActivityTimelineItem["payload"] {
const previousRecord = objectPayload(previous);
const nextRecord = objectPayload(next);
if (!previousRecord && !nextRecord) {
return undefined;
}
return mergeRecords(previousRecord ?? {}, nextRecord ?? {});
}
function mergeRecords(
previous: Record<string, unknown>,
next: Record<string, unknown>
): Record<string, unknown> {
const merged: Record<string, unknown> = { ...previous };
for (const [key, value] of Object.entries(next)) {
const previousValue = objectPayload(merged[key]);
const nextValue = objectPayload(value);
merged[key] =
previousValue && nextValue
? mergeRecords(previousValue, nextValue)
: value;
}
return merged;
}
function durableTimelineItemId(previousId: number, nextId: number): number {
if (nextId > 0) {
return nextId;
}
return previousId > 0 ? previousId : nextId;
}
function earliestPositiveTimestamp(
previous: number | undefined,
next: number | undefined
): number | undefined {
const positiveValues = [previous, next].filter(
(value): value is number => typeof value === "number" && value > 0
);
return positiveValues.length > 0 ? Math.min(...positiveValues) : undefined;
}
function latestPositiveTimestamp(
previous: number | undefined,
next: number | undefined
): number | undefined {
const positiveValues = [previous, next].filter(
(value): value is number => typeof value === "number" && value > 0
);
return positiveValues.length > 0 ? Math.max(...positiveValues) : undefined;
}
function isMessageTimelineItem(
item: WorkspaceAgentActivityTimelineItem
): boolean {
return item.itemType?.trim().toLowerCase().startsWith("message.") ?? false;
}
export function compareTimelineItemsForMerge(
left: WorkspaceAgentActivityTimelineItem,
right: WorkspaceAgentActivityTimelineItem
): number {
const leftOptimistic = isOptimisticUserPromptItem(left);
const rightOptimistic = isOptimisticUserPromptItem(right);
if (leftOptimistic !== rightOptimistic) {
return leftOptimistic ? 1 : -1;
}
const leftSeq = left.seq ?? 0;
const rightSeq = right.seq ?? 0;
return (
(left.occurredAtUnixMs ?? left.createdAtUnixMs ?? 0) -
(right.occurredAtUnixMs ?? right.createdAtUnixMs ?? 0) ||
leftSeq - rightSeq ||
left.id - right.id
);
}