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

467 lines
15 KiB
TypeScript

import { resolveAgentMentionFileThumbnailUrl } from "../shared/mentionFilePresentation";
import type { AgentContextMentionItem } from "./agentRichText/agentFileMentionExtension";
import type {
AgentContextMentionProvider,
AgentContextMentionQueryInput
} from "./agentContextMentionProvider";
import type { AgentMentionProviderQueryDiagnostic } from "./agentMentionSearchDiagnostics";
import {
emptyAgentMentionRawGroups,
normalizeSessionMentionItems,
providerItemToAgentMentionItem,
totalCountsFromRawGroups
} from "./AgentMentionSearchModel";
import type { AgentMentionFilterId } from "./AgentMentionSearchContracts";
import type { AgentMentionIssueTopicGroup } from "./AgentMentionSearchContracts";
import {
AGENT_GENERATED_FILE_PROVIDER_ID,
AGENT_SESSION_PROVIDER_ID,
AGENT_TARGET_PROVIDER_ID,
DEFAULT_SESSION_LIMIT,
FILE_PROVIDER_ID,
WORKSPACE_APP_PROVIDER_ID,
WORKSPACE_ISSUE_PROVIDER_ID
} from "./AgentMentionSearchContracts";
import type { AgentMentionBrowseFetchResult } from "./AgentMentionSearchCache";
import type {
ReferenceProvenanceCatalog,
ReferenceProvenanceFilter
} from "@tutti-os/workspace-file-reference/contracts";
import { referenceProvenanceFilterIsActive } from "@tutti-os/workspace-file-reference/core";
export interface AgentMentionProviderQueryInput {
abortSignal?: AbortSignal;
diagnostics: AgentMentionProviderQueryDiagnostic[];
providerId: string;
workspaceId: string;
currentUserId: string;
query: string;
limit?: number;
sessionCwd?: string;
sectionKey?: string;
provenanceFilter: ReferenceProvenanceFilter | null;
}
export interface AgentMentionProviderGroupedQueryInput extends AgentMentionProviderQueryInput {}
export async function fetchAgentMentionFilterResult(input: {
workspaceId: string;
currentUserId: string;
query: string;
filter: AgentMentionFilterId;
sessionCwd: string;
sectionKey: string;
includeAgentGeneratedFiles: boolean;
fileLimit: number;
currentFileSearchLimit: number;
currentIssueSearchLimit: number;
provenanceCatalog?: ReferenceProvenanceCatalog | null;
provenanceFilter: ReferenceProvenanceFilter | null;
queryProviderMentionItemsById: (
input: AgentMentionProviderQueryInput
) => Promise<AgentContextMentionItem[]>;
queryProviderMentionGroupsById?: (
input: AgentMentionProviderGroupedQueryInput
) => Promise<AgentMentionIssueTopicGroup[] | null>;
}): Promise<AgentMentionBrowseFetchResult> {
const providerDiagnostics: AgentMentionProviderQueryDiagnostic[] = [];
const provenanceFilterActive = referenceProvenanceFilterIsActive(
input.provenanceFilter
);
switch (input.filter) {
case "file": {
// Opened/local files have no durable provenance. Never silently return
// them while the UI says any provenance filter is active.
const fileQuery = provenanceFilterActive
? Promise.resolve([] as AgentContextMentionItem[])
: input.queryProviderMentionItemsById({
providerId: FILE_PROVIDER_ID,
workspaceId: input.workspaceId,
currentUserId: input.currentUserId,
query: input.query,
limit: input.query ? input.currentFileSearchLimit : input.fileLimit,
sessionCwd: input.sessionCwd,
diagnostics: providerDiagnostics,
provenanceFilter: input.provenanceFilter
});
const agentGeneratedFileQuery =
input.includeAgentGeneratedFiles || provenanceFilterActive
? input.queryProviderMentionItemsById({
providerId: AGENT_GENERATED_FILE_PROVIDER_ID,
workspaceId: input.workspaceId,
currentUserId: input.currentUserId,
query: input.query,
limit: input.fileLimit,
sessionCwd: input.sessionCwd,
sectionKey: input.sectionKey,
diagnostics: providerDiagnostics,
provenanceFilter: input.provenanceFilter
})
: Promise.resolve([] as AgentContextMentionItem[]);
const [fileItems, agentGeneratedFileItems] = await Promise.all([
fileQuery,
agentGeneratedFileQuery
]);
const rawGroups = emptyAgentMentionRawGroups();
rawGroups.opened_files = fileItems.filter((item) => item.kind === "file");
rawGroups.agent_generated_files = agentGeneratedFileItems.filter(
(item) => item.kind === "file"
);
return {
providerDiagnostics,
rawGroups,
totalCounts: totalCountsFromRawGroups(rawGroups),
issueTopicGroups: null
};
}
case "session": {
const sessionItems = await input.queryProviderMentionItemsById({
providerId: AGENT_SESSION_PROVIDER_ID,
workspaceId: input.workspaceId,
currentUserId: input.currentUserId,
query: input.query,
// A provenance-aware host owns a room-scoped directory snapshot and
// grouping must see that complete snapshot. Legacy providers retain
// the bounded query contract.
limit: input.provenanceCatalog?.enabledDimensions.includes("agent")
? undefined
: DEFAULT_SESSION_LIMIT,
sessionCwd: input.sessionCwd,
diagnostics: providerDiagnostics,
provenanceFilter: input.provenanceFilter
});
const rawGroups = emptyAgentMentionRawGroups();
rawGroups.sessions = normalizeSessionMentionItems({
items: sessionItems
});
return {
providerDiagnostics,
rawGroups,
totalCounts: totalCountsFromRawGroups(rawGroups),
issueTopicGroups: null
};
}
case "issue": {
// Issue summaries do not yet carry durable provenance. Fail closed
// rather than displaying unfiltered issues under an active filter.
const queryInput = {
providerId: WORKSPACE_ISSUE_PROVIDER_ID,
workspaceId: input.workspaceId,
currentUserId: input.currentUserId,
query: input.query,
limit: input.currentIssueSearchLimit,
sessionCwd: input.sessionCwd,
diagnostics: providerDiagnostics,
provenanceFilter: input.provenanceFilter
};
const issueTopicGroups = provenanceFilterActive
? []
: input.queryProviderMentionGroupsById
? await input.queryProviderMentionGroupsById(queryInput)
: null;
const issueItems = provenanceFilterActive
? []
: issueTopicGroups === null
? await input.queryProviderMentionItemsById(queryInput)
: [];
const rawGroups = emptyAgentMentionRawGroups();
rawGroups.issues = issueItems.filter(
(item) => item.kind === "workspace-issue"
);
return {
providerDiagnostics,
rawGroups,
totalCounts: totalCountsFromRawGroups(rawGroups),
issueTopicGroups
};
}
case "agent": {
const agentItems = await input.queryProviderMentionItemsById({
providerId: AGENT_TARGET_PROVIDER_ID,
workspaceId: input.workspaceId,
currentUserId: input.currentUserId,
query: input.query,
sessionCwd: input.sessionCwd,
diagnostics: providerDiagnostics,
provenanceFilter: input.provenanceFilter
});
const rawGroups = emptyAgentMentionRawGroups();
rawGroups.agents = agentItems.filter(
(item) => item.kind === "agent-target"
);
return {
providerDiagnostics,
rawGroups,
totalCounts: totalCountsFromRawGroups(rawGroups),
issueTopicGroups: null
};
}
case "app": {
const appItems = await input.queryProviderMentionItemsById({
providerId: WORKSPACE_APP_PROVIDER_ID,
workspaceId: input.workspaceId,
currentUserId: input.currentUserId,
query: input.query,
sessionCwd: input.sessionCwd,
diagnostics: providerDiagnostics,
provenanceFilter: input.provenanceFilter
});
const rawGroups = emptyAgentMentionRawGroups();
rawGroups.apps = appItems.filter((item) => item.kind === "workspace-app");
return {
providerDiagnostics,
rawGroups,
totalCounts: totalCountsFromRawGroups(rawGroups),
issueTopicGroups: null
};
}
}
}
export async function queryAgentMentionProviderItems(input: {
provider: AgentContextMentionProvider;
workspaceId: string;
currentUserId: string;
query: string;
limit?: number;
sessionCwd: string;
sectionKey: string;
abortSignal: AbortSignal;
provenanceFilter: ReferenceProvenanceFilter | null;
}): Promise<AgentContextMentionItem[]> {
const queryInput: AgentContextMentionQueryInput = {
keyword: input.query,
maxResults: input.limit,
abortSignal: input.abortSignal,
trigger: "@",
context: {
metadata: {
currentUserId: input.currentUserId,
sectionKey: input.sectionKey || undefined,
sessionCwd: input.sessionCwd || undefined,
target: "agent-gui",
workspaceId: input.workspaceId,
referenceProvenanceFilter: input.provenanceFilter ?? undefined
}
}
};
const items = await input.provider.query(queryInput);
if (input.abortSignal.aborted) {
return [];
}
return mapProviderItemsToAgentMentionItems({
...input,
items
});
}
export async function queryAgentMentionProviderDirectoryItems(input: {
provider: AgentContextMentionProvider | undefined;
workspaceId: string;
currentUserId: string;
directoryPath: string;
sessionCwd: string;
sectionKey: string;
abortSignal: AbortSignal;
provenanceFilter: ReferenceProvenanceFilter | null;
}): Promise<AgentContextMentionItem[]> {
const provider = input.provider;
if (!provider?.queryDirectory) {
throw new Error("Mention provider does not support directory browsing.");
}
const items = await provider.queryDirectory({
keyword: "",
abortSignal: input.abortSignal,
trigger: "@",
directoryPath: input.directoryPath,
context: {
metadata: {
currentUserId: input.currentUserId,
sectionKey: input.sectionKey || undefined,
sessionCwd: input.sessionCwd || undefined,
target: "agent-gui",
workspaceId: input.workspaceId,
referenceProvenanceFilter: input.provenanceFilter ?? undefined
}
}
});
if (input.abortSignal.aborted) {
return [];
}
return mapProviderItemsToAgentMentionItems({
...input,
provider,
items
});
}
export async function queryAgentMentionProviderGroups(input: {
provider: AgentContextMentionProvider;
workspaceId: string;
currentUserId: string;
query: string;
limit?: number;
sessionCwd: string;
sectionKey: string;
abortSignal: AbortSignal;
provenanceFilter: ReferenceProvenanceFilter | null;
}): Promise<AgentMentionIssueTopicGroup[] | null> {
if (!input.provider.queryGroups) {
return null;
}
const result = await input.provider.queryGroups({
keyword: input.query,
maxResults: input.limit,
abortSignal: input.abortSignal,
trigger: "@",
context: {
metadata: {
currentUserId: input.currentUserId,
sectionKey: input.sectionKey || undefined,
sessionCwd: input.sessionCwd || undefined,
target: "agent-gui",
workspaceId: input.workspaceId,
referenceProvenanceFilter: input.provenanceFilter ?? undefined
}
}
});
if (input.abortSignal.aborted) {
return [];
}
return Promise.all(
result.groups.map(async (group): Promise<AgentMentionIssueTopicGroup> => {
const items = await mapProviderItemsToAgentMentionItems({
...input,
items: group.items
});
const uniqueItems = dedupeWorkspaceIssueMentionItems(items);
return {
id: agentMentionIssueTopicGroupId(group.id),
providerGroupId: group.id,
label: group.label,
items: uniqueItems,
totalCount: Math.max(group.totalCount, uniqueItems.length),
nextPageToken: group.nextCursor ?? null,
loadMoreStatus: "idle",
loadMoreError: null
};
})
);
}
export async function queryAgentMentionProviderGroupPage(input: {
provider: AgentContextMentionProvider;
providerGroupId: string;
workspaceId: string;
currentUserId: string;
query: string;
cursor: string;
pageSize: number;
sessionCwd: string;
sectionKey: string;
abortSignal: AbortSignal;
provenanceFilter: ReferenceProvenanceFilter | null;
}): Promise<AgentMentionIssueTopicGroup> {
if (!input.provider.queryGroupPage) {
throw new Error("Mention provider does not support grouped pagination.");
}
const group = await input.provider.queryGroupPage({
groupId: input.providerGroupId,
cursor: input.cursor,
pageSize: input.pageSize,
keyword: input.query,
maxResults: input.pageSize,
abortSignal: input.abortSignal,
trigger: "@",
context: {
metadata: {
currentUserId: input.currentUserId,
sectionKey: input.sectionKey || undefined,
sessionCwd: input.sessionCwd || undefined,
target: "agent-gui",
workspaceId: input.workspaceId,
referenceProvenanceFilter: input.provenanceFilter ?? undefined
}
}
});
const items = await mapProviderItemsToAgentMentionItems({
...input,
provider: input.provider,
items: group.items
});
const uniqueItems = dedupeWorkspaceIssueMentionItems(items);
return {
id: agentMentionIssueTopicGroupId(input.providerGroupId),
providerGroupId: input.providerGroupId,
label: group.label,
items: uniqueItems,
totalCount: Math.max(group.totalCount, uniqueItems.length),
nextPageToken: group.nextCursor ?? null,
loadMoreStatus: "idle",
loadMoreError: null
};
}
export function agentMentionIssueTopicGroupId(
topicId: string
): `issue-topic:${string}` {
return `issue-topic:${encodeURIComponent(topicId)}`;
}
function dedupeWorkspaceIssueMentionItems(
items: readonly AgentContextMentionItem[]
): AgentContextMentionItem[] {
const issueIds = new Set<string>();
return items.filter((item) => {
if (item.kind !== "workspace-issue") {
return true;
}
if (issueIds.has(item.targetId)) {
return false;
}
issueIds.add(item.targetId);
return true;
});
}
async function mapProviderItemsToAgentMentionItems(input: {
provider: AgentContextMentionProvider;
workspaceId: string;
currentUserId: string;
items: readonly any[];
}): Promise<AgentContextMentionItem[]> {
const mentionItems = await Promise.all(
input.items.map(async (item) => {
const mentionItem = providerItemToAgentMentionItem({
currentUserId: input.currentUserId,
insertResult: input.provider.toInsertResult(item),
label: input.provider.getItemLabel(item),
providerId: input.provider.id,
directory: input.provider.getItemDirectory?.(item) ?? null,
subtitle: input.provider.getItemSubtitle?.(item) ?? "",
workspaceId: input.workspaceId
});
if (!mentionItem || mentionItem.kind !== "file") {
return mentionItem;
}
const iconUrl = await Promise.resolve(
input.provider.getItemIconUrl?.(item) ?? null
).catch(() => null);
const resolvedThumbnailUrl = resolveAgentMentionFileThumbnailUrl({
...mentionItem,
thumbnailUrl: iconUrl
});
if (!resolvedThumbnailUrl) {
return mentionItem;
}
return {
...mentionItem,
thumbnailUrl: resolvedThumbnailUrl
};
})
);
return mentionItems.filter(
(item): item is AgentContextMentionItem => item !== null
);
}