import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto"; import { existsSync, readFileSync, statSync } from "node:fs"; import { promises as fs } from "node:fs"; import path from "node:path"; import { publishBossEvent } from "@/lib/boss-events"; import type { VerificationDeliveryMode } from "@/lib/boss-mail"; import { getFixedVerificationCode, getVerificationDeliveryMode } from "@/lib/boss-mail"; import { getPublishedOtaAsset } from "@/lib/boss-ota"; export type DeviceStatus = "online" | "abnormal" | "offline"; export type DeviceSource = "production" | "demo"; export type GoalState = "pending" | "completed"; export type MessageSender = "master" | "device" | "user" | "ops" | "audit"; // Forwarding uses a structured contract so the route can distinguish // single-message forwarding, bundle forwarding, and the legacy notice shape. export type MessageKind = | "text" | "system_notice" | "voice_intent" | "image_intent" | "video_intent" | "forward_notice" | "forward_single" | "forward_bundle" | "attachment" | "analysis_card"; export type AttachmentKind = "image" | "video" | "pdf" | "text" | "office" | "binary"; export type AttachmentStorageBackend = "server_file" | "aliyun_oss"; export type AttachmentAnalysisState = | "not_applicable" | "queued_auto" | "ready_manual" | "processing" | "completed" | "failed"; export interface MessageAttachment { attachmentId: string; fileName: string; mimeType: string; fileSizeBytes: number; attachmentKind: AttachmentKind; storageBackend: AttachmentStorageBackend; storagePath: string; storageSnapshot?: { provider: "aliyun_oss"; accessKeyId: string; accessKeySecretEncrypted: string; bucket: string; endpoint: string; region: string; prefix?: string; }; previewAvailable: boolean; uploadedAt: string; uploadedBy: string; analysisState: AttachmentAnalysisState; analysisSummary?: string; analysisCardId?: string; } export interface ForwardSource { sourceProjectId: string; sourceProjectName: string; sourceThreadId?: string; sourceThreadTitle?: string; sourceMessageId: string; forwardedBy: string; forwardedAt: string; } export interface ForwardBundleItem { messageId: string; senderLabel: string; body: string; kind: string; sentAt: string; } export interface ForwardBundlePayload { sourceProjectId: string; sourceProjectName: string; sourceThreadId?: string; sourceThreadTitle?: string; itemCount: number; startedAt: string; endedAt: string; items: ForwardBundleItem[]; } export type ContextBudgetLevel = "safe" | "watch" | "urgent" | "critical"; export type ThreadState = | "idle" | "running" | "waiting_input" | "waiting_approval" | "context_watch" | "context_urgent" | "compacted" | "handoff_pending" | "completed" | "failed"; export type RiskLevel = "low" | "medium" | "high"; export type AlertStatus = "opened" | "acked" | "resolved"; export type HandoffStatus = "draft" | "ready" | "consumed" | "expired"; export type OpsSeverity = "info" | "warning" | "critical"; export type OpsStatus = "opened" | "acked" | "repairing" | "resolved"; export type ApprovalStatus = "pending" | "approved" | "escalated"; export type ExecutionStatus = "queued" | "running" | "verified" | "failed"; export type VerificationStatus = "passed" | "failed" | "watching"; export type EnrollmentStatus = "ready" | "claimed" | "expired"; export type AuditTrigger = | "milestone" | "merge_candidate" | "failure_escalation" | "scheduled_check" | "human_request"; export type AuditType = "software" | "hardware" | "multimodal" | "chief"; export type AuditResultStatus = | "completed" | "failed" | "needs_retry" | "needs_human_review"; export type AuditDecision = "pass" | "fail" | "warning" | "inconclusive"; export type CapabilityLeaseMode = "exclusive" | "shared_read" | "shared_stream"; export type AuthRole = "member" | "admin" | "highest_admin"; export type LoginMethod = "password" | "code"; export type OtaUpdateStatus = "available" | "scheduled" | "applied" | "skipped"; export type OtaLogStatus = "checked" | "applied" | "skipped"; export type AppLogLevel = "info" | "warn" | "error"; export type AiProvider = "master_codex_node" | "openai_api" | "aliyun_qwen_api"; export type AiAccountRole = "primary" | "backup" | "api_fallback"; export type AiAccountStatus = "ready" | "needs_login" | "needs_api_key" | "degraded" | "disabled"; export type MasterAgentTaskStatus = "queued" | "running" | "completed" | "failed"; export type MasterAgentTaskType = | "conversation_reply" | "attachment_analysis" | "group_dispatch_plan" | "dispatch_execution" | "device_import_resolution"; export type DispatchPlanStatus = | "pending_user_confirmation" | "approved" | "rejected" | "dispatched"; export type DispatchExecutionStatus = "queued" | "running" | "completed" | "failed"; export type ReasoningEffort = "low" | "medium" | "high"; export interface UserSettings { liveUpdates: boolean; showRiskBadges: boolean; confirmDangerousActions: boolean; preferredEntryPoint: "conversations" | "devices" | "me"; } export interface UserProfile { id: string; name: string; avatar: string; account: string; verificationEmail?: string; role: AuthRole; roleLabel: string; accountType: string; qrCodeValue: string; boundCodexNodeId?: string; boundCodexNodeLabel?: string; boundDeviceId?: string; boundAt?: string; version: string; otaVersion?: string; otaSummary?: string[]; hasOta: boolean; settings: UserSettings; } export interface Device { id: string; name: string; avatar: string; account: string; source: DeviceSource; status: DeviceStatus; projects: string[]; quota5h: number; quota7d: number; lastSeenAt: string; endpoint?: string; token?: string; note?: string; } export interface Message { id: string; sender: MessageSender; senderLabel: string; body: string; sentAt: string; kind?: MessageKind; attachments?: MessageAttachment[]; forwardSource?: ForwardSource; forwardBundle?: ForwardBundlePayload; } export interface UserAttachmentStorageConfig { account: string; mode: "server_file" | "oss"; ossProvider?: "aliyun_oss"; aliyunOss?: { enabled: boolean; accessKeyId: string; accessKeySecretEncrypted: string; bucket: string; endpoint: string; region: string; prefix?: string; }; updatedAt: string; validatedAt?: string; } export interface MasterAgentPromptPolicy { globalPrompt: string; updatedAt: string; updatedBy?: string; } export interface UserMasterPrompt { account: string; content: string; updatedAt: string; } export type MasterMemoryScope = "global" | "project"; export type MasterMemoryType = | "user_preference" | "project_progress" | "decision" | "risk" | "blocking_issue" | "research_note" | "workflow_rule"; export interface MasterAgentMemory { memoryId: string; account: string; scope: MasterMemoryScope; projectId?: string; title: string; content: string; memoryType: MasterMemoryType; tags: string[]; sourceMessageId?: string; createdAt: string; updatedAt: string; lastUsedAt?: string; archived: boolean; } export interface GoalItem { id: string; text: string; state: GoalState; note: string; completedAt?: string; completedBy?: string; } export interface VersionEntry { version: string; summary: string; createdAt: string; } export interface ThreadConversationMeta { projectId: string; threadId: string; threadDisplayName: string; folderName: string; activityIconCount: number; updatedAt: string; codexThreadRef?: string; codexFolderRef?: string; } export interface GroupConversationMember { projectId: string; deviceId: string; threadId: string; threadDisplayName: string; folderName: string; } export interface Project { id: string; name: string; pinned: boolean; systemPinned?: boolean; deviceIds: string[]; preview: string; updatedAt: string; lastMessageAt: string; isGroup: boolean; threadMeta: ThreadConversationMeta; groupMembers: GroupConversationMember[]; createdByAgent: boolean; collaborationMode: "development" | "approval_required"; approvalState: "not_required" | "pending_agent" | "pending_user" | "approved" | "rejected"; agentControls?: ProjectAgentControls; unreadCount: number; riskLevel: RiskLevel; contextBudgetPct?: number; contextBudgetLabel?: string; messages: Message[]; goals: GoalItem[]; versions: VersionEntry[]; } export interface DispatchPlanTarget { deviceId: string; projectId: string; threadId: string; threadDisplayName: string; folderName: string; codexFolderRef?: string; codexThreadRef?: string; reason: string; } export interface DispatchPlan { planId: string; groupProjectId: string; requestMessageId: string; requestedBy: string; status: DispatchPlanStatus; targets: DispatchPlanTarget[]; summary: string; createdAt: string; confirmedAt?: string; confirmedBy?: string; confirmedTargetProjectIds?: string[]; } export interface DispatchExecution { executionId: string; planId: string; groupProjectId: string; targetProjectId: string; targetThreadId: string; deviceId: string; status: DispatchExecutionStatus; createdAt: string; completedAt?: string; resultMessageId?: string; completedByDeviceId?: string; } export function buildCollaborationGate( project?: Pick, ) { if (!project) { return { isGroup: false, collaborationMode: "development" as const, requiresMasterAgentApproval: false, approvalState: "not_required" as const, }; } return { isGroup: project.isGroup, collaborationMode: project.collaborationMode, requiresMasterAgentApproval: project.isGroup && project.collaborationMode === "approval_required", approvalState: project.approvalState, }; } export interface ProjectAgentControls { modelOverride?: string; reasoningEffortOverride?: ReasoningEffort; promptOverride?: string; updatedAt: string; } export interface UserProjectAgentControls { account: string; projectId: string; controls: ProjectAgentControls; } export interface DeviceImportCandidate { candidateId: string; deviceId: string; folderName: string; folderRef?: string; threadId: string; threadDisplayName: string; codexFolderRef?: string; codexThreadRef?: string; lastActiveAt: string; suggestedImport: boolean; } export function isDispatchableThreadProject(project: Project) { return ( project.id !== "master-agent" && !project.isGroup && Boolean(project.threadMeta.codexThreadRef?.trim()) && project.deviceIds.length > 0 ); } export interface DeviceImportDraft { draftId: string; deviceId: string; enrollmentId?: string; status: "pending_candidates" | "pending_selection" | "pending_resolution" | "resolved" | "applied"; candidates: DeviceImportCandidate[]; selectedCandidateIds: string[]; appliedProjectNames: string[]; createdAt: string; updatedAt: string; reviewedAt?: string; reviewedBy?: string; resolutionId?: string; } export interface DeviceImportResolutionItem { candidateId: string; action: "create_thread_conversation" | "attach_existing" | "skip"; threadDisplayName: string; folderName: string; targetProjectId?: string; reason: string; } export interface DeviceImportResolution { resolutionId: string; draftId: string; deviceId: string; status: "ready" | "applied"; summary: string; items: DeviceImportResolutionItem[]; createdAt: string; appliedAt?: string; appliedBy?: string; } export interface VerificationCode { id: string; account: string; purpose: "login" | "register" | "forgot-password"; code: string; expiresAt: string; createdAt: string; } export interface VerificationDispatch { dispatchId: string; account: string; purpose: VerificationCode["purpose"]; deliveryMode: VerificationDeliveryMode; requestedAt: string; status: "requested" | "delivered" | "failed" | "rate_limited"; note: string; } export interface AuthAccount { id: string; account: string; passwordHash: string; displayName: string; role: AuthRole; verificationEmail?: string; codexNodeId?: string; codexNodeLabel?: string; primaryDeviceId?: string; isPrimary?: boolean; failedLoginAttempts?: number; lockedUntil?: string; lastLoginAt?: string; lastLoginMethod?: LoginMethod; createdAt: string; updatedAt: string; } export interface AuthSession { sessionId: string; sessionToken: string; restoreToken: string; account: string; role: AuthRole; displayName: string; loginMethod: LoginMethod; createdAt: string; expiresAt: string; lastSeenAt: string; revokedAt?: string; } export interface AiAccount { accountId: string; label: string; role: AiAccountRole; provider: AiProvider; displayName: string; accountIdentifier?: string; nodeId?: string; nodeLabel?: string; model?: string; apiKey?: string; apiKeyMasked?: string; enabled: boolean; isActive: boolean; status: AiAccountStatus; loginStatusNote?: string; lastValidatedAt?: string; lastUsedAt?: string; lastError?: string; lastSwitchedAt?: string; switchReason?: string; createdAt: string; updatedAt: string; } export interface AiAccountSwitchRecord { switchId: string; fromAccountId?: string; fromLabel?: string; toAccountId: string; toLabel: string; role: AiAccountRole; switchedAt: string; reason: string; } export interface AiAccountSummary { accountId: string; label: string; role: AiAccountRole; roleLabel: string; provider: AiProvider; providerLabel: string; displayName: string; accountIdentifier?: string; nodeId?: string; nodeLabel?: string; model?: string; enabled: boolean; isActive: boolean; canGenerate: boolean; status: AiAccountStatus; statusLabel: string; loginStatusNote?: string; apiKeyConfigured: boolean; apiKeyMasked?: string; lastValidatedAt?: string; lastUsedAt?: string; lastError?: string; lastSwitchedAt?: string; switchReason?: string; createdAt: string; updatedAt: string; isEnvironmentFallback?: boolean; } export interface MasterIdentitySummary { accountId?: string; label: string; role: AiAccountRole; roleLabel: string; provider: AiProvider; providerLabel: string; displayName: string; nodeLabel?: string; model?: string; status: AiAccountStatus; statusLabel: string; canGenerate: boolean; switchReason?: string; lastSwitchedAt?: string; note?: string; isEnvironmentFallback?: boolean; } export interface MasterAgentTask { taskId: string; projectId: string; taskType: MasterAgentTaskType; requestMessageId: string; requestText: string; executionPrompt: string; requestedBy: string; requestedByAccount: string; deviceId: string; accountId?: string; accountLabel?: string; attachmentId?: string; attachmentFileName?: string; attachmentDownloadToken?: string; attachmentDownloadExpiresAt?: string; attachmentDownloadUrl?: string; attachmentTextExcerpt?: string; dispatchExecutionId?: string; targetProjectId?: string; targetThreadId?: string; targetThreadDisplayName?: string; targetCodexThreadRef?: string; targetCodexFolderRef?: string; deviceImportDraftId?: string; status: MasterAgentTaskStatus; requestedAt: string; claimedAt?: string; completedAt?: string; replyBody?: string; errorMessage?: string; requestId?: string; } export interface OtaUpdate { releaseId: string; version: string; currentVersion: string; channel: "stable" | "beta"; packageType: "web_console" | "android_shell"; status: OtaUpdateStatus; summary: string[]; targetScope: string; requiredRole: AuthRole; publishedAt: string; packageFileName?: string; packageSizeBytes?: number; packageSha256?: string; downloadUrl?: string; assetUpdatedAt?: string; } export interface OtaUpdateLog { logId: string; releaseId: string; version: string; status: OtaLogStatus; triggeredBy: string; triggeredAt: string; completedAt?: string; note: string; } export interface DeviceSkill { skillId: string; deviceId: string; name: string; description: string; path: string; invocation: string; category: string; updatedAt: string; } export interface AppLogEntry { logId: string; deviceId: string; projectId?: string; level: AppLogLevel; source: "app_client" | "local_agent"; category: string; message: string; detail?: string; mirroredToProject: boolean; createdAt: string; } export interface ThreadContextSnapshot { snapshotId: string; projectId: string; taskId: string; threadId: string; title: string; summary: string; nodeId: string; workerId: string; sourceKind: "codex_app_server" | "codex_sdk" | "worker_estimator"; status: ThreadState; contextBudgetRemainingPct: number; contextBudgetLevel: ContextBudgetLevel; compactionExpectedAt?: string; mustFinishBeforeCompaction: boolean; estimatedRemainingTurns: number; estimatedRemainingLargeMessages: number; lastCompactionAt?: string; compactionCount: number; patchPending: boolean; testsPending: boolean; evidencePending: boolean; checklist: string[]; capturedAt: string; } export interface ThreadHandoffPackage { handoffPackageId: string; projectId: string; taskId: string; fromThreadId: string; toThreadId: string; packageStatus: HandoffStatus; summaryText: string; openQuestions: string[]; criticalFiles: string[]; criticalCommands: string[]; criticalTests: string[]; criticalArtifacts: string[]; decisionLinks: string[]; createdAt: string; readyAt?: string; consumedAt?: string; } export interface ThreadContextAlert { alertId: string; threadId: string; projectId: string; alertType: | "context_watch" | "context_urgent" | "context_critical" | "compaction_risk" | "handoff_missing"; alertStatus: AlertStatus; openedAt: string; resolvedAt?: string; summary: string; masterActions: string[]; } export interface DeviceEnrollment { enrollmentId: string; deviceId: string; label: string; pairingCode: string; token: string; status: EnrollmentStatus; note: string; createdAt: string; expiresAt: string; claimedAt?: string; claimedDeviceId?: string; } export interface OpsFault { faultId: string; faultKey: string; severity: OpsSeverity; status: OpsStatus; nodeId: string; serviceName: string; projectId?: string; threadRef?: string; traceId: string; runbookId: string; firstSeenAt: string; lastSeenAt: string; summary: string; suggestedNextAction: string; autoRepairable: boolean; } export interface OpsRepairTicket { ticketId: string; faultId: string; title: string; approvalStatus: ApprovalStatus; executionStatus: ExecutionStatus; requestedBy: string; approvedBy?: string; targetNodeId: string; actionSummary: string; resultSummary?: string; createdAt: string; updatedAt: string; } export interface OpsRepairVerification { verificationId: string; ticketId: string; verifier: string; status: VerificationStatus; summary: string; verifiedAt: string; } export interface CapabilityRequirement { capabilityType: string; mode: CapabilityLeaseMode; } export interface Capability { capabilityId: string; providerId: string; nodeId: string; capabilityType: string; displayName: string; status: "online" | "offline" | "busy"; healthStatus: "healthy" | "warning" | "critical"; leaseMode: CapabilityLeaseMode; preemptible: boolean; supportedActions: string[]; evidenceModes: string[]; } export interface AuditTaskRequest { protocolVersion: string; auditRequestId: string; projectId: string; projectName: string; taskId: string; sourceThreadRef: string; trigger: AuditTrigger; auditType: AuditType; priority: number; objective: string; systemContextSummary: string; acceptanceCriteria: string[]; riskFocus: string[]; evidenceRefs: string[]; artifactRefs: string[]; capabilityRequirements: CapabilityRequirement[]; timeBudgetSeconds: number; responseMode: string; createdAt: string; metadataJson: Record; } export interface AuditTaskResult { auditRequestId: string; auditType: AuditType; status: AuditResultStatus; decision: AuditDecision; confidence: number; summary: string; findings: string[]; requiredActions: string[]; usedCapabilities: string[]; artifactRefs: string[]; timeline: string[]; durationMs: number; completedAt: string; } export interface BossState { user: UserProfile; devices: Device[]; projects: Project[]; verificationCodes: VerificationCode[]; verificationDispatches: VerificationDispatch[]; authAccounts: AuthAccount[]; authSessions: AuthSession[]; aiAccounts: AiAccount[]; aiAccountSwitchHistory: AiAccountSwitchRecord[]; masterAgentTasks: MasterAgentTask[]; dispatchPlans: DispatchPlan[]; dispatchExecutions: DispatchExecution[]; deviceImportDrafts: DeviceImportDraft[]; deviceImportResolutions: DeviceImportResolution[]; otaUpdates: OtaUpdate[]; otaUpdateLogs: OtaUpdateLog[]; deviceSkills: DeviceSkill[]; appLogs: AppLogEntry[]; userAttachmentStorageConfigs: UserAttachmentStorageConfig[]; masterAgentPromptPolicy: MasterAgentPromptPolicy | null; userMasterPrompts: UserMasterPrompt[]; masterAgentMemories: MasterAgentMemory[]; userProjectAgentControls: UserProjectAgentControls[]; threadContextSnapshots: ThreadContextSnapshot[]; threadHandoffPackages: ThreadHandoffPackage[]; threadContextAlerts: ThreadContextAlert[]; deviceEnrollments: DeviceEnrollment[]; opsFaults: OpsFault[]; opsRepairTickets: OpsRepairTicket[]; opsRepairVerifications: OpsRepairVerification[]; auditRequests: AuditTaskRequest[]; auditResults: AuditTaskResult[]; capabilities: Capability[]; } function detectRuntimeRoot(startDir: string) { let current = startDir; while (true) { if (existsSync(path.join(current, "package.json")) && existsSync(path.join(current, "src", "app"))) { return current; } const parent = path.dirname(current); if (parent === current) { return startDir; } current = parent; } } function resolveRuntimeRoot() { if (process.env.BOSS_RUNTIME_ROOT?.trim()) { return path.resolve(process.env.BOSS_RUNTIME_ROOT); } if (process.env.BOSS_STATE_FILE?.trim()) { return path.dirname(path.dirname(path.resolve(process.env.BOSS_STATE_FILE))); } return detectRuntimeRoot(/* turbopackIgnore: true */ process.cwd()); } const runtimeRoot = resolveRuntimeRoot(); const dataFile = process.env.BOSS_STATE_FILE ? path.resolve(process.env.BOSS_STATE_FILE) : path.join(runtimeRoot, "data", "boss-state.json"); const dataDir = path.dirname(dataFile); const backupFile = `${dataFile}.bak`; const publishedApkPath = path.join(runtimeRoot, "public", "downloads", "boss-android-latest.apk"); const publishedApkMetaPath = path.join(runtimeRoot, "public", "downloads", "boss-android-latest.json"); const defaultSettings: UserSettings = { liveUpdates: true, showRiskBadges: true, confirmDangerousActions: true, preferredEntryPoint: "conversations", }; const PRIMARY_ADMIN_ACCOUNT = "17600003315"; const PRIMARY_ADMIN_PASSWORD = "boss123456"; const PRIMARY_ADMIN_VERIFICATION_EMAIL = "verify@boss.hyzq.net"; const PRIMARY_CODEX_NODE_ID = "mac-studio"; const PRIMARY_CODEX_NODE_LABEL = "本机 Codex · Mac Studio"; const VERIFICATION_SEND_COOLDOWN_MS = 60_000; const VERIFICATION_SEND_WINDOW_MS = 15 * 60_000; const VERIFICATION_SEND_WINDOW_LIMIT = 5; export const AUTH_SESSION_TTL_MS = 30 * 24 * 60 * 60_000; const AUTH_LOGIN_LOCK_THRESHOLD = 5; const AUTH_LOGIN_LOCK_MS = 10 * 60_000; const ENV_OPENAI_ACCOUNT_ID = "env-openai-api"; function baseThreadChecklist(labels: string[]) { return labels; } const initialState: BossState = { user: { id: "user-boss-admin", name: "Boss 超级管理员", avatar: "17", account: PRIMARY_ADMIN_ACCOUNT, verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL, role: "highest_admin", roleLabel: "最高管理员", accountType: "最高管理员 · 本机 Codex 已绑定", qrCodeValue: `boss://user/${PRIMARY_ADMIN_ACCOUNT}`, boundCodexNodeId: PRIMARY_CODEX_NODE_ID, boundCodexNodeLabel: PRIMARY_CODEX_NODE_LABEL, boundDeviceId: PRIMARY_CODEX_NODE_ID, boundAt: "2026-03-26T09:00:00+08:00", version: "1.4.0", otaVersion: "v1.4.1", otaSummary: ["新增登录会话守卫", "新增验证码防重放", "开放 APK 下载 OTA 包元数据"], hasOta: true, settings: defaultSettings, }, devices: [ { id: "mac-studio", name: "Mac Studio", avatar: "M", account: PRIMARY_ADMIN_ACCOUNT, source: "production", status: "online", projects: ["Boss 移动控制台", "硬件审计协作"], quota5h: 68, quota7d: 81, lastSeenAt: "2026-03-25T11:52:00+08:00", endpoint: "mac://kris.local", token: "boss-mac-studio-token", note: "本机 Codex 主节点 · 17600003315 已绑定", }, { id: "win-gpu-01", name: "Windows GPU", avatar: "W", account: "kris.plus.gpu", source: "demo", status: "abnormal", projects: ["Boss 移动控制台", "硬件审计协作"], quota5h: 31, quota7d: 46, lastSeenAt: "2026-03-25T11:40:00+08:00", endpoint: "win://gpu.local", token: "boss-win-gpu-token", note: "摄像头证据通道偶发抖动", }, { id: "cloud-backup", name: "Cloud Backup", avatar: "C", account: "kris.plus.backup", source: "demo", status: "offline", projects: ["Boss 移动控制台"], quota5h: 92, quota7d: 95, lastSeenAt: "2026-03-25T08:15:00+08:00", endpoint: "cloud://standby", token: "boss-cloud-backup-token", note: "standby 节点", }, ], projects: [ { id: "master-agent", name: "主 Agent", pinned: true, systemPinned: true, deviceIds: ["mac-studio"], preview: "已汇总 3 个项目,优先收尾 Boss 移动控制台里 must_finish_before_compaction 的线程。", updatedAt: "2026-03-25T12:06:00+08:00", lastMessageAt: "2026-03-25T12:06:00+08:00", isGroup: false, threadMeta: { projectId: "master-agent", threadId: "thread-master-main", threadDisplayName: "主 Agent 汇总", folderName: "主控线程", activityIconCount: 1, updatedAt: "2026-03-25T12:06:00+08:00", codexThreadRef: "thread-master-main", codexFolderRef: "master-agent", }, groupMembers: [], createdByAgent: true, collaborationMode: "development", approvalState: "not_required", unreadCount: 0, riskLevel: "medium", contextBudgetPct: 71, contextBudgetLabel: "71%", messages: [ { id: "master-summary", sender: "master", senderLabel: "主 Agent", body: "Boss 移动控制台存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。", sentAt: "2026-03-25T12:06:00+08:00", kind: "text", }, ], goals: [], versions: [], }, { id: "boss-console", name: "Boss 移动控制台", pinned: false, deviceIds: ["mac-studio"], preview: "登录、设备页、线程预算与设备绑定链路正在收口到 v13。", updatedAt: "2026-03-25T11:52:00+08:00", lastMessageAt: "2026-03-25T11:52:00+08:00", isGroup: false, threadMeta: { projectId: "boss-console", threadId: "thread-boss-ui", threadDisplayName: "北区试产线回归", folderName: "归档确认", activityIconCount: 1, updatedAt: "2026-03-25T11:52:00+08:00", codexThreadRef: "thread-boss-ui", codexFolderRef: "boss-console", }, groupMembers: [], createdByAgent: true, collaborationMode: "development", approvalState: "not_required", unreadCount: 2, riskLevel: "medium", contextBudgetPct: 62, contextBudgetLabel: "62%", messages: [ { id: "p1", sender: "master", senderLabel: "主 Agent", body: "项目目标页已切成可完成、可编辑、可记录完成时间的结构。", sentAt: "2026-03-25T11:40:00+08:00", kind: "text", }, { id: "p2", sender: "device", senderLabel: "Mac Studio / Codex", body: "登录、注册、忘记密码页已经补齐,并带验证码发送状态回显。", sentAt: "2026-03-25T11:48:00+08:00", kind: "text", }, ], goals: [ { id: "goal-1", text: "完成北区试产线全链路回归,覆盖串口、视觉、OTA 和容灾切换。", state: "completed", note: "已完成 · 09:12 由主 Agent 复核", completedAt: "2026-03-25T09:12:00+08:00", completedBy: "主 Agent", }, { id: "goal-2", text: "所有关键步骤必须留下可交接证据,禁止仅口头确认。", state: "pending", note: "进行中 · 允许用户编辑,主 Agent 会同步重排任务", }, { id: "goal-3", text: "当线程上下文余量进入 urgent 前,必须完成阶段摘要与 handoff。", state: "pending", note: "待处理 · 主 Agent 会优先把压缩前必须收尾的任务推到前面", }, ], versions: [ { version: "v1.3.0", summary: "登录页改为账号密码 / 验证码双模式,并新增 OTA 版本中心与本机最高管理员绑定。", createdAt: "2026-03-26T09:20:00+08:00", }, { version: "v1.2.8", summary: "补齐认证页、线程预算接口、设备绑定草稿与运维审计摘要。", createdAt: "2026-03-25T11:30:00+08:00", }, { version: "v1.2.7", summary: "会话页、设备页、我的页切到微信式一级导航。", createdAt: "2026-03-25T09:15:00+08:00", }, ], }, { id: "audit-collab", name: "硬件审计协作", pinned: false, deviceIds: ["mac-studio", "win-gpu-01"], preview: "Windows 端等待摄像头证据回传,总审计 Agent 正在复核。", updatedAt: "2026-03-25T10:58:00+08:00", lastMessageAt: "2026-03-25T10:58:00+08:00", isGroup: true, threadMeta: { projectId: "audit-collab", threadId: "thread-audit-chief", threadDisplayName: "审计对话", folderName: "审计群聊", activityIconCount: 2, updatedAt: "2026-03-25T10:58:00+08:00", codexThreadRef: "thread-audit-chief", codexFolderRef: "audit-collab", }, groupMembers: [ { projectId: "audit-collab", deviceId: "mac-studio", threadId: "thread-audit-chief", threadDisplayName: "审计对话", folderName: "审计群聊", }, { projectId: "audit-collab", deviceId: "win-gpu-01", threadId: "thread-audit-hardware", threadDisplayName: "Windows 摄像头证据", folderName: "审计群聊", }, ], createdByAgent: true, collaborationMode: "development", approvalState: "not_required", unreadCount: 1, riskLevel: "high", messages: [ { id: "a1", sender: "audit", senderLabel: "审计 Agent", body: "群聊式协作项目不在首页展示单一线程预算,详情页查看各线程风险。", sentAt: "2026-03-25T10:58:00+08:00", kind: "text", }, ], goals: [ { id: "goal-a1", text: "同步 Windows GPU 节点的摄像头证据和串口日志。", state: "pending", note: "待处理 · 由审计 Agent 汇总", }, ], versions: [ { version: "v0.9.3", summary: "接入跨设备线程对话、摄像头证据上传和 chief audit 汇总。", createdAt: "2026-03-24T18:10:00+08:00", }, ], }, ], verificationCodes: [], verificationDispatches: [], authAccounts: [ { id: "account-17600003315", account: PRIMARY_ADMIN_ACCOUNT, passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD), displayName: "Boss 超级管理员", role: "highest_admin", verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL, codexNodeId: PRIMARY_CODEX_NODE_ID, codexNodeLabel: PRIMARY_CODEX_NODE_LABEL, primaryDeviceId: PRIMARY_CODEX_NODE_ID, isPrimary: true, createdAt: "2026-03-25T09:00:00+08:00", updatedAt: "2026-03-26T09:00:00+08:00", }, ], authSessions: [], aiAccounts: [ { accountId: "master-codex-primary", label: "主 GPT", role: "primary", provider: "master_codex_node", displayName: "17600003315 · Master Codex Node", accountIdentifier: PRIMARY_ADMIN_ACCOUNT, nodeId: PRIMARY_CODEX_NODE_ID, nodeLabel: PRIMARY_CODEX_NODE_LABEL, enabled: true, isActive: true, status: "ready", loginStatusNote: "已绑定本机 Codex,可通过 local-agent relay 执行主 Agent 对话。", createdAt: "2026-03-26T09:00:00+08:00", updatedAt: "2026-03-26T09:00:00+08:00", lastSwitchedAt: "2026-03-26T09:00:00+08:00", switchReason: "默认主控身份预留位", }, { accountId: "master-codex-backup", label: "备用 GPT", role: "backup", provider: "master_codex_node", displayName: "备用 Master Codex Node", enabled: false, isActive: false, status: "disabled", loginStatusNote: "备用节点未启用。", createdAt: "2026-03-26T09:00:00+08:00", updatedAt: "2026-03-26T09:00:00+08:00", }, { accountId: "openai-api-fallback", label: "API 容灾", role: "api_fallback", provider: "openai_api", displayName: "OpenAI API", model: "gpt-5.4", enabled: true, isActive: false, status: "needs_api_key", loginStatusNote: "配置 OpenAI API Key 后,可直接为主 Agent 生成真实回复。", createdAt: "2026-03-26T09:00:00+08:00", updatedAt: "2026-03-26T09:00:00+08:00", }, ], aiAccountSwitchHistory: [ { switchId: "aiswitch-seed-primary", toAccountId: "master-codex-primary", toLabel: "主 GPT", role: "primary", switchedAt: "2026-03-26T09:00:00+08:00", reason: "初始化默认主控身份", }, ], userAttachmentStorageConfigs: [ { account: PRIMARY_ADMIN_ACCOUNT, mode: "server_file", updatedAt: nowIso(), }, ], masterAgentPromptPolicy: null, userMasterPrompts: [], masterAgentMemories: [], userProjectAgentControls: [], masterAgentTasks: [], dispatchPlans: [], dispatchExecutions: [], deviceImportDrafts: [], deviceImportResolutions: [], otaUpdates: [ { releaseId: "ota_140_to_141", version: "v2.0.0", currentVersion: "1.4.0", channel: "stable", packageType: "android_shell", status: "available", summary: ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"], targetScope: "Boss Android 原生客户端与 Web 控制台", requiredRole: "highest_admin", publishedAt: "2026-03-26T11:20:00+08:00", packageFileName: "boss-android-latest.apk", downloadUrl: "/api/v1/user/ota/package", }, ], otaUpdateLogs: [ { logId: "otalog_seed_130", releaseId: "ota_seed_check", version: "1.4.0", status: "checked", triggeredBy: "Boss 超级管理员", triggeredAt: "2026-03-26T09:05:00+08:00", note: "本机 Codex 已切到 17600003315,等待管理员决定是否升级到 v1.4.1。", }, ], deviceSkills: [], appLogs: [], threadContextSnapshots: [ { snapshotId: "snapshot-master-main", projectId: "master-agent", taskId: "task-master-summary", threadId: "thread-master-main", title: "主控汇总线程", summary: "正在整理 3 个项目状态、2 条未关闭告警和 1 个待交接线程。", nodeId: "mac-studio", workerId: "worker-mac-master", sourceKind: "worker_estimator", status: "running", contextBudgetRemainingPct: 71, contextBudgetLevel: "safe", compactionExpectedAt: "2026-03-25T13:20:00+08:00", mustFinishBeforeCompaction: false, estimatedRemainingTurns: 18, estimatedRemainingLargeMessages: 6, lastCompactionAt: "2026-03-25T09:48:00+08:00", compactionCount: 1, patchPending: false, testsPending: false, evidencePending: false, checklist: baseThreadChecklist(["持续刷新阶段摘要", "复核风险排序", "回写交接文档"]), capturedAt: "2026-03-25T12:06:00+08:00", }, { snapshotId: "snapshot-boss-ui", projectId: "boss-console", taskId: "task-ui-refine", threadId: "thread-boss-ui", title: "Boss UI 收口线程", summary: "会话页、设备页和我的页已稳定,剩设备绑定与运维摘要联调。", nodeId: "mac-studio", workerId: "worker-mac-ui", sourceKind: "worker_estimator", status: "running", contextBudgetRemainingPct: 62, contextBudgetLevel: "watch", compactionExpectedAt: "2026-03-25T14:05:00+08:00", mustFinishBeforeCompaction: false, estimatedRemainingTurns: 10, estimatedRemainingLargeMessages: 4, lastCompactionAt: "2026-03-25T10:28:00+08:00", compactionCount: 1, patchPending: true, testsPending: true, evidencePending: false, checklist: baseThreadChecklist(["设备绑定页接 API", "回归 lint/build", "回写 README"]), capturedAt: "2026-03-25T11:52:00+08:00", }, { snapshotId: "snapshot-boss-auth", projectId: "boss-console", taskId: "task-auth-delivery", threadId: "thread-boss-auth", title: "认证链路交接线程", summary: "登录/注册/忘记密码已完成,但验证码回显和文档收口必须先固化。", nodeId: "mac-studio", workerId: "worker-mac-auth", sourceKind: "worker_estimator", status: "handoff_pending", contextBudgetRemainingPct: 34, contextBudgetLevel: "urgent", compactionExpectedAt: "2026-03-25T12:24:00+08:00", mustFinishBeforeCompaction: true, estimatedRemainingTurns: 4, estimatedRemainingLargeMessages: 1, lastCompactionAt: "2026-03-25T11:14:00+08:00", compactionCount: 2, patchPending: true, testsPending: true, evidencePending: false, checklist: baseThreadChecklist(["固化 patch", "记录测试结果", "生成 handoff package"]), capturedAt: "2026-03-25T11:54:00+08:00", }, { snapshotId: "snapshot-audit-chief", projectId: "audit-collab", taskId: "task-chief-audit", threadId: "thread-audit-chief", title: "Chief Audit 汇总线程", summary: "总审计正在复核硬件证据、串口日志和运维修复票据。", nodeId: "mac-studio", workerId: "worker-chief-audit", sourceKind: "worker_estimator", status: "running", contextBudgetRemainingPct: 58, contextBudgetLevel: "watch", compactionExpectedAt: "2026-03-25T13:12:00+08:00", mustFinishBeforeCompaction: false, estimatedRemainingTurns: 9, estimatedRemainingLargeMessages: 3, lastCompactionAt: "2026-03-25T10:01:00+08:00", compactionCount: 1, patchPending: false, testsPending: false, evidencePending: true, checklist: baseThreadChecklist(["等待摄像头证据", "核对串口日志", "给出最终 verdict"]), capturedAt: "2026-03-25T10:58:00+08:00", }, { snapshotId: "snapshot-audit-hw", projectId: "audit-collab", taskId: "task-hardware-evidence", threadId: "thread-audit-hardware", title: "Windows 摄像头证据线程", summary: "摄像头关键帧仍缺 1 段,必须先收尾证据再继续扩上下文。", nodeId: "win-gpu-01", workerId: "worker-win-audit", sourceKind: "worker_estimator", status: "context_urgent", contextBudgetRemainingPct: 21, contextBudgetLevel: "critical", compactionExpectedAt: "2026-03-25T12:12:00+08:00", mustFinishBeforeCompaction: true, estimatedRemainingTurns: 2, estimatedRemainingLargeMessages: 1, lastCompactionAt: "2026-03-25T11:36:00+08:00", compactionCount: 3, patchPending: false, testsPending: false, evidencePending: true, checklist: baseThreadChecklist(["保存摄像头关键帧", "补录串口摘要", "通知 chief audit"]), capturedAt: "2026-03-25T11:58:00+08:00", }, ], threadHandoffPackages: [ { handoffPackageId: "handoff-boss-auth", projectId: "boss-console", taskId: "task-auth-delivery", fromThreadId: "thread-boss-auth", toThreadId: "thread-boss-auth-followup", packageStatus: "ready", summaryText: "认证页功能已齐,剩余文档、回归和部署验证待在新线程继续。", openQuestions: ["验证码直回显是否继续保留在公网环境", "是否把帮助页挂到登录页右上角"], criticalFiles: ["src/app/auth", "src/app/api/auth", "src/components/app-ui.tsx"], criticalCommands: ["npm run lint", "npm run build", "curl -sS http://127.0.0.1:3000/api/health"], criticalTests: ["登录验证码发送", "注册后登录", "忘记密码重置"], criticalArtifacts: ["docs/architecture/api_and_service_inventory_cn.md"], decisionLinks: ["boss-console:auth-mvp"], createdAt: "2026-03-25T11:49:00+08:00", readyAt: "2026-03-25T11:54:00+08:00", }, ], threadContextAlerts: [ { alertId: "alert-boss-auth", threadId: "thread-boss-auth", projectId: "boss-console", alertType: "context_urgent", alertStatus: "opened", openedAt: "2026-03-25T11:54:00+08:00", summary: "认证链路线程已进入 urgent,必须优先固化 patch、测试结论和 handoff。", masterActions: ["prepare_handoff", "avoid_large_context_append", "finalize_artifacts"], }, { alertId: "alert-audit-hw", threadId: "thread-audit-hardware", projectId: "audit-collab", alertType: "context_critical", alertStatus: "opened", openedAt: "2026-03-25T11:58:00+08:00", summary: "Windows 摄像头证据线程进入 critical,任何新增背景信息前先收尾证据。", masterActions: ["complete_wrapup", "handoff_required", "freeze_context_growth"], }, ], deviceEnrollments: [ { enrollmentId: "enroll-cloud-backup", deviceId: "cloud-backup", label: "Cloud Backup", pairingCode: "482913", token: "boss-cloud-backup-token", status: "ready", note: "standby 节点待重新连接", createdAt: "2026-03-25T10:40:00+08:00", expiresAt: "2026-03-25T18:40:00+08:00", }, ], opsFaults: [ { faultId: "fault-win-camera", faultKey: "OPS.LOCAL_AGENT.CAMERA_EVIDENCE.DELAYED", severity: "warning", status: "repairing", nodeId: "win-gpu-01", serviceName: "boss-local-agent", projectId: "audit-collab", threadRef: "thread-audit-hardware", traceId: "trace-audit-001", runbookId: "runbook-camera-sync", firstSeenAt: "2026-03-25T11:34:00+08:00", lastSeenAt: "2026-03-25T11:58:00+08:00", summary: "Windows 节点摄像头证据回传延迟,chief audit 正在等待关键帧。", suggestedNextAction: "优先重试本地 agent 证据上传,再做审计复验。", autoRepairable: true, }, { faultId: "fault-context-auth", faultKey: "THREAD.CONTEXT.HANDOFF.REQUIRED", severity: "critical", status: "opened", nodeId: "mac-studio", serviceName: "boss-web", projectId: "boss-console", threadRef: "thread-boss-auth", traceId: "trace-boss-auth-002", runbookId: "runbook-thread-handoff", firstSeenAt: "2026-03-25T11:50:00+08:00", lastSeenAt: "2026-03-25T11:54:00+08:00", summary: "认证线程预算降到 urgent,未完成 handoff 前不应继续追加长上下文。", suggestedNextAction: "执行压缩前收尾,写明关键文件、命令和测试结果。", autoRepairable: false, }, ], opsRepairTickets: [ { ticketId: "ticket-win-camera", faultId: "fault-win-camera", title: "重试 Windows 摄像头证据上传", approvalStatus: "approved", executionStatus: "running", requestedBy: "运维 Agent", approvedBy: "主 Agent", targetNodeId: "win-gpu-01", actionSummary: "重新触发本地 agent 心跳和摄像头证据上传。", resultSummary: "等待运维审计 Agent 复验中。", createdAt: "2026-03-25T11:42:00+08:00", updatedAt: "2026-03-25T11:57:00+08:00", }, ], opsRepairVerifications: [ { verificationId: "verify-win-camera", ticketId: "ticket-win-camera", verifier: "运维审计 Agent", status: "watching", summary: "主控在线,已允许修复动作,但仍需等待关键帧落库后才能关闭工单。", verifiedAt: "2026-03-25T11:58:00+08:00", }, ], auditRequests: [ { protocolVersion: "1.0", auditRequestId: "auditreq_001", projectId: "audit-collab", projectName: "硬件审计协作", taskId: "task-hardware-evidence", sourceThreadRef: "win-gpu-01:thread-audit-hardware", trigger: "failure_escalation", auditType: "multimodal", priority: 75, objective: "验证设备唤醒流程里的屏幕、LED、串口和摄像头证据是否一致。", systemContextSummary: "设备端已刷入固件 v0.9.2,当前 chief audit 等待 Windows 节点关键帧。", acceptanceCriteria: [ "LED 在 2 秒内变蓝", "屏幕切换到 listening 状态", "串口输出唤醒完成", "摄像头观测到机械动作完成", ], riskFocus: ["摄像头关键帧缺失", "串口时间线不一致"], evidenceRefs: ["evidence://serial-log/20260325-1"], artifactRefs: [], capabilityRequirements: [ { capabilityType: "camera", mode: "exclusive" }, { capabilityType: "serial_port", mode: "exclusive" }, ], timeBudgetSeconds: 300, responseMode: "structured_json", createdAt: "2026-03-25T11:36:00+08:00", metadataJson: { requestedBy: "chief-audit" }, }, ], auditResults: [ { auditRequestId: "auditreq_001", auditType: "multimodal", status: "needs_human_review", decision: "warning", confidence: 0.72, summary: "串口链路通过,但摄像头关键帧仍缺最后一段,暂不允许关闭工单。", findings: ["LED 与串口时间线一致", "摄像头关键帧缺失最后 2 秒"], requiredActions: ["补录关键帧", "回放 chief audit 证据摘要"], usedCapabilities: ["camera", "serial_port"], artifactRefs: ["artifact://camera/keyframe-20260325-1"], timeline: ["11:36 接单", "11:44 拉串口", "11:58 等待摄像头关键帧"], durationMs: 1_320_000, completedAt: "2026-03-25T11:58:00+08:00", }, ], capabilities: [ { capabilityId: "cap-camera-win", providerId: "provider-win-gpu", nodeId: "win-gpu-01", capabilityType: "camera", displayName: "Windows 审计摄像头", status: "busy", healthStatus: "warning", leaseMode: "exclusive", preemptible: true, supportedActions: ["snapshot", "record_clip", "start_stream", "stop_stream"], evidenceModes: ["image", "video_clip"], }, { capabilityId: "cap-serial-win", providerId: "provider-win-gpu", nodeId: "win-gpu-01", capabilityType: "serial_port", displayName: "串口日志采集", status: "online", healthStatus: "healthy", leaseMode: "exclusive", preemptible: false, supportedActions: ["open", "read", "write", "capture_log"], evidenceModes: ["serial_log"], }, ], }; const levelPriority: Record = { critical: 0, urgent: 1, watch: 2, safe: 3, }; function cloneInitialState() { return JSON.parse(JSON.stringify(initialState)) as BossState; } function nowIso() { return new Date().toISOString(); } function normalizeThreadMeta( raw: Partial | undefined, project: { id: string; name: string; isGroup: boolean; updatedAt: string }, fallback?: ThreadConversationMeta, ): ThreadConversationMeta { return { projectId: raw?.projectId ?? project.id, threadId: raw?.threadId ?? `thread-${project.id}`, threadDisplayName: raw?.threadDisplayName ?? project.name, folderName: raw?.folderName ?? fallback?.folderName ?? (project.isGroup ? "群聊" : project.name), activityIconCount: Math.max(1, raw?.activityIconCount ?? fallback?.activityIconCount ?? (project.isGroup ? 2 : 1)), updatedAt: raw?.updatedAt ?? project.updatedAt ?? nowIso(), codexThreadRef: raw?.codexThreadRef, codexFolderRef: raw?.codexFolderRef, }; } function normalizeGroupMember( raw: Partial, fallbackProjectId: string, fallbackThreadMeta: ThreadConversationMeta, ): GroupConversationMember { return { projectId: raw.projectId ?? fallbackProjectId, deviceId: raw.deviceId ?? "", threadId: raw.threadId ?? fallbackThreadMeta.threadId, threadDisplayName: raw.threadDisplayName ?? fallbackThreadMeta.threadDisplayName, folderName: raw.folderName ?? fallbackThreadMeta.folderName, }; } function trimToDefined(value?: string) { const trimmed = value?.trim(); return trimmed ? trimmed : undefined; } function parseControlTextOverride(value: unknown) { if (value === undefined || value === null) { return { kind: "clear" as const }; } if (typeof value !== "string") { return { kind: "invalid" as const }; } const trimmed = value.trim(); return trimmed ? { kind: "set" as const, value: trimmed } : { kind: "clear" as const }; } function parseReasoningEffortOverride(value: unknown) { if (value === undefined || value === null) { return { kind: "clear" as const }; } if (!isReasoningEffort(value)) { return { kind: "invalid" as const }; } return { kind: "set" as const, value }; } function normalizeStringSet(values: string[]) { return dedupeStrings(values.map((value) => value.trim()).filter(Boolean)).sort((a, b) => a.localeCompare(b)); } function sameStringSet(a: string[] | undefined, b: string[] | undefined) { const left = normalizeStringSet(a ?? []); const right = normalizeStringSet(b ?? []); return left.length === right.length && left.every((value, index) => value === right[index]); } function dispatchPlanTargetSignature(target: DispatchPlanTarget) { return [ target.projectId, target.deviceId, target.threadId, target.threadDisplayName, target.folderName, target.codexFolderRef ?? "", target.codexThreadRef ?? "", target.reason, ].join("\u001f"); } function sameDispatchPlanTargets(a: DispatchPlanTarget[], b: DispatchPlanTarget[]) { return sameStringSet( a.map((target) => dispatchPlanTargetSignature(target)), b.map((target) => dispatchPlanTargetSignature(target)), ); } function normalizeDispatchPlanTargetsForCreate( state: BossState, targets: DispatchPlanTarget[], ) { if (targets.length === 0) { throw new Error("DISPATCH_PLAN_TARGETS_REQUIRED"); } const validatedTargets = targets.map((target) => validateDispatchTargetAgainstState(state, normalizedDispatchPlanTarget(target, undefined, { allowInvalid: false }) as DispatchPlanTarget), ); const uniqueProjectIds = normalizeStringSet(validatedTargets.map((target) => target.projectId)); if (uniqueProjectIds.length !== validatedTargets.length) { throw new Error("DISPATCH_PLAN_TARGET_DUPLICATE"); } return validatedTargets; } function normalizedDispatchPlanTarget( raw: Partial, fallback?: DispatchPlanTarget, options?: { allowInvalid?: boolean }, ): DispatchPlanTarget | null { const deviceId = trimToDefined(raw.deviceId ?? fallback?.deviceId); const projectId = trimToDefined(raw.projectId ?? fallback?.projectId); const threadId = trimToDefined(raw.threadId ?? fallback?.threadId); const threadDisplayName = trimToDefined(raw.threadDisplayName ?? fallback?.threadDisplayName); const folderName = trimToDefined(raw.folderName ?? fallback?.folderName); const codexFolderRef = trimToDefined(raw.codexFolderRef ?? fallback?.codexFolderRef); const codexThreadRef = trimToDefined(raw.codexThreadRef ?? fallback?.codexThreadRef); const reason = trimToDefined(raw.reason ?? fallback?.reason); if ( !deviceId || !projectId || !threadId || !threadDisplayName || !folderName || !reason ) { if (options?.allowInvalid) return null; throw new Error("DISPATCH_PLAN_TARGET_INVALID"); } return { deviceId, projectId, threadId, threadDisplayName, folderName, codexFolderRef, codexThreadRef, reason, }; } function activeSessionForAccount(state: BossState, account: string) { return ( state.authSessions.find( (session) => session.account === account && !session.revokedAt && new Date(session.expiresAt).getTime() > Date.now(), ) ?? null ); } function requireDispatchActorSession(state: BossState, account: string) { const normalizedAccount = account.trim(); if (!normalizedAccount) { throw new Error("DISPATCH_ACTOR_ACCOUNT_REQUIRED"); } const authAccount = state.authAccounts.find((item) => item.account === normalizedAccount); if (!authAccount) { throw new Error("DISPATCH_ACTOR_ACCOUNT_NOT_FOUND"); } const session = activeSessionForAccount(state, normalizedAccount); if (!session) { throw new Error("DISPATCH_ACTOR_SESSION_REQUIRED"); } return { authAccount, session }; } function validateDispatchTargetAgainstState( state: BossState, target: DispatchPlanTarget, ): DispatchPlanTarget { const project = state.projects.find((item) => item.id === target.projectId); if (!project) { throw new Error("DISPATCH_TARGET_PROJECT_NOT_FOUND"); } const device = state.devices.find((item) => item.id === target.deviceId); if (!device || device.source !== "production") { throw new Error("DISPATCH_TARGET_DEVICE_INVALID"); } if (!project.deviceIds.includes(device.id)) { throw new Error("DISPATCH_TARGET_DEVICE_PROJECT_MISMATCH"); } const matchingGroupMember = project.groupMembers.find( (member) => member.deviceId === device.id && member.threadId === target.threadId, ); if (project.isGroup) { if (!matchingGroupMember) { throw new Error("DISPATCH_TARGET_THREAD_MISMATCH"); } if (matchingGroupMember.threadDisplayName !== target.threadDisplayName) { throw new Error("DISPATCH_TARGET_THREAD_DISPLAY_NAME_MISMATCH"); } if (matchingGroupMember.folderName !== target.folderName) { throw new Error("DISPATCH_TARGET_FOLDER_NAME_MISMATCH"); } } else { if (project.threadMeta.threadId !== target.threadId) { throw new Error("DISPATCH_TARGET_THREAD_MISMATCH"); } if (project.threadMeta.threadDisplayName !== target.threadDisplayName) { throw new Error("DISPATCH_TARGET_THREAD_DISPLAY_NAME_MISMATCH"); } if (project.threadMeta.folderName !== target.folderName) { throw new Error("DISPATCH_TARGET_FOLDER_NAME_MISMATCH"); } } if (target.codexThreadRef && project.threadMeta.codexThreadRef && target.codexThreadRef !== project.threadMeta.codexThreadRef) { throw new Error("DISPATCH_TARGET_CODEX_THREAD_MISMATCH"); } if (target.codexFolderRef && project.threadMeta.codexFolderRef && target.codexFolderRef !== project.threadMeta.codexFolderRef) { throw new Error("DISPATCH_TARGET_CODEX_FOLDER_MISMATCH"); } return target; } function normalizeDispatchPlan(raw: Partial, fallback?: DispatchPlan): DispatchPlan { const fallbackTargets = fallback?.targets ?? []; const targets = ensureArray(raw.targets as Partial[] | undefined, fallbackTargets) .map((target, index) => normalizedDispatchPlanTarget( target, fallbackTargets[index % Math.max(1, fallbackTargets.length)], { allowInvalid: true }, ), ) .filter((target): target is DispatchPlanTarget => Boolean(target)); return { planId: raw.planId ?? fallback?.planId ?? randomToken("dispatch-plan"), groupProjectId: raw.groupProjectId ?? fallback?.groupProjectId ?? "", requestMessageId: raw.requestMessageId ?? fallback?.requestMessageId ?? "", requestedBy: raw.requestedBy ?? fallback?.requestedBy ?? "", status: raw.status ?? fallback?.status ?? "pending_user_confirmation", targets, summary: raw.summary ?? fallback?.summary ?? "", createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(), confirmedAt: raw.confirmedAt ?? fallback?.confirmedAt, confirmedBy: raw.confirmedBy ?? fallback?.confirmedBy, confirmedTargetProjectIds: (() => { const values = normalizeStringSet( ensureArray(raw.confirmedTargetProjectIds, fallback?.confirmedTargetProjectIds ?? []), ); return values.length > 0 ? values : undefined; })(), }; } function normalizeDispatchExecution( raw: Partial, fallback?: DispatchExecution, ): DispatchExecution { return { executionId: raw.executionId ?? fallback?.executionId ?? randomToken("dispatch-exec"), planId: raw.planId ?? fallback?.planId ?? "", groupProjectId: raw.groupProjectId ?? fallback?.groupProjectId ?? "", targetProjectId: raw.targetProjectId ?? fallback?.targetProjectId ?? "", targetThreadId: raw.targetThreadId ?? fallback?.targetThreadId ?? "", deviceId: raw.deviceId ?? fallback?.deviceId ?? "", status: raw.status ?? fallback?.status ?? "queued", createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(), completedAt: raw.completedAt ?? fallback?.completedAt, resultMessageId: raw.resultMessageId ?? fallback?.resultMessageId, completedByDeviceId: raw.completedByDeviceId ?? fallback?.completedByDeviceId, }; } function buildDeviceImportCandidateId(input: { deviceId: string; folderName: string; threadId: string; codexFolderRef?: string; codexThreadRef?: string; }) { const signature = [ input.deviceId, input.codexFolderRef?.trim() || input.folderName.trim(), input.codexThreadRef?.trim() || input.threadId.trim(), ] .filter(Boolean) .join("-"); return `import-${slugify(signature)}`; } function normalizeDeviceImportCandidate( raw: Partial, fallback?: DeviceImportCandidate, ): DeviceImportCandidate { const deviceId = raw.deviceId ?? fallback?.deviceId ?? ""; const folderName = raw.folderName ?? fallback?.folderName ?? ""; const threadId = raw.threadId ?? fallback?.threadId ?? ""; return { candidateId: raw.candidateId ?? fallback?.candidateId ?? buildDeviceImportCandidateId({ deviceId, folderName, threadId, codexFolderRef: raw.codexFolderRef ?? fallback?.codexFolderRef, codexThreadRef: raw.codexThreadRef ?? fallback?.codexThreadRef, }), deviceId, folderName, folderRef: raw.folderRef ?? fallback?.folderRef, threadId, threadDisplayName: raw.threadDisplayName ?? fallback?.threadDisplayName ?? threadId, codexFolderRef: raw.codexFolderRef ?? fallback?.codexFolderRef, codexThreadRef: raw.codexThreadRef ?? fallback?.codexThreadRef, lastActiveAt: raw.lastActiveAt ?? fallback?.lastActiveAt ?? nowIso(), suggestedImport: raw.suggestedImport ?? fallback?.suggestedImport ?? true, }; } function normalizeDeviceImportDraft( raw: Partial, fallback?: DeviceImportDraft, ): DeviceImportDraft { const fallbackCandidates = fallback?.candidates ?? []; return { draftId: raw.draftId ?? fallback?.draftId ?? randomToken("import-draft"), deviceId: raw.deviceId ?? fallback?.deviceId ?? "", enrollmentId: raw.enrollmentId ?? fallback?.enrollmentId, status: raw.status ?? fallback?.status ?? "pending_candidates", candidates: ensureArray( raw.candidates as Partial[] | undefined, fallbackCandidates, ).map((candidate, index) => normalizeDeviceImportCandidate( candidate, fallbackCandidates[index % Math.max(1, fallbackCandidates.length)], ), ), selectedCandidateIds: dedupeStrings( ensureArray(raw.selectedCandidateIds, fallback?.selectedCandidateIds ?? []), ), appliedProjectNames: dedupeStrings( ensureArray(raw.appliedProjectNames, fallback?.appliedProjectNames ?? []), ), createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(), updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(), reviewedAt: raw.reviewedAt ?? fallback?.reviewedAt, reviewedBy: raw.reviewedBy ?? fallback?.reviewedBy, resolutionId: raw.resolutionId ?? fallback?.resolutionId, }; } function normalizeDeviceImportResolution( raw: Partial, fallback?: DeviceImportResolution, ): DeviceImportResolution { const fallbackItems = fallback?.items ?? []; return { resolutionId: raw.resolutionId ?? fallback?.resolutionId ?? randomToken("import-resolution"), draftId: raw.draftId ?? fallback?.draftId ?? "", deviceId: raw.deviceId ?? fallback?.deviceId ?? "", status: raw.status ?? fallback?.status ?? "ready", summary: raw.summary ?? fallback?.summary ?? "", items: ensureArray( raw.items as Partial[] | undefined, fallbackItems, ).map((item, index) => ({ candidateId: item.candidateId ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.candidateId ?? "", action: item.action ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.action ?? "skip", threadDisplayName: item.threadDisplayName ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.threadDisplayName ?? "", folderName: item.folderName ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.folderName ?? "", targetProjectId: item.targetProjectId ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.targetProjectId, reason: item.reason ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.reason ?? "", })), createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(), appliedAt: raw.appliedAt ?? fallback?.appliedAt, appliedBy: raw.appliedBy ?? fallback?.appliedBy, }; } function dedupeStrings(values: string[]) { return [...new Set(values.filter((value) => Boolean(value)))]; } function dedupeGroupMembers(members: GroupConversationMember[]) { const seen = new Set(); const deduped: GroupConversationMember[] = []; for (const member of members) { const key = `${member.projectId}:${member.deviceId}:${member.threadId}`; if (seen.has(key)) continue; seen.add(key); deduped.push(member); } return deduped; } function buildLegacyGroupMembers( projectId: string, deviceIds: string[], threadMeta: ThreadConversationMeta, ) { return dedupeStrings(deviceIds).map((deviceId, index) => ({ projectId, deviceId, threadId: index === 0 ? threadMeta.threadId : `${threadMeta.threadId}:${slugify(deviceId)}`, threadDisplayName: threadMeta.threadDisplayName, folderName: threadMeta.folderName, })); } function normalizeProjectConversationShape( project: Project, options?: { allowedDeviceIds?: Set; }, ) { const allowedDeviceIds = options?.allowedDeviceIds; const normalizedThreadMeta = { ...project.threadMeta, projectId: project.id, }; const normalizedExplicitMembers = dedupeGroupMembers( project.groupMembers.map((member) => normalizeGroupMember(member, project.id, normalizedThreadMeta), ), ); const hasExplicitGroupMembers = normalizedExplicitMembers.length > 0; const legacyGroupRequested = !hasExplicitGroupMembers && project.isGroup; const resolvedGroupMembers = hasExplicitGroupMembers ? normalizedExplicitMembers : legacyGroupRequested ? buildLegacyGroupMembers(project.id, project.deviceIds, normalizedThreadMeta) : []; const filteredGroupMembers = allowedDeviceIds ? resolvedGroupMembers.filter((member) => allowedDeviceIds.has(member.deviceId)) : resolvedGroupMembers; if (filteredGroupMembers.length > 0) { project.isGroup = true; project.groupMembers = dedupeGroupMembers(filteredGroupMembers); project.deviceIds = dedupeStrings(project.groupMembers.map((member) => member.deviceId)); project.threadMeta = { ...normalizedThreadMeta, activityIconCount: Math.max(1, project.groupMembers.length), }; return project; } project.isGroup = false; project.groupMembers = []; project.deviceIds = allowedDeviceIds ? project.deviceIds.filter((deviceId) => allowedDeviceIds.has(deviceId)) : project.deviceIds; project.threadMeta = { ...normalizedThreadMeta, activityIconCount: Math.max(1, normalizedThreadMeta.activityIconCount ?? 1), }; return project; } function normalizeProjectAgentControls( raw: Partial | undefined, ): ProjectAgentControls | undefined { const modelOverride = trimToDefined(raw?.modelOverride); const reasoningEffortOverride = isReasoningEffort(raw?.reasoningEffortOverride) ? raw.reasoningEffortOverride : undefined; const promptOverride = trimToDefined(raw?.promptOverride); if (!modelOverride && !reasoningEffortOverride && !promptOverride) { return undefined; } return { modelOverride, reasoningEffortOverride, promptOverride, updatedAt: raw?.updatedAt ?? nowIso(), }; } function isReasoningEffort(value: unknown): value is ReasoningEffort { return value === "low" || value === "medium" || value === "high"; } function resolveProjectUpdatedAt(project: Pick, latestActivityAt?: string) { return latestIsoTimestamp( project.updatedAt, project.lastMessageAt, project.threadMeta.updatedAt, latestActivityAt, ); } function latestIsoTimestamp(...values: Array) { let latestValue: string | undefined; let latestTime = 0; for (const value of values) { const valueTime = messageTimeValue(value); if (valueTime > latestTime) { latestTime = valueTime; latestValue = value; } } return latestValue ?? nowIso(); } function ensureArray(value: T[] | undefined, fallback: T[]) { return Array.isArray(value) ? value : fallback; } function deriveLevelFromPercent(percent: number): ContextBudgetLevel { if (percent < 25) return "critical"; if (percent < 40) return "urgent"; if (percent < 60) return "watch"; return "safe"; } function deriveRiskFromSnapshots(snapshots: ThreadContextSnapshot[]) { const top = [...snapshots].sort(compareSnapshotsForRisk)[0]; if (!top) return "low" as RiskLevel; if (top.contextBudgetLevel === "critical") return "high" as RiskLevel; if (top.contextBudgetLevel === "urgent" || top.mustFinishBeforeCompaction) { return "high" as RiskLevel; } if (top.contextBudgetLevel === "watch") return "medium" as RiskLevel; return "low" as RiskLevel; } function compareSnapshotsForRisk(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]; } if ((a.compactionExpectedAt ?? "") !== (b.compactionExpectedAt ?? "")) { return (a.compactionExpectedAt ?? "").localeCompare(b.compactionExpectedAt ?? ""); } return (b.capturedAt ?? "").localeCompare(a.capturedAt ?? ""); } function messageTimeValue(value?: string) { if (!value) return 0; const parsed = Date.parse(value); return Number.isNaN(parsed) ? 0 : parsed; } function randomToken(prefix: string) { return `${prefix}-${randomBytes(4).toString("hex")}`; } function randomDigits(length: number) { return Array.from({ length }, () => Math.floor(Math.random() * 10)).join(""); } function slugify(value: string) { return value .toLowerCase() .replace(/[^a-z0-9\u4e00-\u9fa5]+/gi, "-") .replace(/^-+|-+$/g, "") .slice(0, 48) || `item-${randomBytes(2).toString("hex")}`; } const aiRolePriority: Record = { primary: 0, backup: 1, api_fallback: 2, }; export function aiRoleLabel(role: AiAccountRole) { switch (role) { case "primary": return "主 GPT"; case "backup": return "备用 GPT"; case "api_fallback": return "API 容灾"; default: return role; } } export function aiProviderLabel(provider: AiProvider) { switch (provider) { case "master_codex_node": return "Master Codex Node / ChatGPT Plus 节点"; case "openai_api": return "OpenAI API"; case "aliyun_qwen_api": return "阿里百炼 Qwen"; default: return provider; } } export 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 maskApiKey(value?: string) { if (!value?.trim()) return undefined; const trimmed = value.trim(); if (trimmed.length <= 8) return "已配置"; return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`; } function isApiKeyProvider(provider: AiProvider) { return provider === "openai_api" || provider === "aliyun_qwen_api"; } function deriveAiAccountStatus(account: AiAccount): AiAccountStatus { if (!account.enabled) return "disabled"; if (isApiKeyProvider(account.provider)) { if (!account.apiKey?.trim()) return "needs_api_key"; return account.status === "disabled" ? "ready" : account.status; } return account.status === "disabled" ? "needs_login" : account.status; } function aiAccountCanGenerate(account: AiAccount) { if (!account.enabled) return false; if (account.provider === "master_codex_node") { return Boolean(account.nodeId?.trim()); } return Boolean(account.apiKey?.trim()); } function sortAiAccounts(accounts: AiAccount[]) { return [...accounts].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 ?? ""); }); } function normalizeAiAccount(account: AiAccount) { const apiKeyMasked = account.apiKeyMasked ?? maskApiKey(account.apiKey); const status = deriveAiAccountStatus({ ...account, apiKeyMasked, }); return { ...account, apiKeyMasked, status, } satisfies AiAccount; } function buildAiAccountSummary(account: AiAccount, options?: { isEnvironmentFallback?: boolean }) { const normalized = normalizeAiAccount(account); return { accountId: normalized.accountId, label: normalized.label, role: normalized.role, roleLabel: aiRoleLabel(normalized.role), provider: normalized.provider, providerLabel: aiProviderLabel(normalized.provider), displayName: normalized.displayName, accountIdentifier: normalized.accountIdentifier, nodeId: normalized.nodeId, nodeLabel: normalized.nodeLabel, model: normalized.model, enabled: normalized.enabled, isActive: normalized.isActive, canGenerate: aiAccountCanGenerate(normalized), status: normalized.status, statusLabel: aiStatusLabel(normalized.status), loginStatusNote: normalized.loginStatusNote, apiKeyConfigured: Boolean(normalized.apiKey?.trim()), apiKeyMasked: normalized.apiKeyMasked, lastValidatedAt: normalized.lastValidatedAt, lastUsedAt: normalized.lastUsedAt, lastError: normalized.lastError, lastSwitchedAt: normalized.lastSwitchedAt, switchReason: normalized.switchReason, createdAt: normalized.createdAt, updatedAt: normalized.updatedAt, isEnvironmentFallback: options?.isEnvironmentFallback ?? false, } satisfies AiAccountSummary; } function getEnvOpenAiAccount() { const apiKey = process.env.OPENAI_API_KEY?.trim(); if (!apiKey) return null; const createdAt = nowIso(); return normalizeAiAccount({ accountId: ENV_OPENAI_ACCOUNT_ID, label: "API 容灾", role: "api_fallback", provider: "openai_api", displayName: "环境变量 OpenAI API", model: process.env.OPENAI_MODEL?.trim() || "gpt-5.4", apiKey, apiKeyMasked: maskApiKey(apiKey), enabled: true, isActive: false, status: "ready", loginStatusNote: "来自服务器环境变量 OPENAI_API_KEY。", createdAt, updatedAt: createdAt, } satisfies AiAccount); } function resolveActiveAiAccount(state: BossState) { const normalized = sortAiAccounts(state.aiAccounts.map(normalizeAiAccount)); const activeConfigured = normalized.find((account) => account.isActive); const runnable = normalized.find(aiAccountCanGenerate); const envAccount = getEnvOpenAiAccount(); if (activeConfigured && activeConfigured.enabled) { return { account: activeConfigured, isEnvironmentFallback: false }; } if (runnable) { return { account: runnable, isEnvironmentFallback: false }; } if (envAccount) { return { account: envAccount, isEnvironmentFallback: true }; } return { account: activeConfigured ?? normalized[0] ?? envAccount ?? null, isEnvironmentFallback: false, }; } export function getAiAccountSummariesFromState(state: BossState) { const accounts = sortAiAccounts(state.aiAccounts.map(normalizeAiAccount)).map((account) => buildAiAccountSummary(account), ); const envAccount = getEnvOpenAiAccount(); if (envAccount) { accounts.push(buildAiAccountSummary(envAccount, { isEnvironmentFallback: true })); } return sortAiAccounts( accounts.map((account) => ({ accountId: account.accountId, label: account.label, role: account.role, provider: account.provider, displayName: account.displayName, accountIdentifier: account.accountIdentifier, nodeId: account.nodeId, nodeLabel: account.nodeLabel, model: account.model, enabled: account.enabled, isActive: account.isActive, status: account.status, loginStatusNote: account.loginStatusNote, apiKeyMasked: account.apiKeyMasked, createdAt: account.createdAt, updatedAt: account.updatedAt, lastValidatedAt: account.lastValidatedAt, lastUsedAt: account.lastUsedAt, lastError: account.lastError, lastSwitchedAt: account.lastSwitchedAt, switchReason: account.switchReason, apiKey: account.apiKeyConfigured ? "configured" : "", } as AiAccount)), ).map((account) => buildAiAccountSummary( account.accountId === ENV_OPENAI_ACCOUNT_ID ? (envAccount ?? account) : account, { isEnvironmentFallback: account.accountId === ENV_OPENAI_ACCOUNT_ID }, ), ); } export function getMasterIdentitySummaryFromState(state: BossState): MasterIdentitySummary { const resolved = resolveActiveAiAccount(state); if (!resolved.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 账号”至少配置一个可用的 Master Codex Node、OpenAI API 或阿里百炼 Qwen 账号。", }; } const summary = buildAiAccountSummary(resolved.account, { isEnvironmentFallback: resolved.isEnvironmentFallback, }); return { accountId: summary.accountId, label: summary.label, role: summary.role, roleLabel: summary.roleLabel, provider: summary.provider, providerLabel: summary.providerLabel, displayName: summary.displayName, nodeLabel: summary.nodeLabel, model: summary.model, status: summary.status, statusLabel: summary.statusLabel, canGenerate: summary.canGenerate, switchReason: summary.switchReason, lastSwitchedAt: summary.lastSwitchedAt, note: summary.loginStatusNote ?? (summary.canGenerate ? "当前账号可直接生成主 Agent 回复。" : "当前账号只完成了身份占位,还没有接通真实生成能力。"), isEnvironmentFallback: summary.isEnvironmentFallback, }; } function normalizeMessage(raw: Partial): Message { return { id: raw.id ?? randomToken("msg"), sender: raw.sender ?? "master", senderLabel: raw.senderLabel ?? "主 Agent", body: raw.body ?? "", sentAt: raw.sentAt ?? nowIso(), kind: raw.kind ?? "text", attachments: Array.isArray(raw.attachments) ? raw.attachments.map((attachment) => normalizeMessageAttachment(attachment)) : undefined, forwardSource: raw.forwardSource, forwardBundle: raw.forwardBundle, }; } function normalizeMessageAttachment(raw: Partial): MessageAttachment { return { attachmentId: raw.attachmentId ?? randomToken("att"), fileName: raw.fileName ?? "", mimeType: raw.mimeType ?? "application/octet-stream", fileSizeBytes: raw.fileSizeBytes ?? 0, attachmentKind: raw.attachmentKind ?? "binary", storageBackend: raw.storageBackend ?? "server_file", storagePath: raw.storagePath ?? "", storageSnapshot: raw.storageSnapshot?.provider === "aliyun_oss" ? { provider: "aliyun_oss", accessKeyId: raw.storageSnapshot.accessKeyId ?? "", accessKeySecretEncrypted: raw.storageSnapshot.accessKeySecretEncrypted ?? "", bucket: raw.storageSnapshot.bucket ?? "", endpoint: raw.storageSnapshot.endpoint ?? "", region: raw.storageSnapshot.region ?? "", prefix: raw.storageSnapshot.prefix, } : undefined, previewAvailable: raw.previewAvailable ?? false, uploadedAt: raw.uploadedAt ?? nowIso(), uploadedBy: raw.uploadedBy ?? "system", analysisState: raw.analysisState ?? "not_applicable", analysisSummary: raw.analysisSummary, analysisCardId: raw.analysisCardId, }; } function buildAttachmentMessageBody(attachment: MessageAttachment) { const sizeKb = Math.max(1, Math.round(attachment.fileSizeBytes / 1024)); return `已发送附件:${attachment.fileName}(${attachment.attachmentKind},${sizeKb} KB)`; } function normalizeAttachmentStorageConfig( raw: Partial, fallback: UserAttachmentStorageConfig, ): UserAttachmentStorageConfig { return { account: raw.account ?? fallback.account, mode: raw.mode ?? fallback.mode, ossProvider: raw.ossProvider ?? fallback.ossProvider, aliyunOss: raw.aliyunOss ? { enabled: raw.aliyunOss.enabled ?? fallback.aliyunOss?.enabled ?? false, accessKeyId: raw.aliyunOss.accessKeyId ?? fallback.aliyunOss?.accessKeyId ?? "", accessKeySecretEncrypted: raw.aliyunOss.accessKeySecretEncrypted ?? fallback.aliyunOss?.accessKeySecretEncrypted ?? "", bucket: raw.aliyunOss.bucket ?? fallback.aliyunOss?.bucket ?? "", endpoint: raw.aliyunOss.endpoint ?? fallback.aliyunOss?.endpoint ?? "", region: raw.aliyunOss.region ?? fallback.aliyunOss?.region ?? "", prefix: raw.aliyunOss.prefix ?? fallback.aliyunOss?.prefix, } : fallback.aliyunOss, updatedAt: raw.updatedAt ?? fallback.updatedAt, validatedAt: raw.validatedAt ?? fallback.validatedAt, }; } function normalizeMasterAgentPromptPolicy( raw: Partial | null | undefined, fallback?: MasterAgentPromptPolicy | null, ): MasterAgentPromptPolicy | null { if (!raw) { return fallback ?? null; } const globalPrompt = raw.globalPrompt?.trim(); if (!globalPrompt) { return fallback ?? null; } return { globalPrompt, updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(), updatedBy: raw.updatedBy?.trim() || fallback?.updatedBy, }; } function normalizeUserMasterPrompt( raw: Partial, fallback?: UserMasterPrompt, ): UserMasterPrompt { const account = raw.account ?? fallback?.account ?? ""; return { account, content: raw.content?.trim() ?? fallback?.content ?? "", updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(), }; } function normalizeUserProjectAgentControls( raw: Partial, fallback?: UserProjectAgentControls, ): UserProjectAgentControls | null { const account = trimToDefined(raw.account) ?? trimToDefined(fallback?.account); const projectId = trimToDefined(raw.projectId) ?? trimToDefined(fallback?.projectId); const controls = normalizeProjectAgentControls(raw.controls ?? fallback?.controls); if (!account || !projectId || !controls) { return null; } return { account, projectId, controls, }; } function normalizeMasterMemoryTags(values: string[] | undefined) { return dedupeStrings( (values ?? []) .map((value) => value.trim()) .filter((value) => Boolean(value)), ); } function normalizeUserMasterMemory( raw: Partial, fallback?: MasterAgentMemory, ): MasterAgentMemory { const scope = raw.scope ?? fallback?.scope ?? "global"; const projectId = scope === "project" ? raw.projectId ?? fallback?.projectId : undefined; return { memoryId: raw.memoryId ?? fallback?.memoryId ?? randomToken("memory"), account: raw.account ?? fallback?.account ?? "", scope, projectId, title: raw.title?.trim() ?? fallback?.title ?? "", content: raw.content?.trim() ?? fallback?.content ?? "", memoryType: raw.memoryType ?? fallback?.memoryType ?? "user_preference", tags: normalizeMasterMemoryTags(raw.tags ?? fallback?.tags ?? []), sourceMessageId: raw.sourceMessageId ?? fallback?.sourceMessageId, createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(), updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(), lastUsedAt: raw.lastUsedAt ?? fallback?.lastUsedAt, archived: raw.archived ?? fallback?.archived ?? false, }; } function normalizeProject(raw: Partial, fallback?: Project): Project { const base = fallback ?? cloneInitialState().projects[0]; const projectId = raw.id ?? base.id; const projectName = raw.name ?? base.name; const projectUpdatedAt = latestIsoTimestamp(raw.updatedAt, raw.lastMessageAt, base.updatedAt, base.lastMessageAt); const threadMetaFallback = fallback?.id === projectId ? fallback.threadMeta : undefined; const threadMeta = normalizeThreadMeta(raw.threadMeta, { id: projectId, name: projectName, isGroup: raw.isGroup ?? base.isGroup ?? false, updatedAt: projectUpdatedAt, }, threadMetaFallback); const project: Project = { ...base, ...raw, pinned: raw.pinned ?? base.pinned, systemPinned: raw.systemPinned ?? base.systemPinned, deviceIds: ensureArray(raw.deviceIds, base.deviceIds), unreadCount: typeof raw.unreadCount === "number" ? raw.unreadCount : base.unreadCount ?? 0, riskLevel: raw.riskLevel ?? base.riskLevel ?? "low", messages: ensureArray(raw.messages, base.messages).map(normalizeMessage), goals: ensureArray(raw.goals, base.goals).map((goal) => ({ id: goal.id ?? randomToken("goal"), text: goal.text ?? "", state: goal.state ?? "pending", note: goal.note ?? "", completedAt: goal.completedAt, completedBy: goal.completedBy, })), versions: ensureArray(raw.versions, base.versions).map((version) => ({ version: version.version ?? "v0.0.0", summary: version.summary ?? "", createdAt: version.createdAt ?? nowIso(), })), threadMeta, createdByAgent: raw.createdByAgent ?? false, collaborationMode: raw.collaborationMode ?? "development", approvalState: raw.approvalState ?? "not_required", agentControls: normalizeProjectAgentControls(raw.agentControls), }; project.groupMembers = ensureArray(raw.groupMembers, []).map((member) => normalizeGroupMember(member, projectId, project.threadMeta), ); normalizeProjectConversationShape(project); project.updatedAt = resolveProjectUpdatedAt(project, project.threadMeta.updatedAt); return project; } function normalizeState(raw: Partial | undefined): BossState { const base = cloneInitialState(); if (!raw) return syncDerivedState(base); const state: BossState = { user: { ...base.user, ...raw.user, settings: { ...base.user.settings, ...(raw.user?.settings ?? {}), }, }, devices: ensureArray(raw.devices, base.devices).map((device, index) => ({ ...base.devices[index % base.devices.length], ...device, source: device.source ?? (device.id === PRIMARY_CODEX_NODE_ID ? "production" : "demo"), })), projects: ensureArray(raw.projects, base.projects).map((project, index) => normalizeProject(project, base.projects[index % base.projects.length]), ), verificationCodes: ensureArray(raw.verificationCodes, base.verificationCodes), verificationDispatches: ensureArray(raw.verificationDispatches, base.verificationDispatches).map( (dispatch) => ({ dispatchId: dispatch.dispatchId ?? randomToken("vdispatch"), account: dispatch.account ?? "", purpose: dispatch.purpose ?? "login", deliveryMode: dispatch.deliveryMode ?? getVerificationDeliveryMode(), requestedAt: dispatch.requestedAt ?? nowIso(), status: dispatch.status ?? "requested", note: dispatch.note ?? "", }), ), authAccounts: ensureArray(raw.authAccounts, base.authAccounts).map((account) => ({ id: account.id ?? `account-${slugify(account.account ?? "unknown")}`, account: account.account ?? "", passwordHash: account.passwordHash ?? hashPassword("boss123456"), displayName: account.displayName ?? "Boss 成员", role: account.role ?? "member", verificationEmail: account.verificationEmail, codexNodeId: account.codexNodeId, codexNodeLabel: account.codexNodeLabel, primaryDeviceId: account.primaryDeviceId, isPrimary: account.isPrimary ?? false, failedLoginAttempts: account.failedLoginAttempts ?? 0, lockedUntil: account.lockedUntil, lastLoginAt: account.lastLoginAt, lastLoginMethod: account.lastLoginMethod, createdAt: account.createdAt ?? nowIso(), updatedAt: account.updatedAt ?? account.createdAt ?? nowIso(), })), authSessions: ensureArray(raw.authSessions, base.authSessions).map((session) => ({ sessionId: session.sessionId ?? randomToken("session"), sessionToken: session.sessionToken ?? randomBytes(24).toString("hex"), restoreToken: session.restoreToken ?? randomBytes(24).toString("hex"), account: session.account ?? "", role: session.role ?? "member", displayName: session.displayName ?? "Boss 成员", loginMethod: session.loginMethod ?? "password", createdAt: session.createdAt ?? nowIso(), expiresAt: session.expiresAt ?? new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(), lastSeenAt: session.lastSeenAt ?? session.createdAt ?? nowIso(), revokedAt: session.revokedAt, })), aiAccounts: ensureArray(raw.aiAccounts, base.aiAccounts).map((account, index) => { const fallback = base.aiAccounts[index % base.aiAccounts.length]; return { ...fallback, ...account, accountId: account.accountId ?? fallback.accountId ?? `ai-${randomToken("account")}`, label: account.label ?? fallback.label ?? "AI 账号", role: account.role ?? fallback.role ?? "api_fallback", provider: account.provider ?? fallback.provider ?? "openai_api", displayName: account.displayName ?? fallback.displayName ?? "未命名 AI", model: account.model ?? fallback.model, apiKey: account.apiKey, apiKeyMasked: account.apiKeyMasked ?? fallback.apiKeyMasked, enabled: account.enabled ?? fallback.enabled ?? true, isActive: account.isActive ?? fallback.isActive ?? false, status: account.status ?? fallback.status ?? "needs_api_key", loginStatusNote: account.loginStatusNote ?? fallback.loginStatusNote, createdAt: account.createdAt ?? nowIso(), updatedAt: account.updatedAt ?? account.createdAt ?? nowIso(), } satisfies AiAccount; }), aiAccountSwitchHistory: ensureArray( raw.aiAccountSwitchHistory, base.aiAccountSwitchHistory, ).map((item) => ({ switchId: item.switchId ?? randomToken("aiswitch"), fromAccountId: item.fromAccountId, fromLabel: item.fromLabel, toAccountId: item.toAccountId ?? "", toLabel: item.toLabel ?? "AI 账号", role: item.role ?? "api_fallback", switchedAt: item.switchedAt ?? nowIso(), reason: item.reason ?? "未注明切换原因", })), masterAgentTasks: ensureArray(raw.masterAgentTasks, base.masterAgentTasks).map((task) => ({ taskId: task.taskId ?? randomToken("mastertask"), projectId: task.projectId ?? "master-agent", taskType: task.taskType ?? "conversation_reply", requestMessageId: task.requestMessageId ?? "", requestText: task.requestText ?? "", executionPrompt: task.executionPrompt ?? task.requestText ?? "", requestedBy: task.requestedBy ?? "用户", requestedByAccount: task.requestedByAccount ?? "", deviceId: task.deviceId ?? PRIMARY_CODEX_NODE_ID, accountId: task.accountId, accountLabel: task.accountLabel, attachmentId: task.attachmentId, attachmentFileName: task.attachmentFileName, attachmentDownloadToken: task.attachmentDownloadToken, attachmentDownloadExpiresAt: task.attachmentDownloadExpiresAt, attachmentDownloadUrl: task.attachmentDownloadUrl, attachmentTextExcerpt: task.attachmentTextExcerpt, dispatchExecutionId: task.dispatchExecutionId, targetProjectId: task.targetProjectId, targetThreadId: task.targetThreadId, targetThreadDisplayName: task.targetThreadDisplayName, deviceImportDraftId: task.deviceImportDraftId, status: task.status ?? "queued", requestedAt: task.requestedAt ?? nowIso(), claimedAt: task.claimedAt, completedAt: task.completedAt, replyBody: task.replyBody, errorMessage: task.errorMessage, requestId: task.requestId, })), dispatchPlans: ensureArray(raw.dispatchPlans, base.dispatchPlans).map((plan, index) => normalizeDispatchPlan(plan, base.dispatchPlans[index % Math.max(1, base.dispatchPlans.length)]), ), dispatchExecutions: ensureArray(raw.dispatchExecutions, base.dispatchExecutions).map((execution, index) => normalizeDispatchExecution( execution, base.dispatchExecutions[index % Math.max(1, base.dispatchExecutions.length)], ), ), deviceImportDrafts: ensureArray(raw.deviceImportDrafts, base.deviceImportDrafts).map((draft, index) => normalizeDeviceImportDraft( draft, base.deviceImportDrafts[index % Math.max(1, base.deviceImportDrafts.length)], ), ), deviceImportResolutions: ensureArray( raw.deviceImportResolutions, base.deviceImportResolutions, ).map((resolution, index) => normalizeDeviceImportResolution( resolution, base.deviceImportResolutions[index % Math.max(1, base.deviceImportResolutions.length)], ), ), otaUpdates: ensureArray(raw.otaUpdates, base.otaUpdates).map((update, index) => ({ ...base.otaUpdates[index % base.otaUpdates.length], ...update, summary: ensureArray(update.summary, base.otaUpdates[index % base.otaUpdates.length].summary), })), otaUpdateLogs: ensureArray(raw.otaUpdateLogs, base.otaUpdateLogs).map((log, index) => ({ ...base.otaUpdateLogs[index % base.otaUpdateLogs.length], ...log, })), deviceSkills: ensureArray(raw.deviceSkills, base.deviceSkills).map((skill) => ({ ...skill, invocation: skill.invocation ?? `$${skill.name}`, category: skill.category ?? "本机技能", updatedAt: skill.updatedAt ?? nowIso(), })), appLogs: ensureArray(raw.appLogs, base.appLogs).map((log) => ({ ...log, mirroredToProject: log.mirroredToProject ?? false, createdAt: log.createdAt ?? nowIso(), })), userAttachmentStorageConfigs: ensureArray( raw.userAttachmentStorageConfigs, base.userAttachmentStorageConfigs, ).map((config, index) => normalizeAttachmentStorageConfig( config, base.userAttachmentStorageConfigs[index % base.userAttachmentStorageConfigs.length], ), ), masterAgentPromptPolicy: normalizeMasterAgentPromptPolicy( raw.masterAgentPromptPolicy, base.masterAgentPromptPolicy, ), userMasterPrompts: ensureArray(raw.userMasterPrompts, base.userMasterPrompts).map( (prompt, index) => normalizeUserMasterPrompt( prompt, base.userMasterPrompts[index % Math.max(1, base.userMasterPrompts.length)], ), ), masterAgentMemories: ensureArray(raw.masterAgentMemories, base.masterAgentMemories).map( (memory, index) => normalizeUserMasterMemory( memory, base.masterAgentMemories[index % Math.max(1, base.masterAgentMemories.length)], ), ), userProjectAgentControls: ensureArray( raw.userProjectAgentControls, base.userProjectAgentControls, ) .map((controls, index) => normalizeUserProjectAgentControls( controls, base.userProjectAgentControls[index % Math.max(1, base.userProjectAgentControls.length)], ), ) .filter((item): item is UserProjectAgentControls => Boolean(item)), threadContextSnapshots: ensureArray(raw.threadContextSnapshots, base.threadContextSnapshots).map( (snapshot, index) => ({ ...base.threadContextSnapshots[index % base.threadContextSnapshots.length], ...snapshot, contextBudgetLevel: snapshot.contextBudgetLevel ?? deriveLevelFromPercent(snapshot.contextBudgetRemainingPct ?? 100), checklist: ensureArray(snapshot.checklist, base.threadContextSnapshots[index % base.threadContextSnapshots.length].checklist), }), ), threadHandoffPackages: ensureArray(raw.threadHandoffPackages, base.threadHandoffPackages).map( (item) => ({ ...item, openQuestions: ensureArray(item.openQuestions, []), criticalFiles: ensureArray(item.criticalFiles, []), criticalCommands: ensureArray(item.criticalCommands, []), criticalTests: ensureArray(item.criticalTests, []), criticalArtifacts: ensureArray(item.criticalArtifacts, []), decisionLinks: ensureArray(item.decisionLinks, []), }), ), threadContextAlerts: ensureArray(raw.threadContextAlerts, base.threadContextAlerts).map((item) => ({ ...item, masterActions: ensureArray(item.masterActions, []), })), deviceEnrollments: ensureArray(raw.deviceEnrollments, base.deviceEnrollments), opsFaults: ensureArray(raw.opsFaults, base.opsFaults), opsRepairTickets: ensureArray(raw.opsRepairTickets, base.opsRepairTickets), opsRepairVerifications: ensureArray(raw.opsRepairVerifications, base.opsRepairVerifications), auditRequests: ensureArray(raw.auditRequests, base.auditRequests).map((item) => ({ ...item, acceptanceCriteria: ensureArray(item.acceptanceCriteria, []), riskFocus: ensureArray(item.riskFocus, []), evidenceRefs: ensureArray(item.evidenceRefs, []), artifactRefs: ensureArray(item.artifactRefs, []), capabilityRequirements: ensureArray(item.capabilityRequirements, []), metadataJson: item.metadataJson ?? {}, })), auditResults: ensureArray(raw.auditResults, base.auditResults).map((item) => ({ ...item, findings: ensureArray(item.findings, []), requiredActions: ensureArray(item.requiredActions, []), usedCapabilities: ensureArray(item.usedCapabilities, []), artifactRefs: ensureArray(item.artifactRefs, []), timeline: ensureArray(item.timeline, []), })), capabilities: ensureArray(raw.capabilities, base.capabilities).map((item) => ({ ...item, supportedActions: ensureArray(item.supportedActions, []), evidenceModes: ensureArray(item.evidenceModes, []), })), }; if (!state.projects.some((project) => project.id === "master-agent")) { state.projects.unshift(base.projects[0]); } return syncDerivedState(state); } function latestProjectTimestamp(state: BossState, projectId: string) { const project = state.projects.find((item) => item.id === projectId); const messageTimes = (project?.messages ?? []).map((message) => messageTimeValue(message.sentAt)); const snapshotTimes = state.threadContextSnapshots .filter((snapshot) => snapshot.projectId === projectId) .map((snapshot) => messageTimeValue(snapshot.capturedAt)); const alertTimes = state.threadContextAlerts .filter((alert) => alert.projectId === projectId) .map((alert) => messageTimeValue(alert.openedAt)); const opsTimes = state.opsFaults .filter((fault) => fault.projectId === projectId) .map((fault) => messageTimeValue(fault.lastSeenAt)); const auditTimes = state.auditResults .filter((result) => state.auditRequests.some( (request) => request.auditRequestId === result.auditRequestId && request.projectId === projectId, ), ) .map((result) => messageTimeValue(result.completedAt)); const latest = Math.max(0, ...messageTimes, ...snapshotTimes, ...alertTimes, ...opsTimes, ...auditTimes); return latest ? new Date(latest).toISOString() : project?.lastMessageAt ?? nowIso(); } function deriveProjectPreview(state: BossState, project: Project) { if (project.id === "master-agent") { return project.preview; } const relevantSnapshots = state.threadContextSnapshots .filter((snapshot) => snapshot.projectId === project.id) .sort(compareSnapshotsForRisk); const topSnapshot = relevantSnapshots[0]; if (topSnapshot && (topSnapshot.contextBudgetLevel !== "safe" || topSnapshot.mustFinishBeforeCompaction)) { return `${topSnapshot.title} · ${topSnapshot.contextBudgetLevel} ${topSnapshot.contextBudgetRemainingPct}% · ${topSnapshot.summary}`; } const latestAudit = state.auditResults .map((result) => ({ result, request: state.auditRequests.find((request) => request.auditRequestId === result.auditRequestId), })) .filter( (item): item is { result: AuditTaskResult; request: AuditTaskRequest } => Boolean(item.request && item.request.projectId === project.id), ) .sort((a, b) => messageTimeValue(b.result.completedAt) - messageTimeValue(a.result.completedAt))[0]; if (latestAudit) { return `审计 · ${latestAudit.result.summary}`; } const openFault = state.opsFaults .filter((fault) => fault.projectId === project.id && fault.status !== "resolved") .sort((a, b) => messageTimeValue(b.lastSeenAt) - messageTimeValue(a.lastSeenAt))[0]; if (openFault) { return `运维 · ${openFault.summary}`; } const lastMessage = [...project.messages].sort( (a, b) => messageTimeValue(b.sentAt) - messageTimeValue(a.sentAt), )[0]; if (lastMessage) { return lastMessage.body; } return project.preview; } function updateMasterProjectSummary(state: BossState) { const masterProject = state.projects.find((project) => project.id === "master-agent"); if (!masterProject) return; const riskThreads = [...state.threadContextSnapshots] .filter( (snapshot) => snapshot.projectId !== "master-agent" && (snapshot.contextBudgetLevel !== "safe" || snapshot.mustFinishBeforeCompaction), ) .sort(compareSnapshotsForRisk); const openFaults = state.opsFaults.filter((fault) => fault.status !== "resolved"); const pendingAudits = state.auditRequests.filter( (request) => !state.auditResults.some((result) => result.auditRequestId === request.auditRequestId), ); const summaryParts = []; if (riskThreads[0]) { summaryParts.push( `${riskThreads[0].title} ${riskThreads[0].contextBudgetLevel} ${riskThreads[0].contextBudgetRemainingPct}%`, ); } if (openFaults[0]) { summaryParts.push(`运维 ${openFaults[0].faultKey}`); } if (pendingAudits.length > 0) { summaryParts.push(`审计待处理 ${pendingAudits.length} 条`); } masterProject.preview = summaryParts.join(" · ") || "当前无高风险线程,主 Agent 正在维护项目总结、运维账本和交接文档。"; masterProject.riskLevel = riskThreads[0] ? deriveRiskFromSnapshots([riskThreads[0]]) : openFaults.some((fault) => fault.severity === "critical") ? "high" : "low"; const masterSnapshot = state.threadContextSnapshots.find( (snapshot) => snapshot.projectId === "master-agent", ); masterProject.contextBudgetPct = masterSnapshot?.contextBudgetRemainingPct; masterProject.contextBudgetLabel = masterSnapshot ? `${masterSnapshot.contextBudgetRemainingPct}%` : undefined; masterProject.lastMessageAt = latestProjectTimestamp(state, "master-agent"); masterProject.updatedAt = masterProject.lastMessageAt; const summaryMessage = masterProject.messages.find((message) => message.id === "master-summary"); if (summaryMessage) { summaryMessage.body = masterProject.preview; summaryMessage.sentAt = masterProject.lastMessageAt; } } function firstAvailableOta(state: BossState) { return state.otaUpdates.find((item) => item.status === "available" || item.status === "scheduled") ?? null; } function readPublishedOtaAsset() { if (!existsSync(publishedApkPath)) { return null; } const stats = statSync(publishedApkPath); type PublishedOtaMetadata = { fileName?: string; urlPath?: string; sizeBytes?: number; updatedAt?: string; sha256?: string; versionName?: string; }; let metadata: PublishedOtaMetadata | null = null; if (existsSync(publishedApkMetaPath)) { try { metadata = JSON.parse(readFileSync(publishedApkMetaPath, "utf8")) as PublishedOtaMetadata; } catch { metadata = null; } } return { fileName: metadata?.fileName ?? path.basename(publishedApkPath), downloadUrl: metadata?.urlPath ?? "/api/v1/user/ota/package", sizeBytes: metadata?.sizeBytes ?? stats.size, assetUpdatedAt: metadata?.updatedAt ?? stats.mtime.toISOString(), packageSha256: metadata?.sha256, versionName: metadata?.versionName, }; } function syncPublishedOtaAsset(state: BossState) { const asset = readPublishedOtaAsset(); for (const release of state.otaUpdates) { if (release.packageType !== "android_shell") continue; release.currentVersion = state.user.version; if (!asset) { release.packageFileName = undefined; release.packageSizeBytes = undefined; release.packageSha256 = undefined; release.downloadUrl = undefined; release.assetUpdatedAt = undefined; continue; } release.packageFileName = asset.fileName; release.packageSizeBytes = asset.sizeBytes; release.packageSha256 = asset.packageSha256; release.downloadUrl = asset.downloadUrl; release.assetUpdatedAt = asset.assetUpdatedAt; if (asset.versionName) { release.version = asset.versionName.startsWith("v") ? asset.versionName : `v${asset.versionName}`; } } } function isProductionDevice(device: Device) { return device.source === "production"; } function pushProjectLedgerMessage( state: BossState, projectId: string, message: Omit & { sentAt?: string }, ) { const project = state.projects.find((item) => item.id === projectId); if (!project) return null; const entry: Message = { id: randomToken("msg"), sentAt: message.sentAt ?? nowIso(), ...message, }; project.messages.push(entry); project.lastMessageAt = entry.sentAt; project.preview = entry.body; return entry; } function shouldAutoReplyToMirroredLog(entry: AppLogEntry) { if (entry.level !== "info") return true; return ( entry.category.startsWith("apk.") || entry.category.startsWith("ota.") || entry.category.startsWith("runtime.") || entry.category.startsWith("chat.message_failed") ); } function buildMasterAgentLogReply(state: BossState, entry: AppLogEntry) { const device = state.devices.find((item) => item.id === entry.deviceId); const availableOta = firstAvailableOta(state); const lines = [ `已收到 ${device?.name ?? entry.deviceId} 的${entry.level === "error" ? "错误" : "实时"}日志:${entry.category}。`, `现象:${entry.message}${entry.detail ? `,附加信息:${entry.detail}` : ""}`, ]; if (entry.level === "error") { lines.push("我会优先沿着这条报错路径继续收口,并尽量把 patch、验证和 APK 调整一起推进。"); } else { lines.push("我会把这条最新日志并入当前调度判断,继续对齐主 Agent 对话和 APK 优化方向。"); } if (availableOta?.downloadUrl) { lines.push(`当前可验证的 OTA 包是 ${availableOta.version},下载地址已经就绪。`); } lines.push("继续把目标、复现步骤或预期结果发给我,我会结合最新日志继续推进。"); return lines.join(""); } function ensurePrimaryAdminBinding(state: BossState) { state.user.id = "user-boss-admin"; state.user.name = "Boss 超级管理员"; state.user.avatar = "17"; state.user.account = PRIMARY_ADMIN_ACCOUNT; state.user.verificationEmail = PRIMARY_ADMIN_VERIFICATION_EMAIL; state.user.role = "highest_admin"; state.user.roleLabel = "最高管理员"; state.user.accountType = "最高管理员 · 本机 Codex 已绑定"; state.user.qrCodeValue = `boss://user/${PRIMARY_ADMIN_ACCOUNT}`; state.user.boundCodexNodeId = PRIMARY_CODEX_NODE_ID; state.user.boundCodexNodeLabel = PRIMARY_CODEX_NODE_LABEL; state.user.boundDeviceId = PRIMARY_CODEX_NODE_ID; state.user.boundAt = state.user.boundAt ?? "2026-03-26T09:00:00+08:00"; if (!state.user.version || state.user.version === "1.2.7" || state.user.version === "1.3.0") { state.user.version = "1.4.0"; } let adminAccount = state.authAccounts.find((item) => item.account === PRIMARY_ADMIN_ACCOUNT); if (!adminAccount) { adminAccount = { id: `account-${PRIMARY_ADMIN_ACCOUNT}`, account: PRIMARY_ADMIN_ACCOUNT, passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD), displayName: "Boss 超级管理员", role: "highest_admin", verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL, codexNodeId: PRIMARY_CODEX_NODE_ID, codexNodeLabel: PRIMARY_CODEX_NODE_LABEL, primaryDeviceId: PRIMARY_CODEX_NODE_ID, isPrimary: true, createdAt: nowIso(), updatedAt: nowIso(), }; state.authAccounts.unshift(adminAccount); } else { adminAccount.displayName = "Boss 超级管理员"; adminAccount.role = "highest_admin"; adminAccount.verificationEmail = PRIMARY_ADMIN_VERIFICATION_EMAIL; adminAccount.codexNodeId = PRIMARY_CODEX_NODE_ID; adminAccount.codexNodeLabel = PRIMARY_CODEX_NODE_LABEL; adminAccount.primaryDeviceId = PRIMARY_CODEX_NODE_ID; adminAccount.isPrimary = true; } for (const account of state.authAccounts) { if (account.account !== PRIMARY_ADMIN_ACCOUNT) { account.isPrimary = false; } } const primaryDevice = state.devices.find((item) => item.id === PRIMARY_CODEX_NODE_ID); if (primaryDevice) { primaryDevice.account = PRIMARY_ADMIN_ACCOUNT; primaryDevice.note = "本机 Codex 主节点 · 17600003315 已绑定"; } const seededOta = state.otaUpdates.find((item) => item.releaseId === "ota_130_to_131" || item.releaseId === "ota_140_to_141", ); if (seededOta) { seededOta.releaseId = "ota_140_to_141"; seededOta.version = "v2.0.0"; seededOta.currentVersion = state.user.version; seededOta.channel = "stable"; seededOta.packageType = "android_shell"; seededOta.status = seededOta.status === "applied" ? "applied" : "available"; seededOta.summary = ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"]; seededOta.targetScope = "Boss Android 原生客户端与 Web 控制台"; seededOta.requiredRole = "highest_admin"; seededOta.publishedAt = seededOta.publishedAt || nowIso(); seededOta.packageFileName = seededOta.packageFileName || "boss-android-latest.apk"; seededOta.downloadUrl = seededOta.downloadUrl || "/api/v1/user/ota/package"; } else { state.otaUpdates.push({ releaseId: "ota_140_to_141", version: "v2.0.0", currentVersion: state.user.version, channel: "stable", packageType: "android_shell", status: "available", summary: ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"], targetScope: "Boss Android 原生客户端与 Web 控制台", requiredRole: "highest_admin", publishedAt: nowIso(), packageFileName: "boss-android-latest.apk", downloadUrl: "/api/v1/user/ota/package", }); } } function ensureDefaultAiAccounts(state: BossState) { const defaults = cloneInitialState().aiAccounts; for (const fallback of defaults) { if (!state.aiAccounts.some((item) => item.accountId === fallback.accountId)) { state.aiAccounts.push(fallback); } } const primary = state.aiAccounts.find((item) => item.accountId === "master-codex-primary"); if (primary) { primary.role = "primary"; primary.provider = "master_codex_node"; primary.enabled = true; primary.nodeId = primary.nodeId || PRIMARY_CODEX_NODE_ID; primary.nodeLabel = primary.nodeLabel || PRIMARY_CODEX_NODE_LABEL; primary.displayName = primary.displayName || "17600003315 · Master Codex Node"; if (primary.status !== "degraded") { primary.status = "ready"; } if (!primary.loginStatusNote || primary.loginStatusNote.includes("还未接通")) { primary.loginStatusNote = "已绑定本机 Codex,可通过 local-agent relay 执行主 Agent 对话。"; } } const fallbackApi = state.aiAccounts.find((item) => item.accountId === "openai-api-fallback"); if (fallbackApi) { fallbackApi.role = "api_fallback"; fallbackApi.provider = "openai_api"; fallbackApi.model = fallbackApi.model || "gpt-5.4"; if (!fallbackApi.loginStatusNote) { fallbackApi.loginStatusNote = "配置 OpenAI API Key 后,可直接为主 Agent 生成真实回复。"; } } } function syncUserOtaState(state: BossState) { const available = firstAvailableOta(state); state.user.hasOta = Boolean(available); state.user.otaVersion = available?.version; state.user.otaSummary = available?.summary ?? []; } function syncDerivedState(input: BossState) { const state = input; ensurePrimaryAdminBinding(state); ensureDefaultAiAccounts(state); syncPublishedOtaAsset(state); syncUserOtaState(state); state.aiAccounts = sortAiAccounts(state.aiAccounts).slice(0, 24); state.aiAccountSwitchHistory = state.aiAccountSwitchHistory .sort((a, b) => b.switchedAt.localeCompare(a.switchedAt)) .slice(0, 40); state.masterAgentTasks = state.masterAgentTasks .sort((a, b) => b.requestedAt.localeCompare(a.requestedAt)) .slice(0, 80); state.dispatchPlans = state.dispatchPlans .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) .slice(0, 80); state.dispatchExecutions = state.dispatchExecutions .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) .slice(0, 160); state.deviceImportDrafts = state.deviceImportDrafts .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) .slice(0, 40); state.deviceImportResolutions = state.deviceImportResolutions .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) .slice(0, 80); state.devices = state.devices.filter(isProductionDevice); const visibleDeviceIds = new Set(state.devices.map((device) => device.id)); state.threadContextSnapshots = state.threadContextSnapshots.filter((item) => visibleDeviceIds.has(item.nodeId), ); const visibleThreadIds = new Set(state.threadContextSnapshots.map((item) => item.threadId)); state.threadHandoffPackages = state.threadHandoffPackages.filter((item) => visibleThreadIds.has(item.fromThreadId), ); state.threadContextAlerts = state.threadContextAlerts.filter((item) => visibleThreadIds.has(item.threadId), ); state.deviceEnrollments = state.deviceEnrollments.filter((item) => visibleDeviceIds.has(item.deviceId)); state.deviceImportDrafts = state.deviceImportDrafts.filter((item) => visibleDeviceIds.has(item.deviceId), ); const visibleImportDraftIds = new Set(state.deviceImportDrafts.map((item) => item.draftId)); state.deviceImportResolutions = state.deviceImportResolutions.filter( (item) => visibleDeviceIds.has(item.deviceId) && visibleImportDraftIds.has(item.draftId), ); state.deviceSkills = state.deviceSkills .filter((skill) => visibleDeviceIds.has(skill.deviceId)) .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); state.appLogs = state.appLogs.slice(0, 120); state.verificationDispatches = state.verificationDispatches.slice(0, 80); state.authSessions = state.authSessions .filter((session) => !session.revokedAt && new Date(session.expiresAt).getTime() > Date.now()) .slice(0, 20); state.opsFaults = state.opsFaults.filter((item) => visibleDeviceIds.has(item.nodeId)); const visibleFaultIds = new Set(state.opsFaults.map((item) => item.faultId)); state.opsRepairTickets = state.opsRepairTickets.filter((item) => visibleFaultIds.has(item.faultId)); const visibleTicketIds = new Set(state.opsRepairTickets.map((item) => item.ticketId)); state.opsRepairVerifications = state.opsRepairVerifications.filter((item) => visibleTicketIds.has(item.ticketId), ); state.auditRequests = state.auditRequests.filter((item) => { const sourceNodeId = item.sourceThreadRef.split(":")[0]; return visibleDeviceIds.has(sourceNodeId); }); const visibleAuditIds = new Set(state.auditRequests.map((item) => item.auditRequestId)); state.auditResults = state.auditResults.filter((item) => visibleAuditIds.has(item.auditRequestId)); state.capabilities = state.capabilities.filter((item) => visibleDeviceIds.has(item.nodeId)); for (const project of state.projects) { project.deviceIds = project.deviceIds.filter((deviceId) => visibleDeviceIds.has(deviceId)); const projectSnapshots = state.threadContextSnapshots .filter((snapshot) => snapshot.projectId === project.id) .sort(compareSnapshotsForRisk); normalizeProjectConversationShape(project, { allowedDeviceIds: visibleDeviceIds }); project.riskLevel = deriveRiskFromSnapshots(projectSnapshots); if (project.isGroup) { project.contextBudgetPct = undefined; project.contextBudgetLabel = undefined; } else { const topSnapshot = projectSnapshots[0]; project.contextBudgetPct = topSnapshot?.contextBudgetRemainingPct; project.contextBudgetLabel = topSnapshot ? `${topSnapshot.contextBudgetRemainingPct}%` : undefined; } project.lastMessageAt = latestProjectTimestamp(state, project.id); project.updatedAt = resolveProjectUpdatedAt(project, project.lastMessageAt); project.preview = deriveProjectPreview(state, project); project.unreadCount = Math.max(0, project.unreadCount ?? 0); } updateMasterProjectSummary(state); return state; } async function ensureStateFile() { await fs.mkdir(dataDir, { recursive: true }); try { await fs.access(dataFile); } catch { const initialJson = JSON.stringify(syncDerivedState(cloneInitialState()), null, 2); await fs.writeFile(dataFile, initialJson, "utf8"); await fs.writeFile(backupFile, initialJson, "utf8"); } } let stateWriteQueue: Promise = Promise.resolve(); let lastPersistedStateText: string | null = null; let stateMutationQueue: Promise = Promise.resolve(); export async function readState(): Promise { await ensureStateFile(); const raw = await fs.readFile(dataFile, "utf8"); try { const state = normalizeState(JSON.parse(raw) as Partial); lastPersistedStateText = JSON.stringify(state, null, 2); return state; } catch { const fallbackText = (await fs.readFile(backupFile, "utf8").catch(() => null)) ?? lastPersistedStateText ?? JSON.stringify(syncDerivedState(cloneInitialState()), null, 2); const state = normalizeState(JSON.parse(fallbackText) as Partial); lastPersistedStateText = JSON.stringify(state, null, 2); return state; } } export async function writeState(state: BossState) { const nextState = syncDerivedState(state); const serialized = JSON.stringify(nextState, null, 2); const tempFile = `${dataFile}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`; lastPersistedStateText = serialized; const persist = async () => { await ensureStateFile(); await fs.writeFile(tempFile, serialized, "utf8"); await fs.rename(tempFile, dataFile); await fs.writeFile(backupFile, serialized, "utf8"); }; stateWriteQueue = stateWriteQueue.then(persist, persist); await stateWriteQueue; } async function mutateState(mutator: (state: BossState) => Promise | T) { let result!: T; const run = async () => { const state = await readState(); result = await mutator(state); await writeState(state); }; stateMutationQueue = stateMutationQueue.then(run, run); await stateMutationQueue; return result; } async function mutateStateIfChanged( mutator: (state: BossState) => Promise<{ result: T; changed: boolean }> | { result: T; changed: boolean }, ) { let result!: T; const run = async () => { const state = await readState(); const outcome = await mutator(state); result = outcome.result; if (outcome.changed) { await writeState(state); } }; stateMutationQueue = stateMutationQueue.then(run, run); await stateMutationQueue; return result; } async function loadPersistedStateRaw() { await ensureStateFile(); const parseStateText = (text: string) => JSON.parse(text) as Partial; const tryRead = async (filePath: string) => { const text = await fs.readFile(filePath, "utf8"); return parseStateText(text); }; try { return await tryRead(dataFile); } catch { try { return await tryRead(backupFile); } catch { if (lastPersistedStateText) { return parseStateText(lastPersistedStateText); } return JSON.parse(JSON.stringify(syncDerivedState(cloneInitialState()))) as Partial; } } } export async function getProject(projectId: string) { const state = await readState(); return state.projects.find((project) => project.id === projectId) ?? null; } export async function hasPersistedProject(projectId: string) { const rawState = await loadPersistedStateRaw(); return Array.isArray(rawState.projects) && rawState.projects.some((project) => project?.id === projectId); } function findUserProjectAgentControls( state: BossState, projectId: string, account?: string, ) { const normalizedAccount = trimToDefined(account); if (!normalizedAccount) { return null; } return ( state.userProjectAgentControls.find( (item) => item.projectId === projectId && item.account === normalizedAccount, ) ?? null ); } export async function getProjectAgentControls(projectId: string, account?: string) { if (projectId !== "master-agent") { return null; } const state = await readState(); const scopedControls = findUserProjectAgentControls(state, projectId, account); if (scopedControls?.controls) { return scopedControls.controls; } return state.projects.find((project) => project.id === projectId)?.agentControls ?? null; } export async function updateProjectAgentControls( projectId: string, payload: { modelOverride?: unknown; reasoningEffortOverride?: unknown; promptOverride?: unknown; }, account?: string, ) { if (projectId !== "master-agent") { throw new Error("MASTER_AGENT_CONTROLS_SCOPE_RESTRICTED"); } const modelOverrideInput = Object.prototype.hasOwnProperty.call(payload, "modelOverride") ? parseControlTextOverride(payload.modelOverride) : { kind: "preserve" as const }; const reasoningEffortInput = Object.prototype.hasOwnProperty.call(payload, "reasoningEffortOverride") ? parseReasoningEffortOverride(payload.reasoningEffortOverride) : { kind: "preserve" as const }; const promptOverrideInput = Object.prototype.hasOwnProperty.call(payload, "promptOverride") ? parseControlTextOverride(payload.promptOverride) : { kind: "preserve" as const }; if (modelOverrideInput.kind === "invalid") { throw new Error("INVALID_MODEL_OVERRIDE"); } if (reasoningEffortInput.kind === "invalid") { throw new Error("INVALID_REASONING_EFFORT_OVERRIDE"); } if (promptOverrideInput.kind === "invalid") { throw new Error("INVALID_PROMPT_OVERRIDE"); } return mutateStateIfChanged((state) => { const project = state.projects.find((item) => item.id === projectId); if (!project) throw new Error("PROJECT_NOT_FOUND"); const normalizedAccount = trimToDefined(account); const currentEntry = findUserProjectAgentControls(state, projectId, normalizedAccount ?? undefined); const currentControls = currentEntry?.controls ?? project.agentControls; const modelOverride = modelOverrideInput.kind === "set" ? modelOverrideInput.value : modelOverrideInput.kind === "clear" ? undefined : currentControls?.modelOverride; const reasoningEffortOverride = reasoningEffortInput.kind === "set" ? reasoningEffortInput.value : reasoningEffortInput.kind === "clear" ? undefined : currentControls?.reasoningEffortOverride; const promptOverride = promptOverrideInput.kind === "set" ? promptOverrideInput.value : promptOverrideInput.kind === "clear" ? undefined : currentControls?.promptOverride; const currentModelOverride = currentControls?.modelOverride; const currentReasoningEffortOverride = currentControls?.reasoningEffortOverride; const currentPromptOverride = currentControls?.promptOverride; if ( currentModelOverride === modelOverride && currentReasoningEffortOverride === reasoningEffortOverride && currentPromptOverride === promptOverride ) { return { result: currentControls, changed: false }; } const nextControls = { modelOverride, reasoningEffortOverride, promptOverride, updatedAt: nowIso(), } satisfies ProjectAgentControls; const normalizedControls = normalizeProjectAgentControls(nextControls) ?? null; if (normalizedAccount) { state.userProjectAgentControls = state.userProjectAgentControls.filter( (item) => !(item.projectId === projectId && item.account === normalizedAccount), ); if (normalizedControls) { state.userProjectAgentControls.unshift({ account: normalizedAccount, projectId, controls: normalizedControls, }); } } else { project.agentControls = normalizedControls ?? undefined; } project.threadMeta.updatedAt = nextControls.updatedAt; project.updatedAt = nextControls.updatedAt; return { result: normalizedControls, changed: true }; }); } export async function getDevice(deviceId: string) { const state = await readState(); return state.devices.find((device) => device.id === deviceId) ?? null; } export async function getAttachmentStorageConfig(account: string) { const state = await readState(); return ( state.userAttachmentStorageConfigs.find((item) => item.account === account) ?? { account, mode: "server_file" as const, updatedAt: nowIso(), } ); } export async function upsertAttachmentStorageConfig(config: UserAttachmentStorageConfig) { return mutateState((state) => { const index = state.userAttachmentStorageConfigs.findIndex( (item) => item.account === config.account, ); if (index >= 0) { state.userAttachmentStorageConfigs[index] = config; } else { state.userAttachmentStorageConfigs.push(config); } return config; }); } export async function getMasterAgentPromptPolicy() { const state = await readState(); return state.masterAgentPromptPolicy ?? null; } export async function updateMasterAgentPromptPolicy(input: { globalPrompt: string; updatedBy?: string; }) { const globalPrompt = input.globalPrompt.trim(); if (!globalPrompt) { throw new Error("MASTER_AGENT_PROMPT_REQUIRED"); } return mutateState((state) => { const policy: MasterAgentPromptPolicy = { globalPrompt, updatedBy: input.updatedBy?.trim() || undefined, updatedAt: nowIso(), }; state.masterAgentPromptPolicy = policy; return policy; }); } export async function getUserMasterPrompt(account: string) { const state = await readState(); return state.userMasterPrompts.find((item) => item.account === account) ?? null; } export async function updateUserMasterPrompt(account: string, content: string) { const trimmedContent = content.trim(); if (!trimmedContent) { throw new Error("USER_MASTER_PROMPT_REQUIRED"); } return mutateState((state) => { const next: UserMasterPrompt = { account, content: trimmedContent, updatedAt: nowIso(), }; const existing = state.userMasterPrompts.find((item) => item.account === account); if (existing) { Object.assign(existing, next); } else { state.userMasterPrompts.unshift(next); } return next; }); } export async function clearUserMasterPrompt(account: string) { return mutateState((state) => { const before = state.userMasterPrompts.length; state.userMasterPrompts = state.userMasterPrompts.filter((item) => item.account !== account); return { cleared: before !== state.userMasterPrompts.length }; }); } export async function listUserMasterMemories( account: string, options?: { includeArchived?: boolean; scope?: MasterMemoryScope; projectId?: string }, ) { const state = await readState(); const includeArchived = options?.includeArchived ?? false; return [...state.masterAgentMemories] .filter((memory) => { if (memory.account !== account) return false; if (!includeArchived && memory.archived) return false; if (options?.scope && memory.scope !== options.scope) return false; if (options?.projectId && memory.projectId !== options.projectId) return false; return true; }) .sort((a, b) => { const timeDiff = messageTimeValue(b.lastUsedAt ?? b.updatedAt ?? b.createdAt) - messageTimeValue(a.lastUsedAt ?? a.updatedAt ?? a.createdAt); if (timeDiff !== 0) return timeDiff; return b.memoryId.localeCompare(a.memoryId); }); } export async function createUserMasterMemory(input: { account: string; scope: MasterMemoryScope; projectId?: string; title: string; content: string; memoryType: MasterMemoryType; tags?: string[]; sourceMessageId?: string; }) { const title = input.title.trim(); const content = input.content.trim(); if (!title) { throw new Error("USER_MASTER_MEMORY_TITLE_REQUIRED"); } if (!content) { throw new Error("USER_MASTER_MEMORY_CONTENT_REQUIRED"); } if (input.scope === "project" && !input.projectId?.trim()) { throw new Error("USER_MASTER_MEMORY_PROJECT_ID_REQUIRED"); } return mutateState((state) => { const now = nowIso(); const memory: MasterAgentMemory = { memoryId: randomToken("memory"), account: input.account, scope: input.scope, projectId: input.scope === "project" ? input.projectId?.trim() : undefined, title, content, memoryType: input.memoryType, tags: normalizeMasterMemoryTags(input.tags), sourceMessageId: input.sourceMessageId, createdAt: now, updatedAt: now, lastUsedAt: now, archived: false, }; state.masterAgentMemories.unshift(memory); return memory; }); } export async function updateUserMasterMemory( memoryId: string, account: string, patch: Partial< Pick< MasterAgentMemory, "scope" | "projectId" | "title" | "content" | "memoryType" | "tags" | "sourceMessageId" | "lastUsedAt" > >, ) { return mutateState((state) => { const memory = state.masterAgentMemories.find( (item) => item.memoryId === memoryId && item.account === account, ); if (!memory) { throw new Error("USER_MASTER_MEMORY_NOT_FOUND"); } if (patch.scope) { memory.scope = patch.scope; } if (memory.scope === "project" && patch.projectId !== undefined) { memory.projectId = patch.projectId.trim() || undefined; } if (memory.scope !== "project") { memory.projectId = undefined; } if (patch.title !== undefined) { const title = patch.title.trim(); if (!title) throw new Error("USER_MASTER_MEMORY_TITLE_REQUIRED"); memory.title = title; } if (patch.content !== undefined) { const content = patch.content.trim(); if (!content) throw new Error("USER_MASTER_MEMORY_CONTENT_REQUIRED"); memory.content = content; } if (patch.memoryType) { memory.memoryType = patch.memoryType; } if (patch.tags) { memory.tags = normalizeMasterMemoryTags(patch.tags); } if (patch.sourceMessageId !== undefined) { memory.sourceMessageId = patch.sourceMessageId; } if (patch.lastUsedAt !== undefined) { memory.lastUsedAt = patch.lastUsedAt; } memory.updatedAt = nowIso(); return memory; }); } export async function archiveUserMasterMemory(memoryId: string, account: string) { return mutateState((state) => { const memory = state.masterAgentMemories.find( (item) => item.memoryId === memoryId && item.account === account, ); if (!memory) { throw new Error("USER_MASTER_MEMORY_NOT_FOUND"); } memory.archived = true; memory.updatedAt = nowIso(); return memory; }); } export async function touchUserMasterMemories(memoryIds: string[], account: string) { const normalizedIds = Array.from(new Set(memoryIds.map((value) => value.trim()).filter(Boolean))); if (normalizedIds.length === 0) { return []; } return mutateState((state) => { const now = nowIso(); const touched: MasterAgentMemory[] = []; for (const memory of state.masterAgentMemories) { if (memory.account !== account) continue; if (!normalizedIds.includes(memory.memoryId)) continue; memory.lastUsedAt = now; touched.push(memory); } return touched; }); } function normalizeAutoMemoryText(value: string | undefined) { return (value ?? "") .replace(/\s+/g, " ") .replace(/[。;;!!]+$/g, "") .trim(); } function isLowValueAutoMemoryText(text: string) { const normalized = normalizeAutoMemoryText(text); if (!normalized) { return true; } if (normalized.length < 10) { return true; } if (/^(好的|收到|明白|继续|先这样|可以|行|没问题|辛苦了|谢谢|了解|嗯嗯)$/i.test(normalized)) { return true; } if (/(马上|稍后|回头|等下|一会|临时|先看下|先试试|先这样)/i.test(normalized)) { return true; } return false; } function inferAutoMemoryType(text: string): MasterMemoryType | null { if (!text.trim()) return null; if (/(微信|wechat|中文回复|中文沟通|UI风格|交互风格|偏好|习惯|默认)/i.test(text)) { return "user_preference"; } if (/(规则|约束|优先|先.*再|必须|不要|需要|流程|逻辑)/i.test(text)) { return "workflow_rule"; } if (/(阻塞|卡住|失败|异常|报错|问题|bug|未打通)/i.test(text)) { return "blocking_issue"; } if (/(风险|隐患|告警)/i.test(text)) { return "risk"; } if (/(决定|改成|采用|统一|确定|方案)/i.test(text)) { return "decision"; } if (/(调研|研究|结论)/i.test(text)) { return "research_note"; } if (/(进度|完成|已接通|已打通|上线|当前.*状态|回归|发布)/i.test(text)) { return "project_progress"; } return null; } function inferProjectAutoMemoryType(text: string): Exclude | null { if (!text.trim()) return null; if (/(阻塞|卡住|失败|异常|报错|问题|bug|未打通)/i.test(text)) { return "blocking_issue"; } if (/(风险|隐患|告警)/i.test(text)) { return "risk"; } if (/(决定|改成|采用|统一|确定|方案)/i.test(text)) { return "decision"; } if (/(调研|研究|结论)/i.test(text)) { return "research_note"; } if (/(进度|完成|已接通|已打通|上线|当前.*状态|回归|发布)/i.test(text)) { return "project_progress"; } if (/(规则|约束|优先|先.*再|必须|不要|需要|流程|逻辑)/i.test(text)) { return "workflow_rule"; } return null; } function buildAutoMemoryTitle(memoryType: MasterMemoryType, label?: string) { const typeLabel = memoryType === "user_preference" ? "偏好" : memoryType === "workflow_rule" ? "工作规则" : memoryType === "blocking_issue" ? "阻塞" : memoryType === "risk" ? "风险" : memoryType === "decision" ? "决策" : memoryType === "research_note" ? "调研结论" : "项目进度"; return label ? `${label} · ${typeLabel}` : typeLabel; } function detectReferencedProjectForMemory(state: BossState, text: string) { const lowered = text.toLowerCase(); const candidates = state.projects .filter((project) => project.id !== "master-agent") .flatMap((project) => { const rawAliases = [ project.id, project.name, project.threadMeta.folderName, project.threadMeta.threadDisplayName, ] .map((value) => value.trim()) .filter(Boolean); const aliases = Array.from( new Set( rawAliases.flatMap((alias) => { const normalized = alias.trim(); if (!normalized) { return []; } const tokenCandidates = normalized .split(/[\s\-_/]+/) .map((token) => token.trim()) .filter((token) => token.length >= 3); return [normalized, ...tokenCandidates]; }), ), ); return aliases.map((alias) => ({ projectId: project.id, projectName: project.name, alias, })); }) .sort((left, right) => right.alias.length - left.alias.length); return candidates.find((candidate) => lowered.includes(candidate.alias.toLowerCase())) ?? null; } function upsertAutoMasterMemoryInState( state: BossState, input: { account: string; scope: MasterMemoryScope; projectId?: string; title: string; content: string; memoryType: MasterMemoryType; tags: string[]; sourceMessageId?: string; }, ) { const now = nowIso(); const existing = state.masterAgentMemories.find( (memory) => memory.account === input.account && memory.scope === input.scope && (memory.projectId ?? undefined) === (input.projectId ?? undefined) && memory.title === input.title, ); if (existing) { existing.content = input.content; existing.memoryType = input.memoryType; existing.tags = normalizeMasterMemoryTags(input.tags); existing.sourceMessageId = input.sourceMessageId ?? existing.sourceMessageId; existing.archived = false; existing.updatedAt = now; existing.lastUsedAt = now; return existing; } const memory: MasterAgentMemory = { memoryId: randomToken("memory"), account: input.account, scope: input.scope, projectId: input.scope === "project" ? input.projectId : undefined, title: input.title, content: input.content, memoryType: input.memoryType, tags: normalizeMasterMemoryTags(input.tags), sourceMessageId: input.sourceMessageId, createdAt: now, updatedAt: now, lastUsedAt: now, archived: false, }; state.masterAgentMemories.unshift(memory); return memory; } function autoCaptureMasterAgentMemoriesInState( state: BossState, input: { account: string; requestText: string; replyText: string; sourceMessageId?: string; }, ) { const requestText = normalizeAutoMemoryText(input.requestText); const replyText = normalizeAutoMemoryText(input.replyText); if (!requestText && !replyText) { return []; } const createdOrUpdated: MasterAgentMemory[] = []; const combined = [requestText, replyText].filter(Boolean).join(" "); const preferenceCandidate = isLowValueAutoMemoryText(requestText) ? "" : requestText; const projectCandidate = isLowValueAutoMemoryText(replyText) ? combined : replyText || combined; const preferenceType = inferAutoMemoryType(preferenceCandidate); if ( preferenceCandidate && (preferenceType === "user_preference" || preferenceType === "workflow_rule") ) { createdOrUpdated.push( upsertAutoMasterMemoryInState(state, { account: input.account, scope: "global", title: buildAutoMemoryTitle(preferenceType), content: preferenceCandidate, memoryType: preferenceType, tags: preferenceType === "user_preference" ? ["用户偏好"] : ["工作方式"], sourceMessageId: input.sourceMessageId, }), ); } const referencedProject = detectReferencedProjectForMemory(state, combined); const projectType = inferProjectAutoMemoryType(projectCandidate) ?? inferProjectAutoMemoryType(combined); if (referencedProject && projectType && !isLowValueAutoMemoryText(projectCandidate)) { createdOrUpdated.push( upsertAutoMasterMemoryInState(state, { account: input.account, scope: "project", projectId: referencedProject.projectId, title: buildAutoMemoryTitle(projectType, referencedProject.projectName), content: projectCandidate, memoryType: projectType, tags: [referencedProject.projectName, referencedProject.alias], sourceMessageId: input.sourceMessageId, }), ); } return createdOrUpdated; } function preferredDeviceForAccount( state: BossState, account: string, preferredDeviceId?: string, ) { if (preferredDeviceId) { const preferred = state.devices.find( (device) => device.id === preferredDeviceId && device.source === "production" && device.account === account, ); if (preferred) { return preferred; } } return ( state.devices.find( (device) => device.source === "production" && device.account === account, ) ?? null ); } export function getPreferredDeviceIdForAccountFromState( state: BossState, account: string, preferredDeviceId?: string, ) { return preferredDeviceForAccount(state, account, preferredDeviceId)?.id; } export async function getPreferredDeviceIdForAccount( account: string, preferredDeviceId?: string, ) { const state = await readState(); return getPreferredDeviceIdForAccountFromState(state, account, preferredDeviceId); } export async function toggleGoal(projectId: string, goalId: string) { return mutateState((state) => { const project = state.projects.find((item) => item.id === projectId); if (!project) throw new Error("PROJECT_NOT_FOUND"); const goal = project.goals.find((item) => item.id === goalId); if (!goal) throw new Error("GOAL_NOT_FOUND"); if (goal.state === "completed") { goal.state = "pending"; goal.completedAt = undefined; goal.completedBy = undefined; goal.note = "重新打开 · 等待继续执行"; } else { goal.state = "completed"; goal.completedAt = nowIso(); goal.completedBy = "用户 / 主 Agent"; goal.note = "已完成 · 由用户或主 Agent 标记"; } project.lastMessageAt = nowIso(); return goal; }); } export async function updateGoalText(projectId: string, goalId: string, text: string) { return mutateState((state) => { const project = state.projects.find((item) => item.id === projectId); if (!project) throw new Error("PROJECT_NOT_FOUND"); const goal = project.goals.find((item) => item.id === goalId); if (!goal) throw new Error("GOAL_NOT_FOUND"); if (!text.trim()) throw new Error("GOAL_TEXT_REQUIRED"); goal.text = text.trim(); goal.note = "已编辑 · 主 Agent 将据此重排后续任务"; project.lastMessageAt = nowIso(); return goal; }); } export async function createGoal(projectId: string, text: string) { return mutateState((state) => { const project = state.projects.find((item) => item.id === projectId); if (!project) throw new Error("PROJECT_NOT_FOUND"); if (!text.trim()) throw new Error("GOAL_TEXT_REQUIRED"); const goal: GoalItem = { id: randomToken("goal"), text: text.trim(), state: "pending", note: "新建目标 · 等待主 Agent 安排优先级", }; project.goals.unshift(goal); project.lastMessageAt = nowIso(); return goal; }); } export async function issueVerificationCode( account: string, purpose: VerificationCode["purpose"], ) { return mutateState((state) => { validateVerificationPurpose(state, account, purpose); validateVerificationSendWindow(state, account, purpose); const code = getVerificationDeliveryMode() === "email" ? randomDigits(6) : getFixedVerificationCode(); const record: VerificationCode = { id: `${purpose}-${Date.now()}`, account, purpose, code, createdAt: nowIso(), expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), }; state.verificationCodes = [ record, ...state.verificationCodes.filter( (item) => !(item.account === account && item.purpose === purpose), ), ].slice(0, 30); recordVerificationDispatch( state, account, purpose, getVerificationDeliveryMode(), "requested", "验证码请求已创建,等待投递结果。", ); return record; }); } export function hashPassword(password: string) { const normalized = password.normalize("NFKC"); const salt = randomBytes(16).toString("hex"); const hash = scryptSync(normalized, `boss:${salt}`, 64).toString("hex"); return `scrypt$${salt}$${hash}`; } function hashPasswordLegacy(password: string) { return createHash("sha256").update(`boss:${password.normalize("NFKC")}`).digest("hex"); } function verifyPasswordHash(password: string, passwordHash: string) { const normalized = password.normalize("NFKC"); if (!passwordHash.startsWith("scrypt$")) { return passwordHash === hashPasswordLegacy(normalized); } const [, salt, expectedHash] = passwordHash.split("$"); if (!salt || !expectedHash) return false; const expectedBuffer = Buffer.from(expectedHash, "hex"); const actualBuffer = scryptSync(normalized, `boss:${salt}`, expectedBuffer.length); return expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer); } function recordVerificationDispatch( state: BossState, account: string, purpose: VerificationCode["purpose"], deliveryMode: VerificationDeliveryMode, status: VerificationDispatch["status"], note: string, ) { state.verificationDispatches.unshift({ dispatchId: randomToken("vdispatch"), account, purpose, deliveryMode, requestedAt: nowIso(), status, note, }); } function validateVerificationSendWindow( state: BossState, account: string, purpose: VerificationCode["purpose"], ) { const relevant = state.verificationDispatches .filter((item) => item.account === account && item.purpose === purpose) .sort((a, b) => b.requestedAt.localeCompare(a.requestedAt)); const latest = relevant[0]; if (latest && Date.now() - new Date(latest.requestedAt).getTime() < VERIFICATION_SEND_COOLDOWN_MS) { throw new Error("VERIFICATION_CODE_COOLDOWN"); } const recentCount = relevant.filter( (item) => Date.now() - new Date(item.requestedAt).getTime() < VERIFICATION_SEND_WINDOW_MS, ).length; if (recentCount >= VERIFICATION_SEND_WINDOW_LIMIT) { throw new Error("VERIFICATION_RATE_LIMITED"); } } function validateVerificationPurpose( state: BossState, account: string, purpose: VerificationCode["purpose"], ) { const existing = state.authAccounts.find((item) => item.account === account); if (purpose === "register") { if (existing) { throw new Error("ACCOUNT_ALREADY_EXISTS"); } return; } if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } } function findValidCodeIndex( state: BossState, account: string, purpose: VerificationCode["purpose"], code: string, ) { const index = state.verificationCodes.findIndex( (item) => item.account === account && item.purpose === purpose && item.code === code, ); if (index < 0) return null; const record = state.verificationCodes[index]; if (new Date(record.expiresAt).getTime() <= Date.now()) { return null; } return index; } function consumeVerificationCode( state: BossState, account: string, purpose: VerificationCode["purpose"], code: string, ) { const index = findValidCodeIndex(state, account, purpose, code); if (index === null) return false; if (index >= 0) { state.verificationCodes.splice(index, 1); } return true; } function shouldAcceptDirectFixedVerificationCode( purpose: VerificationCode["purpose"], code?: string, ) { if (purpose !== "login") return false; if (getVerificationDeliveryMode() !== "fixed") return false; return code?.trim() === getFixedVerificationCode(); } function isLikelyEmailAccount(account: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(account); } function verificationRecipientForAccount(state: BossState, account: string) { if (isLikelyEmailAccount(account)) { return account; } return state.authAccounts.find((item) => item.account === account)?.verificationEmail ?? null; } function registerLoginFailure(account: AuthAccount) { const attempts = (account.failedLoginAttempts ?? 0) + 1; account.failedLoginAttempts = attempts; account.updatedAt = nowIso(); if (attempts >= AUTH_LOGIN_LOCK_THRESHOLD) { account.lockedUntil = new Date(Date.now() + AUTH_LOGIN_LOCK_MS).toISOString(); account.failedLoginAttempts = 0; } } function clearLoginFailure(account: AuthAccount) { account.failedLoginAttempts = 0; account.lockedUntil = undefined; } function activeAuthSession(state: BossState, token?: string | null) { if (!token) return null; const session = state.authSessions.find((item) => item.sessionToken === token); if (!session || session.revokedAt) return null; if (new Date(session.expiresAt).getTime() <= Date.now()) { session.revokedAt = nowIso(); return null; } return session; } function activeAuthSessionByRestoreToken(state: BossState, restoreToken?: string | null) { if (!restoreToken) return null; const session = state.authSessions.find((item) => item.restoreToken === restoreToken); if (!session || session.revokedAt) return null; if (new Date(session.expiresAt).getTime() <= Date.now()) { session.revokedAt = nowIso(); return null; } return session; } export async function createAuthSession(params: { account: string; role: AuthRole; displayName: string; loginMethod: LoginMethod; }) { return mutateState((state) => { state.authSessions = state.authSessions.filter((session) => session.account !== params.account); const createdAt = nowIso(); const session: AuthSession = { sessionId: randomToken("session"), sessionToken: randomBytes(24).toString("hex"), restoreToken: randomBytes(24).toString("hex"), account: params.account, role: params.role, displayName: params.displayName, loginMethod: params.loginMethod, createdAt, expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(), lastSeenAt: createdAt, }; state.authSessions.unshift(session); return session; }); } export async function createPrimaryAdminSession() { return mutateState((state) => { let existing = state.authAccounts.find((item) => item.account === PRIMARY_ADMIN_ACCOUNT); if (!existing) { existing = { id: `account-${PRIMARY_ADMIN_ACCOUNT}`, account: PRIMARY_ADMIN_ACCOUNT, passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD), displayName: "Boss 超级管理员", role: "highest_admin", verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL, codexNodeId: PRIMARY_CODEX_NODE_ID, codexNodeLabel: PRIMARY_CODEX_NODE_LABEL, primaryDeviceId: PRIMARY_CODEX_NODE_ID, isPrimary: true, createdAt: nowIso(), updatedAt: nowIso(), }; state.authAccounts.unshift(existing); } clearLoginFailure(existing); existing.updatedAt = nowIso(); existing.lastLoginAt = nowIso(); existing.lastLoginMethod = "password"; const session = { sessionId: randomToken("session"), sessionToken: randomBytes(24).toString("hex"), restoreToken: randomBytes(24).toString("hex"), account: existing.account, role: existing.role, displayName: existing.displayName, loginMethod: "password" as const, createdAt: nowIso(), expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(), lastSeenAt: nowIso(), } satisfies AuthSession; state.authSessions = [ session, ...state.authSessions.filter((item) => item.account !== existing.account), ].slice(0, 20); return { account: existing.account, role: existing.role, displayName: existing.displayName, loginMethod: session.loginMethod, sessionToken: session.sessionToken, restoreToken: session.restoreToken, sessionExpiresAt: session.expiresAt, }; }); } export async function getVerificationDeliveryTarget(account: string) { const state = await readState(); return verificationRecipientForAccount(state, account); } export async function getAuthSession(token?: string | null) { return mutateState((state) => { const session = activeAuthSession(state, token); if (!session) return null; session.lastSeenAt = nowIso(); return { ...session }; }); } export async function restoreAuthSession(restoreToken?: string | null) { return mutateState((state) => { const session = activeAuthSessionByRestoreToken(state, restoreToken); if (!session) return null; session.lastSeenAt = nowIso(); return { ...session }; }); } export async function revokeAuthSession(token?: string | null) { if (!token) return; await mutateState((state) => { const session = state.authSessions.find((item) => item.sessionToken === token); if (session && !session.revokedAt) { session.revokedAt = nowIso(); } }); } function setActiveAiAccountInState( state: BossState, accountId: string, reason: string, options?: { preserveLastSwitchAt?: boolean }, ) { const currentActive = state.aiAccounts.find((item) => item.isActive); const nextActive = state.aiAccounts.find((item) => item.accountId === accountId); if (!nextActive) { throw new Error("AI_ACCOUNT_NOT_FOUND"); } const switchedAt = nowIso(); for (const account of state.aiAccounts) { account.isActive = account.accountId === accountId; account.updatedAt = switchedAt; if (account.accountId === accountId) { account.lastSwitchedAt = options?.preserveLastSwitchAt ? account.lastSwitchedAt : switchedAt; account.switchReason = reason; } } if (currentActive?.accountId !== nextActive.accountId || reason !== nextActive.switchReason) { state.aiAccountSwitchHistory.unshift({ switchId: randomToken("aiswitch"), fromAccountId: currentActive?.accountId, fromLabel: currentActive?.label, toAccountId: nextActive.accountId, toLabel: nextActive.label, role: nextActive.role, switchedAt, reason, }); } return nextActive; } export async function listAiAccounts() { const state = await readState(); return { accounts: getAiAccountSummariesFromState(state), activeIdentity: getMasterIdentitySummaryFromState(state), switchHistory: [...state.aiAccountSwitchHistory].sort((a, b) => b.switchedAt.localeCompare(a.switchedAt), ), }; } export async function getAiAccount(accountId: string) { const state = await readState(); const account = state.aiAccounts.find((item) => item.accountId === accountId); if (!account) return null; return buildAiAccountSummary(account); } export async function getRuntimeAiAccountById(accountId: string) { if (accountId === ENV_OPENAI_ACCOUNT_ID) { return getEnvOpenAiAccount(); } const state = await readState(); return state.aiAccounts.find((item) => item.accountId === accountId) ?? null; } export async function saveAiAccount(payload: { accountId?: string; label: string; role: AiAccountRole; provider: AiProvider; displayName: string; accountIdentifier?: string; nodeId?: string; nodeLabel?: string; model?: string; apiKey?: string; enabled?: boolean; setActive?: boolean; loginStatusNote?: string; }) { return mutateState((state) => { const existing = payload.accountId ? state.aiAccounts.find((item) => item.accountId === payload.accountId) : null; const accountId = existing?.accountId ?? payload.accountId?.trim() ?? `ai-${slugify(`${payload.label}-${payload.displayName}`)}`; const defaultModel = payload.provider === "aliyun_qwen_api" ? "qwen3.5-plus" : payload.provider === "openai_api" ? "gpt-5.4" : undefined; const next: AiAccount = normalizeAiAccount({ accountId, label: payload.label.trim() || aiRoleLabel(payload.role), role: payload.role, provider: payload.provider, displayName: payload.displayName.trim() || "未命名 AI", accountIdentifier: payload.accountIdentifier?.trim() || undefined, nodeId: payload.nodeId?.trim() || undefined, nodeLabel: payload.nodeLabel?.trim() || undefined, model: payload.model?.trim() || defaultModel, apiKey: isApiKeyProvider(payload.provider) ? payload.apiKey?.trim() ? payload.apiKey.trim() : existing?.apiKey : undefined, apiKeyMasked: isApiKeyProvider(payload.provider) ? maskApiKey(payload.apiKey?.trim() || existing?.apiKey) : undefined, enabled: payload.enabled ?? existing?.enabled ?? true, isActive: existing?.isActive ?? false, status: isApiKeyProvider(payload.provider) ? payload.apiKey?.trim() || existing?.apiKey ? existing?.status === "degraded" ? "degraded" : "ready" : "needs_api_key" : payload.nodeId?.trim() || existing?.nodeId ? existing?.status === "degraded" ? "degraded" : "ready" : "needs_login", loginStatusNote: payload.loginStatusNote?.trim() || existing?.loginStatusNote, lastValidatedAt: existing?.lastValidatedAt, lastUsedAt: existing?.lastUsedAt, lastError: existing?.lastError, lastSwitchedAt: existing?.lastSwitchedAt, switchReason: existing?.switchReason, createdAt: existing?.createdAt ?? nowIso(), updatedAt: nowIso(), }); if (existing) { const index = state.aiAccounts.findIndex((item) => item.accountId === existing.accountId); state.aiAccounts[index] = next; } else { state.aiAccounts.unshift(next); } if (payload.setActive ?? (!existing && next.role === "primary")) { setActiveAiAccountInState(state, next.accountId, existing ? "手动更新 AI 账号配置" : "新增 AI 账号并设为当前主控"); } return buildAiAccountSummary(next); }); } export async function deleteAiAccount(accountId: string) { if (accountId === ENV_OPENAI_ACCOUNT_ID) { throw new Error("ENV_AI_ACCOUNT_READ_ONLY"); } return mutateState((state) => { const target = state.aiAccounts.find((item) => item.accountId === accountId); if (!target) { throw new Error("AI_ACCOUNT_NOT_FOUND"); } state.aiAccounts = state.aiAccounts.filter((item) => item.accountId !== accountId); if (target.isActive) { const fallback = sortAiAccounts(state.aiAccounts).find((item) => item.enabled); if (fallback) { setActiveAiAccountInState(state, fallback.accountId, `删除 ${target.label} 后自动切换`); } } return true; }); } export async function activateAiAccount(accountId: string, reason: string) { if (accountId === ENV_OPENAI_ACCOUNT_ID) { const state = await readState(); return { activeIdentity: { ...getMasterIdentitySummaryFromState(state), accountId: ENV_OPENAI_ACCOUNT_ID, label: "API 容灾", role: "api_fallback" as const, roleLabel: aiRoleLabel("api_fallback"), provider: "openai_api" as const, providerLabel: aiProviderLabel("openai_api"), isEnvironmentFallback: true, }, }; } return mutateState((state) => { setActiveAiAccountInState(state, accountId, reason); return { activeIdentity: getMasterIdentitySummaryFromState(state), }; }); } export async function updateAiAccountHealth(params: { accountId: string; status: AiAccountStatus; lastError?: string; lastValidatedAt?: string; lastUsedAt?: string; switchReason?: string; activate?: boolean; }) { if (params.accountId === ENV_OPENAI_ACCOUNT_ID) { return; } await mutateState((state) => { const account = state.aiAccounts.find((item) => item.accountId === params.accountId); if (!account) { throw new Error("AI_ACCOUNT_NOT_FOUND"); } account.status = params.status; account.lastError = params.lastError; account.lastValidatedAt = params.lastValidatedAt ?? account.lastValidatedAt; account.lastUsedAt = params.lastUsedAt ?? account.lastUsedAt; account.updatedAt = nowIso(); if (params.switchReason) { account.switchReason = params.switchReason; account.lastSwitchedAt = nowIso(); } if (params.activate) { setActiveAiAccountInState( state, account.accountId, params.switchReason ?? "AI 账号状态更新后设为当前主控", { preserveLastSwitchAt: true }, ); } }); } export async function getMasterAgentRuntimeAccount() { const state = await readState(); const resolved = resolveActiveAiAccount(state); if (!resolved.account) { return null; } return { account: resolved.account, summary: getMasterIdentitySummaryFromState(state), isEnvironmentFallback: resolved.isEnvironmentFallback, }; } export async function queueMasterAgentTask(payload: { taskId?: string; projectId?: string; taskType?: MasterAgentTaskType; requestMessageId: string; requestText: string; executionPrompt: string; requestedBy: string; requestedByAccount: string; deviceId: string; accountId?: string; accountLabel?: string; attachmentId?: string; attachmentFileName?: string; attachmentDownloadToken?: string; attachmentDownloadExpiresAt?: string; attachmentDownloadUrl?: string; attachmentTextExcerpt?: string; deviceImportDraftId?: string; dispatchExecutionId?: string; targetProjectId?: string; targetThreadId?: string; targetThreadDisplayName?: string; targetCodexThreadRef?: string; targetCodexFolderRef?: string; }) { const task = await mutateState((state) => { const task: MasterAgentTask = { taskId: payload.taskId ?? randomToken("mastertask"), projectId: payload.projectId ?? "master-agent", taskType: payload.taskType ?? "conversation_reply", requestMessageId: payload.requestMessageId, requestText: payload.requestText, executionPrompt: payload.executionPrompt, requestedBy: payload.requestedBy, requestedByAccount: payload.requestedByAccount, deviceId: payload.deviceId, accountId: payload.accountId, accountLabel: payload.accountLabel, attachmentId: payload.attachmentId, attachmentFileName: payload.attachmentFileName, attachmentDownloadToken: payload.attachmentDownloadToken, attachmentDownloadExpiresAt: payload.attachmentDownloadExpiresAt, attachmentDownloadUrl: payload.attachmentDownloadUrl, attachmentTextExcerpt: payload.attachmentTextExcerpt, deviceImportDraftId: payload.deviceImportDraftId, dispatchExecutionId: payload.dispatchExecutionId, targetProjectId: payload.targetProjectId, targetThreadId: payload.targetThreadId, targetThreadDisplayName: payload.targetThreadDisplayName, targetCodexThreadRef: payload.targetCodexThreadRef, targetCodexFolderRef: payload.targetCodexFolderRef, status: "queued", requestedAt: nowIso(), }; state.masterAgentTasks.unshift(task); return task; }); publishBossEvent("master_agent.task.updated", { taskId: task.taskId, deviceId: task.deviceId, status: task.status, }); return task; } export async function createDispatchPlan(input: { groupProjectId: string; requestMessageId: string; requestedBy: string; summary?: string; targets: DispatchPlanTarget[]; }) { return mutateState((state) => { return upsertDispatchPlanInState(state, input); }); } function upsertDispatchPlanInState( state: BossState, input: { groupProjectId: string; requestMessageId: string; requestedBy: string; summary?: string; targets: DispatchPlanTarget[]; }, ) { const groupProjectId = input.groupProjectId.trim(); const requestMessageId = input.requestMessageId.trim(); const requestedBy = input.requestedBy.trim(); const summary = input.summary?.trim() ?? ""; if (!groupProjectId) throw new Error("DISPATCH_PLAN_GROUP_PROJECT_REQUIRED"); if (!requestMessageId) throw new Error("DISPATCH_PLAN_REQUEST_MESSAGE_REQUIRED"); if (!requestedBy) throw new Error("DISPATCH_PLAN_REQUESTED_BY_REQUIRED"); const groupProject = state.projects.find((item) => item.id === groupProjectId); if (!groupProject) throw new Error("DISPATCH_PLAN_GROUP_PROJECT_NOT_FOUND"); if (!groupProject.isGroup) throw new Error("DISPATCH_PLAN_GROUP_PROJECT_INVALID"); const validatedTargets = normalizeDispatchPlanTargetsForCreate(state, input.targets); const existing = state.dispatchPlans.find( (plan) => plan.groupProjectId === groupProjectId && plan.requestMessageId === requestMessageId, ); if (existing) { const payloadMatches = existing.requestedBy === requestedBy && existing.summary === summary && sameDispatchPlanTargets(existing.targets, validatedTargets); if (!payloadMatches) { throw new Error("DISPATCH_PLAN_RETRY_MISMATCH"); } if (groupProject.collaborationMode === "approval_required") { groupProject.approvalState = "pending_user"; } return existing; } const plan: DispatchPlan = { planId: randomToken("dispatch-plan"), groupProjectId, requestMessageId, requestedBy, status: "pending_user_confirmation", targets: validatedTargets, summary, createdAt: nowIso(), }; state.dispatchPlans.unshift(plan); if (groupProject.collaborationMode === "approval_required") { groupProject.approvalState = "pending_user"; const targetSummary = validatedTargets .map((target) => { const project = state.projects.find((item) => item.id === target.projectId); return `《${project?.threadMeta.threadDisplayName ?? project?.name ?? target.projectId}》`; }) .join("、"); pushProjectLedgerMessage(state, groupProjectId, { sender: "master", senderLabel: "主 Agent", body: `主 Agent 已生成推荐,等待你确认后再下发到 ${validatedTargets.length} 个线程:${targetSummary}。`, kind: "system_notice", }); } else { groupProject.approvalState = "not_required"; } return plan; } export async function listDispatchPlansByProject(groupProjectId: string) { const state = await readState(); const normalizedGroupProjectId = groupProjectId.trim(); return state.dispatchPlans .filter((plan) => plan.groupProjectId === normalizedGroupProjectId) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); } function applyDispatchPlanConfirmationInState( state: BossState, input: { planId: string; confirmedBy: string; approvedTargetProjectIds: string[]; }, ) { const plan = state.dispatchPlans.find((item) => item.planId === input.planId); if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND"); if (plan.status === "rejected") throw new Error("DISPATCH_PLAN_REJECTED"); const confirmedBy = input.confirmedBy.trim(); if (!confirmedBy) throw new Error("DISPATCH_PLAN_CONFIRMED_BY_REQUIRED"); requireDispatchActorSession(state, confirmedBy); const approvedTargetProjectIds = normalizeStringSet(input.approvedTargetProjectIds); if (approvedTargetProjectIds.length === 0) { throw new Error("DISPATCH_PLAN_APPROVED_TARGETS_REQUIRED"); } const canonicalTargetProjectIds = normalizeStringSet(plan.targets.map((target) => target.projectId)); if (approvedTargetProjectIds.some((projectId) => !canonicalTargetProjectIds.includes(projectId))) { throw new Error("DISPATCH_PLAN_APPROVED_TARGETS_INVALID"); } if (plan.confirmedBy && plan.confirmedBy !== confirmedBy) { throw new Error("DISPATCH_PLAN_CONFIRMED_BY_MISMATCH"); } if (plan.confirmedTargetProjectIds?.length && !sameStringSet(plan.confirmedTargetProjectIds, approvedTargetProjectIds)) { throw new Error("DISPATCH_PLAN_APPROVED_TARGETS_MISMATCH"); } if (plan.status !== "dispatched") { plan.status = "approved"; } if (!plan.confirmedAt) { plan.confirmedAt = nowIso(); } plan.confirmedBy = confirmedBy; plan.confirmedTargetProjectIds = approvedTargetProjectIds; return plan; } export async function confirmDispatchPlan(input: { planId: string; confirmedBy: string; approvedTargetProjectIds: string[]; }) { return mutateState((state) => { return applyDispatchPlanConfirmationInState(state, input); }); } export async function rejectDispatchPlan(input: { groupProjectId: string; planId: string; rejectedBy: string; }) { const result = await mutateState((state) => { const groupProjectId = input.groupProjectId.trim(); if (!groupProjectId) throw new Error("PROJECT_NOT_FOUND"); const groupProject = state.projects.find((item) => item.id === groupProjectId); if (!groupProject) throw new Error("PROJECT_NOT_FOUND"); if (!groupProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT"); requireDispatchActorSession(state, input.rejectedBy); const plan = state.dispatchPlans.find((item) => item.planId === input.planId); if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND"); if (plan.groupProjectId !== groupProjectId) { throw new Error("DISPATCH_PLAN_PROJECT_MISMATCH"); } if (plan.status === "dispatched") { throw new Error("DISPATCH_PLAN_ALREADY_DISPATCHED"); } if (plan.status !== "rejected") { plan.status = "rejected"; } groupProject.approvalState = "rejected"; const notice = pushProjectLedgerMessage(state, groupProjectId, { sender: "master", senderLabel: "主 Agent", body: "已拒绝主 Agent 推荐,本次不会下发到任何线程。", kind: "system_notice", }) ?? null; return { plan: { ...plan }, notice: notice ? { ...notice } : null, collaborationGate: buildCollaborationGate(groupProject), }; }); publishBossEvent("project.messages.updated", { projectId: input.groupProjectId }); publishBossEvent("conversation.updated", { projectId: input.groupProjectId }); return result; } export async function createDispatchExecutionsFromPlan(input: { planId: string; confirmedBy: string; }) { return mutateState((state) => { const plan = state.dispatchPlans.find((item) => item.planId === input.planId); if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND"); if (plan.status === "rejected") throw new Error("DISPATCH_PLAN_REJECTED"); const confirmedBy = input.confirmedBy.trim(); if (!confirmedBy) throw new Error("DISPATCH_PLAN_CONFIRMED_BY_REQUIRED"); requireDispatchActorSession(state, confirmedBy); if (!plan.confirmedAt || !plan.confirmedBy || !plan.confirmedTargetProjectIds?.length) { throw new Error("DISPATCH_PLAN_NOT_CONFIRMED"); } if (plan.confirmedBy !== confirmedBy) { throw new Error("DISPATCH_PLAN_CONFIRMED_BY_MISMATCH"); } const groupProject = state.projects.find((item) => item.id === plan.groupProjectId); if (!groupProject) throw new Error("PROJECT_NOT_FOUND"); const canonicalTargetProjectIds = normalizeStringSet(plan.confirmedTargetProjectIds); const existingExecutions = state.dispatchExecutions.filter((item) => item.planId === plan.planId); if (existingExecutions.length > 0) { const existingTargetIds = normalizeStringSet(existingExecutions.map((execution) => execution.targetProjectId)); if (!sameStringSet(existingTargetIds, canonicalTargetProjectIds)) { throw new Error("DISPATCH_EXECUTION_SET_MISMATCH"); } if (plan.status !== "dispatched") { plan.status = "dispatched"; } groupProject.approvalState = groupProject.collaborationMode === "approval_required" ? "approved" : "not_required"; ensureDispatchExecutionTasksInState(state, plan, existingExecutions); return existingExecutions; } const targets = plan.targets.filter((target) => canonicalTargetProjectIds.includes(target.projectId), ); if (targets.length === 0) { throw new Error("DISPATCH_EXECUTION_TARGETS_REQUIRED"); } const createdAt = nowIso(); const executions = targets.map((target) => { const execution: DispatchExecution = { executionId: randomToken("dispatch-exec"), planId: plan.planId, groupProjectId: plan.groupProjectId, targetProjectId: target.projectId, targetThreadId: target.threadId, deviceId: target.deviceId, status: "queued", createdAt, }; state.dispatchExecutions.unshift(execution); return execution; }); ensureDispatchExecutionTasksInState(state, plan, executions); plan.status = "dispatched"; groupProject.approvalState = groupProject.collaborationMode === "approval_required" ? "approved" : "not_required"; return executions; }); } function buildDispatchExecutionPrompt(input: { groupProject: Project; plan: DispatchPlan; target: DispatchPlanTarget; }) { const requestMessage = input.groupProject.messages.find( (message) => message.id === input.plan.requestMessageId, ); const requestText = requestMessage?.body ?? input.plan.summary; return [ "你正在执行 Boss 控制台的线程分发任务。", "你的输出必须是 JSON,并且只能包含两个字符串字段:rawThreadReply、replyBody。", "rawThreadReply:写成目标线程直接回到群里的原始结果,不要冒充主 Agent。", "replyBody:写成主 Agent 给群里的简短汇总,必须保留“主 Agent 汇总:”前缀。", "不要输出 Markdown 代码块,不要输出额外解释。", `groupProjectId: ${input.groupProject.id}`, `groupProjectName: ${input.groupProject.name}`, `threadProjectId: ${input.target.projectId}`, `threadId: ${input.target.threadId}`, `threadTitle: ${input.target.threadDisplayName}`, `folderName: ${input.target.folderName}`, `requestText: ${requestText}`, `dispatchSummary: ${input.plan.summary}`, ].join("\n"); } function ensureDispatchExecutionTaskInState( state: BossState, plan: DispatchPlan, execution: DispatchExecution, ) { const groupProject = state.projects.find((item) => item.id === execution.groupProjectId); if (!groupProject) { throw new Error("DISPATCH_EXECUTION_GROUP_PROJECT_NOT_FOUND"); } const target = plan.targets.find( (item) => item.projectId === execution.targetProjectId && item.threadId === execution.targetThreadId && item.deviceId === execution.deviceId, ); if (!target) { throw new Error("DISPATCH_EXECUTION_TARGET_NOT_FOUND"); } const existing = state.masterAgentTasks.find( (task) => task.taskType === "dispatch_execution" && (task.dispatchExecutionId === execution.executionId || (task.projectId === execution.groupProjectId && task.requestMessageId === plan.planId && task.targetProjectId === execution.targetProjectId && task.targetThreadId === execution.targetThreadId)), ); if (existing) { existing.dispatchExecutionId = existing.dispatchExecutionId ?? execution.executionId; existing.targetProjectId = existing.targetProjectId ?? execution.targetProjectId; existing.targetThreadId = existing.targetThreadId ?? execution.targetThreadId; existing.targetThreadDisplayName = existing.targetThreadDisplayName ?? target.threadDisplayName; existing.targetCodexThreadRef = existing.targetCodexThreadRef ?? target.codexThreadRef; existing.targetCodexFolderRef = existing.targetCodexFolderRef ?? target.codexFolderRef; existing.executionPrompt = existing.executionPrompt || buildDispatchExecutionPrompt({ groupProject, plan, target, }); return existing; } const requestedBy = plan.confirmedBy ?? plan.requestedBy; const requestMessage = groupProject.messages.find((message) => message.id === plan.requestMessageId); const task: MasterAgentTask = { taskId: randomToken("mastertask"), projectId: execution.groupProjectId, taskType: "dispatch_execution", requestMessageId: plan.planId, requestText: requestMessage?.body ?? plan.summary, executionPrompt: buildDispatchExecutionPrompt({ groupProject, plan, target, }), requestedBy, requestedByAccount: requestedBy, deviceId: execution.deviceId, dispatchExecutionId: execution.executionId, targetProjectId: execution.targetProjectId, targetThreadId: execution.targetThreadId, targetThreadDisplayName: target.threadDisplayName, targetCodexThreadRef: target.codexThreadRef, targetCodexFolderRef: target.codexFolderRef, status: "queued", requestedAt: nowIso(), }; state.masterAgentTasks.unshift(task); return task; } function ensureDispatchExecutionTasksInState( state: BossState, plan: DispatchPlan, executions: DispatchExecution[], ) { return executions.map((execution) => ensureDispatchExecutionTaskInState(state, plan, execution)); } export async function confirmDispatchPlanAndCreateExecutions(input: { groupProjectId: string; planId: string; confirmedBy: string; approvedTargetProjectIds: string[]; }) { const result = await mutateState((state) => { const groupProjectId = input.groupProjectId.trim(); if (!groupProjectId) throw new Error("PROJECT_NOT_FOUND"); const groupProject = state.projects.find((item) => item.id === groupProjectId); if (!groupProject) throw new Error("PROJECT_NOT_FOUND"); if (!groupProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT"); const plan = applyDispatchPlanConfirmationInState(state, { planId: input.planId, confirmedBy: input.confirmedBy, approvedTargetProjectIds: input.approvedTargetProjectIds, }); if (plan.groupProjectId !== groupProjectId) { throw new Error("DISPATCH_PLAN_PROJECT_MISMATCH"); } const canonicalTargetProjectIds = normalizeStringSet(plan.confirmedTargetProjectIds ?? []); const existingExecutions = state.dispatchExecutions.filter((item) => item.planId === plan.planId); let executions: DispatchExecution[]; let createdNotice: Message | null = null; if (existingExecutions.length > 0) { const existingTargetIds = normalizeStringSet(existingExecutions.map((execution) => execution.targetProjectId)); if (!sameStringSet(existingTargetIds, canonicalTargetProjectIds)) { throw new Error("DISPATCH_EXECUTION_SET_MISMATCH"); } if (plan.status !== "dispatched") { plan.status = "dispatched"; } groupProject.approvalState = groupProject.collaborationMode === "approval_required" ? "approved" : "not_required"; executions = existingExecutions; } else { const targets = plan.targets.filter((target) => canonicalTargetProjectIds.includes(target.projectId), ); if (targets.length === 0) { throw new Error("DISPATCH_EXECUTION_TARGETS_REQUIRED"); } const createdAt = nowIso(); executions = targets.map((target) => { const execution: DispatchExecution = { executionId: randomToken("dispatch-exec"), planId: plan.planId, groupProjectId: plan.groupProjectId, targetProjectId: target.projectId, targetThreadId: target.threadId, deviceId: target.deviceId, status: "queued", createdAt, }; state.dispatchExecutions.unshift(execution); return execution; }); plan.status = "dispatched"; groupProject.approvalState = groupProject.collaborationMode === "approval_required" ? "approved" : "not_required"; const targetSummary = executions .map((execution) => { const project = state.projects.find((item) => item.id === execution.targetProjectId); return `《${project?.threadMeta.threadDisplayName ?? project?.name ?? execution.targetProjectId}》`; }) .join("、"); createdNotice = pushProjectLedgerMessage(state, groupProjectId, { sender: "master", senderLabel: "主 Agent", body: `已确认下发到 ${executions.length} 个线程:${targetSummary}。`, kind: "system_notice", }); } ensureDispatchExecutionTasksInState(state, plan, executions); return { plan: { ...plan }, executions: executions.map((execution) => ({ ...execution })), notice: createdNotice ? { ...createdNotice } : null, collaborationGate: buildCollaborationGate(groupProject), }; }); publishBossEvent("project.messages.updated", { projectId: input.groupProjectId }); publishBossEvent("conversation.updated", { projectId: input.groupProjectId }); return result; } export async function completeDispatchExecution(payload: { executionId: string; completedByDeviceId: string; completedByDeviceToken: string; status: "completed" | "failed"; resultMessageId?: string; }) { return mutateState((state) => { const execution = state.dispatchExecutions.find((item) => item.executionId === payload.executionId); if (!execution) throw new Error("DISPATCH_EXECUTION_NOT_FOUND"); const deviceId = payload.completedByDeviceId.trim(); if (!deviceId) throw new Error("DISPATCH_EXECUTION_DEVICE_REQUIRED"); if (execution.deviceId !== deviceId) { throw new Error("DISPATCH_EXECUTION_DEVICE_MISMATCH"); } const device = state.devices.find((item) => item.id === deviceId); if (!device || device.source !== "production") { throw new Error("DISPATCH_EXECUTION_DEVICE_INVALID"); } if (!device.token || device.token !== payload.completedByDeviceToken.trim()) { throw new Error("DISPATCH_EXECUTION_DEVICE_TOKEN_INVALID"); } const nextResultMessageId = payload.resultMessageId?.trim() || undefined; if (execution.status === "completed" || execution.status === "failed") { const sameStatus = execution.status === payload.status; const sameResultMessageId = (execution.resultMessageId ?? undefined) === nextResultMessageId; if (!sameStatus || !sameResultMessageId) { throw new Error("DISPATCH_EXECUTION_COMPLETION_MISMATCH"); } return execution; } execution.status = payload.status; execution.completedAt = nowIso(); execution.resultMessageId = nextResultMessageId ?? execution.resultMessageId; execution.completedByDeviceId = deviceId; return execution; }); } function summarizeDispatchExecutionReply(rawThreadReply: string, threadTitle: string) { const compact = rawThreadReply.trim().replace(/\s+/g, " "); if (!compact) { return `主 Agent 汇总:${threadTitle} 已返回执行结果。`; } if (compact.length <= 72) { return `主 Agent 汇总:${threadTitle} 已返回执行结果:${compact}`; } return `主 Agent 汇总:${threadTitle} 已返回执行结果:${compact.slice(0, 69)}...`; } function appendDispatchExecutionResultInState( state: BossState, payload: { dispatchExecutionId: string; completedByDeviceId: string; status: "completed" | "failed"; groupProjectId: string; targetProjectId: string; targetThreadId: string; targetThreadDisplayName?: string; rawThreadReply?: string; masterSummary?: string; }, ) { const execution = state.dispatchExecutions.find( (item) => item.executionId === payload.dispatchExecutionId, ); if (!execution) throw new Error("DISPATCH_EXECUTION_NOT_FOUND"); if (execution.groupProjectId !== payload.groupProjectId) { throw new Error("DISPATCH_EXECUTION_GROUP_PROJECT_MISMATCH"); } if (execution.targetProjectId !== payload.targetProjectId) { throw new Error("DISPATCH_EXECUTION_TARGET_PROJECT_MISMATCH"); } if (execution.targetThreadId !== payload.targetThreadId) { throw new Error("DISPATCH_EXECUTION_TARGET_THREAD_MISMATCH"); } if (execution.deviceId !== payload.completedByDeviceId) { throw new Error("DISPATCH_EXECUTION_DEVICE_MISMATCH"); } const groupProject = state.projects.find((item) => item.id === payload.groupProjectId); if (!groupProject) { throw new Error("PROJECT_NOT_FOUND"); } const device = state.devices.find((item) => item.id === payload.completedByDeviceId); const threadTitle = payload.targetThreadDisplayName?.trim() || state.projects.find((item) => item.id === payload.targetProjectId)?.threadMeta.threadDisplayName || payload.targetThreadId; if (execution.status === "completed" || execution.status === "failed") { if (execution.status !== payload.status) { throw new Error("DISPATCH_EXECUTION_COMPLETION_MISMATCH"); } const existingMirroredResult = execution.resultMessageId ? findProjectMessage(groupProject, execution.resultMessageId) : null; if ( payload.status === "completed" && payload.rawThreadReply?.trim() && existingMirroredResult && existingMirroredResult.body !== payload.rawThreadReply.trim() ) { throw new Error("DISPATCH_EXECUTION_COMPLETION_MISMATCH"); } return { execution: { ...execution }, mirroredResult: existingMirroredResult, masterSummary: null, }; } let mirroredResult: Message | null = null; let masterSummary: Message | null = null; if (payload.status === "completed") { if (!payload.rawThreadReply?.trim()) { throw new Error("DISPATCH_EXECUTION_RAW_REPLY_REQUIRED"); } mirroredResult = pushProjectLedgerMessage(state, payload.groupProjectId, { sender: "device", senderLabel: `${threadTitle} · ${device?.name ?? payload.completedByDeviceId}`, body: payload.rawThreadReply.trim(), kind: "text", }); masterSummary = pushProjectLedgerMessage(state, payload.groupProjectId, { sender: "master", senderLabel: "主 Agent", body: payload.masterSummary?.trim() || summarizeDispatchExecutionReply(payload.rawThreadReply, threadTitle), kind: "text", }); } else { masterSummary = pushProjectLedgerMessage(state, payload.groupProjectId, { sender: "ops", senderLabel: "主 Agent Relay", body: `${threadTitle} 执行失败,请稍后重试。`, kind: "text", }); } execution.status = payload.status; execution.completedAt = nowIso(); execution.completedByDeviceId = payload.completedByDeviceId; execution.resultMessageId = mirroredResult?.id ?? execution.resultMessageId; return { execution: { ...execution }, mirroredResult, masterSummary, }; } export async function appendDispatchExecutionResult(payload: { dispatchExecutionId: string; completedByDeviceId: string; status: "completed" | "failed"; groupProjectId: string; targetProjectId: string; targetThreadId: string; targetThreadDisplayName?: string; rawThreadReply?: string; masterSummary?: string; }) { const result = await mutateState((state) => appendDispatchExecutionResultInState(state, payload), ); publishBossEvent("project.messages.updated", { projectId: payload.groupProjectId }); publishBossEvent("conversation.updated", { projectId: payload.groupProjectId }); return result; } export async function getMasterAgentTask(taskId: string) { const state = await readState(); return state.masterAgentTasks.find((item) => item.taskId === taskId) ?? null; } export async function reassignMasterAgentTaskExecution(payload: { taskId: string; deviceId: string; accountId?: string; accountLabel?: string; executionPrompt?: string; }) { const task = await mutateState((state) => { const next = state.masterAgentTasks.find((item) => item.taskId === payload.taskId); if (!next) { throw new Error("MASTER_AGENT_TASK_NOT_FOUND"); } if (next.status !== "queued") { return { ...next }; } next.deviceId = payload.deviceId; next.accountId = payload.accountId; next.accountLabel = payload.accountLabel; if (payload.executionPrompt?.trim()) { next.executionPrompt = payload.executionPrompt.trim(); } return { ...next }; }); publishBossEvent("master_agent.task.updated", { taskId: task.taskId, deviceId: task.deviceId, status: task.status, }); return task; } export async function claimNextMasterAgentTask(deviceId: string) { let attachmentProjectId: string | undefined; let dispatchExecutionProjectId: string | undefined; const task = await mutateState((state) => { const next = state.masterAgentTasks.find( (item) => item.deviceId === deviceId && item.status === "queued", ); if (!next) return null; next.status = "running"; next.claimedAt = nowIso(); if (next.taskType === "attachment_analysis" && next.attachmentId) { const project = state.projects.find((item) => item.id === next.projectId); const match = project ? findProjectAttachment(project, next.attachmentId) : null; if (match) { match.attachment.analysisState = "processing"; match.attachment.analysisSummary = undefined; match.attachment.analysisCardId = undefined; attachmentProjectId = next.projectId; } } if (next.taskType === "dispatch_execution" && next.dispatchExecutionId) { const execution = state.dispatchExecutions.find( (item) => item.executionId === next.dispatchExecutionId, ); if (execution && execution.status === "queued") { execution.status = "running"; dispatchExecutionProjectId = execution.groupProjectId; } } return { ...next }; }); if (task) { publishBossEvent("master_agent.task.updated", { taskId: task.taskId, deviceId: task.deviceId, status: task.status, }); if (attachmentProjectId) { publishBossEvent("project.messages.updated", { projectId: attachmentProjectId }); publishBossEvent("conversation.updated", { projectId: attachmentProjectId }); } if (dispatchExecutionProjectId) { publishBossEvent("conversation.updated", { projectId: dispatchExecutionProjectId }); } } return task; } export async function completeMasterAgentTask(payload: { taskId: string; deviceId: string; status: "completed" | "failed"; replyBody?: string; errorMessage?: string; requestId?: string; dispatchExecutionId?: string; targetProjectId?: string; targetThreadId?: string; rawThreadReply?: string; dispatchPlan?: { summary?: string; targets: DispatchPlanTarget[]; }; }) { const result = await mutateState((state) => { const task = state.masterAgentTasks.find((item) => item.taskId === payload.taskId); if (!task) { throw new Error("MASTER_AGENT_TASK_NOT_FOUND"); } if (task.deviceId !== payload.deviceId) { throw new Error("MASTER_AGENT_TASK_DEVICE_MISMATCH"); } task.status = payload.status; task.completedAt = nowIso(); task.replyBody = payload.replyBody?.trim() || undefined; task.errorMessage = payload.errorMessage?.trim() || undefined; task.requestId = payload.requestId; const linkedAccount = task.accountId ? state.aiAccounts.find((item) => item.accountId === task.accountId) : undefined; if (linkedAccount) { linkedAccount.updatedAt = task.completedAt; linkedAccount.lastUsedAt = task.completedAt; linkedAccount.lastValidatedAt = task.completedAt; linkedAccount.lastError = task.errorMessage; linkedAccount.status = payload.status === "completed" ? "ready" : "degraded"; if (!linkedAccount.isActive) { setActiveAiAccountInState( state, linkedAccount.accountId, payload.status === "completed" ? "Master Codex Node 完成回复后自动切回当前主控" : "Master Codex Node 失败后记录当前主控身份", { preserveLastSwitchAt: true }, ); } } let attachmentProjectId: string | undefined; let createdDispatchPlan: DispatchPlan | undefined; let dispatchExecutionResult: | ReturnType | undefined; if (task.taskType === "attachment_analysis" && task.attachmentId) { const project = state.projects.find((item) => item.id === task.projectId); const match = project ? findProjectAttachment(project, task.attachmentId) : null; if (match) { attachmentProjectId = project?.id; if (payload.status === "completed") { const summary = summarizeAttachmentAnalysis(task.replyBody ?? ""); match.attachment.analysisState = "completed"; match.attachment.analysisSummary = summary; pushProjectLedgerMessage(state, task.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", body: summary, kind: "text", }); if (task.replyBody) { const card = pushProjectLedgerMessage(state, task.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", body: task.replyBody, kind: "analysis_card", }); match.attachment.analysisCardId = card?.id; } else { match.attachment.analysisCardId = undefined; } } else if (payload.status === "failed") { match.attachment.analysisState = "failed"; match.attachment.analysisSummary = task.errorMessage ?? "附件分析失败,请稍后重试。"; match.attachment.analysisCardId = undefined; pushProjectLedgerMessage(state, task.projectId, { sender: "ops", senderLabel: task.accountLabel ? `主 Agent Relay · ${task.accountLabel}` : "主 Agent Relay", body: `附件分析失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`, kind: "text", }); } } } if (task.taskType === "group_dispatch_plan") { if (payload.status === "completed") { if (!payload.dispatchPlan) { throw new Error("MASTER_AGENT_GROUP_DISPATCH_PLAN_REQUIRED"); } createdDispatchPlan = upsertDispatchPlanInState(state, { groupProjectId: task.projectId, requestMessageId: task.requestMessageId, requestedBy: task.requestedByAccount, summary: payload.dispatchPlan.summary, targets: payload.dispatchPlan.targets, }); } } else if (task.taskType === "device_import_resolution") { if (!task.deviceImportDraftId) { throw new Error("MASTER_AGENT_DEVICE_IMPORT_DRAFT_REQUIRED"); } const draft = state.deviceImportDrafts.find((item) => item.draftId === task.deviceImportDraftId); if (!draft) { throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); } if (payload.status === "completed") { const resolutionReply = parseDeviceImportResolutionReply(state, draft, task.replyBody ?? ""); upsertDeviceImportResolutionInState(state, { deviceId: draft.deviceId, reviewedBy: task.requestedByAccount, summary: resolutionReply.summary, items: resolutionReply.items, draftId: draft.draftId, }); } publishBossEvent("devices.updated", { deviceId: draft.deviceId }); } else if (task.taskType === "dispatch_execution") { if (!task.dispatchExecutionId || !task.targetProjectId || !task.targetThreadId) { throw new Error("MASTER_AGENT_DISPATCH_EXECUTION_CONTEXT_REQUIRED"); } dispatchExecutionResult = appendDispatchExecutionResultInState(state, { dispatchExecutionId: payload.dispatchExecutionId?.trim() || task.dispatchExecutionId, completedByDeviceId: payload.deviceId, status: payload.status, groupProjectId: task.projectId, targetProjectId: payload.targetProjectId?.trim() || task.targetProjectId, targetThreadId: payload.targetThreadId?.trim() || task.targetThreadId, targetThreadDisplayName: task.targetThreadDisplayName, rawThreadReply: payload.rawThreadReply?.trim() || task.replyBody, masterSummary: payload.replyBody?.trim(), }); } else if (!attachmentProjectId && payload.status === "completed" && task.replyBody) { const isThreadConversationReply = task.taskType === "conversation_reply" && task.projectId !== "master-agent" && Boolean(task.targetProjectId && task.targetThreadId); if (isThreadConversationReply) { const threadProject = state.projects.find( (item) => item.id === (task.targetProjectId ?? task.projectId), ); const device = state.devices.find((item) => item.id === payload.deviceId); pushProjectLedgerMessage(state, threadProject?.id ?? task.projectId, { sender: "device", senderLabel: task.targetThreadDisplayName?.trim() || threadProject?.threadMeta.threadDisplayName || device?.name || "线程", body: task.replyBody, kind: "text", }); } else { pushProjectLedgerMessage(state, task.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", body: task.replyBody, kind: "text", }); autoCaptureMasterAgentMemoriesInState(state, { account: task.requestedByAccount, requestText: task.requestText, replyText: task.replyBody, sourceMessageId: task.requestMessageId, }); } } else if (!attachmentProjectId && payload.status === "failed") { const isThreadConversationReply = task.taskType === "conversation_reply" && task.projectId !== "master-agent" && Boolean(task.targetProjectId && task.targetThreadId); pushProjectLedgerMessage(state, task.projectId, { sender: "ops", senderLabel: isThreadConversationReply ? "线程执行失败" : task.accountLabel ? `主 Agent Relay · ${task.accountLabel}` : "主 Agent Relay", body: isThreadConversationReply ? `${task.targetThreadDisplayName ?? "当前线程"} 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}` : `Master Codex Node 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`, kind: "text", }); } return { ...task, dispatchPlan: createdDispatchPlan ? { ...createdDispatchPlan } : undefined, dispatchExecution: dispatchExecutionResult?.execution, }; }); publishBossEvent("master_agent.task.updated", { taskId: result.taskId, deviceId: result.deviceId, status: result.status, }); publishBossEvent("project.messages.updated", { projectId: result.projectId }); publishBossEvent("conversation.updated", { projectId: result.projectId }); return result; } export async function recordVerificationDelivery( account: string, purpose: VerificationCode["purpose"], status: VerificationDispatch["status"], note: string, ) { return mutateState((state) => { recordVerificationDispatch(state, account, purpose, getVerificationDeliveryMode(), status, note); }); } export async function registerAccount(account: string, password: string, code: string) { await mutateState((state) => { if (!consumeVerificationCode(state, account, "register", code)) { throw new Error("INVALID_VERIFICATION_CODE"); } if (state.authAccounts.some((item) => item.account === account)) { throw new Error("ACCOUNT_ALREADY_EXISTS"); } state.authAccounts.push({ id: `account-${slugify(account)}`, account, passwordHash: hashPassword(password), displayName: account, role: "member", verificationEmail: isLikelyEmailAccount(account) ? account : undefined, createdAt: nowIso(), updatedAt: nowIso(), }); }); } export async function loginAccount(params: { account: string; password?: string; code?: string; method?: LoginMethod; }) { return mutateState((state) => { const method = params.method ?? (params.password?.trim() ? "password" : "code"); const existing = state.authAccounts.find((item) => item.account === params.account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } if (existing.lockedUntil && new Date(existing.lockedUntil).getTime() > Date.now()) { throw new Error("LOGIN_TEMPORARILY_LOCKED"); } if (method === "password") { if (!params.password?.trim()) { throw new Error("PASSWORD_REQUIRED"); } if (!verifyPasswordHash(params.password, existing.passwordHash)) { registerLoginFailure(existing); throw new Error("INVALID_ACCOUNT_OR_PASSWORD"); } if (!existing.passwordHash.startsWith("scrypt$")) { existing.passwordHash = hashPassword(params.password); } } else { if (!params.code?.trim()) { throw new Error("VERIFICATION_CODE_REQUIRED"); } const directFixedCode = shouldAcceptDirectFixedVerificationCode("login", params.code); if (!directFixedCode && !consumeVerificationCode(state, params.account, "login", params.code)) { registerLoginFailure(existing); throw new Error("INVALID_VERIFICATION_CODE"); } } clearLoginFailure(existing); existing.updatedAt = nowIso(); existing.lastLoginAt = nowIso(); existing.lastLoginMethod = method; const session = { sessionId: randomToken("session"), sessionToken: randomBytes(24).toString("hex"), restoreToken: randomBytes(24).toString("hex"), account: existing.account, role: existing.role, displayName: existing.displayName, loginMethod: method, createdAt: nowIso(), expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(), lastSeenAt: nowIso(), } satisfies AuthSession; state.authSessions = [ session, ...state.authSessions.filter((item) => item.account !== existing.account), ].slice(0, 20); return { account: existing.account, role: existing.role, displayName: existing.displayName, loginMethod: method, sessionToken: session.sessionToken, restoreToken: session.restoreToken, sessionExpiresAt: session.expiresAt, }; }); } export async function resetAccountPassword(account: string, password: string, code: string) { await mutateState((state) => { if (!consumeVerificationCode(state, account, "forgot-password", code)) { throw new Error("INVALID_VERIFICATION_CODE"); } const existing = state.authAccounts.find((item) => item.account === account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } existing.passwordHash = hashPassword(password); clearLoginFailure(existing); existing.updatedAt = nowIso(); state.authSessions = state.authSessions.filter((item) => item.account !== account); }); } function masterActionsForSnapshot(snapshot: ThreadContextSnapshot) { const actions = new Set(); if (snapshot.contextBudgetLevel === "watch") { actions.add("prepare_handoff"); actions.add("avoid_large_context_append"); } if (snapshot.contextBudgetLevel === "urgent") { actions.add("prepare_handoff"); actions.add("avoid_large_context_append"); actions.add("finalize_artifacts"); } if (snapshot.contextBudgetLevel === "critical") { actions.add("complete_wrapup"); actions.add("handoff_required"); actions.add("freeze_context_growth"); } if (snapshot.mustFinishBeforeCompaction) { actions.add("finalize_artifacts"); } if (!actions.size) { actions.add("refresh_summary"); } return [...actions]; } function nextReportSeconds(level: ContextBudgetLevel) { switch (level) { case "critical": case "urgent": return 5; case "watch": return 15; default: return 30; } } function upsertThreadAlert(state: BossState, snapshot: ThreadContextSnapshot) { const existing = state.threadContextAlerts.find((alert) => alert.threadId === snapshot.threadId); const shouldOpen = snapshot.contextBudgetLevel !== "safe" || snapshot.mustFinishBeforeCompaction; if (!shouldOpen) { if (existing && existing.alertStatus !== "resolved") { existing.alertStatus = "resolved"; existing.resolvedAt = nowIso(); existing.summary = "线程预算已恢复到 safe,handoff 风险暂时解除。"; } return existing ?? null; } const alertType = snapshot.contextBudgetLevel === "critical" ? "context_critical" : snapshot.contextBudgetLevel === "urgent" ? "context_urgent" : snapshot.mustFinishBeforeCompaction ? "compaction_risk" : "context_watch"; const summary = `${snapshot.title} 进入 ${snapshot.contextBudgetLevel},预算 ${snapshot.contextBudgetRemainingPct}%${snapshot.mustFinishBeforeCompaction ? ",必须先收尾" : ""}。`; const masterActions = masterActionsForSnapshot(snapshot); if (existing) { existing.alertType = alertType; existing.alertStatus = "opened"; existing.summary = summary; existing.masterActions = masterActions; existing.resolvedAt = undefined; return existing; } const alert: ThreadContextAlert = { alertId: randomToken("alert"), threadId: snapshot.threadId, projectId: snapshot.projectId, alertType, alertStatus: "opened", openedAt: nowIso(), summary, masterActions, }; state.threadContextAlerts.unshift(alert); return alert; } function ensureHandoffPackage(state: BossState, snapshot: ThreadContextSnapshot) { const existing = state.threadHandoffPackages.find( (item) => item.fromThreadId === snapshot.threadId && item.packageStatus !== "consumed", ); if (snapshot.contextBudgetLevel === "safe" && !snapshot.mustFinishBeforeCompaction) { return existing ?? null; } if (existing) { if (snapshot.contextBudgetLevel === "critical" || snapshot.mustFinishBeforeCompaction) { existing.packageStatus = "ready"; existing.readyAt = nowIso(); } existing.summaryText = snapshot.summary; existing.criticalFiles = Array.from(new Set([...existing.criticalFiles])); return existing; } const handoff: ThreadHandoffPackage = { handoffPackageId: randomToken("handoff"), projectId: snapshot.projectId, taskId: snapshot.taskId, fromThreadId: snapshot.threadId, toThreadId: `${snapshot.threadId}-followup`, packageStatus: snapshot.contextBudgetLevel === "critical" || snapshot.mustFinishBeforeCompaction ? "ready" : "draft", summaryText: snapshot.summary, openQuestions: [], criticalFiles: [], criticalCommands: [], criticalTests: [], criticalArtifacts: [], decisionLinks: [], createdAt: nowIso(), readyAt: snapshot.contextBudgetLevel === "critical" || snapshot.mustFinishBeforeCompaction ? nowIso() : undefined, }; state.threadHandoffPackages.unshift(handoff); return handoff; } export async function upsertThreadContextSnapshot( workerId: string, payload: Omit & { contextBudgetLevel?: ContextBudgetLevel; }, ) { const normalized = await mutateState((state) => { const snapshot: ThreadContextSnapshot = { snapshotId: randomToken("snapshot"), workerId, contextBudgetLevel: payload.contextBudgetLevel ?? deriveLevelFromPercent(payload.contextBudgetRemainingPct), ...payload, }; state.threadContextSnapshots = [ snapshot, ...state.threadContextSnapshots.filter((item) => item.threadId !== payload.threadId), ]; upsertThreadAlert(state, snapshot); ensureHandoffPackage(state, snapshot); return snapshot; }); publishBossEvent("project.context_risk.updated", { projectId: normalized.projectId, deviceId: normalized.nodeId, note: normalized.threadId, }); publishBossEvent("conversation.updated", { projectId: normalized.projectId, deviceId: normalized.nodeId, }); return { accepted: true, thread_id: normalized.threadId, context_budget_level: normalized.contextBudgetLevel, next_required_report_in_seconds: nextReportSeconds(normalized.contextBudgetLevel), master_actions: masterActionsForSnapshot(normalized), }; } export async function createDeviceEnrollment(payload: { name: string; avatar: string; account: string; endpoint?: string; projects: string[]; note?: string; }) { const { device, enrollment, deviceId } = await mutateState((state) => { const nextDeviceId = slugify(payload.name); const token = randomToken("boss"); const pairingCode = randomDigits(6); let device = state.devices.find((item) => item.id === nextDeviceId); if (!device) { device = { id: nextDeviceId, name: payload.name, avatar: payload.avatar, account: payload.account, source: "production", status: "offline", projects: payload.projects, quota5h: 100, quota7d: 100, lastSeenAt: nowIso(), endpoint: payload.endpoint, token, note: payload.note, }; state.devices.push(device); } else { device.name = payload.name; device.avatar = payload.avatar; device.account = payload.account; device.source = "production"; device.projects = payload.projects; device.endpoint = payload.endpoint; device.note = payload.note; device.token = token; } const enrollment: DeviceEnrollment = { enrollmentId: randomToken("enroll"), deviceId: nextDeviceId, label: payload.name, pairingCode, token, status: "ready", note: payload.note ?? "等待本地 agent 首次心跳 claim", createdAt: nowIso(), expiresAt: new Date(Date.now() + 8 * 60 * 60_000).toISOString(), }; state.deviceEnrollments = [ enrollment, ...state.deviceEnrollments.filter((item) => item.deviceId !== nextDeviceId), ]; return { device, enrollment, deviceId: nextDeviceId }; }); publishBossEvent("devices.updated", { deviceId }); return { device, enrollment }; } export async function updateDevice(deviceId: string, payload: Partial) { const device = await mutateState((state) => { const nextDevice = state.devices.find((item) => item.id === deviceId); if (!nextDevice) throw new Error("DEVICE_NOT_FOUND"); if (payload.name) nextDevice.name = payload.name.trim(); if (payload.avatar) nextDevice.avatar = payload.avatar.trim().slice(0, 2) || nextDevice.avatar; if (payload.account) nextDevice.account = payload.account.trim(); if (payload.status) nextDevice.status = payload.status; if (payload.endpoint !== undefined) nextDevice.endpoint = payload.endpoint; if (payload.note !== undefined) nextDevice.note = payload.note; if (payload.projects) { nextDevice.projects = payload.projects.filter(Boolean); } nextDevice.lastSeenAt = nowIso(); return nextDevice; }); publishBossEvent("devices.updated", { deviceId }); return device; } function claimEnrollment( state: BossState, deviceId: string, pairingCode?: string, token?: string, ) { const enrollment = state.deviceEnrollments.find((item) => item.deviceId === deviceId); if (!enrollment) return null; const expired = new Date(enrollment.expiresAt).getTime() < Date.now(); if (expired) { enrollment.status = "expired"; return null; } if (token && token === enrollment.token) { enrollment.status = "claimed"; enrollment.claimedAt = nowIso(); enrollment.claimedDeviceId = deviceId; return enrollment; } if (pairingCode && pairingCode === enrollment.pairingCode) { enrollment.status = "claimed"; enrollment.claimedAt = nowIso(); enrollment.claimedDeviceId = deviceId; return enrollment; } return null; } function hasAuthorizedDeviceToken( state: BossState, deviceId: string, token?: string, ) { if (!token) return false; const device = state.devices.find((item) => item.id === deviceId); if (device?.token && device.token === token) { return true; } const enrollment = state.deviceEnrollments.find((item) => item.deviceId === deviceId); if (!enrollment || enrollment.token !== token) { return false; } return new Date(enrollment.expiresAt).getTime() > Date.now(); } export async function verifyDeviceToken(deviceId: string, token?: string) { if (!token) return false; const state = await readState(); return hasAuthorizedDeviceToken(state, deviceId, token); } function upsertDeviceImportDraftFromHeartbeat( state: BossState, payload: { deviceId: string; enrollmentId?: string; candidates: DeviceImportCandidate[]; }, ) { const existing = state.deviceImportDrafts.find((item) => item.deviceId === payload.deviceId); if (payload.candidates.length === 0) { if (existing?.status === "applied" && existing.appliedProjectNames.length > 0) { return existing; } const waitingDraft = normalizeDeviceImportDraft({ draftId: existing?.draftId ?? randomToken("import-draft"), deviceId: payload.deviceId, enrollmentId: payload.enrollmentId ?? existing?.enrollmentId, status: "pending_candidates", candidates: [], selectedCandidateIds: [], appliedProjectNames: [], createdAt: existing?.createdAt ?? nowIso(), updatedAt: nowIso(), }, existing); waitingDraft.reviewedAt = undefined; waitingDraft.reviewedBy = undefined; waitingDraft.resolutionId = undefined; state.deviceImportResolutions = state.deviceImportResolutions.filter( (item) => item.draftId !== waitingDraft.draftId, ); state.deviceImportDrafts = [ waitingDraft, ...state.deviceImportDrafts.filter((item) => item.draftId !== waitingDraft.draftId), ]; return waitingDraft; } const selectedCandidateIds = dedupeStrings( (existing?.selectedCandidateIds ?? []).filter((candidateId) => payload.candidates.some((candidate) => candidate.candidateId === candidateId), ), ); const previousCandidateIds = existing?.candidates.map((candidate) => candidate.candidateId) ?? []; const nextCandidateIds = payload.candidates.map((candidate) => candidate.candidateId); const selectionChanged = !sameStringSet(existing?.selectedCandidateIds ?? [], selectedCandidateIds) || !sameStringSet(previousCandidateIds, nextCandidateIds); const keepAppliedState = !selectionChanged && existing?.status === "applied" && Boolean(existing.resolutionId) && selectedCandidateIds.length > 0; const keepResolvedState = !selectionChanged && selectedCandidateIds.length > 0 && Boolean(existing?.resolutionId); const nextDraft = normalizeDeviceImportDraft({ draftId: existing?.draftId ?? randomToken("import-draft"), deviceId: payload.deviceId, enrollmentId: payload.enrollmentId ?? existing?.enrollmentId, status: keepAppliedState ? "applied" : selectedCandidateIds.length > 0 ? keepResolvedState ? "resolved" : "pending_resolution" : "pending_selection", candidates: payload.candidates, selectedCandidateIds, appliedProjectNames: keepAppliedState ? existing.appliedProjectNames : [], createdAt: existing?.createdAt ?? nowIso(), updatedAt: nowIso(), reviewedAt: keepResolvedState || keepAppliedState ? existing?.reviewedAt : undefined, reviewedBy: keepResolvedState || keepAppliedState ? existing?.reviewedBy : undefined, resolutionId: keepResolvedState || keepAppliedState ? existing?.resolutionId : undefined, }, existing); if (!keepResolvedState && !keepAppliedState) { state.deviceImportResolutions = state.deviceImportResolutions.filter( (item) => item.draftId !== nextDraft.draftId, ); } state.deviceImportDrafts = [ nextDraft, ...state.deviceImportDrafts.filter((item) => item.draftId !== nextDraft.draftId), ]; return nextDraft; } export async function upsertDeviceHeartbeat(payload: { deviceId: string; token?: string; pairingCode?: string; name: string; avatar: string; account: string; status: DeviceStatus; quota5h: number; quota7d: number; projects: string[]; endpoint?: string; projectCandidates?: Array<{ folderName: string; folderRef?: string; threadId: string; threadDisplayName: string; codexFolderRef?: string; codexThreadRef?: string; lastActiveAt?: string; suggestedImport?: boolean; }>; }) { const result = await mutateState((state) => { const existingDevice = state.devices.find((item) => item.id === payload.deviceId) ?? null; const claimedEnrollment = claimEnrollment( state, payload.deviceId, payload.pairingCode, payload.token, ); const normalizedCandidates = ensureArray(payload.projectCandidates, []).map((candidate) => normalizeDeviceImportCandidate({ deviceId: payload.deviceId, folderName: candidate.folderName, folderRef: candidate.folderRef, threadId: candidate.threadId, threadDisplayName: candidate.threadDisplayName, codexFolderRef: candidate.codexFolderRef, codexThreadRef: candidate.codexThreadRef, lastActiveAt: candidate.lastActiveAt ?? nowIso(), suggestedImport: candidate.suggestedImport ?? true, }), ); const reportedProjectCandidates = Array.isArray(payload.projectCandidates); const shouldAutoImportLegacyProjects = !reportedProjectCandidates && normalizedCandidates.length === 0; let device = existingDevice; if (!device) { device = { id: payload.deviceId, name: payload.name, avatar: payload.avatar, account: payload.account, source: "production", status: payload.status, projects: payload.projects, quota5h: payload.quota5h, quota7d: payload.quota7d, lastSeenAt: nowIso(), endpoint: payload.endpoint, token: claimedEnrollment?.token ?? payload.token ?? randomToken("boss"), note: claimedEnrollment?.note, }; state.devices.push(device); } else { if (device.token && payload.token && device.token !== payload.token && !claimedEnrollment) { throw new Error("DEVICE_TOKEN_MISMATCH"); } device.name = payload.name; device.avatar = payload.avatar; device.account = payload.account; device.source = "production"; device.status = payload.status; device.projects = payload.projects; device.quota5h = payload.quota5h; device.quota7d = payload.quota7d; device.lastSeenAt = nowIso(); device.endpoint = payload.endpoint ?? device.endpoint; device.token = claimedEnrollment?.token ?? payload.token ?? device.token; } if (shouldAutoImportLegacyProjects) { for (const projectName of payload.projects) { const existing = state.projects.find((item) => item.name === projectName); if (!existing) { state.projects.push( normalizeProject({ id: slugify(projectName), name: projectName, pinned: false, deviceIds: [payload.deviceId], preview: `${payload.name} 已自动上报项目文件夹`, updatedAt: nowIso(), lastMessageAt: nowIso(), isGroup: false, unreadCount: 0, riskLevel: "low", contextBudgetPct: 80, contextBudgetLabel: "80%", messages: [ { id: randomToken("auto"), sender: "device", senderLabel: payload.name, body: `本机发现新的项目目录:${projectName}`, sentAt: nowIso(), kind: "text", }, ], goals: [], versions: [], }), ); } else if (!existing.deviceIds.includes(payload.deviceId)) { existing.deviceIds.push(payload.deviceId); existing.isGroup = existing.deviceIds.length > 1; } } } let draft = upsertDeviceImportDraftFromHeartbeat(state, { deviceId: payload.deviceId, enrollmentId: claimedEnrollment?.enrollmentId, candidates: normalizedCandidates, }); if ( draft && shouldAutoSyncHeartbeatCandidates({ wasExistingDevice: Boolean(existingDevice), device, claimedEnrollment, draft, }) ) { const autoSyncDraft = draft; const selectedCandidateIds = resolveAutoSyncCandidateIds(autoSyncDraft); if (selectedCandidateIds.length > 0) { autoSyncDraft.selectedCandidateIds = selectedCandidateIds; autoSyncDraft.status = "pending_resolution"; autoSyncDraft.updatedAt = nowIso(); autoSyncDraft.reviewedAt = undefined; autoSyncDraft.reviewedBy = undefined; autoSyncDraft.resolutionId = undefined; state.deviceImportResolutions = state.deviceImportResolutions.filter( (item) => item.draftId !== autoSyncDraft.draftId, ); const selectedCandidates = autoSyncDraft.candidates.filter((candidate) => autoSyncDraft.selectedCandidateIds.includes(candidate.candidateId), ); const items = selectedCandidates.map((candidate) => resolveDeviceImportAction(state, payload.deviceId, candidate), ); upsertDeviceImportResolutionInState(state, { deviceId: payload.deviceId, reviewedBy: "system:auto_sync", summary: summarizeDeviceImportResolution(device.name, items), items, draftId: autoSyncDraft.draftId, }); const applied = applyDeviceImportResolutionInState(state, { deviceId: payload.deviceId, appliedBy: "system:auto_sync", draftId: autoSyncDraft.draftId, pruneMissingCandidates: true, }); draft = applied.draft; } } return { device, token: claimedEnrollment?.token ?? device.token, pairingStatus: claimedEnrollment?.status, importDraft: draft, }; }); publishBossEvent("devices.updated", { deviceId: payload.deviceId }); publishBossEvent("conversation.updated", { deviceId: payload.deviceId }); return result; } function resolveDeviceImportAction( state: BossState, deviceId: string, candidate: DeviceImportCandidate, ): DeviceImportResolutionItem { const directMatch = state.projects.find( (project) => !project.isGroup && ((candidate.codexThreadRef && project.threadMeta.codexThreadRef === candidate.codexThreadRef) || project.threadMeta.threadId === candidate.threadId), ); if (directMatch) { return { candidateId: candidate.candidateId, action: "attach_existing", threadDisplayName: candidate.threadDisplayName, folderName: candidate.folderName, targetProjectId: directMatch.id, reason: `已匹配到现有会话《${directMatch.name}》,直接补充设备与线程映射。`, }; } const similarByFolder = state.projects.find( (project) => !project.isGroup && project.deviceIds.includes(deviceId) && project.threadMeta.folderName === candidate.folderName && project.threadMeta.threadDisplayName === candidate.threadDisplayName, ); if (similarByFolder) { return { candidateId: candidate.candidateId, action: "attach_existing", threadDisplayName: candidate.threadDisplayName, folderName: candidate.folderName, targetProjectId: similarByFolder.id, reason: `同设备下已有同名线程《${similarByFolder.name}》,避免重复导入。`, }; } return { candidateId: candidate.candidateId, action: "create_thread_conversation", threadDisplayName: candidate.threadDisplayName, folderName: candidate.folderName, reason: `建议把 ${candidate.threadDisplayName} 作为独立聊天窗口导入。`, }; } function summarizeDeviceImportResolution( deviceName: string, items: DeviceImportResolutionItem[], ) { const createCount = items.filter((item) => item.action === "create_thread_conversation").length; const attachCount = items.filter((item) => item.action === "attach_existing").length; const skipCount = items.filter((item) => item.action === "skip").length; return `${deviceName} 导入建议:新建 ${createCount} 个会话,关联 ${attachCount} 个现有会话${skipCount > 0 ? `,跳过 ${skipCount} 项` : ""}。`; } function resolveAutoSyncCandidateIds(draft: DeviceImportDraft) { const suggestedCandidateIds = draft.candidates .filter((candidate) => candidate.suggestedImport !== false) .map((candidate) => candidate.candidateId); return dedupeStrings( suggestedCandidateIds.length > 0 ? suggestedCandidateIds : draft.candidates.map((candidate) => candidate.candidateId), ); } function shouldAutoSyncHeartbeatCandidates(input: { wasExistingDevice: boolean; device: Device; claimedEnrollment: DeviceEnrollment | null; draft: DeviceImportDraft | null; }) { if (!input.wasExistingDevice) return false; if (input.device.source !== "production") return false; if (!input.draft || input.draft.candidates.length === 0) return false; if ( input.claimedEnrollment?.enrollmentId && input.draft.enrollmentId === input.claimedEnrollment.enrollmentId ) { return false; } return true; } export async function getLatestDeviceImportDraft(deviceId: string) { const state = await readState(); const draft = state.deviceImportDrafts.find((item) => item.deviceId === deviceId) ?? null; const resolution = draft?.resolutionId ? state.deviceImportResolutions.find((item) => item.resolutionId === draft.resolutionId) ?? null : state.deviceImportResolutions.find((item) => item.deviceId === deviceId) ?? null; const reviewTask = draft ? state.masterAgentTasks.find( (item) => item.taskType === "device_import_resolution" && item.deviceImportDraftId === draft.draftId, ) ?? null : null; return { draft, resolution, reviewTask }; } export async function previewDeviceImportResolution(input: { deviceId: string }) { const state = await readState(); const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId); if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); if (draft.selectedCandidateIds.length === 0) { throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED"); } const device = state.devices.find((item) => item.id === input.deviceId); if (!device) throw new Error("DEVICE_NOT_FOUND"); const selectedCandidates = draft.candidates.filter((candidate) => draft.selectedCandidateIds.includes(candidate.candidateId), ); const items = selectedCandidates.map((candidate) => resolveDeviceImportAction(state, input.deviceId, candidate), ); return { draft: { ...draft }, device: { ...device }, items, summary: summarizeDeviceImportResolution(device.name, items), }; } function upsertDeviceImportResolutionInState( state: BossState, input: { deviceId: string; reviewedBy: string; summary: string; items: DeviceImportResolutionItem[]; draftId?: string; }, ) { const draft = state.deviceImportDrafts.find( (item) => item.draftId === input.draftId || item.deviceId === input.deviceId, ) ?? null; if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); if (draft.selectedCandidateIds.length === 0) { throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED"); } const existingResolution = state.deviceImportResolutions.find((item) => item.draftId === draft.draftId); const resolution = normalizeDeviceImportResolution({ resolutionId: existingResolution?.resolutionId ?? draft.resolutionId ?? randomToken("import-resolution"), draftId: draft.draftId, deviceId: input.deviceId, status: "ready", summary: input.summary, items: input.items, createdAt: existingResolution?.createdAt ?? nowIso(), }); draft.status = "resolved"; draft.updatedAt = nowIso(); draft.reviewedAt = nowIso(); draft.reviewedBy = input.reviewedBy; draft.resolutionId = resolution.resolutionId; draft.appliedProjectNames = []; state.deviceImportResolutions = [ resolution, ...state.deviceImportResolutions.filter((item) => item.draftId !== draft.draftId), ]; return { draft: { ...draft }, resolution }; } export async function selectDeviceImportCandidates(input: { deviceId: string; selectedCandidateIds: string[]; selectedBy: string; }) { const draft = await mutateState((state) => { const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId); if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); const availableCandidateIds = new Set(draft.candidates.map((item) => item.candidateId)); const nextSelected = dedupeStrings(input.selectedCandidateIds).filter((candidateId) => availableCandidateIds.has(candidateId), ); draft.selectedCandidateIds = nextSelected; draft.status = nextSelected.length > 0 ? "pending_resolution" : "pending_selection"; draft.appliedProjectNames = []; draft.updatedAt = nowIso(); draft.reviewedBy = input.selectedBy; draft.reviewedAt = undefined; draft.resolutionId = undefined; state.deviceImportResolutions = state.deviceImportResolutions.filter( (item) => item.draftId !== draft.draftId, ); return { ...draft }; }); publishBossEvent("devices.updated", { deviceId: input.deviceId }); return draft; } export async function resolveDeviceImportDraft(input: { deviceId: string; reviewedBy: string; }) { const result = await mutateState((state) => { const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId); if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND"); if (draft.selectedCandidateIds.length === 0) { throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED"); } const device = state.devices.find((item) => item.id === input.deviceId); if (!device) throw new Error("DEVICE_NOT_FOUND"); const selectedCandidates = draft.candidates.filter((candidate) => draft.selectedCandidateIds.includes(candidate.candidateId), ); const items = selectedCandidates.map((candidate) => resolveDeviceImportAction(state, input.deviceId, candidate), ); return upsertDeviceImportResolutionInState(state, { deviceId: input.deviceId, reviewedBy: input.reviewedBy, summary: summarizeDeviceImportResolution(device.name, items), items, draftId: draft.draftId, }); }); publishBossEvent("devices.updated", { deviceId: input.deviceId }); publishBossEvent("conversation.updated", { deviceId: input.deviceId }); return result; } function parseDeviceImportResolutionReply( state: BossState, draft: DeviceImportDraft, replyBody: string, ) { const trimmed = replyBody.trim(); if (!trimmed) { throw new Error("DEVICE_IMPORT_RESOLUTION_REPLY_REQUIRED"); } const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i); const jsonCandidate = fencedMatch?.[1]?.trim() ?? trimmed; let parsed: | { summary?: string; items?: Array<{ candidateId?: string; action?: DeviceImportResolutionItem["action"]; targetProjectId?: string; reason?: string; }>; } | null = null; try { parsed = JSON.parse(jsonCandidate); } catch { throw new Error("DEVICE_IMPORT_RESOLUTION_JSON_INVALID"); } const selectedCandidates = draft.candidates.filter((candidate) => draft.selectedCandidateIds.includes(candidate.candidateId), ); const candidateMap = new Map(selectedCandidates.map((candidate) => [candidate.candidateId, candidate])); const seenCandidateIds = new Set(); const items: DeviceImportResolutionItem[] = []; for (const rawItem of ensureArray(parsed?.items, [])) { const candidateId = rawItem?.candidateId?.trim(); if (!candidateId || seenCandidateIds.has(candidateId)) continue; const candidate = candidateMap.get(candidateId); if (!candidate) continue; seenCandidateIds.add(candidateId); const heuristic = resolveDeviceImportAction(state, draft.deviceId, candidate); items.push({ candidateId, action: rawItem.action === "attach_existing" || rawItem.action === "create_thread_conversation" || rawItem.action === "skip" ? rawItem.action : heuristic.action, threadDisplayName: candidate.threadDisplayName, folderName: candidate.folderName, targetProjectId: typeof rawItem.targetProjectId === "string" && rawItem.targetProjectId.trim() ? rawItem.targetProjectId.trim() : heuristic.targetProjectId, reason: rawItem.reason?.trim() || heuristic.reason, }); } for (const candidate of selectedCandidates) { if (!seenCandidateIds.has(candidate.candidateId)) { items.push(resolveDeviceImportAction(state, draft.deviceId, candidate)); } } const device = state.devices.find((item) => item.id === draft.deviceId); return { summary: parsed?.summary?.trim() || summarizeDeviceImportResolution(device?.name ?? draft.deviceId, items), items, }; } function buildImportedThreadProject(device: Device, candidate: DeviceImportCandidate) { const projectId = candidate.codexThreadRef?.trim() && candidate.codexFolderRef?.trim() ? slugify(`${device.id}-${candidate.codexFolderRef}-${candidate.codexThreadRef}`) : slugify(`${device.id}-${candidate.folderName}-${candidate.threadId}`); const now = nowIso(); return normalizeProject({ id: projectId, name: candidate.threadDisplayName, pinned: false, systemPinned: false, deviceIds: [device.id], preview: `已从 ${device.name} 导入线程 ${candidate.threadDisplayName}`, updatedAt: now, lastMessageAt: now, isGroup: false, unreadCount: 0, riskLevel: "low", threadMeta: { projectId, threadId: candidate.threadId, threadDisplayName: candidate.threadDisplayName, folderName: candidate.folderName, activityIconCount: 1, updatedAt: candidate.lastActiveAt || now, codexFolderRef: candidate.codexFolderRef ?? candidate.folderRef, codexThreadRef: candidate.codexThreadRef, }, groupMembers: [], createdByAgent: true, collaborationMode: "development", approvalState: "not_required", messages: [ { id: randomToken("msg"), sender: "master", senderLabel: "主 Agent", body: `已从设备 ${device.name} 导入线程《${candidate.threadDisplayName}》。`, sentAt: now, kind: "text", }, ], goals: [], versions: [], }); } function candidateThreadSignature(candidate: DeviceImportCandidate) { return ( trimToDefined(candidate.codexThreadRef) ?? trimToDefined(candidate.threadId) ?? `${candidate.folderName}:${candidate.threadDisplayName}` ); } function projectThreadSignature(project: Project) { return ( trimToDefined(project.threadMeta.codexThreadRef) ?? trimToDefined(project.threadMeta.threadId) ?? `${project.threadMeta.folderName}:${project.threadMeta.threadDisplayName}` ); } function pruneStaleAutoImportedProjectsForDevice( state: BossState, device: Device, selectedCandidates: DeviceImportCandidate[], ) { const activeSignatures = new Set(selectedCandidates.map((candidate) => candidateThreadSignature(candidate))); const reservedProjectIds = new Set(["master-agent", "boss-console", "audit-collab"]); state.projects = state.projects.filter((project) => { if (reservedProjectIds.has(project.id)) return true; if (project.isGroup) return true; if (!project.createdByAgent) return true; if (!project.deviceIds.includes(device.id)) return true; if (project.deviceIds.length !== 1) return true; if (!trimToDefined(project.threadMeta.codexFolderRef) && !trimToDefined(project.threadMeta.folderName)) { return true; } return activeSignatures.has(projectThreadSignature(project)); }); } function applyDeviceImportResolutionInState( state: BossState, input: { deviceId: string; appliedBy: string; draftId?: string; pruneMissingCandidates?: boolean; }, ) { const draft = state.deviceImportDrafts.find( (item) => item.draftId === input.draftId || item.deviceId === input.deviceId, ) ?? null; if (!draft || !draft.resolutionId) throw new Error("DEVICE_IMPORT_RESOLUTION_NOT_FOUND"); const resolution = state.deviceImportResolutions.find( (item) => item.resolutionId === draft.resolutionId, ); if (!resolution) throw new Error("DEVICE_IMPORT_RESOLUTION_NOT_FOUND"); const device = state.devices.find((item) => item.id === input.deviceId); if (!device) throw new Error("DEVICE_NOT_FOUND"); if (draft.status === "applied" && resolution.status === "applied") { const importedProjects = state.projects.filter( (project) => !project.isGroup && project.deviceIds.includes(device.id) && draft.appliedProjectNames.includes(project.name), ); return { draft: { ...draft }, resolution: { ...resolution }, importedProjects: importedProjects.map((project) => ({ ...project })), }; } if (draft.status !== "resolved") { throw new Error("DEVICE_IMPORT_RESOLUTION_STALE"); } const selectedCandidates = draft.candidates.filter((candidate) => draft.selectedCandidateIds.includes(candidate.candidateId), ); const importedProjects: Project[] = []; for (const item of resolution.items) { const candidate = draft.candidates.find((entry) => entry.candidateId === item.candidateId); if (!candidate || item.action === "skip") { continue; } let targetProject = item.targetProjectId ? state.projects.find((project) => project.id === item.targetProjectId) : undefined; if (item.action === "create_thread_conversation" && !targetProject) { const draftProject = buildImportedThreadProject(device, candidate); targetProject = state.projects.find((project) => project.id === draftProject.id) ?? state.projects.find( (project) => !project.isGroup && project.deviceIds.includes(device.id) && ((candidate.codexThreadRef && project.threadMeta.codexThreadRef === candidate.codexThreadRef) || project.threadMeta.threadId === candidate.threadId), ); if (!targetProject) { targetProject = draftProject; state.projects.unshift(targetProject); } } else if (item.action === "attach_existing" && !targetProject) { continue; } if (!targetProject) continue; if (!targetProject.deviceIds.includes(device.id)) { targetProject.deviceIds.push(device.id); } targetProject.threadMeta.threadDisplayName = candidate.threadDisplayName; targetProject.threadMeta.folderName = candidate.folderName; targetProject.threadMeta.threadId = candidate.threadId; targetProject.threadMeta.codexFolderRef = candidate.codexFolderRef ?? candidate.folderRef; targetProject.threadMeta.codexThreadRef = candidate.codexThreadRef; targetProject.threadMeta.updatedAt = candidate.lastActiveAt; targetProject.preview = `已导入 ${candidate.threadDisplayName}`; targetProject.updatedAt = nowIso(); targetProject.lastMessageAt = targetProject.updatedAt; importedProjects.push({ ...targetProject }); } if (input.pruneMissingCandidates) { pruneStaleAutoImportedProjectsForDevice(state, device, selectedCandidates); } device.projects = dedupeStrings( selectedCandidates.map((candidate) => candidate.folderName), ); resolution.status = "applied"; resolution.appliedAt = nowIso(); resolution.appliedBy = input.appliedBy; draft.status = "applied"; draft.appliedProjectNames = importedProjects.map((project) => project.name); draft.updatedAt = nowIso(); return { draft: { ...draft }, resolution: { ...resolution }, importedProjects, }; } export async function applyDeviceImportResolution(input: { deviceId: string; appliedBy: string; }) { const result = await mutateState((state) => applyDeviceImportResolutionInState(state, { deviceId: input.deviceId, appliedBy: input.appliedBy, }), ); publishBossEvent("devices.updated", { deviceId: input.deviceId }); publishBossEvent("conversation.updated", { deviceId: input.deviceId }); return result; } export async function upsertDeviceSkills(payload: { deviceId: string; skills: Array<{ name: string; description?: string; path: string; invocation?: string; category?: string; }>; }) { const nextSkills = await mutateState((state) => { const device = state.devices.find((item) => item.id === payload.deviceId); if (!device) throw new Error("DEVICE_NOT_FOUND"); const syncedAt = nowIso(); const skills = payload.skills.map((skill) => ({ skillId: `${payload.deviceId}:${slugify(skill.name)}`, deviceId: payload.deviceId, name: skill.name, description: skill.description?.trim() || "未提供说明", path: skill.path, invocation: skill.invocation?.trim() || `[$${skill.name}](${skill.path})`, category: skill.category?.trim() || device.name, updatedAt: syncedAt, })); state.deviceSkills = [ ...skills, ...state.deviceSkills.filter((item) => item.deviceId !== payload.deviceId), ]; device.lastSeenAt = syncedAt; return skills; }); publishBossEvent("devices.skills.updated", { deviceId: payload.deviceId, note: `${nextSkills.length}`, }); return nextSkills; } export async function appendAppLog(payload: { deviceId: string; projectId?: string; level: AppLogLevel; source: "app_client" | "local_agent"; category: string; message: string; detail?: string; mirrorToMaster?: boolean; }) { const { entry, mirroredProjectId } = await mutateState((state) => { const entry: AppLogEntry = { logId: randomToken("applog"), deviceId: payload.deviceId, projectId: payload.projectId, level: payload.level, source: payload.source, category: payload.category, message: payload.message.trim(), detail: payload.detail?.trim(), mirroredToProject: Boolean(payload.mirrorToMaster), createdAt: nowIso(), }; state.appLogs.unshift(entry); let mirroredProjectId: string | undefined; if (payload.mirrorToMaster) { const device = state.devices.find((item) => item.id === payload.deviceId); pushProjectLedgerMessage(state, "master-agent", { sender: payload.level === "error" ? "ops" : "device", senderLabel: `${device?.name ?? payload.deviceId} · APP 日志`, body: `[${payload.category}] ${payload.message}${payload.detail ? `\n${payload.detail}` : ""}`, kind: "text", }); if (shouldAutoReplyToMirroredLog(entry)) { pushProjectLedgerMessage(state, "master-agent", { sender: "master", senderLabel: "主 Agent", body: buildMasterAgentLogReply(state, entry), kind: "text", }); } mirroredProjectId = "master-agent"; } return { entry, mirroredProjectId }; }); publishBossEvent("app.logs.updated", { deviceId: payload.deviceId, projectId: payload.projectId, note: payload.category, }); if (mirroredProjectId) { publishBossEvent("project.messages.updated", { projectId: mirroredProjectId, deviceId: payload.deviceId, }); publishBossEvent("conversation.updated", { projectId: mirroredProjectId, deviceId: payload.deviceId, }); } return entry; } export async function updateConversationAction( projectId: string, action: "toggle_pin" | "mark_read", ) { const project = await mutateState((state) => { const nextProject = state.projects.find((item) => item.id === projectId); if (!nextProject) throw new Error("PROJECT_NOT_FOUND"); if (action === "toggle_pin") { if (nextProject.systemPinned) { throw new Error("MASTER_PROJECT_PIN_LOCKED"); } nextProject.pinned = !nextProject.pinned; } if (action === "mark_read") { nextProject.unreadCount = 0; } return nextProject; }); publishBossEvent("conversation.updated", { projectId }); return project; } export async function renameProjectThread(input: { projectId: string; threadDisplayName: string; requestedBy: string; }) { const threadDisplayName = input.threadDisplayName.trim(); if (!threadDisplayName) { throw new Error("THREAD_DISPLAY_NAME_REQUIRED"); } const project = await mutateState((state) => { const nextProject = state.projects.find((item) => item.id === input.projectId); if (!nextProject) throw new Error("PROJECT_NOT_FOUND"); if (nextProject.isGroup) throw new Error("PROJECT_IS_GROUP_CHAT"); const updatedAt = nowIso(); nextProject.name = threadDisplayName; nextProject.threadMeta.threadDisplayName = threadDisplayName; nextProject.threadMeta.updatedAt = updatedAt; nextProject.updatedAt = updatedAt; return nextProject; }); publishBossEvent("conversation.updated", { projectId: input.projectId, note: `renamed by ${input.requestedBy}`, }); return project; } export async function renameGroupChat(input: { projectId: string; name: string; requestedBy: string; }) { const name = input.name.trim(); if (!name) { throw new Error("GROUP_CHAT_NAME_REQUIRED"); } const project = await mutateState((state) => { const nextProject = state.projects.find((item) => item.id === input.projectId); if (!nextProject) throw new Error("PROJECT_NOT_FOUND"); if (!nextProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT"); const updatedAt = nowIso(); nextProject.name = name; nextProject.threadMeta.threadDisplayName = name; nextProject.threadMeta.updatedAt = updatedAt; nextProject.updatedAt = updatedAt; return nextProject; }); publishBossEvent("conversation.updated", { projectId: input.projectId, note: `renamed by ${input.requestedBy}`, }); return project; } export async function createProjectGroupChat(input: { sourceProjectId: string; memberProjectIds: string[]; createdBy: string; }) { const project = await mutateState((state) => { const source = state.projects.find((item) => item.id === input.sourceProjectId); if (!source) throw new Error("GROUP_CHAT_SOURCE_NOT_FOUND"); return createGroupChatFromProjectIds(state, { requestedProjectIds: [input.sourceProjectId, ...input.memberProjectIds], createdBy: input.createdBy, defaultRiskLevel: source.riskLevel, }); }); publishBossEvent("project.messages.updated", { projectId: project.id }); publishBossEvent("conversation.updated", { projectId: project.id }); return project; } export async function createIndependentGroupChat(input: { memberProjectIds: string[]; createdBy: string; }) { const project = await mutateState((state) => createGroupChatFromProjectIds(state, { requestedProjectIds: input.memberProjectIds, createdBy: input.createdBy, }), ); publishBossEvent("project.messages.updated", { projectId: project.id }); publishBossEvent("conversation.updated", { projectId: project.id }); return project; } function resolveGroupChatThreadProjects( state: BossState, requestedProjectIds: string[], ) { const memberProjects: Project[] = []; const seenProjectIds = new Set(); for (const projectId of requestedProjectIds) { if (!projectId || seenProjectIds.has(projectId)) { continue; } seenProjectIds.add(projectId); const memberProject = state.projects.find((item) => item.id === projectId); if (!memberProject) { throw new Error("GROUP_CHAT_MEMBER_NOT_FOUND"); } if (!isDispatchableThreadProject(memberProject)) { throw new Error("GROUP_CHAT_MEMBER_NOT_THREAD"); } memberProjects.push(memberProject); } return memberProjects; } export async function replaceGroupChatMembers(input: { projectId: string; memberProjectIds: string[]; requestedBy: string; }) { const result = await mutateState((state) => { const groupProject = state.projects.find((item) => item.id === input.projectId); if (!groupProject) { throw new Error("PROJECT_NOT_FOUND"); } if (!groupProject.isGroup) { throw new Error("PROJECT_NOT_GROUP_CHAT"); } const memberProjects = resolveGroupChatThreadProjects(state, input.memberProjectIds); if (memberProjects.length < 2) { throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS"); } const now = nowIso(); groupProject.groupMembers = memberProjects.map((memberProject) => ({ projectId: memberProject.id, deviceId: memberProject.deviceIds[0] ?? memberProject.id, threadId: memberProject.threadMeta.threadId, threadDisplayName: memberProject.threadMeta.threadDisplayName, folderName: memberProject.threadMeta.folderName, })); groupProject.deviceIds = dedupeStrings(groupProject.groupMembers.map((member) => member.deviceId)); groupProject.threadMeta.activityIconCount = Math.max(1, groupProject.groupMembers.length); groupProject.threadMeta.folderName = "群聊"; groupProject.threadMeta.updatedAt = now; groupProject.updatedAt = now; groupProject.lastMessageAt = now; groupProject.approvalState = "not_required"; const memberLabel = memberProjects .map((project) => project.threadMeta.threadDisplayName || project.name) .join("、"); pushProjectLedgerMessage(state, groupProject.id, { sender: "master", senderLabel: "主 Agent", body: `已更新群成员:${memberLabel}`, kind: "system_notice", sentAt: now, }); return { project: { ...groupProject }, groupMembers: groupProject.groupMembers.map((member) => ({ ...member })), }; }); publishBossEvent("project.messages.updated", { projectId: input.projectId }); publishBossEvent("conversation.updated", { projectId: input.projectId, note: `group members updated by ${input.requestedBy}`, }); return result; } function createGroupChatFromProjectIds( state: BossState, input: { requestedProjectIds: string[]; createdBy: string; defaultRiskLevel?: Project["riskLevel"]; }, ) { const memberProjects = resolveGroupChatThreadProjects(state, input.requestedProjectIds); if (memberProjects.length < 2) { throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS"); } const now = nowIso(); const projectId = randomToken("project"); const threadId = randomToken("thread"); const threadDisplayName = buildAutoGroupChatName(memberProjects); const folderName = "群聊"; const groupMembers = memberProjects.map((memberProject) => ({ projectId: memberProject.id, deviceId: memberProject.deviceIds[0] ?? memberProject.id, threadId: memberProject.threadMeta.threadId, threadDisplayName: memberProject.threadMeta.threadDisplayName, folderName: memberProject.threadMeta.folderName, })); const seedProject = memberProjects[0]; const nextProject = normalizeProject({ id: projectId, name: threadDisplayName, pinned: false, systemPinned: false, deviceIds: dedupeStrings(groupMembers.map((member) => member.deviceId)), preview: `已创建群聊《${threadDisplayName}》`, updatedAt: now, lastMessageAt: now, isGroup: true, unreadCount: 0, riskLevel: input.defaultRiskLevel ?? seedProject?.riskLevel ?? "normal", threadMeta: { projectId, threadId, threadDisplayName, folderName, activityIconCount: Math.max(1, memberProjects.length), updatedAt: now, }, groupMembers, createdByAgent: true, collaborationMode: "development", approvalState: "not_required", messages: [ { id: randomToken("msg"), sender: "master", senderLabel: input.createdBy || "群聊创建", body: `已由 ${input.createdBy || "系统"} 创建群聊《${threadDisplayName}》。`, sentAt: now, kind: "text", }, ], goals: [], versions: [], }); state.projects.unshift(nextProject); return nextProject; } function buildAutoGroupChatName(memberProjects: Project[]) { const titles = memberProjects .map((project) => project.threadMeta.threadDisplayName || project.name) .filter((title) => typeof title === "string" && title.trim().length > 0); if (titles.length === 0) { return "新群聊"; } if (titles.length === 1) { return titles[0]; } if (titles.length === 2) { return `${titles[0]}、${titles[1]}`; } return `${titles[0]}、${titles[1]}等${titles.length}个线程`; } export async function appendProjectMessage(payload: { projectId: string; sender?: MessageSender; senderLabel?: string; body?: string; kind?: MessageKind; attachments?: MessageAttachment[]; }) { const message = await mutateState((state) => { const project = state.projects.find((item) => item.id === payload.projectId); if (!project) throw new Error("PROJECT_NOT_FOUND"); const body = payload.body?.trim(); if (!body && payload.kind === "text") { throw new Error("MESSAGE_BODY_REQUIRED"); } if (payload.kind === "attachment" && (!payload.attachments || payload.attachments.length === 0)) { throw new Error("ATTACHMENT_REQUIRED"); } const firstAttachment = payload.attachments?.[0]; const message: Message = { id: randomToken("msg"), sender: payload.sender ?? "user", senderLabel: payload.senderLabel ?? "你", body: body ?? (payload.kind === "attachment" ? buildAttachmentMessageBody( firstAttachment ?? { attachmentId: randomToken("att"), fileName: "附件", mimeType: "application/octet-stream", fileSizeBytes: 0, attachmentKind: "binary", storageBackend: "server_file", storagePath: "", previewAvailable: false, uploadedAt: nowIso(), uploadedBy: payload.senderLabel ?? "你", analysisState: "not_applicable", }, ) : payload.kind === "voice_intent" ? "已提交语音转文字请求,等待主 Agent 记录语音摘要。" : payload.kind === "image_intent" ? "已登记图片证据上传请求,等待对象存储通道接入。" : payload.kind === "video_intent" ? "已登记视频证据上传请求,等待对象存储通道接入。" : "已提交消息。"), sentAt: nowIso(), kind: payload.kind ?? "text", attachments: payload.attachments?.map((attachment) => normalizeMessageAttachment(attachment)), }; project.messages.push(message); project.unreadCount = 0; project.lastMessageAt = message.sentAt; project.preview = message.body; return message; }); publishBossEvent("project.messages.updated", { projectId: payload.projectId }); publishBossEvent("conversation.updated", { projectId: payload.projectId }); return message; } export async function appendAttachmentMessage(payload: { projectId: string; sender?: MessageSender; senderLabel?: string; attachment: MessageAttachment; body?: string; }) { return appendProjectMessage({ projectId: payload.projectId, sender: payload.sender ?? "user", senderLabel: payload.senderLabel ?? "你", body: payload.body ?? buildAttachmentMessageBody(payload.attachment), kind: "attachment", attachments: [payload.attachment], }); } function findProjectMessage(project: Project, messageId: string) { return project.messages.find((message) => message.id === messageId) ?? null; } export function findProjectAttachment( project: Project, attachmentId: string, ): { message: Message; attachment: MessageAttachment } | null { for (const message of project.messages) { const attachment = message.attachments?.find((item) => item.attachmentId === attachmentId); if (attachment) { return { message, attachment }; } } return null; } export async function getProjectAttachment(projectId: string, attachmentId: string) { const state = await readState(); const project = state.projects.find((item) => item.id === projectId); if (!project) { return null; } const match = findProjectAttachment(project, attachmentId); if (!match) { return null; } return { project, message: match.message, attachment: match.attachment, }; } export async function getAttachmentById(attachmentId: string) { const state = await readState(); for (const project of state.projects) { const match = findProjectAttachment(project, attachmentId); if (match) { return { project, message: match.message, attachment: match.attachment, }; } } return null; } function summarizeAttachmentAnalysis(body: string) { const compact = body.replace(/\s+/g, " ").trim(); if (!compact) { return "附件分析已完成。"; } return compact.length <= 120 ? compact : `${compact.slice(0, 117)}...`; } export async function updateAttachmentAnalysisResult(payload: { projectId: string; attachmentId: string; status: Exclude; summary?: string; cardBody?: string; }) { return mutateState((state) => { const project = state.projects.find((item) => item.id === payload.projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } const match = findProjectAttachment(project, payload.attachmentId); if (!match) { throw new Error("ATTACHMENT_NOT_FOUND"); } match.attachment.analysisState = payload.status; match.attachment.analysisSummary = payload.status === "completed" ? payload.summary?.trim() || summarizeAttachmentAnalysis(payload.cardBody ?? "") : payload.summary; match.attachment.analysisCardId = undefined; if (payload.status === "completed" && payload.cardBody?.trim()) { const summary = payload.summary?.trim() || summarizeAttachmentAnalysis(payload.cardBody); pushProjectLedgerMessage(state, payload.projectId, { sender: "master", senderLabel: "主 Agent", body: summary, kind: "text", }); const card = pushProjectLedgerMessage(state, payload.projectId, { sender: "master", senderLabel: "主 Agent", body: payload.cardBody.trim(), kind: "analysis_card", }); match.attachment.analysisCardId = card?.id; match.attachment.analysisSummary = summary; } return { projectId: payload.projectId, attachmentId: payload.attachmentId, analysisState: match.attachment.analysisState, analysisSummary: match.attachment.analysisSummary, analysisCardId: match.attachment.analysisCardId, }; }).then((result) => { publishBossEvent("project.messages.updated", { projectId: result.projectId }); publishBossEvent("conversation.updated", { projectId: result.projectId }); return result; }); } function requiresForwardApproval(source: Project, target: Project) { return source.collaborationMode === "approval_required" && target.id !== "master-agent"; } function buildForwardSingleMessage(input: { source: Project; target: Project; message: Message; requestedBy: string; }) { const sentAt = nowIso(); const body = `转发自《${input.source.name}》到《${input.target.name}》:${input.message.body}`; return { id: randomToken("forward"), sender: "user" as const, senderLabel: "你", body, sentAt, kind: "forward_single" as const, forwardSource: { sourceProjectId: input.source.id, sourceProjectName: input.source.name, sourceThreadId: input.source.threadMeta?.threadId, sourceThreadTitle: input.source.threadMeta?.threadDisplayName, sourceMessageId: input.message.id, forwardedBy: input.requestedBy, forwardedAt: sentAt, }, } satisfies Message; } function buildForwardBundleMessage(input: { source: Project; target: Project; messages: Message[]; requestedBy: string; }) { const sentAt = nowIso(); const startedAt = input.messages[0]?.sentAt ?? sentAt; const endedAt = input.messages[input.messages.length - 1]?.sentAt ?? sentAt; const body = `转发自《${input.source.name}》到《${input.target.name}》:${input.messages.length} 条消息,最后一条:${ input.messages[input.messages.length - 1]?.body ?? "" }`; return { id: randomToken("forward"), sender: "user" as const, senderLabel: "你", body, sentAt, kind: "forward_bundle" as const, forwardBundle: { sourceProjectId: input.source.id, sourceProjectName: input.source.name, sourceThreadId: input.source.threadMeta?.threadId, sourceThreadTitle: input.source.threadMeta?.threadDisplayName, itemCount: input.messages.length, startedAt, endedAt, items: input.messages.map((message) => ({ messageId: message.id, senderLabel: message.senderLabel, body: message.body, kind: message.kind ?? "text", sentAt: message.sentAt, })), }, } satisfies Message; } export async function forwardProjectMessage( payload: | { sourceProjectId: string; mode: "single"; targetProjectId: string; sourceMessageId: string; requestedBy: string; } | { sourceProjectId: string; mode: "bundle"; targetProjectId: string; sourceMessageIds: string[]; requestedBy: string; }, ) { const state = await readState(); const source = state.projects.find((item) => item.id === payload.sourceProjectId); const target = state.projects.find((item) => item.id === payload.targetProjectId); if (!source || !target) throw new Error("PROJECT_NOT_FOUND"); if (requiresForwardApproval(source, target)) { return { approvalRequired: true, approvalReason: "NON_DEVELOPMENT_THREAD_FORWARD", } as const; } if (payload.mode === "single") { const sourceMessage = findProjectMessage(source, payload.sourceMessageId); if (!sourceMessage) throw new Error("MESSAGE_NOT_FOUND"); const message = await mutateState((state) => { const sourceProject = state.projects.find((item) => item.id === payload.sourceProjectId); const targetProject = state.projects.find((item) => item.id === payload.targetProjectId); if (!sourceProject || !targetProject) throw new Error("PROJECT_NOT_FOUND"); const sourceLedgerMessage = findProjectMessage(sourceProject, payload.sourceMessageId); if (!sourceLedgerMessage) throw new Error("MESSAGE_NOT_FOUND"); const message = buildForwardSingleMessage({ source: sourceProject, target: targetProject, message: sourceLedgerMessage, requestedBy: payload.requestedBy, }); targetProject.messages.push(message); targetProject.unreadCount += 1; targetProject.lastMessageAt = message.sentAt; targetProject.preview = message.body; sourceProject.messages.push({ id: randomToken("forward-log"), sender: "master", senderLabel: "主 Agent", body: `已转发到《${targetProject.name}》。`, sentAt: message.sentAt, kind: "forward_notice", }); sourceProject.lastMessageAt = message.sentAt; return message; }); publishBossEvent("project.messages.updated", { projectId: payload.sourceProjectId }); publishBossEvent("project.messages.updated", { projectId: payload.targetProjectId }); publishBossEvent("conversation.updated", { projectId: payload.sourceProjectId }); publishBossEvent("conversation.updated", { projectId: payload.targetProjectId }); return { message }; } const sourceMessageIds = payload.mode === "bundle" ? payload.sourceMessageIds : []; const sourceMessages = sourceMessageIds .map((messageId) => findProjectMessage(source, messageId)) .filter((message): message is Message => Boolean(message)); if (sourceMessages.length <= 1 || sourceMessages.length !== sourceMessageIds.length) { throw new Error("MESSAGE_NOT_FOUND"); } const message = await mutateState((state) => { const sourceProject = state.projects.find((item) => item.id === payload.sourceProjectId); const targetProject = state.projects.find((item) => item.id === payload.targetProjectId); if (!sourceProject || !targetProject) throw new Error("PROJECT_NOT_FOUND"); const bundleMessages = sourceMessageIds .map((messageId) => findProjectMessage(sourceProject, messageId)) .filter((item): item is Message => Boolean(item)); if (bundleMessages.length <= 1 || bundleMessages.length !== sourceMessageIds.length) { throw new Error("MESSAGE_NOT_FOUND"); } const message = buildForwardBundleMessage({ source: sourceProject, target: targetProject, messages: bundleMessages, requestedBy: payload.requestedBy, }); targetProject.messages.push(message); targetProject.unreadCount += 1; targetProject.lastMessageAt = message.sentAt; targetProject.preview = message.body; sourceProject.messages.push({ id: randomToken("forward-log"), sender: "master", senderLabel: "主 Agent", body: `已转发到《${targetProject.name}》。`, sentAt: message.sentAt, kind: "forward_notice", }); sourceProject.lastMessageAt = message.sentAt; return message; }); publishBossEvent("project.messages.updated", { projectId: payload.sourceProjectId }); publishBossEvent("project.messages.updated", { projectId: payload.targetProjectId }); publishBossEvent("conversation.updated", { projectId: payload.sourceProjectId }); publishBossEvent("conversation.updated", { projectId: payload.targetProjectId }); return { message }; } export async function updateUserSettings(settings: Partial) { const nextSettings = await mutateState((state) => { state.user.settings = { ...state.user.settings, ...settings, }; return state.user.settings; }); publishBossEvent("conversation.updated", { deviceId: PRIMARY_CODEX_NODE_ID }); return nextSettings; } export async function getOtaStatus() { const state = await readState(); const available = firstAvailableOta(state); const asset = await getPublishedOtaAsset(); const availableRelease = available ? { ...available, packageFileName: asset?.fileName, packageSizeBytes: asset?.sizeBytes, packageSha256: asset?.sha256, downloadUrl: asset?.downloadUrl, assetUpdatedAt: asset?.updatedAt, } : null; return { currentVersion: state.user.version, hasOta: state.user.hasOta, availableRelease, logs: state.otaUpdateLogs, canApply: state.user.role === "highest_admin", boundCodexNodeLabel: state.user.boundCodexNodeLabel, roleLabel: state.user.roleLabel, }; } export async function checkForOta() { const result = await mutateState((state) => { const available = firstAvailableOta(state); state.otaUpdateLogs.unshift({ logId: randomToken("otalog"), releaseId: available?.releaseId ?? "ota_check_empty", version: available?.version ?? state.user.version, status: "checked", triggeredBy: state.user.name, triggeredAt: nowIso(), note: available ? `检查到可用 OTA:${available.version},目标范围:${available.targetScope}。` : "检查完成,当前已是最新版本。", }); return { currentVersion: state.user.version, availableRelease: available, hasOta: Boolean(available), deviceId: state.user.boundDeviceId, note: available?.version ?? state.user.version, }; }); const asset = await getPublishedOtaAsset(); publishBossEvent("ota.updated", { deviceId: result.deviceId, note: result.note, }); return { ...result, availableRelease: result.availableRelease ? { ...result.availableRelease, packageFileName: asset?.fileName, packageSizeBytes: asset?.sizeBytes, packageSha256: asset?.sha256, downloadUrl: asset?.downloadUrl, assetUpdatedAt: asset?.updatedAt, } : null, }; } export async function performOta() { const result = await mutateState((state) => { const available = firstAvailableOta(state); if (!available) { throw new Error("NO_OTA_AVAILABLE"); } if (state.user.role !== "highest_admin") { throw new Error("ADMIN_REQUIRED_FOR_OTA"); } const nextVersion = available.version; const otaSummary = [...available.summary]; state.user.version = nextVersion; available.status = "applied"; state.otaUpdateLogs.unshift({ logId: randomToken("otalog"), releaseId: available.releaseId, version: available.version, status: "applied", triggeredBy: state.user.name, triggeredAt: nowIso(), completedAt: nowIso(), note: `由 ${state.user.roleLabel} 在 ${state.user.boundCodexNodeLabel ?? PRIMARY_CODEX_NODE_LABEL} 发起 OTA。`, }); const project = state.projects.find((item) => item.id === "master-agent"); if (project) { project.messages.push({ id: randomToken("ota"), sender: "ops", senderLabel: "OTA 控制面", body: `已完成 OTA 升级到 ${nextVersion},变更:${otaSummary.join(";") || "无"}`, sentAt: nowIso(), kind: "text", }); project.lastMessageAt = nowIso(); if (!project.versions.some((entry) => entry.version === nextVersion)) { project.versions.unshift({ version: nextVersion, summary: `通过 OTA 发布:${otaSummary.join(";") || "无"}`, createdAt: nowIso(), }); } } return { version: state.user.version, summary: otaSummary, deviceId: state.user.boundDeviceId, nextVersion, }; }); const asset = await getPublishedOtaAsset(); publishBossEvent("ota.updated", { deviceId: result.deviceId, projectId: "master-agent", note: result.nextVersion, }); publishBossEvent("project.messages.updated", { projectId: "master-agent" }); publishBossEvent("conversation.updated", { projectId: "master-agent" }); return { ...result, downloadUrl: asset?.downloadUrl, packageFileName: asset?.fileName, packageSizeBytes: asset?.sizeBytes, packageSha256: asset?.sha256, }; } export async function approveRepairTicket(ticketId: string) { return mutateState((state) => { const ticket = state.opsRepairTickets.find((item) => item.ticketId === ticketId); if (!ticket) throw new Error("TICKET_NOT_FOUND"); ticket.approvalStatus = "approved"; ticket.executionStatus = "running"; ticket.approvedBy = "主 Agent"; ticket.updatedAt = nowIso(); return ticket; }); } export async function verifyRepairTicket(ticketId: string) { return mutateState((state) => { const ticket = state.opsRepairTickets.find((item) => item.ticketId === ticketId); if (!ticket) throw new Error("TICKET_NOT_FOUND"); ticket.executionStatus = "verified"; ticket.updatedAt = nowIso(); const verification = state.opsRepairVerifications.find((item) => item.ticketId === ticketId); if (verification) { verification.status = "passed"; verification.summary = "最新一轮修复已通过复验,可关闭工单。"; verification.verifiedAt = nowIso(); } const fault = state.opsFaults.find((item) => item.faultId === ticket.faultId); if (fault) { fault.status = "resolved"; fault.lastSeenAt = nowIso(); } return ticket; }); }