Files
boss/src/lib/boss-projections.ts
2026-05-17 02:20:08 +08:00

1470 lines
47 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type {
AiAccountRole,
AiProvider,
AiAccountStatus,
AppLogEntry,
AuthSession,
AuditTaskRequest,
AuditTaskResult,
BossState,
Capability,
ContextBudgetLevel,
Device,
DeviceEnrollment,
DeviceImportDraft,
DeviceImportResolution,
ProjectExecutionPolicy,
DeviceSkill,
MasterIdentitySummary,
MasterAgentMemory,
MasterAgentPromptPolicy,
OpsFault,
OpsRepairTicket,
OpsRepairVerification,
Project,
ProjectAgentControls,
RiskLevel,
ThreadContextAlert,
ThreadContextSnapshot,
ThreadHandoffPackage,
UserMasterPrompt,
} from "@/lib/boss-data";
import {
canAccessDevice,
canAccessProject,
canViewSkill,
filterDevicesForSession,
filterProjectDevicesForSession,
filterProjectsForSession,
type PermissionSession,
} from "@/lib/boss-permissions";
export interface ContextIndicator {
visible: boolean;
style: "ring_percent";
percent?: number;
level?: ContextBudgetLevel;
}
export interface ConversationItem {
conversationId: string;
conversationType: "master_agent" | "single_device" | "group" | "folder_archive";
projectId: string;
projectTitle: string;
threadTitle: string;
folderLabel: string;
folderKey?: string;
threadCount?: number;
searchAliases?: string[];
searchTargetProjectIds?: string[];
preview: string;
lastMessagePreview: string;
activityIconCount: number;
topPinnedLabel?: "置顶";
manualPinned: boolean;
latestReplyAt: string;
latestReplyLabel: string;
unreadCount: number;
riskLevel: RiskLevel;
activeDeviceCount: number;
deviceNamesPreview: string[];
avatar: {
primary: string;
secondary?: string;
overflowCount?: number;
};
groupMembers?: Array<{
threadId: string;
avatar: string;
title: string;
}>;
contextBudgetIndicator: ContextIndicator;
contextBudgetSourceNodeId?: string;
contextBudgetUpdatedAt?: string;
mustFinishBeforeCompaction: boolean;
}
function conversationHistoryWasCleared(state: BossState) {
return Boolean(state.conversationHistoryClearedAt?.trim());
}
export interface ThreadContextView {
snapshot: ThreadContextSnapshot;
handoffPackage?: ThreadHandoffPackage;
alerts: ThreadContextAlert[];
}
export interface ProjectDetailView {
project: Project;
agentControls?: ProjectAgentControls | null;
devices: Device[];
masterIdentity?: MasterIdentitySummary;
activeThreadContexts: ThreadContextView[];
nextCompactionRiskThreadId?: string;
threadsRequiringHandoff: ThreadContextView[];
masterContextStrategySummary: string;
recentAppLogs: AppLogEntry[];
openFaults: OpsFault[];
relatedAuditResults: AuditTaskResult[];
}
export interface ThreadContextDetailView {
snapshot: ThreadContextSnapshot;
handoffPackage?: ThreadHandoffPackage;
alerts: ThreadContextAlert[];
currentChecklist: string[];
masterActions: string[];
}
export interface DeviceWorkspaceView {
selectedDevice?: Device;
relatedThreads: ThreadContextSnapshot[];
activeEnrollment?: DeviceEnrollment;
importDraft?: DeviceImportDraft;
importResolution?: DeviceImportResolution;
projectExecutionPolicies?: ProjectExecutionPolicy[];
}
export interface OpsSummaryView {
mode: "active" | "idle";
faults: OpsFault[];
tickets: Array<
OpsRepairTicket & {
verification?: OpsRepairVerification;
}
>;
}
export interface AuditSummaryView {
pendingRequests: AuditTaskRequest[];
latestResults: AuditTaskResult[];
capabilities: Capability[];
}
export interface SkillInventoryDeviceGroup {
device: Device;
skills: DeviceSkill[];
}
export interface SkillInventoryView {
boundDeviceId?: string;
groups: SkillInventoryDeviceGroup[];
}
const levelPriority: Record<ContextBudgetLevel, number> = {
critical: 0,
urgent: 1,
watch: 2,
safe: 3,
};
const aiRolePriority: Record<AiAccountRole, number> = {
primary: 0,
backup: 1,
api_fallback: 2,
};
const shanghaiFormatter = new Intl.DateTimeFormat("zh-CN", {
timeZone: "Asia/Shanghai",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
const shanghaiDayFormatter = new Intl.DateTimeFormat("zh-CN", {
timeZone: "Asia/Shanghai",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
export function formatTimestampLabel(value?: string, fallback = "刚刚") {
if (!value) return fallback;
if (!value.includes("T")) return value;
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
const diff = Date.now() - date.getTime();
if (Math.abs(diff) < 60_000) return "刚刚";
if (diff >= 0 && diff < 24 * 60 * 60_000) {
return shanghaiFormatter.format(date);
}
return shanghaiDayFormatter.format(date);
}
const STALE_CONTEXT_SYNC_LABEL = "待同步";
const STALE_CONTEXT_REPLY_THRESHOLD_MS = 7 * 24 * 60 * 60_000;
const PROCESS_PREVIEW_PREFIXES = [
"我先",
"我现在",
"我会先",
"我发现",
"我准备",
"接下来",
"正在",
"先看",
"先读",
"我把",
"我再",
"目前在",
"现在在",
"补一组",
"处理一下",
"先确认",
"准备",
"同步一下",
"我这边已经",
];
const PROCESS_PREVIEW_CONTAINS = [
"我继续",
"我已经在",
"正在跑",
"正在检查",
"正在处理",
"正在同步",
"我会直接",
"我先把",
"先补",
"再接",
];
const PROCESS_PREVIEW_NUMBERED_HINTS = [
"先",
"再",
"接下来",
"然后",
"检查",
"确认",
"处理",
"同步",
"补",
"排查",
"推进",
"回你",
"回传",
"会把",
"我会",
];
const PROCESS_PREVIEW_BLOCK_MARKERS = [
"失败",
"报错",
"错误",
"阻塞",
"不能",
"无法",
"崩溃",
"超时",
"exception",
"error",
"fatal",
"结论",
"最终",
"总结",
"已完成",
"已经完成",
"验证通过",
"测试通过",
"已修复",
"修好了",
"已部署",
"已安装",
"可以直接",
];
const LEAKED_TITLE_PREFIXES = [
"你当前接手的项目根目录是",
"你现在接手的项目根目录是",
"你现在以目标线程身份直接回复用户",
"你正在向主 Agent 同步当前项目状态",
"只回复对用户真正有用的内容",
"只输出 JSON",
];
const LEAKED_TITLE_CONTAINS = [
"不要发送内部字段",
"不要自称主 Agent",
"不要解释系统如何分发",
"不要输出 JSON",
"项目名称:",
"线程名称:",
"文件夹:",
"同步原因:",
"当前消息:",
"用户当前消息:",
];
function formatConversationLatestReplyLabel(value: string, hasVisibleContext: boolean) {
if (hasVisibleContext && value.includes("T")) {
const date = new Date(value);
const diff = Date.now() - date.getTime();
if (!Number.isNaN(date.getTime()) && diff >= STALE_CONTEXT_REPLY_THRESHOLD_MS) {
return STALE_CONTEXT_SYNC_LABEL;
}
}
return formatTimestampLabel(value);
}
function compareSnapshots(a: ThreadContextSnapshot, b: ThreadContextSnapshot) {
if (a.mustFinishBeforeCompaction !== b.mustFinishBeforeCompaction) {
return a.mustFinishBeforeCompaction ? -1 : 1;
}
if (levelPriority[a.contextBudgetLevel] !== levelPriority[b.contextBudgetLevel]) {
return levelPriority[a.contextBudgetLevel] - levelPriority[b.contextBudgetLevel];
}
return (a.compactionExpectedAt ?? "").localeCompare(b.compactionExpectedAt ?? "");
}
function projectType(project: Project): ConversationItem["conversationType"] {
if (project.id === "master-agent") return "master_agent";
return project.isGroup ? "group" : "single_device";
}
function buildFolderKey(project: Project) {
if (project.id === "master-agent" || project.isGroup) return undefined;
const deviceId = project.deviceIds[0];
const folderRef = (project.threadMeta.codexFolderRef?.trim() || project.threadMeta.folderName.trim()).toLowerCase();
if (!deviceId || !folderRef) return undefined;
return `${deviceId}:${folderRef}`;
}
function isTopPinnedConversation(project: Project) {
return Boolean(project.pinned || project.systemPinned || project.id === "audit-collab");
}
function getThreadAvatarFallback(title: string) {
const trimmed = title.trim();
if (!trimmed) return "A";
return trimmed.slice(0, 1).toUpperCase();
}
function getGroupMemberAvatar(member: Project["groupMembers"][number], device?: Device) {
const avatar = device?.avatar?.trim();
if (avatar) return avatar;
return getThreadAvatarFallback(member.threadDisplayName);
}
function aiRoleLabel(role: AiAccountRole) {
switch (role) {
case "primary":
return "主 GPT";
case "backup":
return "备用 GPT";
case "api_fallback":
return "API 容灾";
default:
return role;
}
}
function aiProviderLabel(provider: AiProvider) {
switch (provider) {
case "master_codex_node":
return "Master Codex Node";
case "openai_api":
return "OpenAI API";
case "aliyun_qwen_api":
return "阿里百炼 Qwen";
case "deepseek_api":
return "DeepSeek API";
default:
return provider;
}
}
function aiStatusLabel(status: AiAccountStatus) {
switch (status) {
case "ready":
return "可用";
case "needs_login":
return "待登录";
case "needs_api_key":
return "待配置 Key";
case "degraded":
return "异常";
case "disabled":
return "已停用";
default:
return status;
}
}
function canGenerateAiAccount(account: BossState["aiAccounts"][number]) {
if (!account.enabled) return false;
if (account.provider === "master_codex_node") {
return Boolean(account.nodeId?.trim());
}
return Boolean(account.apiKey?.trim());
}
function getProjectMasterIdentity(state: BossState): MasterIdentitySummary {
const accounts = [...state.aiAccounts].sort((a, b) => {
if (a.isActive !== b.isActive) return a.isActive ? -1 : 1;
if (aiRolePriority[a.role] !== aiRolePriority[b.role]) {
return aiRolePriority[a.role] - aiRolePriority[b.role];
}
return (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "");
});
const account = accounts.find((item) => item.isActive) ?? accounts.find(canGenerateAiAccount) ?? accounts[0];
if (!account) {
return {
label: "API 容灾",
role: "api_fallback",
roleLabel: aiRoleLabel("api_fallback"),
provider: "openai_api",
providerLabel: aiProviderLabel("openai_api"),
displayName: "未配置 AI 账号",
status: "needs_api_key",
statusLabel: aiStatusLabel("needs_api_key"),
canGenerate: false,
note: "请到“我的 > AI 账号”补齐主控 AI 账号。",
};
}
return {
accountId: account.accountId,
label: account.label,
role: account.role,
roleLabel: aiRoleLabel(account.role),
provider: account.provider,
providerLabel: aiProviderLabel(account.provider),
displayName: account.displayName,
nodeLabel: account.nodeLabel,
model: account.model,
status: account.status,
statusLabel: aiStatusLabel(account.status),
canGenerate: canGenerateAiAccount(account),
switchReason: account.switchReason,
lastSwitchedAt: account.lastSwitchedAt,
note: account.loginStatusNote,
};
}
export function getMasterAgentPromptPolicyView(state: BossState): MasterAgentPromptPolicy | null {
return state.masterAgentPromptPolicy ?? null;
}
export function getUserMasterPromptView(state: BossState, account: string): UserMasterPrompt | null {
return state.userMasterPrompts.find((item) => item.account === account) ?? null;
}
export function listUserMasterMemoriesView(
state: BossState,
account: string,
options?: { includeArchived?: boolean },
): MasterAgentMemory[] {
const includeArchived = options?.includeArchived ?? false;
return [...state.masterAgentMemories]
.filter((memory) => memory.account === account && (includeArchived || !memory.archived))
.sort((a, b) => {
const aTime = Date.parse(a.lastUsedAt ?? a.updatedAt ?? a.createdAt) || 0;
const bTime = Date.parse(b.lastUsedAt ?? b.updatedAt ?? b.createdAt) || 0;
if (aTime !== bTime) return bTime - aTime;
return b.memoryId.localeCompare(a.memoryId);
});
}
function threadViewsForProject(state: BossState, projectId: string) {
return state.threadContextSnapshots
.filter((snapshot) => snapshot.projectId === projectId)
.sort(compareSnapshots)
.map((snapshot) => ({
snapshot,
handoffPackage: state.threadHandoffPackages.find(
(item) => item.fromThreadId === snapshot.threadId && item.packageStatus !== "expired",
),
alerts: state.threadContextAlerts.filter((alert) => alert.threadId === snapshot.threadId),
}));
}
function buildConversationItem(state: BossState, project: Project): ConversationItem {
const devices = state.devices.filter((device) => project.deviceIds.includes(device.id));
const threadViews = threadViewsForProject(state, project.id);
const topThread = threadViews[0]?.snapshot;
const { folderLabel, threadTitle, projectTitle } = buildProjectDisplayTitles(project);
const activityIconCount = deriveConversationActivityIconCount(state, project);
const topPinnedLabel = isTopPinnedConversation(project) ? "置顶" : undefined;
const latestConversationActivityAt = deriveLatestConversationActivityAt(project);
const compactPreview = compactImportedThreadPreview(project.preview);
const groupMembers = project.isGroup
? project.groupMembers.map((member) => ({
threadId: member.threadId,
avatar: getGroupMemberAvatar(
member,
state.devices.find((device) => device.id === member.deviceId),
),
title: member.threadDisplayName,
}))
: undefined;
return {
conversationId: `conv-${project.id}`,
conversationType: projectType(project),
projectId: project.id,
projectTitle,
threadTitle,
folderLabel,
folderKey: buildFolderKey(project),
preview: compactPreview,
lastMessagePreview: compactPreview,
activityIconCount,
topPinnedLabel,
manualPinned: Boolean(project.pinned && !project.systemPinned),
latestReplyAt: latestConversationActivityAt,
latestReplyLabel: formatConversationLatestReplyLabel(
latestConversationActivityAt,
Boolean(topThread),
),
unreadCount: project.unreadCount,
riskLevel: project.riskLevel,
activeDeviceCount: devices.length,
deviceNamesPreview: devices.map((device) => device.name),
avatar: {
primary: devices[0]?.avatar ?? "A",
secondary: project.isGroup ? devices[1]?.avatar : undefined,
overflowCount: Math.max(0, devices.length - 2) || undefined,
},
groupMembers,
contextBudgetIndicator: {
visible: Boolean(topThread),
style: "ring_percent",
percent: topThread?.contextBudgetRemainingPct,
level: topThread?.contextBudgetLevel,
},
contextBudgetSourceNodeId: topThread?.nodeId,
contextBudgetUpdatedAt: topThread?.capturedAt,
mustFinishBeforeCompaction: Boolean(topThread?.mustFinishBeforeCompaction),
} satisfies ConversationItem;
}
function buildProjectDisplayTitles(project: Project) {
const folderLabel = normalizeConversationTitle(project.threadMeta?.folderName ?? "");
const folderFallback = pickConversationTitleFallback([
folderLabel,
project.threadMeta?.codexFolderRef,
project.name,
]);
const threadTitle = sanitizeConversationTitle(project.threadMeta?.threadDisplayName ?? project.name, [
folderFallback,
project.name,
project.threadMeta?.codexFolderRef,
]);
const projectTitle = projectType(project) === "single_device"
? threadTitle ||
sanitizeConversationTitle(project.name, [folderFallback, project.threadMeta?.codexFolderRef])
: sanitizeConversationTitle(project.name, [
threadTitle,
folderFallback,
project.threadMeta?.codexFolderRef,
]);
return {
folderLabel,
threadTitle,
projectTitle,
};
}
function cloneProjectWithDisplayTitles(project: Project): Project {
const { folderLabel, threadTitle, projectTitle } = buildProjectDisplayTitles(project);
return {
...project,
name: projectTitle || project.name,
threadMeta: {
...project.threadMeta,
threadDisplayName: threadTitle || project.threadMeta.threadDisplayName,
folderName: folderLabel || project.threadMeta.folderName,
},
};
}
function deriveLatestConversationActivityAt(project: Project) {
const messageCandidates = [
project.lastMessageAt,
...project.messages.map((message) => message.sentAt),
].filter(Boolean) as string[];
let latest = messageCandidates[0];
let latestTs = latest ? Date.parse(latest) : Number.NEGATIVE_INFINITY;
for (const candidate of messageCandidates.slice(1)) {
const candidateTs = Date.parse(candidate);
if (!Number.isFinite(candidateTs)) {
continue;
}
if (!Number.isFinite(latestTs) || candidateTs > latestTs) {
latest = candidate;
latestTs = candidateTs;
}
}
return latest ?? project.lastMessageAt ?? project.updatedAt;
}
function deriveConversationActivityIconCount(state: BossState, project: Project): number {
let count = 0;
if (
state.dispatchPlans.some(
(plan) => plan.groupProjectId === project.id && plan.status === "pending_user_confirmation",
)
) {
count += 1;
}
count += state.dispatchExecutions.filter(
(execution) =>
(execution.groupProjectId === project.id || execution.targetProjectId === project.id) &&
(execution.status === "queued" || execution.status === "running"),
).length;
count += state.masterAgentTasks.filter(
(task) =>
task.projectId === project.id &&
task.taskType !== "device_import_resolution" &&
(task.status === "queued" || task.status === "running"),
).length;
return Math.max(0, Math.min(4, count));
}
function sortConversationItems(items: ConversationItem[]) {
return items.sort((a, b) => {
if (a.projectId === "master-agent") return -1;
if (b.projectId === "master-agent") return 1;
const aPinned = Boolean(a.topPinnedLabel);
const bPinned = Boolean(b.topPinnedLabel);
if (aPinned !== bPinned) return aPinned ? -1 : 1;
return b.latestReplyAt.localeCompare(a.latestReplyAt);
});
}
function buildFolderSearchAliases(items: ConversationItem[]) {
const aliases: string[] = [];
const targetProjectIds: string[] = [];
for (const item of items) {
const alias = (item.threadTitle?.trim() || item.projectTitle?.trim() || "").trim();
if (!alias) continue;
aliases.push(alias);
targetProjectIds.push(item.projectId);
}
return aliases.length > 0
? {
aliases,
targetProjectIds,
}
: undefined;
}
function compactImportedThreadPreview(preview?: string) {
const value = preview?.trim();
if (!value) return "";
if (/^已从设备.+导入线程《.+》[。.]?$/.test(value)) {
return "已导入线程";
}
if (isLikelyProcessPreview(value)) {
return "";
}
return compactConversationPreview(value);
}
function isLikelyProcessPreview(preview: string) {
const normalized = compactProcessPreview(preview);
if (!normalized) {
return false;
}
if (containsProcessPreviewMarker(normalized, PROCESS_PREVIEW_BLOCK_MARKERS)) {
return false;
}
if (isStructuredNumberedProcessPreview(preview)) {
return true;
}
return (
PROCESS_PREVIEW_PREFIXES.some((marker) => normalized.startsWith(marker)) ||
containsProcessPreviewMarker(normalized, PROCESS_PREVIEW_CONTAINS)
);
}
function compactProcessPreview(value: string) {
return value
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n")
.replace(/\n{2,}/g, "\n")
.trim()
.toLowerCase();
}
function containsProcessPreviewMarker(value: string, markers: string[]) {
return markers.some((marker) => value.includes(marker));
}
function isStructuredNumberedProcessPreview(preview: string) {
const numberedLines = preview
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n")
.split("\n")
.map((line) => compactProcessPreview(line))
.filter((line) => /^\d+[.)\u3001]\s*/.test(line));
if (numberedLines.length < 2) {
return false;
}
return containsProcessPreviewMarker(
numberedLines.join(" "),
PROCESS_PREVIEW_NUMBERED_HINTS,
);
}
function compactConversationPreview(preview: string) {
const structuredPreview = compactStructuredSummaryPreview(preview);
const flattened = (structuredPreview || preview)
.replace(/\[[^\]]+\]\(([^)]+)\)/g, "$1")
.replace(/`([^`]+)`/g, "$1")
.replace(/\s+/g, " ")
.trim();
if (!flattened) {
return "";
}
return flattened.length <= 72 ? flattened : `${flattened.slice(0, 72).trimEnd()}`;
}
function compactStructuredSummaryPreview(preview: string) {
const raw = preview.trim();
if (!raw.startsWith("{") || !raw.endsWith("}")) {
return "";
}
try {
const parsed = JSON.parse(raw) as Record<string, unknown>;
if (!parsed || Array.isArray(parsed)) {
return "";
}
const segments = [
formatStructuredSummarySegment("目标", parsed.projectGoal),
formatStructuredSummarySegment("进度", parsed.currentProgress),
formatStructuredSummarySegment("版本", parsed.versionRecord),
formatStructuredSummarySegment("下一步", parsed.recommendedNextStep),
].filter(Boolean);
return segments.join(" ");
} catch {
return "";
}
}
function formatStructuredSummarySegment(label: string, value: unknown) {
const normalized = typeof value === "string" ? value.trim() : "";
return normalized ? `${label}${normalized}` : "";
}
function normalizeConversationTitle(value?: string) {
const source = value?.replace(/\u0000/g, "") ?? "";
const firstLine = source
.split(/\r?\n/)
.map((line) => line.trim())
.find(Boolean);
if (!firstLine) {
return "";
}
return firstLine.replace(/\s+/g, " ").trim();
}
function stripTrailingConversationTitleNoise(value: string) {
return value.replace(/['"}\]]{2,}$/g, "").trimEnd();
}
function looksLikeLeakedConversationTitle(value?: string) {
const normalized = normalizeConversationTitle(value);
if (!normalized) {
return false;
}
return (
LEAKED_TITLE_PREFIXES.some((marker) => normalized.startsWith(marker)) ||
LEAKED_TITLE_CONTAINS.some((marker) => normalized.includes(marker))
);
}
function extractWorkspaceProjectName(value?: string) {
const normalized = normalizeConversationTitle(value).replaceAll("\\", "/");
if (!normalized) {
return "";
}
const patterns = [
/\/Users\/[^/]+\/code\/([^/\s"'`,。;!?]+)/i,
/\/home\/[^/]+\/code\/([^/\s"'`,。;!?]+)/i,
/[A-Za-z]:\/Users\/[^/]+\/code\/([^/\s"'`,。;!?]+)/i,
];
for (const pattern of patterns) {
const match = normalized.match(pattern);
if (match?.[1]) {
return match[1].split("/")[0]?.trim() ?? "";
}
}
return "";
}
function pickConversationTitleFallback(candidates: Array<string | undefined>) {
for (const candidate of candidates) {
const extractedProjectName = extractWorkspaceProjectName(candidate);
if (extractedProjectName && !looksLikeLeakedConversationTitle(extractedProjectName)) {
return extractedProjectName;
}
const normalized = stripTrailingConversationTitleNoise(
trimLocalWorkspacePrefix(normalizeConversationTitle(candidate)),
);
if (normalized && !looksLikeLeakedConversationTitle(normalized)) {
return normalized;
}
}
return "";
}
function sanitizeConversationTitle(value: string | undefined, fallbackCandidates: Array<string | undefined> = []) {
const normalized = normalizeConversationTitle(value);
const trimmed = stripTrailingConversationTitleNoise(trimLocalWorkspacePrefix(normalized));
if (trimmed && !looksLikeLeakedConversationTitle(normalized) && !looksLikeLeakedConversationTitle(trimmed)) {
return trimmed;
}
const extractedProjectName = extractWorkspaceProjectName(normalized);
if (extractedProjectName && !looksLikeLeakedConversationTitle(extractedProjectName)) {
return extractedProjectName;
}
const fallback = pickConversationTitleFallback(fallbackCandidates);
return fallback || trimmed;
}
function trimLocalWorkspacePrefix(label?: string) {
const value = label?.trim();
if (!value) return "";
const normalized = value.replaceAll("\\", "/");
const patterns = [
/^\/Users\/[^/]+\/code\/(.+)$/i,
/^\/home\/[^/]+\/code\/(.+)$/i,
/^[A-Za-z]:\/Users\/[^/]+\/code\/(.+)$/i,
];
for (const pattern of patterns) {
const match = normalized.match(pattern);
if (match?.[1]) {
return match[1];
}
}
return value;
}
export function getConversationItems(state: BossState): ConversationItem[] {
const conversations = state.projects.map((project) => buildConversationItem(state, project));
return sortConversationItems(conversations);
}
function stateForSession(state: BossState, session: PermissionSession): BossState {
const visibleDevices = filterDevicesForSession(state, session);
const visibleDeviceIds = new Set(visibleDevices.map((device) => device.id));
const visibleProjects = filterProjectsForSession(state, session).map((project) => ({
...project,
deviceIds: project.deviceIds.filter((deviceId) => visibleDeviceIds.has(deviceId)),
groupMembers: project.groupMembers.filter((member) => visibleDeviceIds.has(member.deviceId)),
}));
const scopedVisibleProjects = visibleProjects.map((project) =>
project.id === "master-agent" && session.role !== "highest_admin"
? projectWithAccountScopedMasterMessages(project, session.account)
: project,
);
const visibleProjectIds = new Set(scopedVisibleProjects.map((project) => project.id));
const canSeeThreadOnDevice = (projectId: string, deviceId: string) =>
visibleProjectIds.has(projectId) && visibleDeviceIds.has(deviceId);
return {
...state,
devices: visibleDevices,
projects: scopedVisibleProjects,
deviceSkills: state.deviceSkills.filter((skill) =>
visibleDeviceIds.has(skill.deviceId) &&
(session.role === "highest_admin" ||
canViewSkill(state, session, skill.skillId, { deviceId: skill.deviceId })),
),
threadStatusDocuments: state.threadStatusDocuments.filter((document) =>
canSeeThreadOnDevice(document.projectId, document.deviceId),
),
threadProgressEvents: state.threadProgressEvents.filter((event) =>
canSeeThreadOnDevice(event.projectId, event.deviceId),
),
threadContextSnapshots: state.threadContextSnapshots.filter((snapshot) =>
canSeeThreadOnDevice(snapshot.projectId, snapshot.nodeId),
),
threadHandoffPackages: state.threadHandoffPackages.filter((item) =>
visibleProjectIds.has(item.projectId),
),
threadContextAlerts: state.threadContextAlerts.filter((alert) =>
visibleProjectIds.has(alert.projectId),
),
dispatchPlans: state.dispatchPlans
.filter((plan) => visibleProjectIds.has(plan.groupProjectId))
.map((plan) => ({
...plan,
targets: plan.targets.filter(
(target) =>
visibleProjectIds.has(target.projectId) &&
visibleDeviceIds.has(target.deviceId),
),
confirmedTargetProjectIds: plan.confirmedTargetProjectIds?.filter((projectId) =>
visibleProjectIds.has(projectId),
),
})),
dispatchExecutions: state.dispatchExecutions.filter(
(execution) =>
visibleProjectIds.has(execution.groupProjectId) &&
visibleProjectIds.has(execution.targetProjectId) &&
visibleDeviceIds.has(execution.deviceId),
),
masterAgentTasks: state.masterAgentTasks.filter(
(task) =>
task.requestedByAccount === session.account ||
visibleProjectIds.has(task.projectId) ||
Boolean(task.targetProjectId && visibleProjectIds.has(task.targetProjectId)),
),
appLogs: state.appLogs.filter((log) =>
visibleDeviceIds.has(log.deviceId) ||
Boolean(log.projectId && visibleProjectIds.has(log.projectId)),
),
};
}
function projectWithAccountScopedMasterMessages(project: Project, account: string): Project {
const messages = project.messages.filter((message) => message.account === account);
const latestMessage = [...messages].sort(
(left, right) => Date.parse(right.sentAt) - Date.parse(left.sentAt),
)[0];
return {
...project,
messages,
preview: latestMessage?.body ?? "",
lastMessageAt: latestMessage?.sentAt ?? project.updatedAt,
unreadCount: messages.filter((message) => message.sender !== "user").length,
};
}
export function getAuthorizedStateSnapshot(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
): BossState {
return stateForSession(state, session);
}
export function getConversationItemsForSession(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
): ConversationItem[] {
return getConversationItems(stateForSession(state, session));
}
export interface ConversationFolderView {
folderKey: string;
folderLabel: string;
deviceId?: string;
deviceName?: string;
threadCount: number;
threads: ConversationItem[];
}
export interface ProjectMessagesRealtimePayload {
ok: true;
project: Project;
devices: Device[];
}
export function getConversationHomeItems(state: BossState): ConversationItem[] {
const flatItems = getConversationItems(state);
const projectMap = new Map(state.projects.map((project) => [project.id, project]));
const grouped = new Map<string, ConversationItem[]>();
const passthrough: ConversationItem[] = [];
for (const item of flatItems) {
const project = projectMap.get(item.projectId);
if (!project || item.conversationType !== "single_device") {
passthrough.push(item);
continue;
}
const folderKey = buildFolderKey(project);
if (!folderKey) {
passthrough.push(item);
continue;
}
const bucket = grouped.get(folderKey) ?? [];
bucket.push(item);
grouped.set(folderKey, bucket);
}
for (const [folderKey, items] of grouped) {
if (items.length <= 1) {
passthrough.push(items[0]);
continue;
}
const latestItem = [...items].sort((a, b) => b.latestReplyAt.localeCompare(a.latestReplyAt))[0];
const project = projectMap.get(latestItem.projectId);
const device = project?.deviceIds[0]
? state.devices.find((entry) => entry.id === project.deviceIds[0])
: undefined;
const topContextItem = [...items]
.filter((item) => item.contextBudgetIndicator.visible)
.sort((a, b) => {
if (a.mustFinishBeforeCompaction !== b.mustFinishBeforeCompaction) {
return a.mustFinishBeforeCompaction ? -1 : 1;
}
const aLevel = a.contextBudgetIndicator.level ?? "safe";
const bLevel = b.contextBudgetIndicator.level ?? "safe";
if (levelPriority[aLevel] !== levelPriority[bLevel]) {
return levelPriority[aLevel] - levelPriority[bLevel];
}
return b.latestReplyAt.localeCompare(a.latestReplyAt);
})[0];
const recentThreadLabel = trimLocalWorkspacePrefix(latestItem.threadTitle);
const searchAliases = buildFolderSearchAliases(items);
const latestPreview = compactImportedThreadPreview(latestItem.preview);
const latestMessagePreview = compactImportedThreadPreview(
latestItem.lastMessagePreview || latestItem.preview,
);
const historyCleared = conversationHistoryWasCleared(state);
passthrough.push({
conversationId: `folder-${folderKey}`,
conversationType: "folder_archive",
projectId: folderKey,
projectTitle:
project?.threadMeta.folderName ??
(latestItem.folderLabel || latestItem.projectTitle),
threadTitle:
project?.threadMeta.folderName ??
(latestItem.folderLabel || latestItem.threadTitle),
folderLabel: recentThreadLabel ? `${items.length} 个线程 · 最近:${recentThreadLabel}` : `${items.length} 个线程`,
folderKey,
threadCount: items.length,
topPinnedLabel: items.some((entry) => entry.topPinnedLabel) ? "置顶" : undefined,
manualPinned: items.some((entry) => entry.manualPinned),
...(searchAliases
? {
searchAliases: searchAliases.aliases,
searchTargetProjectIds: searchAliases.targetProjectIds,
}
: {}),
preview:
latestPreview ||
(historyCleared ? "" : `包含 ${items.length} 个线程,最近活跃:《${recentThreadLabel || latestItem.threadTitle}`),
lastMessagePreview:
latestMessagePreview ||
latestPreview ||
(historyCleared ? "" : `包含 ${items.length} 个线程,最近活跃:《${recentThreadLabel || latestItem.threadTitle}`),
activityIconCount: Math.max(0, Math.min(4, items.reduce((sum, entry) => sum + entry.activityIconCount, 0))),
latestReplyAt: latestItem.latestReplyAt,
latestReplyLabel: latestItem.latestReplyLabel,
unreadCount: items.reduce((sum, entry) => sum + entry.unreadCount, 0),
riskLevel: items.some((entry) => entry.riskLevel === "high")
? "high"
: items.some((entry) => entry.riskLevel === "medium")
? "medium"
: "low",
activeDeviceCount: 1,
deviceNamesPreview: device ? [device.name] : latestItem.deviceNamesPreview,
avatar: {
primary: device?.avatar ?? latestItem.avatar.primary,
},
contextBudgetIndicator: {
visible: Boolean(topContextItem),
style: "ring_percent",
percent: topContextItem?.contextBudgetIndicator.percent,
level: topContextItem?.contextBudgetIndicator.level,
},
contextBudgetSourceNodeId: topContextItem?.contextBudgetSourceNodeId,
contextBudgetUpdatedAt: topContextItem?.contextBudgetUpdatedAt,
mustFinishBeforeCompaction: Boolean(topContextItem?.mustFinishBeforeCompaction),
});
}
return sortConversationItems(passthrough);
}
export function getConversationHomeItemsForSession(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
): ConversationItem[] {
return getConversationHomeItems(stateForSession(state, session));
}
export function getConversationWebItems(state: BossState): ConversationItem[] {
return getConversationHomeItems(state).map((item) => ({
...item,
topPinnedLabel: undefined,
manualPinned: false,
}));
}
export function getConversationHomeItemForProject(state: BossState, projectId: string): ConversationItem | null {
const normalizedProjectId = projectId.trim();
if (!normalizedProjectId) {
return null;
}
return (
getConversationHomeItems(state).find((item) => {
if (item.projectId === normalizedProjectId) {
return true;
}
return Array.isArray(item.searchTargetProjectIds)
? item.searchTargetProjectIds.includes(normalizedProjectId)
: false;
}) ?? null
);
}
export function getConversationThreadItemForProject(state: BossState, projectId: string): ConversationItem | null {
const normalizedProjectId = projectId.trim();
if (!normalizedProjectId) {
return null;
}
return getConversationItems(state).find((item) => item.projectId === normalizedProjectId) ?? null;
}
export function getConversationFolderView(
state: BossState,
folderKey: string,
): ConversationFolderView | null {
const flatItems = getConversationItems(state).filter(
(item) => item.conversationType === "single_device" && item.folderKey === folderKey,
);
if (flatItems.length === 0) {
return null;
}
const project = state.projects.find((entry) => buildFolderKey(entry) === folderKey);
const deviceId = project?.deviceIds[0];
const device = deviceId ? state.devices.find((entry) => entry.id === deviceId) : undefined;
return {
folderKey,
folderLabel: project?.threadMeta.folderName ?? flatItems[0].folderLabel,
deviceId,
deviceName: device?.name,
threadCount: flatItems.length,
threads: sortConversationItems(flatItems),
};
}
export function getConversationFolderViewForSession(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
folderKey: string,
): ConversationFolderView | null {
return getConversationFolderView(stateForSession(state, session), folderKey);
}
export function buildProjectMessagesRealtimePayload(
state: BossState,
projectId: string,
): ProjectMessagesRealtimePayload | null {
const normalizedProjectId = projectId.trim();
if (!normalizedProjectId) {
return null;
}
const project = state.projects.find((item) => item.id === normalizedProjectId);
if (!project) {
return null;
}
return {
ok: true,
project: cloneProjectWithDisplayTitles(project),
devices: state.devices.filter((device) => project.deviceIds.includes(device.id)),
};
}
export function buildProjectMessagesRealtimePayloadForSession(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
projectId: string,
): ProjectMessagesRealtimePayload | null {
if (!canAccessProject(state, session, projectId, "project.view")) {
return null;
}
const project = state.projects.find((item) => item.id === projectId);
if (!project) {
return null;
}
const scopedProject =
project.id === "master-agent" && session.role !== "highest_admin"
? projectWithAccountScopedMasterMessages(cloneProjectWithDisplayTitles(project), session.account)
: cloneProjectWithDisplayTitles(project);
return {
ok: true,
project: scopedProject,
devices: filterProjectDevicesForSession(state, session, project),
};
}
function resolveProjectAgentControls(
state: BossState,
projectId: string,
account?: string,
) {
const normalizedAccount = account?.trim();
const scoped = normalizedAccount
? (
state.userProjectAgentControls.find(
(item) => item.projectId === projectId && item.account === normalizedAccount,
) ?? null
)
: null;
const projectControls = scoped?.controls ?? state.projects.find((item) => item.id === projectId)?.agentControls ?? null;
if (projectId === "master-agent") {
return projectControls;
}
const globalControls = normalizedAccount
? (
state.userProjectAgentControls.find(
(item) => item.projectId === "master-agent" && item.account === normalizedAccount,
)?.controls ?? state.projects.find((item) => item.id === "master-agent")?.agentControls ?? null
)
: state.projects.find((item) => item.id === "master-agent")?.agentControls ?? null;
const explicitTakeover = projectControls?.takeoverEnabled;
const inheritedGlobalTakeover = globalControls?.globalTakeoverEnabled;
const effectiveTakeoverEnabled =
explicitTakeover !== undefined ? explicitTakeover : Boolean(inheritedGlobalTakeover);
const takeoverInheritedFromGlobal =
explicitTakeover === undefined && inheritedGlobalTakeover !== undefined;
if (!projectControls && !takeoverInheritedFromGlobal && !effectiveTakeoverEnabled) {
return null;
}
return {
...(projectControls ?? { updatedAt: globalControls?.updatedAt ?? new Date().toISOString() }),
updatedAt: projectControls?.updatedAt ?? globalControls?.updatedAt ?? new Date().toISOString(),
effectiveTakeoverEnabled,
takeoverInheritedFromGlobal,
};
}
export function getProjectDetailView(state: BossState, projectId: string, account?: string): ProjectDetailView | null {
const project = state.projects.find((item) => item.id === projectId);
if (!project) return null;
const displayProject = cloneProjectWithDisplayTitles(project);
const activeThreadContexts = threadViewsForProject(state, projectId);
const threadsRequiringHandoff = activeThreadContexts.filter(
(item) =>
item.snapshot.mustFinishBeforeCompaction ||
item.snapshot.contextBudgetLevel === "urgent" ||
item.snapshot.contextBudgetLevel === "critical",
);
const openFaults = state.opsFaults.filter(
(fault) => fault.projectId === projectId && fault.status !== "resolved",
);
const relatedAuditResults = state.auditResults.filter((result) =>
state.auditRequests.some(
(request) => request.auditRequestId === result.auditRequestId && request.projectId === projectId,
),
);
const topRisk = threadsRequiringHandoff[0]?.snapshot ?? activeThreadContexts[0]?.snapshot;
const masterContextStrategySummary = topRisk
? `${topRisk.title} 需要优先处理,当前 ${topRisk.contextBudgetLevel} ${topRisk.contextBudgetRemainingPct}%${topRisk.mustFinishBeforeCompaction ? ",必须先固化 patch / 测试 / 证据" : ""}`
: "当前没有高风险线程,主 Agent 可以继续按正常优先级调度。";
const projectDeviceIds = new Set(project.deviceIds);
const recentAppLogs = [...state.appLogs]
.filter((log) =>
projectId === "master-agent"
? true
: log.projectId === projectId || projectDeviceIds.has(log.deviceId),
)
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
.slice(0, 6);
return {
project: displayProject,
agentControls: resolveProjectAgentControls(state, projectId, account),
devices: state.devices.filter((device) => project.deviceIds.includes(device.id)),
masterIdentity: projectId === "master-agent" ? getProjectMasterIdentity(state) : undefined,
activeThreadContexts,
nextCompactionRiskThreadId: topRisk?.threadId,
threadsRequiringHandoff,
masterContextStrategySummary,
recentAppLogs,
openFaults,
relatedAuditResults,
};
}
export function getProjectDetailViewForSession(
state: BossState,
projectId: string,
session: Pick<AuthSession, "account" | "role" | "displayName">,
): ProjectDetailView | null {
if (!canAccessProject(state, session, projectId, "project.view")) {
return null;
}
const detail = getProjectDetailView(state, projectId, session.account);
if (!detail) {
return null;
}
const visibleProjectIds = new Set(filterProjectsForSession(state, session).map((project) => project.id));
const visibleDeviceIds = new Set(filterDevicesForSession(state, session).map((device) => device.id));
return {
...detail,
devices: filterProjectDevicesForSession(state, session, detail.project),
activeThreadContexts: detail.activeThreadContexts.filter((item) =>
visibleProjectIds.has(item.snapshot.projectId) && visibleDeviceIds.has(item.snapshot.nodeId),
),
threadsRequiringHandoff: detail.threadsRequiringHandoff.filter((item) =>
visibleProjectIds.has(item.snapshot.projectId) && visibleDeviceIds.has(item.snapshot.nodeId),
),
recentAppLogs: detail.recentAppLogs.filter((log) =>
visibleDeviceIds.has(log.deviceId) ||
Boolean(log.projectId && visibleProjectIds.has(log.projectId)),
),
};
}
export function getThreadContextDetailView(
state: BossState,
threadId: string,
): ThreadContextDetailView | null {
const snapshot = state.threadContextSnapshots.find((item) => item.threadId === threadId);
if (!snapshot) return null;
const handoffPackage = state.threadHandoffPackages.find(
(item) => item.fromThreadId === threadId && item.packageStatus !== "expired",
);
const alerts = state.threadContextAlerts.filter((item) => item.threadId === threadId);
const masterActions = Array.from(
new Set(alerts.flatMap((alert) => alert.masterActions)),
);
return {
snapshot,
handoffPackage,
alerts,
currentChecklist: snapshot.checklist,
masterActions,
};
}
export function getDeviceWorkspaceView(
state: BossState,
deviceId?: string,
): DeviceWorkspaceView {
if (!deviceId) {
return {
relatedThreads: [],
};
}
const selectedDevice = state.devices.find((item) => item.id === deviceId);
return {
selectedDevice: selectedDevice ? { ...selectedDevice } : undefined,
relatedThreads: state.threadContextSnapshots.filter((item) => item.nodeId === deviceId),
activeEnrollment: state.deviceEnrollments.find((item) => item.deviceId === deviceId),
importDraft: state.deviceImportDrafts.find((item) => item.deviceId === deviceId),
importResolution: state.deviceImportResolutions.find((item) => item.deviceId === deviceId),
projectExecutionPolicies: state.projectExecutionPolicies.filter((item) => item.deviceId === deviceId),
};
}
export function getDeviceWorkspaceViewForSession(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
deviceId?: string,
): DeviceWorkspaceView {
if (!deviceId || !canAccessDevice(state, session, deviceId, "device.view")) {
return { relatedThreads: [] };
}
return getDeviceWorkspaceView(stateForSession(state, session), deviceId);
}
export function getOpsSummaryView(state: BossState): OpsSummaryView {
const tickets = state.opsRepairTickets.map((ticket) => ({
...ticket,
verification: state.opsRepairVerifications.find((item) => item.ticketId === ticket.ticketId),
}));
const mode =
state.opsFaults.some((fault) => fault.status !== "resolved") ||
state.threadContextSnapshots.some(
(snapshot) =>
snapshot.contextBudgetLevel === "urgent" || snapshot.contextBudgetLevel === "critical",
)
? "active"
: "idle";
return {
mode,
faults: [...state.opsFaults].sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt)),
tickets: tickets.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)),
};
}
export function getAuditSummaryView(state: BossState): AuditSummaryView {
const completedIds = new Set(state.auditResults.map((result) => result.auditRequestId));
return {
pendingRequests: state.auditRequests.filter(
(request) => !completedIds.has(request.auditRequestId),
),
latestResults: [...state.auditResults].sort((a, b) =>
b.completedAt.localeCompare(a.completedAt),
),
capabilities: state.capabilities,
};
}
export function getSkillInventoryView(state: BossState): SkillInventoryView {
return getSkillInventoryViewForAccount(
state,
state.user.account,
state.user.boundDeviceId,
);
}
export function getSkillInventoryViewForAccount(
state: BossState,
account: string,
boundDeviceId?: string,
): SkillInventoryView {
const devices = state.devices
.filter(
(device) =>
device.account === account || device.id === boundDeviceId,
)
.sort((a, b) => {
if (a.id === boundDeviceId) return -1;
if (b.id === boundDeviceId) return 1;
return b.lastSeenAt.localeCompare(a.lastSeenAt);
});
return {
boundDeviceId,
groups: devices
.map((device) => ({
device,
skills: state.deviceSkills
.filter((skill) => skill.deviceId === device.id)
.sort((a, b) => a.name.localeCompare(b.name, "zh-CN")),
}))
.filter((group) => group.skills.length > 0),
};
}
export function getSkillInventoryViewForSession(
state: BossState,
session: Pick<AuthSession, "account" | "role" | "displayName">,
boundDeviceId?: string,
): SkillInventoryView {
const devices = filterDevicesForSession(state, session)
.filter((device) => !boundDeviceId || device.id === boundDeviceId || session.role === "highest_admin")
.sort((a, b) => {
if (a.id === boundDeviceId) return -1;
if (b.id === boundDeviceId) return 1;
return b.lastSeenAt.localeCompare(a.lastSeenAt);
});
return {
boundDeviceId,
groups: devices
.map((device) => ({
device,
skills: state.deviceSkills
.filter((skill) => skill.deviceId === device.id)
.filter((skill) =>
session.role === "highest_admin" ||
canViewSkill(state, session, skill.skillId, { deviceId: device.id }),
)
.sort((a, b) => a.name.localeCompare(b.name, "zh-CN")),
}))
.filter((group) => group.skills.length > 0),
};
}