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

221 lines
6.5 KiB
TypeScript

import {
normalizeWorkspaceUserProjectPath,
resolveWorkspaceUserProjectDisplayLabel,
workspaceUserProjectPathIdentityKey
} from "@tutti-os/workspace-user-project/core";
import type { AgentHostUserProject } from "../../../host/agentHostApi";
const AGENT_GUI_CONVERSATION_PROJECT_SUMMARY_CACHE_LIMIT = 512;
const agentGUIConversationProjectSummaryCache = new Map<
string,
AgentGUIConversationProjectSummary
>();
export interface AgentGUIConversationProjectSummary {
id: string;
path: string;
label: string;
sectionKey?: string;
createdAtUnixMs?: number;
updatedAtUnixMs?: number;
lastUsedAtUnixMs?: number;
pinnedAtUnixMs: number;
}
export type AgentGUIConversationUserProject = Pick<
AgentHostUserProject,
| "id"
| "path"
| "label"
| "sectionKey"
| "createdAtUnixMs"
| "updatedAtUnixMs"
| "lastUsedAtUnixMs"
| "pinnedAtUnixMs"
>;
export interface AgentGUIConversationProjectResolver {
resolveSectionKey: (
railSectionKey: string | null | undefined
) => AgentGUIConversationProjectSummary | null;
}
export function createAgentGUIConversationProjectResolver(
userProjects: readonly AgentGUIConversationUserProject[] = []
): AgentGUIConversationProjectResolver {
const projectBySectionKey =
buildAgentGUIConversationProjectIndex(userProjects);
return {
resolveSectionKey: (railSectionKey) => {
const sectionKey = normalizeAgentGUIProjectSectionKey(railSectionKey);
if (!sectionKey || sectionKey === "conversations") {
return null;
}
const project = projectBySectionKey.get(sectionKey);
return project
? agentGUIConversationProjectSummaryFromProject(project)
: null;
}
};
}
export function resolveAgentGUIConversationProjectBySectionKey(
railSectionKey: string | null | undefined,
userProjects: readonly AgentGUIConversationUserProject[] = []
): AgentGUIConversationProjectSummary | null {
return createAgentGUIConversationProjectResolver(
userProjects
).resolveSectionKey(railSectionKey);
}
export function resolveAgentGUISelectedUserProject(
selectedPath: string | null | undefined,
userProjects: readonly AgentGUIConversationUserProject[] = []
): AgentGUIConversationProjectSummary | null {
const normalizedSelectedPath =
workspaceUserProjectPathIdentityKey(selectedPath);
if (!normalizedSelectedPath) {
return null;
}
const projectByNormalizedPath =
buildAgentGUISelectedProjectPathIndex(userProjects);
const project = lookupAgentGUISelectedProject(
normalizedSelectedPath,
projectByNormalizedPath
);
return project
? agentGUIConversationProjectSummaryFromProject(project)
: null;
}
function buildAgentGUIConversationProjectIndex(
userProjects: readonly AgentGUIConversationUserProject[]
): ReadonlyMap<string, AgentGUIConversationUserProject> {
const projectBySectionKey = new Map<
string,
AgentGUIConversationUserProject
>();
for (const project of userProjects) {
const sectionKey = normalizeAgentGUIProjectSectionKey(project.sectionKey);
if (
!sectionKey ||
sectionKey === "conversations" ||
projectBySectionKey.has(sectionKey)
) {
continue;
}
projectBySectionKey.set(sectionKey, project);
}
return projectBySectionKey;
}
function normalizeAgentGUIProjectSectionKey(
sectionKey: string | null | undefined
): string {
return sectionKey?.trim() ?? "";
}
function agentGUIConversationProjectSummaryFromProject(
matchedProject: AgentGUIConversationUserProject
): AgentGUIConversationProjectSummary {
const sectionKey = normalizeAgentGUIProjectSectionKey(
matchedProject.sectionKey
);
const summary: AgentGUIConversationProjectSummary = {
id: matchedProject.id,
path: matchedProject.path,
label: resolveWorkspaceUserProjectDisplayLabel(matchedProject),
pinnedAtUnixMs: matchedProject.pinnedAtUnixMs
};
if (sectionKey) {
summary.sectionKey = sectionKey;
}
if (matchedProject.createdAtUnixMs !== undefined) {
summary.createdAtUnixMs = matchedProject.createdAtUnixMs;
}
if (matchedProject.updatedAtUnixMs !== undefined) {
summary.updatedAtUnixMs = matchedProject.updatedAtUnixMs;
}
if (matchedProject.lastUsedAtUnixMs !== undefined) {
summary.lastUsedAtUnixMs = matchedProject.lastUsedAtUnixMs;
}
return cachedAgentGUIConversationProjectSummary(summary);
}
function buildAgentGUISelectedProjectPathIndex(
userProjects: readonly AgentGUIConversationUserProject[]
): ReadonlyMap<string, AgentGUIConversationUserProject> {
const projectByNormalizedPath = new Map<
string,
AgentGUIConversationUserProject
>();
for (const project of userProjects) {
const projectPath = workspaceUserProjectPathIdentityKey(project.path);
if (!projectPath || projectByNormalizedPath.has(projectPath)) {
continue;
}
projectByNormalizedPath.set(projectPath, project);
}
return projectByNormalizedPath;
}
function lookupAgentGUISelectedProject(
normalizedSelectedPath: string,
projectByNormalizedPath: ReadonlyMap<string, AgentGUIConversationUserProject>
): AgentGUIConversationUserProject | null {
let currentPath = normalizedSelectedPath;
while (currentPath) {
const project = projectByNormalizedPath.get(currentPath);
if (project) {
return project;
}
const slashIndex = currentPath.lastIndexOf("/");
if (slashIndex <= 0) {
break;
}
currentPath = currentPath.slice(0, slashIndex);
}
if (normalizedSelectedPath === "/") {
return projectByNormalizedPath.get("/") ?? null;
}
return null;
}
export function normalizeAgentGUIProjectPath(
path: string | null | undefined
): string {
return normalizeWorkspaceUserProjectPath(path);
}
function cachedAgentGUIConversationProjectSummary(
summary: AgentGUIConversationProjectSummary
): AgentGUIConversationProjectSummary {
const key = [
summary.id,
summary.path,
summary.label,
summary.sectionKey ?? "",
summary.createdAtUnixMs ?? "",
summary.updatedAtUnixMs ?? "",
summary.lastUsedAtUnixMs ?? "",
summary.pinnedAtUnixMs ?? ""
].join("\u001f");
const cached = agentGUIConversationProjectSummaryCache.get(key);
if (cached) {
return cached;
}
if (
agentGUIConversationProjectSummaryCache.size >=
AGENT_GUI_CONVERSATION_PROJECT_SUMMARY_CACHE_LIMIT
) {
const oldestKey = agentGUIConversationProjectSummaryCache
.keys()
.next().value;
if (oldestKey) {
agentGUIConversationProjectSummaryCache.delete(oldestKey);
}
}
agentGUIConversationProjectSummaryCache.set(key, summary);
return summary;
}