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

800 lines
24 KiB
TypeScript

import {
createRichTextMentionHref,
isRichTextFolderHref
} from "@tutti-os/ui-rich-text/core";
import { getOptionalAgentHostApi } from "../../agentActivityHost";
import type { AgentContextMentionItem } from "./agentRichText/agentFileMentionExtension";
import type { AgentContextMentionDirectoryDescriptor } from "./agentContextMentionProvider";
import { normalizeAgentSessionMentionTitle } from "./agentRichText/agentFileMentionExtension";
import { dirnameFromPath } from "./agentRichText/agentMentionMarkdown";
import { resolveAgentSessionMentionIconUrl } from "./agentRichText/agentMentionPresentation";
import type { AgentContextMentionInsertResult } from "./agentContextMentionProvider";
import type { AgentMentionProviderQueryDiagnostic } from "./agentMentionSearchDiagnostics";
import type {
AgentMentionFilterId,
AgentMentionGroup,
AgentMentionGroupId,
AgentMentionIssueTopicGroup,
AgentMentionRawGroups,
AgentMentionTotalCounts
} from "./AgentMentionSearchContracts";
import {
AGENT_GENERATED_FILE_PROVIDER_ID,
AGENT_MENTION_LIFECYCLE_LOG_PREFIX,
AGENT_SESSION_PROVIDER_ID,
AGENT_TARGET_PROVIDER_ID,
FILE_PROVIDER_ID,
WORKSPACE_APP_PROVIDER_ID,
WORKSPACE_ISSUE_PROVIDER_ID,
type AgentMentionLifecycleDiagnosticLog
} from "./AgentMentionSearchContracts";
import { presentAgentGeneratedFileMentionItems } from "./agentMentionAgentGeneratedFilesPresentation";
import type {
ReferenceProvenanceCatalog,
ReferenceProvenanceFilter,
ReferenceProvenanceOption
} from "@tutti-os/workspace-file-reference/contracts";
import {
buildEmptyGroup,
compactText,
groupIdsForFilter,
mentionGroupPageSize,
resolveMentionGroupItems,
resolveMentionGroupTotalCount,
shouldShowEmptyGroup
} from "./agentMentionSearchHelpers";
import { resolveReferenceProvenanceAgentLabelParts } from "@tutti-os/workspace-file-reference/core";
type AgentProvenanceMentionItem = Extract<
AgentContextMentionItem,
{ kind: "session" | "agent-target" }
>;
export function buildAgentMentionGroups(input: {
agentGeneratedBrowsePath: string | null;
currentFileSearchLimit: number;
currentFilter: AgentMentionFilterId;
currentQuery: string;
expandedCounts: Partial<Record<AgentMentionGroupId, number>>;
issueTopicGroups: readonly AgentMentionIssueTopicGroup[] | null;
rawGroups: AgentMentionRawGroups;
totalCounts: AgentMentionTotalCounts;
provenanceCatalog: ReferenceProvenanceCatalog | null;
provenanceFilter: ReferenceProvenanceFilter | null;
}): AgentMentionGroup[] {
if (input.currentFilter === "issue" && input.issueTopicGroups !== null) {
return input.issueTopicGroups.map((group) => ({
id: group.id,
label: group.label,
items: group.items,
totalCount: group.totalCount,
visibleCount: group.items.length,
hasMore: group.nextPageToken !== null,
expandStatus: group.loadMoreStatus
}));
}
const provenanceGroups = buildAgentProvenanceGroups(input);
if (provenanceGroups) {
return provenanceGroups;
}
const orderedGroupIds = groupIdsForFilter(input.currentFilter);
return orderedGroupIds
.map((groupId) => {
const rawItems = resolveMentionGroupItems(groupId, input.rawGroups);
const items =
groupId === "agent_generated_files"
? presentAgentGeneratedFileMentionItems({
files: rawItems,
browsePath: input.agentGeneratedBrowsePath,
query: input.currentQuery
})
: rawItems;
if (items.length === 0) {
if (
!shouldShowEmptyGroup(
groupId,
input.currentFilter,
input.currentQuery
)
) {
return null;
}
return buildEmptyGroup(groupId, input.currentQuery);
}
const pageSize = mentionGroupPageSize(input.currentFilter, groupId);
const visibleCount =
groupId === "apps"
? items.length
: Math.min(items.length, input.expandedCounts[groupId] ?? pageSize);
const totalCount = resolveMentionGroupTotalCount(
groupId,
input.totalCounts,
items.length
);
return {
id: groupId,
items: items.slice(0, visibleCount),
totalCount,
visibleCount,
hasMore:
groupId !== "apps" &&
(items.length > visibleCount ||
((groupId === "opened_files" ||
groupId === "files" ||
groupId === "agent_generated_files") &&
items.length >= input.currentFileSearchLimit) ||
totalCount > visibleCount)
} satisfies AgentMentionGroup;
})
.filter((group): group is AgentMentionGroup => group !== null);
}
export function cloneAgentMentionIssueTopicGroups(
groups: readonly AgentMentionIssueTopicGroup[] | null
): AgentMentionIssueTopicGroup[] | null {
return (
groups?.map((group) => ({ ...group, items: [...group.items] })) ?? null
);
}
export function issueTopicPaginationChanges(
groupsAtStart: readonly AgentMentionIssueTopicGroup[] | null,
currentGroups: readonly AgentMentionIssueTopicGroup[] | null
): AgentMentionIssueTopicGroup[] {
if (!groupsAtStart || !currentGroups) {
return [];
}
const startingGroups = new Map(
groupsAtStart.map((group) => [group.id, group] as const)
);
return currentGroups.flatMap((group) => {
const startingGroup = startingGroups.get(group.id);
if (!startingGroup) {
return [];
}
const startingIssueIds = new Set(
startingGroup.items
.filter((item) => item.kind === "workspace-issue")
.map((item) => item.targetId)
);
const appendedItems = group.items.filter(
(item) =>
item.kind !== "workspace-issue" || !startingIssueIds.has(item.targetId)
);
if (
appendedItems.length === 0 &&
group.nextPageToken === startingGroup.nextPageToken
) {
return [];
}
return [{ ...group, items: appendedItems }];
});
}
function buildAgentProvenanceGroups(input: {
currentFilter: AgentMentionFilterId;
expandedCounts: Partial<Record<AgentMentionGroupId, number>>;
provenanceCatalog: ReferenceProvenanceCatalog | null;
provenanceFilter: ReferenceProvenanceFilter | null;
rawGroups: AgentMentionRawGroups;
}): AgentMentionGroup[] | null {
if (
(input.currentFilter !== "session" && input.currentFilter !== "agent") ||
!input.provenanceCatalog?.enabledDimensions.includes("agent")
) {
return null;
}
const selectedAgentTargetIds = input.provenanceFilter?.agentTargetIds ?? null;
const selectedAgentTargetIdSet =
selectedAgentTargetIds === null ? null : new Set(selectedAgentTargetIds);
const memberOptionsById = new Map(
input.provenanceCatalog.memberOptions.map((member) => [member.id, member])
);
const agentOptionsById = new Map(
input.provenanceCatalog.agentOptions.map((agent) => [agent.id, agent])
);
const sourceItems = agentProvenanceItemsForFilter(input)
.map((item) =>
projectAgentProvenanceMemberPresentation(
item,
memberOptionsById,
agentOptionsById
)
)
.filter((item) => {
if (selectedAgentTargetIdSet === null) {
return true;
}
const agentTargetId = agentTargetIdForMentionItem(item);
return (
agentTargetId !== null && selectedAgentTargetIdSet.has(agentTargetId)
);
});
const cataloguedItemIds = new Set<string>();
const catalogGroups = agentProvenanceGroupSpecs(
input.currentFilter,
input.provenanceCatalog
).flatMap((group) => {
const items = sourceItems.filter(group.matches);
if (items.length === 0) {
return [];
}
items.forEach((item) => cataloguedItemIds.add(item.targetId));
return [
buildAgentProvenanceGroup({
currentFilter: input.currentFilter,
expandedCounts: input.expandedCounts,
id: group.id,
label: group.label,
items
})
];
});
if (selectedAgentTargetIdSet !== null) {
return catalogGroups;
}
const unmatchedGroups = new Map<
string,
{
id: `agent:${string}`;
label: string;
items: AgentProvenanceMentionItem[];
}
>();
for (const item of sourceItems) {
if (cataloguedItemIds.has(item.targetId)) {
continue;
}
const identity = unmatchedAgentProvenanceIdentity(item);
const key = `uncatalogued:${identity}`;
const existing = unmatchedGroups.get(key);
if (existing) {
existing.items.push(item);
continue;
}
unmatchedGroups.set(key, {
id: agentProvenanceMentionGroupId(key),
label: unmatchedAgentProvenanceLabel(item),
items: [item]
});
}
return [
...catalogGroups,
...[...unmatchedGroups.values()].map((group) =>
buildAgentProvenanceGroup({
currentFilter: input.currentFilter,
expandedCounts: input.expandedCounts,
...group
})
)
];
}
function agentProvenanceGroupSpecs(
currentFilter: AgentMentionFilterId,
catalog: ReferenceProvenanceCatalog
): Array<{
id: AgentMentionGroupId;
label: string;
matches: (item: AgentProvenanceMentionItem) => boolean;
}> {
if (currentFilter === "session") {
return catalog.memberOptions.map((member) => ({
id: memberProvenanceMentionGroupId(member.id),
label: member.label,
matches: (item) =>
item.kind === "session" && item.initiatorUserId?.trim() === member.id
}));
}
const groupedAgentTargetIds = new Set<string>();
const memberGroups = catalog.memberOptions.flatMap((member) => {
const agentTargetIds = catalog.agentOptions.flatMap((option) =>
option.parentMemberId?.trim() === member.id ? [option.id] : []
);
if (agentTargetIds.length === 0) {
return [];
}
agentTargetIds.forEach((agentTargetId) =>
groupedAgentTargetIds.add(agentTargetId)
);
return [
{
id: memberProvenanceMentionGroupId(member.id),
label: member.label,
matches: (item: AgentProvenanceMentionItem) => {
const agentTargetId = agentTargetIdForMentionItem(item);
return (
agentTargetId !== null && agentTargetIds.includes(agentTargetId)
);
}
}
];
});
const unownedAgentGroups = catalog.agentOptions.flatMap((option) =>
groupedAgentTargetIds.has(option.id)
? []
: [
{
id: agentProvenanceMentionGroupId(option.id),
label: option.label,
matches: (item: AgentProvenanceMentionItem) =>
agentTargetIdForMentionItem(item) === option.id
}
]
);
return [...memberGroups, ...unownedAgentGroups];
}
function agentProvenanceItemsForFilter(input: {
currentFilter: AgentMentionFilterId;
rawGroups: AgentMentionRawGroups;
}): AgentProvenanceMentionItem[] {
if (input.currentFilter === "session") {
return input.rawGroups.sessions.filter((item) => item.kind === "session");
}
return input.rawGroups.agents.filter((item) => item.kind === "agent-target");
}
function agentTargetIdForMentionItem(
item: AgentProvenanceMentionItem
): string | null {
if (item.kind === "session") {
return item.agentTargetId?.trim() || null;
}
if (item.kind === "agent-target") {
return item.targetId.trim() || null;
}
return null;
}
function projectAgentProvenanceMemberPresentation(
item: AgentProvenanceMentionItem,
memberOptionsById: ReadonlyMap<string, ReferenceProvenanceOption>,
agentOptionsById: ReadonlyMap<string, ReferenceProvenanceOption>
): AgentProvenanceMentionItem {
if (item.kind !== "session") {
return item;
}
const initiatorUserId = item.initiatorUserId?.trim();
const member = initiatorUserId
? memberOptionsById.get(initiatorUserId)
: undefined;
if (!member) {
return item;
}
const initiatorAvatarUrl =
member.iconUrl?.trim() || item.initiatorAvatarUrl?.trim();
const agentTargetId = item.agentTargetId?.trim();
const agentOption = agentTargetId
? agentOptionsById.get(agentTargetId)
: undefined;
const agentLabelParts =
agentOption && agentOption.label === item.agentName
? resolveReferenceProvenanceAgentLabelParts(
agentOption,
memberOptionsById
)
: null;
return {
...item,
initiatorName: member.label,
...(initiatorAvatarUrl ? { initiatorAvatarUrl } : {}),
...(agentLabelParts
? {
agentLabel: agentLabelParts.agentLabel,
agentOwnerLabel: agentLabelParts.ownerLabel
}
: {})
};
}
function unmatchedAgentProvenanceIdentity(
item: AgentProvenanceMentionItem
): string {
if (item.kind === "session") {
return item.initiatorUserId?.trim() || item.targetId;
}
return item.targetId;
}
function unmatchedAgentProvenanceLabel(
item: AgentProvenanceMentionItem
): string {
if (item.kind === "session") {
return (
item.initiatorName.trim() ||
item.initiatorUserId?.trim() ||
item.agentName.trim() ||
item.title
);
}
return item.name;
}
function buildAgentProvenanceGroup(input: {
currentFilter: AgentMentionFilterId;
expandedCounts: Partial<Record<AgentMentionGroupId, number>>;
id: AgentMentionGroupId;
label: string;
items: AgentContextMentionItem[];
}): AgentMentionGroup {
const pageSize = mentionGroupPageSize(input.currentFilter, input.id);
const visibleCount = Math.min(
input.items.length,
input.expandedCounts[input.id] ?? pageSize
);
return {
id: input.id,
label: input.label,
items: input.items.slice(0, visibleCount),
totalCount: input.items.length,
visibleCount,
hasMore: input.items.length > visibleCount
};
}
export function agentProvenanceMentionGroupId(
agentTargetId: string
): `agent:${string}` {
return `agent:${encodeURIComponent(agentTargetId)}`;
}
export function memberProvenanceMentionGroupId(
memberId: string
): `member:${string}` {
return `member:${encodeURIComponent(memberId)}`;
}
export function emptyAgentMentionRawGroups(): AgentMentionRawGroups {
return {
apps: [],
agents: [],
opened_files: [],
agent_generated_files: [],
sessions: [],
issues: []
};
}
export function cloneAgentMentionRawGroups(
rawGroups: AgentMentionRawGroups
): AgentMentionRawGroups {
return {
apps: [...rawGroups.apps],
agents: [...rawGroups.agents],
opened_files: [...rawGroups.opened_files],
agent_generated_files: [...rawGroups.agent_generated_files],
sessions: [...rawGroups.sessions],
issues: [...rawGroups.issues]
};
}
export function totalCountsFromRawGroups(
rawGroups: AgentMentionRawGroups
): AgentMentionTotalCounts {
return {
apps: rawGroups.apps.length,
agents: rawGroups.agents.length,
opened_files: rawGroups.opened_files.length,
agent_generated_files: rawGroups.agent_generated_files.length,
my_sessions: rawGroups.sessions.filter(
(item) => item.kind === "session" && item.scope === "my_sessions"
).length,
collab_sessions: rawGroups.sessions.filter(
(item) => item.kind === "session" && item.scope === "collab_sessions"
).length,
issues: rawGroups.issues.length
};
}
export function rawGroupItemCount(rawGroups: AgentMentionRawGroups): number {
return Object.values(rawGroups).reduce(
(count, items) => count + items.length,
0
);
}
export function providerDiagnosticsSummary(
diagnostics: readonly AgentMentionProviderQueryDiagnostic[]
): string {
return diagnostics
.map(
(diagnostic) =>
`${diagnostic.providerId}:${diagnostic.status}:${diagnostic.resultCount}:${diagnostic.durationMs}`
)
.join(",");
}
export function elapsedDiagnosticMs(now: number, startedAt: number): number {
const durationMs = now - startedAt;
return Number.isFinite(durationMs) ? Math.max(0, Math.round(durationMs)) : 0;
}
export function diagnosticErrorKind(error: unknown): string {
if (error instanceof Error && error.name.trim()) {
return error.name.trim();
}
if (error === null) {
return "null";
}
return typeof error;
}
export function logAgentMentionLifecycleDiagnostic(
payload: AgentMentionLifecycleDiagnosticLog
): void {
try {
console.info(AGENT_MENTION_LIFECYCLE_LOG_PREFIX, JSON.stringify(payload));
} catch (logError) {
// Diagnostic logging must never affect mention search state.
console.error(
"[agent-gui] logAgentMentionLifecycleDiagnostic console.info failed",
logError
);
}
try {
getOptionalAgentHostApi()?.debug?.logRuntimeDiagnostics?.({
source: "renderer-workspace-surface",
level: "info",
event: `agent-gui.mention.${payload.event}`,
// i18n-check-ignore: Internal diagnostic log message.
message: "Agent GUI mention search lifecycle event.",
details: payload.details
});
} catch (logError) {
// Diagnostic logging must never affect mention search state.
console.error(
"[agent-gui] logAgentMentionLifecycleDiagnostic host diagnostics failed",
logError
);
}
}
export function normalizeSessionMentionItems(input: {
items: readonly AgentContextMentionItem[];
}): AgentContextMentionItem[] {
return input.items.filter((item) => item.kind === "session");
}
export function providerItemToAgentMentionItem(input: {
currentUserId: string;
directory?: AgentContextMentionDirectoryDescriptor | null;
providerId: string;
insertResult: AgentContextMentionInsertResult;
label: string;
subtitle: string;
workspaceId: string;
}): AgentContextMentionItem | null {
const label = compactText(input.label);
if (!label) return null;
if (input.insertResult.kind === "markdown-link") {
const href = input.insertResult.href.trim();
const directoryPath = input.directory?.path.trim() ?? "";
return {
kind: "file",
href,
path: directoryPath || href,
name: label,
contextLabel: compactText(input.subtitle) || undefined,
entryKind:
directoryPath || isRichTextFolderHref(href) ? "directory" : "unknown",
directoryPath: dirnameFromProviderWorkspaceFileHref(
directoryPath || href
),
...(directoryPath
? {
childCount: normalizeMentionDirectoryChildCount(
input.directory?.childCount
),
mentionNavigation: "workspace-folder" as const
}
: {})
};
}
if (input.insertResult.kind !== "mention") {
return null;
}
const mention = input.insertResult.mention;
const targetId = mention.entityId.trim();
if (!targetId) {
return null;
}
const scope = normalizeMentionScope(mention.scope);
const presentation = mention.presentation ?? {};
const workspaceId = scope.workspaceId || input.workspaceId;
if (
input.providerId === FILE_PROVIDER_ID ||
input.providerId === AGENT_GENERATED_FILE_PROVIDER_ID
) {
const directoryPath = input.directory?.path.trim() ?? "";
return {
kind: "file",
href: createRichTextMentionHref({
providerId: input.providerId,
entityId: targetId,
label,
scope
}),
path: directoryPath || targetId,
name: label,
contextLabel: compactText(input.subtitle) || undefined,
entryKind:
directoryPath || isRichTextFolderHref(targetId)
? "directory"
: "unknown",
directoryPath: dirnameFromProviderWorkspaceFileHref(
directoryPath || targetId
),
...(directoryPath
? {
childCount: normalizeMentionDirectoryChildCount(
input.directory?.childCount
),
mentionNavigation: "workspace-folder" as const
}
: {}),
thumbnailUrl: presentation.thumbnailUrl?.trim() || undefined
};
}
if (input.providerId === WORKSPACE_ISSUE_PROVIDER_ID) {
return {
kind: "workspace-issue",
href: createRichTextMentionHref({
providerId: "workspace-issue",
entityId: targetId,
label,
scope: {
workspaceId,
...(scope.topicId ? { topicId: scope.topicId } : {})
}
}),
workspaceId,
targetId,
topicId: scope.topicId,
name: label,
title: label,
iconUrl: presentation.iconUrl?.trim() || undefined,
status: presentation.status?.trim() || undefined,
contentPreview:
compactText(presentation.description) ||
compactText(input.subtitle) ||
undefined
};
}
if (input.providerId === WORKSPACE_APP_PROVIDER_ID) {
const appId = targetId;
return {
kind: "workspace-app",
href: createRichTextMentionHref({
providerId: "workspace-app",
entityId: appId,
label,
scope: { workspaceId }
}),
workspaceId,
targetId: appId,
appId,
name: label,
description:
compactText(presentation.description) ||
compactText(presentation.subtitle) ||
compactText(input.subtitle) ||
undefined,
iconUrl: presentation.iconUrl?.trim() || undefined,
referencesListSupported: presentation.referencesListSupported === "true"
};
}
if (input.providerId === AGENT_TARGET_PROVIDER_ID) {
const agentProviderId = presentation.agentProviderId?.trim() || undefined;
return {
kind: "agent-target",
href: createRichTextMentionHref({
providerId: "agent-target",
entityId: targetId,
label,
scope: { workspaceId }
}),
workspaceId,
targetId,
name: label,
description:
compactText(presentation.description) ||
compactText(presentation.subtitle) ||
compactText(input.subtitle) ||
undefined,
agentProviderId,
iconUrl: presentation.iconUrl?.trim() || undefined,
availabilityStatus: presentation.status?.trim() || undefined
};
}
if (input.providerId === AGENT_SESSION_PROVIDER_ID) {
const agentName = presentation.subtitle?.trim() || "";
const title = normalizeAgentSessionMentionTitle(label) || label;
const description = compactText(presentation.description);
const summaryPreview =
description || compactText(input.subtitle) || undefined;
return {
kind: "session",
href: createRichTextMentionHref({
providerId: "agent-session",
entityId: targetId,
label,
scope: {
workspaceId,
...(scope.agentTargetId ? { agentTargetId: scope.agentTargetId } : {})
}
}),
workspaceId,
targetId,
name: label,
title,
scope: mentionSessionScope({
currentUserId: input.currentUserId,
rawScope: scope.scope,
userId: scope.userId
}),
...(scope.userId ? { initiatorUserId: scope.userId } : {}),
initiatorName: "",
agentName,
agentIconUrl: resolveAgentSessionMentionIconUrl({
agentIconUrl:
presentation.agentIconUrl?.trim() ||
presentation.iconUrl?.trim() ||
undefined,
agentProviderId: presentation.agentProviderId
}),
...(scope.agentTargetId ? { agentTargetId: scope.agentTargetId } : {}),
status: presentation.status?.trim() || undefined,
inputPreview: description || undefined,
summaryPreview
};
}
return null;
}
function normalizeMentionDirectoryChildCount(
value: number | null | undefined
): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
return undefined;
}
return Math.floor(value);
}
export function normalizeMentionScope(
scope?: Readonly<Record<string, string>>
): Record<string, string> {
return Object.fromEntries(
Object.entries(scope ?? {})
.map(([key, value]) => [key.trim(), value.trim()] as const)
.filter(([key, value]) => key.length > 0 && value.length > 0)
);
}
export function mentionSessionScope(input: {
currentUserId: string;
rawScope: string | undefined;
userId?: string;
}): Extract<AgentContextMentionItem, { kind: "session" }>["scope"] {
const rawScope = input.rawScope?.trim() ?? "";
if (rawScope === "my_sessions" || rawScope === "collab_sessions") {
return rawScope;
}
const userId = input.userId?.trim() ?? "";
const currentUserId = input.currentUserId.trim();
if (
!userId ||
!currentUserId ||
userId === "local" ||
currentUserId === "local"
) {
return "my_sessions";
}
return userId === currentUserId ? "my_sessions" : "collab_sessions";
}
export function dirnameFromProviderWorkspaceFileHref(href: string): string {
return dirnameFromPath(href) || "/";
}