Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/model/agentGuiConversationRailMembershipRefresh.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

111 lines
3.3 KiB
TypeScript

import type { ConversationRailSectionMembership } from "../controller/agentConversationRailQueryModel";
export interface ConversationRailMembershipRecord {
agentTargetId?: string | null;
id: string;
pendingCreation?: boolean;
pinnedAtUnixMs?: number | null;
projectionSource?: "pending_activation" | "runtime_overlay";
railSectionKey?: string | null;
title: string;
}
export type ConversationRailMembershipRefreshPlan =
| { kind: "none" }
| {
kind: "refresh_pages";
pageIds: readonly string[];
reconcilingSessionIds: readonly string[];
refreshSearch: boolean;
};
export function planRuntimeRailMembershipRefresh(input: {
activeConversationId?: string | null;
agentTargetId?: string | null;
loadedSections: readonly ConversationRailSectionMembership[] | null;
next: readonly ConversationRailMembershipRecord[];
previous: readonly ConversationRailMembershipRecord[];
searchActive?: boolean;
}): ConversationRailMembershipRefreshPlan {
const targetId = input.agentTargetId?.trim() ?? "";
const visible = (record: ConversationRailMembershipRecord) =>
!targetId || record.agentTargetId?.trim() === targetId;
const previousPendingIds = new Set(
input.previous
.filter((record) => record.pendingCreation === true && visible(record))
.map((record) => record.id)
);
const canonical = (records: readonly ConversationRailMembershipRecord[]) =>
new Map(
records
.filter(
(record) =>
record.pendingCreation !== true &&
record.projectionSource === undefined &&
visible(record)
)
.map((record) => [record.id, record] as const)
);
const previousById = canonical(input.previous);
const nextById = canonical(input.next);
const loadedIds = new Set(
(input.loadedSections ?? []).flatMap((section) => section.sessionIds)
);
const pageIds = new Set<string>();
const reconcilingSessionIds: string[] = [];
let refreshSearch = false;
const addPage = (record: ConversationRailMembershipRecord) => {
const pageId =
record.pinnedAtUnixMs != null ? "pinned" : record.railSectionKey?.trim();
if (pageId) pageIds.add(pageId);
};
for (const [id, previous] of previousById) {
const next = nextById.get(id);
if (!next) {
addPage(previous);
refreshSearch ||= Boolean(input.searchActive);
continue;
}
if (
(previous.pinnedAtUnixMs ?? 0) !== (next.pinnedAtUnixMs ?? 0) ||
previous.railSectionKey !== next.railSectionKey
) {
addPage(previous);
addPage(next);
}
if (previous.title !== next.title) {
refreshSearch ||= Boolean(input.searchActive);
}
}
for (const [id, next] of nextById) {
if (previousById.has(id)) continue;
if (previousPendingIds.has(id)) {
if (!loadedIds.has(id)) {
addPage(next);
reconcilingSessionIds.push(id);
}
continue;
}
if (
id === (input.activeConversationId?.trim() ?? "") ||
loadedIds.has(id)
) {
continue;
}
addPage(next);
refreshSearch ||= Boolean(input.searchActive);
}
return pageIds.size > 0 || refreshSearch
? {
kind: "refresh_pages",
pageIds: [...pageIds],
reconcilingSessionIds,
refreshSearch
}
: { kind: "none" };
}