import { createHash, createHmac, randomBytes, scryptSync, timingSafeEqual } from "node:crypto"; import { existsSync, readFileSync, statSync } from "node:fs"; import path from "node:path"; import { publishBossEvent } from "@/lib/boss-events"; import { deliverAdminNotification, type AdminNotificationDeliveryResult, type AdminNotificationDeliveryStatus, } from "@/lib/boss-admin-notification-delivery"; import type { VerificationDeliveryMode } from "@/lib/boss-mail"; import { getFixedVerificationCode, getVerificationDeliveryMode } from "@/lib/boss-mail"; import { getPublishedOtaAsset } from "@/lib/boss-ota"; import { buildOperationalRiskFaultDrafts, buildRiskSlaNotificationDrafts } from "@/lib/boss-risk-notifications"; import { createBossStateStore } from "@/lib/boss-state-store"; import { buildMasterAgentTaskSlaRow, isMasterAgentTaskAutoRecoverable, } from "@/lib/master-agent-task-sla"; import { BOSS_NATIVE_ORCHESTRATOR } from "@/lib/execution/backends/boss-native-orchestrator"; import { OMX_TEAM_BACKEND, getOmxTeamBackendSelectionState, type OmxTeamBackendSelectionState, } from "@/lib/execution/backends/omx-team-backend"; import { selectOrchestrationBackend } from "@/lib/execution/orchestration-backend-selector"; import { MASTER_CODEX_NODE_OUTPUT_LEAKED, sanitizeSensitiveTaskFailureDetailForLog, sanitizeSensitiveTaskFailureDetailForTransport, sanitizeSensitiveUserVisibleText, shouldBlockSensitiveMasterAgentOutput, } from "@/lib/execution/sensitive-output-guard"; import { hasRecentThreadConversationExternalActivity } from "@/lib/thread-execution-conflict"; 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" | "thread_process" | "system_notice" | "control_summary" | "execution_progress" | "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 type ExecutionProgressStatus = "queued" | "running" | "completed" | "failed"; export type ExecutionProgressStepStatus = "pending" | "running" | "done" | "failed"; export type ExecutionProgressArtifactKind = "file" | "image" | "link" | "log"; export type ExecutionProgressControlMode = "codex_thread" | "native_remote_control"; export interface ExecutionProgressStep { id: string; text: string; status: ExecutionProgressStepStatus; } export interface ExecutionProgressBranchDetails { additions?: number; deletions?: number; changedFiles?: number; gitStatus?: string; githubCliStatus?: "available" | "unavailable" | "unknown"; } export interface ExecutionProgressArtifact { id?: string; label: string; kind: ExecutionProgressArtifactKind; path?: string; url?: string; } export interface ExecutionProgressAgent { name: string; role?: string; status?: string; } export interface ExecutionProgressApproval { id?: string; kind?: string; label: string; status?: string; riskLevel?: string; detail?: string; } export interface ExecutionProgressWarning { id?: string; message: string; severity?: string; } export interface ExecutionProgressFileChange { id?: string; path: string; kind?: string; status?: string; } export interface ExecutionProgressThreadStatus { type: string; activeFlags?: string[]; waitingOnApproval?: boolean; waitingOnUserInput?: boolean; } export type ExecutionProgressRealtimeStatus = "started" | "streaming" | "closed" | "error"; export interface ExecutionProgressRealtime { status: ExecutionProgressRealtimeStatus; sessionId?: string; version?: string; transcriptRole?: string; transcriptPreview?: string; audioChunkCount?: number; itemCount?: number; lastError?: string; closeReason?: string; } export interface ExecutionProgressModelRoute { fromModel: string; toModel: string; reason?: string; } export interface ExecutionProgressTokenUsage { totalTokens: number; inputTokens?: number; cachedInputTokens?: number; outputTokens?: number; reasoningOutputTokens?: number; modelContextWindow?: number; contextPercent?: number; } export interface ExecutionProgressAccountStatus { authMode?: string; planType?: string; limitId?: string; limitName?: string; usedPercent?: number; windowDurationMins?: number; resetsAt?: number; rateLimitReachedType?: string; creditsBalance?: string; hasCredits?: boolean; unlimitedCredits?: boolean; } export interface ExecutionProgressModelVerification { verifications: string[]; } export interface ExecutionProgressMcpServer { name: string; status?: string; error?: string; } export interface ExecutionProgressRemoteControl { status: string; serverName?: string; environmentId?: string; } export interface ExecutionProgressWindowsSandbox { status?: string; setupMode?: string; error?: string; } export interface ExecutionProgressThreadGoal { objective?: string; status: string; tokenBudget?: number; tokensUsed?: number; timeUsedSeconds?: number; } export interface ExecutionProgressThreadSettings { model?: string; modelProvider?: string; approvalPolicy?: string; approvalsReviewer?: string; sandboxPolicy?: string; permissionProfile?: string; serviceTier?: string; effort?: string; summary?: string; collaborationMode?: string; personality?: string; } export interface ExecutionProgressCompaction { status: string; message?: string; } export interface ExecutionProgressThreadCollaboration { tool?: string; status: string; target?: string; agentStatus?: string; receiverCount?: number; } export interface ExecutionProgressToolActivity { kind: string; name: string; status: string; detail?: string; } export interface ExecutionProgressReasoningSummary { status: string; summary: string; } export interface ExecutionProgressStreamEvents { status: string; agentDeltaCount?: number; planDeltaCount?: number; reasoningDeltaCount?: number; toolProgressCount?: number; commandOutputChunkCount?: number; terminalInteractionCount?: number; fileOutputChunkCount?: number; } export interface ExecutionProgressSnapshot { taskId: string; projectId: string; targetProjectId?: string; taskType?: MasterAgentTaskType; phase?: MasterAgentTaskPhase; controlMode?: ExecutionProgressControlMode; runtimeKind?: ComputerControlRuntimeKind; controlPlatform?: ComputerControlPlatform; computerUseProvider?: ComputerUseProvider; title: string; status: ExecutionProgressStatus; steps: ExecutionProgressStep[]; branch?: ExecutionProgressBranchDetails; artifacts?: ExecutionProgressArtifact[]; agents?: ExecutionProgressAgent[]; approvals?: ExecutionProgressApproval[]; warnings?: ExecutionProgressWarning[]; fileChanges?: ExecutionProgressFileChange[]; threadStatus?: ExecutionProgressThreadStatus; realtime?: ExecutionProgressRealtime; modelRoute?: ExecutionProgressModelRoute; tokenUsage?: ExecutionProgressTokenUsage; accountStatus?: ExecutionProgressAccountStatus; modelVerification?: ExecutionProgressModelVerification; mcpServers?: ExecutionProgressMcpServer[]; remoteControl?: ExecutionProgressRemoteControl; windowsSandbox?: ExecutionProgressWindowsSandbox; threadGoal?: ExecutionProgressThreadGoal; threadSettings?: ExecutionProgressThreadSettings; compaction?: ExecutionProgressCompaction; threadCollaboration?: ExecutionProgressThreadCollaboration; toolActivities?: ExecutionProgressToolActivity[]; reasoningSummary?: ExecutionProgressReasoningSummary; streamEvents?: ExecutionProgressStreamEvents; updatedAt: string; } export interface ExecutionProgressInput { steps?: Array & { text?: string }>; phase?: MasterAgentTaskPhase; branch?: Partial; artifacts?: Array & { label?: string }>; agents?: Array & { name?: string }>; approvals?: Array & { label?: string }>; warnings?: Array & { message?: string }>; fileChanges?: Array & { path?: string }>; threadStatus?: Partial; realtime?: Partial; modelRoute?: Partial; tokenUsage?: Partial; accountStatus?: Partial & { accessToken?: unknown; apiKey?: unknown }; modelVerification?: Partial & { turnId?: unknown }; mcpServers?: Array & { name?: string }>; remoteControl?: Partial & { installationId?: unknown }; windowsSandbox?: Partial & { sourcePath?: unknown; samplePaths?: unknown }; threadGoal?: Partial; threadSettings?: Partial & { cwd?: unknown }; compaction?: Partial & { turnId?: unknown }; threadCollaboration?: Partial & { senderThreadId?: unknown; receiverThreadId?: unknown; newThreadId?: unknown; prompt?: unknown; }; toolActivities?: Array< Partial & { arguments?: unknown; result?: unknown; contentItems?: unknown; url?: unknown; path?: unknown; command?: unknown; cwd?: unknown; } >; reasoningSummary?: Partial & { content?: unknown; itemId?: unknown; }; streamEvents?: Partial & { delta?: unknown; text?: unknown; content?: unknown; output?: unknown; command?: unknown; itemId?: unknown; turnId?: unknown; }; } 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 AuthAccountStatus = "active" | "disabled"; export type BossPermission = | "device.view" | "device.manage" | "project.view" | "thread.chat" | "master_agent.ask" | "master_agent.takeover" | "computer.control" | "skill.view" | "skill.use" | "skill.manage" | "account.manage" | "audit.view"; export type LoginMethod = "password" | "code"; export type DeviceExecutionMode = "gui" | "cli"; export type ProjectConflictAllowPolicy = "forbid" | "allow_once" | "allow_always"; export type ProjectConflictState = "none" | "warning" | "blocked"; 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" | "google_oauth" | "chatgpt_oauth" | "openai_api" | "aliyun_qwen_api" | "deepseek_api" | "minimax_api" | "glm_api" | "hyzq_api" | "custom_api"; export type AiAccountRole = "primary" | "backup" | "api_fallback"; export type AiAccountStatus = "ready" | "needs_login" | "needs_api_key" | "degraded" | "disabled"; export type MasterAgentTaskStatus = | "queued" | "running" | "needs_user_action" | "completed" | "failed" | "timed_out" | "canceled"; export type MasterAgentTaskPhase = | "queued" | "claimed" | "executor_starting" | "turn_started" | "awaiting_reply" | "completing" | "completed" | "recoverable_failed" | "terminal_failed" | "timed_out" | "canceled" | "needs_user_action"; export type DialogGuardInterventionAction = | "allow_once" | "allow_for_device_dialog" | "deny" | "handled_on_device" | "cancel_task"; export type DialogGuardInterventionStatus = "pending" | "resolved"; export type MasterAgentTaskType = | "conversation_reply" | "attachment_analysis" | "group_dispatch_plan" | "dispatch_execution" | "device_import_resolution" | "device_maintenance" | "browser_control" | "desktop_control"; export type DeviceMaintenanceKind = "codex_remote_control"; export type CodexRemoteControlAction = "start" | "stop"; export type ComputerControlIntentCategory = | "discussion_only" | "project_development" | "thread_collaboration" | "thread_rollback" | "thread_compact" | "thread_archive" | "thread_unarchive" | "thread_rename" | "thread_goal_sync" | "thread_metadata_sync" | "thread_fork" | "browser_control" | "desktop_control"; export type ComputerControlRuntimeKind = | "codex-thread-runtime" | "browser-automation-runtime" | "computer-use-runtime"; export type ComputerControlPlatform = "macos"; export type ComputerUseProvider = | "boss-native-computer-use" | "codex-computer-use" | "cua-driver-computer-use" | "openai-computer-use"; export type ComputerControlRiskLevel = "low" | "medium" | "high"; export type ComputerControlConfirmationPolicy = | "none" | "light_confirm" | "strong_confirm"; export type DispatchPlanStatus = | "pending_user_confirmation" | "approved" | "rejected" | "dispatched"; export type DispatchExecutionStatus = "queued" | "running" | "completed" | "failed"; export type ReasoningEffort = "low" | "medium" | "high"; export type OrchestrationBackendId = import("@/lib/execution/orchestration-backend").OrchestrationBackendId; export type OrchestrationBackendOverride = "omx-team"; export const CURRENT_BOSS_STATE_SCHEMA_VERSION = 1; 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; companyId?: string; source: DeviceSource; status: DeviceStatus; projects: string[]; quota5h: number; quota7d: number; lastSeenAt: string; endpoint?: string; token?: string; note?: string; capabilities?: DeviceCapabilities; preferredExecutionMode?: DeviceExecutionMode; revokedAt?: string; revokedBy?: string; revokeReason?: string; } export interface Message { id: string; sender: MessageSender; senderLabel: string; body: string; sentAt: string; account?: string; kind?: MessageKind; externalMessageId?: string; controlTarget?: string; attachments?: MessageAttachment[]; forwardSource?: ForwardSource; forwardBundle?: ForwardBundlePayload; executionProgress?: ExecutionProgressSnapshot; } export interface DeviceObservedAssistantMessage { messageId: string; body: string; sentAt: string; phase?: string; } 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; } type ProjectUnderstandingSyncReply = { projectGoal?: string; currentProgress?: string; technicalArchitecture?: string; currentBlockers?: string; recommendedNextStep?: string; versionRecord?: string; }; export interface ThreadConversationMeta { projectId: string; threadId: string; threadDisplayName: string; folderName: string; activityIconCount: number; updatedAt: string; lastObservedCodexActivityAt?: string; lastProjectUnderstandingRequestedAt?: string; lastProjectUnderstandingSyncedAt?: string; codexThreadRef?: string; codexFolderRef?: string; } export interface ThreadMetadataGitInfoPatch { sha?: string | null; branch?: string | null; originUrl?: string | null; } 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"; lightDispatchReminderEnabled?: boolean; orchestrationBackendOverride?: OrchestrationBackendOverride; agentControls?: ProjectAgentControls; unreadCount: number; riskLevel: RiskLevel; contextBudgetPct?: number; contextBudgetLabel?: string; projectUnderstanding?: ProjectUnderstandingSnapshot; 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; requestedOrchestrationBackendId?: OrchestrationBackendOverride; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: string; orchestrationFallbackReason?: string; createdAt: string; confirmedAt?: string; confirmedBy?: string; confirmedTargetProjectIds?: string[]; } export interface DispatchExecution { executionId: string; planId: string; groupProjectId: string; targetProjectId: string; targetThreadId: string; deviceId: string; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: 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, lightDispatchReminderEnabled: false, }; } return { isGroup: project.isGroup, collaborationMode: project.collaborationMode, requiresMasterAgentApproval: project.isGroup && project.collaborationMode === "approval_required", approvalState: project.approvalState, lightDispatchReminderEnabled: Boolean(project.lightDispatchReminderEnabled), }; } export interface ProjectAgentControls { modelOverride?: string; reasoningEffortOverride?: ReasoningEffort; fastModelOverride?: string; deepModelOverride?: string; promptOverride?: string; backendOverride?: "claw-runtime"; takeoverEnabled?: boolean; globalTakeoverEnabled?: boolean; effectiveTakeoverEnabled?: boolean; takeoverInheritedFromGlobal?: boolean; updatedAt: string; } export interface DeviceCapabilityState { connected: boolean; lastSeenAt?: string; lastActiveProjectId?: string; metadata?: Record; } export interface DeviceCapabilities { gui: DeviceCapabilityState; cli: DeviceCapabilityState; browserAutomation: DeviceCapabilityState; computerUse: DeviceCapabilityState; codexAppServer: DeviceCapabilityState; } export interface DeviceCapabilitiesInput { gui?: Partial; cli?: Partial; browserAutomation?: Partial; computerUse?: Partial; codexAppServer?: Partial; } export interface DeviceUpdatePayload extends Partial> { capabilities?: DeviceCapabilitiesInput; } export interface ProjectExecutionPolicy { deviceId: string; folderKey?: string; projectId: string; activeCliExecution?: boolean; recentExternalActivityAt?: string; conflictState: ProjectConflictState; allowPolicy: ProjectConflictAllowPolicy; updatedAt: string; } export interface ProjectOrchestrationBackendChoice { backendId: OrchestrationBackendId; label: string; selectable: boolean; current: boolean; } export interface ProjectOrchestrationBackendState { projectId: string; requestedBackendId: OrchestrationBackendId; currentBackendId: OrchestrationBackendId; currentBackendLabel: string; requestedBackendLabel: string; availableChoices: ProjectOrchestrationBackendChoice[]; omxAvailability: OmxTeamBackendSelectionState["availability"]; } 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; recentAssistantMessages?: DeviceObservedAssistantMessage[]; } 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 DeviceImportProjectUnderstanding { candidateId: string; threadDisplayName: string; folderName: string; projectGoal: string; currentProgress: string; technicalArchitecture: string; currentBlockers: string; recommendedNextStep: string; sourceTaskId: string; updatedAt: string; } export interface ProjectUnderstandingSnapshot { projectGoal: string; currentProgress: string; technicalArchitecture: string; currentBlockers: string; recommendedNextStep: string; sourceTaskId: string; updatedAt: string; sourceKind: "device_import" | "thread_sync"; } export type ThreadStatusSourceKind = "device_import" | "full_sync" | "incremental_sync"; export type ThreadProgressEventType = | "phase_changed" | "progress_updated" | "blocker_added" | "blocker_resolved" | "next_step_changed" | "architecture_updated" | "handoff_ready"; export interface ThreadStatusDocument { documentId: string; projectId: string; threadId: string; threadDisplayName: string; folderName: string; deviceId: string; projectGoal: string; currentPhase: string; currentProgress: string; technicalArchitecture: string; currentBlockers: string; recommendedNextStep: string; keyFiles: string[]; keyCommands: string[]; updatedAt: string; sourceTaskId: string; sourceKind: ThreadStatusSourceKind; } export interface ThreadProgressEvent { eventId: string; projectId: string; threadId: string; threadDisplayName: string; deviceId: string; eventType: ThreadProgressEventType; summary: string; phase?: string; blockerDelta?: string; nextStepDelta?: string; createdAt: string; sourceTaskId: string; sourceMessageId?: 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; status?: AuthAccountStatus; companyId?: string; verificationEmail?: string; codexNodeId?: string; codexNodeLabel?: string; primaryDeviceId?: string; isPrimary?: boolean; failedLoginAttempts?: number; lockedUntil?: string; lastLoginAt?: string; lastLoginMethod?: LoginMethod; mfaRequired?: boolean; mfaSecret?: string; mfaEnabledAt?: string; 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 AdminCompany { companyId: string; name: string; ownerAccount?: string; successOwnerAccount?: string; planTier?: "trial" | "standard" | "enterprise"; contractExpiresAt?: string; status: "active" | "disabled"; note?: string; createdAt: string; updatedAt: string; } export interface AdminNotification { notificationId: string; kind: "risk_sla_overdue"; severity: OpsSeverity; companyId: string; riskId: string; title: string; body: string; status: "open" | "acknowledged" | "resolved"; deliveryStatus?: AdminNotificationDeliveryStatus; deliveryTarget?: string; deliveredAt?: string; deliveryError?: string; createdAt: string; acknowledgedAt?: string; acknowledgedBy?: string; } export interface AdminRiskTimelineEvent { eventId: string; riskId: string; notificationId?: string; companyId?: string; action: string; actorAccount: string; note?: string; createdAt: string; } export interface AccountDeviceGrant { grantId: string; account: string; deviceId: string; permissions: BossPermission[]; grantedBy: string; grantedAt: string; expiresAt?: string; note?: string; } export interface AccountProjectGrant { grantId: string; account: string; projectId: string; deviceId?: string; permissions: BossPermission[]; inheritFromDeviceGrant?: boolean; grantedBy: string; grantedAt: string; expiresAt?: string; note?: string; } export interface AccountSkillGrant { grantId: string; account: string; skillId: string; deviceId?: string; projectId?: string; permissions: BossPermission[]; grantedBy: string; grantedAt: string; expiresAt?: string; note?: string; } export interface SkillCatalogEntry { skillId: string; name: string; description: string; sourceType: "gitea" | "skillhub" | "local"; sourceUrl?: string; version?: string; checksum?: string; category?: string; updatedAt: string; } export type SkillLifecycleAction = "install" | "update" | "uninstall" | "rollback" | "version_lock"; export type SkillLifecycleRequestStatus = "pending" | "accepted" | "running" | "completed" | "failed" | "canceled"; export interface SkillLifecycleRequest { requestId: string; action: SkillLifecycleAction; status: SkillLifecycleRequestStatus; deviceId: string; skillId?: string; sourceUrl?: string; trustedSource?: string; trustedSourceId?: string; checksum?: string; expectedChecksum?: string; targetVersion?: string; rollbackToVersion?: string; lockedVersion?: string; requestedBy: string; requestedAt: string; claimedByDeviceId?: string; claimedAt?: string; completedAt?: string; updatedAt: string; note?: string; resultSummary?: string; error?: string; } export type PermissionAuditSnapshot = Record; export interface PermissionAuditMeta { ipAddress?: string; userAgent?: string; requestId?: string; beforeJson?: PermissionAuditSnapshot; afterJson?: PermissionAuditSnapshot; } export interface PermissionAuditLog { auditId: string; actorAccount: string; action: | "grant.created" | "grant.updated" | "grant.revoked" | "skill.assigned" | "skill.revoked" | "skill.lifecycle.requested" | "skill.lifecycle.completed" | "account.updated" | "account.mfa_updated" | "account.password_reset" | "account.reclaimed" | "company.updated" | "company.assigned" | "company.status_updated" | "device.revoked" | "risk.notification.created" | "risk.notification.dispatched" | "dialog_guard.intervention_required" | "dialog_guard.intervention_resolved" | "backup.snapshot_created" | "backup.snapshot_verified" | "backup.restore_previewed" | "backup.restore_dry_run" | "backup.snapshot_restored" | "master_agent.task_retried" | "task.authorized" | "task.denied"; targetAccount?: string; deviceId?: string; projectId?: string; skillId?: string; permissions?: BossPermission[]; detail?: string; ipAddress?: string; userAgent?: string; requestId?: string; beforeJson?: PermissionAuditSnapshot; afterJson?: PermissionAuditSnapshot; createdAt: string; } export interface AiAccount { accountId: string; label: string; role: AiAccountRole; provider: AiProvider; displayName: string; accountIdentifier?: string; nodeId?: string; nodeLabel?: string; model?: string; apiBaseUrl?: 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; apiBaseUrl?: 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; sourceMessageId?: string; sourceMessageBody?: string; sourceMessageSentAt?: string; requestedBy: string; requestedByAccount: string; authorizedDeviceIds?: string[]; authorizedProjectIds?: string[]; authorizedSkillIds?: string[]; requiredPermissions?: BossPermission[]; deviceId: string; accountId?: string; accountLabel?: string; modelChannelAttemptedDeviceIds?: string[]; attachmentId?: string; attachmentFileName?: string; attachmentDownloadToken?: string; attachmentDownloadExpiresAt?: string; attachmentDownloadUrl?: string; attachmentTextExcerpt?: string; dispatchExecutionId?: string; sourceProjectId?: string; sourceThreadId?: string; sourceThreadDisplayName?: string; sourceCodexThreadRef?: string; targetProjectId?: string; targetThreadId?: string; targetThreadDisplayName?: string; targetCodexThreadRef?: string; targetTurnId?: string; targetCodexTurnId?: string; targetCodexFolderRef?: string; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: string; deviceImportDraftId?: string; deviceImportCandidateId?: string; deviceImportCandidateFolderName?: string; maintenanceKind?: DeviceMaintenanceKind; codexRemoteControlAction?: CodexRemoteControlAction; projectUnderstandingTargetProjectId?: string; projectUnderstandingReason?: "heartbeat_activity" | "thread_reply"; projectUnderstandingReplyProjectId?: string; projectUnderstandingNotifyOnCompletion?: boolean; relayViaMasterAgent?: boolean; mirrorBossUserMessageToCodexDesktop?: boolean; rollbackNumTurns?: number; rollbackReason?: string; compactReason?: string; threadLifecycleAction?: "archive" | "unarchive"; threadLifecycleReason?: string; threadRenameName?: string; threadRenameReason?: string; threadGoalObjective?: string; threadGoalStatus?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete"; threadGoalTokenBudget?: number; threadGoalReason?: string; threadMetadataGitInfo?: ThreadMetadataGitInfoPatch; threadMetadataReason?: string; threadForkEphemeral?: boolean; threadForkReason?: string; intentCategory?: ComputerControlIntentCategory; runtimeKind?: ComputerControlRuntimeKind; controlPlatform?: ComputerControlPlatform; computerUseProvider?: ComputerUseProvider; riskLevel?: ComputerControlRiskLevel; confirmationPolicy?: ComputerControlConfirmationPolicy; requiresUserConfirmation?: boolean; confirmationScopeKey?: string; externalReplyTarget?: ExternalReplyTarget; status: MasterAgentTaskStatus; phase?: MasterAgentTaskPhase; requestedAt: string; claimedAt?: string; lastClaimedAt?: string; leaseExpiresAt?: string; lastProgressAt?: string; lastErrorCode?: string; recoverable?: boolean; nextRetryAt?: string; attemptCount?: number; maxAttempts?: number; completedAt?: string; canceledAt?: string; canceledBy?: string; cancelReason?: string; lastErrorKind?: string; replyBody?: string; errorMessage?: string; requestId?: string; targetUrl?: string; targetApp?: string; } export interface DialogGuardIntervention { interventionId: string; dialogId: string; requestId?: string; taskId: string; deviceId: string; projectId: string; appName?: string; platform?: string; risk: ComputerControlRiskLevel; summary: string; recommendedAction?: DialogGuardInterventionAction; availableActions: DialogGuardInterventionAction[]; status: DialogGuardInterventionStatus; decision?: DialogGuardInterventionAction; decisionBy?: string; decisionNote?: string; createdAt: string; resolvedAt?: string; } export interface ExternalReplyTarget { provider: "telegram"; chatId: string; messageId?: number; threadId?: number; sessionKey?: string; deliveredAt?: string; deliveryError?: string; } export interface TelegramGroupProjectRoute { chatId: string; threadId?: number; projectId: string; label?: string; } export interface TelegramIntegrationState { enabled: boolean; mode: "webhook" | "polling"; botToken?: string; botUsername?: string; dmPolicy: "allowlist" | "open" | "disabled"; allowFrom: string[]; groupPolicy: "allowlist" | "open" | "disabled"; groups: string[]; requireMentionInGroups: boolean; defaultProjectId: string; groupProjectRoutes: TelegramGroupProjectRoute[]; webhookSecret?: string; webhookUrl?: string; lastConfiguredAt?: string; lastConfiguredBy?: string; lastError?: string; processedUpdateIds: number[]; } 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; ownerAccount?: string; slaDueAt?: string; riskNote?: 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; ownerAccount?: string; slaDueAt?: string; riskNote?: 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 { schemaVersion: number; migratedAt: string; user: UserProfile; devices: Device[]; projects: Project[]; conversationHistoryClearedAt?: string; verificationCodes: VerificationCode[]; verificationDispatches: VerificationDispatch[]; adminCompanies: AdminCompany[]; adminNotifications: AdminNotification[]; adminRiskTimeline: AdminRiskTimelineEvent[]; authAccounts: AuthAccount[]; authSessions: AuthSession[]; accountDeviceGrants: AccountDeviceGrant[]; accountProjectGrants: AccountProjectGrant[]; accountSkillGrants: AccountSkillGrant[]; skillCatalog: SkillCatalogEntry[]; skillLifecycleRequests: SkillLifecycleRequest[]; permissionAuditLogs: PermissionAuditLog[]; dialogGuardInterventions: DialogGuardIntervention[]; aiAccounts: AiAccount[]; aiAccountSwitchHistory: AiAccountSwitchRecord[]; masterAgentTasks: MasterAgentTask[]; dispatchPlans: DispatchPlan[]; dispatchExecutions: DispatchExecution[]; deviceImportDrafts: DeviceImportDraft[]; deviceImportResolutions: DeviceImportResolution[]; threadStatusDocuments: ThreadStatusDocument[]; threadProgressEvents: ThreadProgressEvent[]; 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[]; projectExecutionPolicies: ProjectExecutionPolicy[]; telegramIntegration?: TelegramIntegrationState; } 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 backupFile = `${dataFile}.bak`; const stateStore = createBossStateStore({ dataFile, backupFile }); 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 LEGACY_PRIMARY_ADMIN_ACCOUNT = "17600003315"; const PRIMARY_ADMIN_ACCOUNT = "krisolo"; const PRIMARY_ADMIN_PASSWORD = process.env.BOSS_PRIMARY_ADMIN_PASSWORD ?? "Admin_yqs_asd."; 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_REVOKED_SESSION_RETENTION_MS = 7 * 24 * 60 * 60_000; const AUTH_LOGIN_LOCK_THRESHOLD = 5; const AUTH_LOGIN_LOCK_MS = 10 * 60_000; const THREAD_STATUS_FULL_SYNC_INTERVAL_MS = 30 * 60_000; const ENV_OPENAI_ACCOUNT_ID = "env-openai-api"; function baseThreadChecklist(labels: string[]) { return labels; } const initialState: BossState = { schemaVersion: CURRENT_BOSS_STATE_SCHEMA_VERSION, migratedAt: "2026-04-27T00:00:00.000Z", 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: ["硬件审计协作"], quota5h: 68, quota7d: 81, lastSeenAt: "2026-03-25T11:52:00+08:00", endpoint: "mac://kris.local", token: "boss-mac-studio-token", note: `本机 Codex 主节点 · ${PRIMARY_ADMIN_ACCOUNT} 已绑定`, capabilities: { gui: { connected: true, lastSeenAt: "2026-03-25T11:52:00+08:00", lastActiveProjectId: "master-agent", }, cli: { connected: true, lastSeenAt: "2026-03-25T11:52:00+08:00", lastActiveProjectId: "master-agent", }, browserAutomation: { connected: true, lastSeenAt: "2026-04-22T10:00:00+08:00", lastActiveProjectId: "master-agent", }, computerUse: { connected: false, lastSeenAt: "2026-04-22T10:00:00+08:00", lastActiveProjectId: "", }, codexAppServer: { connected: false, lastSeenAt: "2026-05-16T00:00:00+08:00", lastActiveProjectId: "", }, }, preferredExecutionMode: "cli", }, { id: "win-gpu-01", name: "Windows GPU", avatar: "W", account: "kris.plus.gpu", source: "demo", status: "abnormal", projects: ["硬件审计协作"], quota5h: 31, quota7d: 46, lastSeenAt: "2026-03-25T11:40:00+08:00", endpoint: "win://gpu.local", token: "boss-win-gpu-token", note: "摄像头证据通道偶发抖动", capabilities: { gui: { connected: true, lastSeenAt: "2026-04-06T08:50:00+08:00", lastActiveProjectId: "audit-collab", }, cli: { connected: false, lastSeenAt: "2026-04-06T08:40:00+08:00", lastActiveProjectId: "", }, browserAutomation: { connected: true, lastSeenAt: "2026-04-22T10:00:00+08:00", lastActiveProjectId: "audit-collab", }, computerUse: { connected: false, lastSeenAt: "2026-04-22T10:00:00+08:00", lastActiveProjectId: "", }, codexAppServer: { connected: false, lastSeenAt: "2026-05-16T00:00:00+08:00", lastActiveProjectId: "", }, }, preferredExecutionMode: "gui", }, { id: "cloud-backup", name: "Cloud Backup", avatar: "C", account: "kris.plus.backup", source: "demo", status: "offline", projects: [], quota5h: 92, quota7d: 95, lastSeenAt: "2026-03-25T08:15:00+08:00", endpoint: "cloud://standby", token: "boss-cloud-backup-token", note: "standby 节点", capabilities: { gui: { connected: false, lastSeenAt: "2026-04-06T08:15:00+08:00", lastActiveProjectId: "", }, cli: { connected: false, lastSeenAt: "2026-04-06T08:15:00+08:00", lastActiveProjectId: "", }, browserAutomation: { connected: false, lastSeenAt: "2026-04-22T10:00:00+08:00", lastActiveProjectId: "", }, computerUse: { connected: false, lastSeenAt: "2026-04-22T10:00:00+08:00", lastActiveProjectId: "", }, codexAppServer: { connected: false, lastSeenAt: "2026-05-16T00:00:00+08:00", lastActiveProjectId: "", }, }, preferredExecutionMode: "cli", }, ], projects: [ { id: "master-agent", name: "主 Agent", pinned: true, systemPinned: true, deviceIds: ["mac-studio"], preview: "已汇总当前活跃项目,优先收尾 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: "当前存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。", sentAt: "2026-03-25T12:06:00+08:00", kind: "text", }, ], goals: [], versions: [], }, { 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: [], adminCompanies: [], adminNotifications: [], adminRiskTimeline: [], accountDeviceGrants: [], accountProjectGrants: [], accountSkillGrants: [], skillCatalog: [], skillLifecycleRequests: [], permissionAuditLogs: [], dialogGuardInterventions: [], authAccounts: [ { id: `account-${PRIMARY_ADMIN_ACCOUNT}`, account: PRIMARY_ADMIN_ACCOUNT, passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD), displayName: "Boss 超级管理员", role: "highest_admin", status: "active", 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: `${PRIMARY_ADMIN_ACCOUNT} · 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 已切到 ${PRIMARY_ADMIN_ACCOUNT},等待管理员决定是否升级到 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-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: [], threadContextAlerts: [ { 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, }, ], 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"], }, ], projectExecutionPolicies: [], threadStatusDocuments: [], threadProgressEvents: [], telegramIntegration: { enabled: false, mode: "webhook", dmPolicy: "allowlist", allowFrom: [], groupPolicy: "allowlist", groups: [], requireMentionInGroups: true, defaultProjectId: "master-agent", groupProjectRoutes: [], processedUpdateIds: [], }, }; 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(), lastObservedCodexActivityAt: raw?.lastObservedCodexActivityAt ?? fallback?.lastObservedCodexActivityAt, lastProjectUnderstandingRequestedAt: raw?.lastProjectUnderstandingRequestedAt ?? fallback?.lastProjectUnderstandingRequestedAt, lastProjectUnderstandingSyncedAt: raw?.lastProjectUnderstandingSyncedAt ?? fallback?.lastProjectUnderstandingSyncedAt, 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 normalizeThreadMetadataGitInfoPatch(value: unknown): ThreadMetadataGitInfoPatch | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) { return undefined; } const input = value as { sha?: unknown; branch?: unknown; originUrl?: unknown; }; const patch: ThreadMetadataGitInfoPatch = {}; for (const key of ["sha", "branch", "originUrl"] as const) { const field = input[key]; if (field === null) { patch[key] = null; } else if (typeof field === "string") { const trimmed = field.trim(); if (trimmed) { patch[key] = trimmed; } } } return Object.keys(patch).length > 0 ? patch : undefined; } function normalizeAuditSnapshot(value: unknown): PermissionAuditSnapshot | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) { return undefined; } return JSON.parse(JSON.stringify(value)) as PermissionAuditSnapshot; } function normalizeDeviceCapabilityState( raw: Partial | undefined, fallbackLastSeenAt: string, ): DeviceCapabilityState { return { connected: Boolean(raw?.connected), lastSeenAt: trimToDefined(raw?.lastSeenAt) ?? fallbackLastSeenAt, lastActiveProjectId: trimToDefined(raw?.lastActiveProjectId) ?? "", metadata: raw?.metadata && typeof raw.metadata === "object" ? JSON.parse(JSON.stringify(raw.metadata)) : undefined, }; } function normalizeDeviceCapabilities( raw: DeviceCapabilitiesInput | undefined, fallbackLastSeenAt: string, ): DeviceCapabilities { return { gui: normalizeDeviceCapabilityState(raw?.gui, fallbackLastSeenAt), cli: normalizeDeviceCapabilityState(raw?.cli, fallbackLastSeenAt), browserAutomation: normalizeDeviceCapabilityState(raw?.browserAutomation, fallbackLastSeenAt), computerUse: normalizeDeviceCapabilityState(raw?.computerUse, fallbackLastSeenAt), codexAppServer: normalizeDeviceCapabilityState(raw?.codexAppServer, fallbackLastSeenAt), }; } function refreshDeviceCapabilityLastSeenAt( raw: DeviceCapabilitiesInput | undefined, fallbackLastSeenAt: string, preserveExistingLastSeenAt = true, ): DeviceCapabilitiesInput | undefined { if (!raw) { return raw; } return { gui: raw.gui ? { ...raw.gui, lastSeenAt: preserveExistingLastSeenAt ? trimToDefined(raw.gui.lastSeenAt) ?? fallbackLastSeenAt : fallbackLastSeenAt, } : raw.gui, cli: raw.cli ? { ...raw.cli, lastSeenAt: preserveExistingLastSeenAt ? trimToDefined(raw.cli.lastSeenAt) ?? fallbackLastSeenAt : fallbackLastSeenAt, } : raw.cli, browserAutomation: raw.browserAutomation ? { ...raw.browserAutomation, lastSeenAt: preserveExistingLastSeenAt ? trimToDefined(raw.browserAutomation.lastSeenAt) ?? fallbackLastSeenAt : fallbackLastSeenAt, } : raw.browserAutomation, computerUse: raw.computerUse ? { ...raw.computerUse, lastSeenAt: preserveExistingLastSeenAt ? trimToDefined(raw.computerUse.lastSeenAt) ?? fallbackLastSeenAt : fallbackLastSeenAt, } : raw.computerUse, codexAppServer: raw.codexAppServer ? { ...raw.codexAppServer, lastSeenAt: preserveExistingLastSeenAt ? trimToDefined(raw.codexAppServer.lastSeenAt) ?? fallbackLastSeenAt : fallbackLastSeenAt, } : raw.codexAppServer, }; } function normalizePreferredExecutionMode(value: unknown): DeviceExecutionMode { return value === "gui" ? "gui" : "cli"; } function normalizeProjectExecutionPolicy( raw: Partial, ): ProjectExecutionPolicy { return { deviceId: trimToDefined(raw.deviceId) ?? "", folderKey: trimToDefined(raw.folderKey), projectId: trimToDefined(raw.projectId) ?? "", activeCliExecution: Boolean(raw.activeCliExecution), recentExternalActivityAt: trimToDefined(raw.recentExternalActivityAt), conflictState: raw.conflictState === "warning" || raw.conflictState === "blocked" ? raw.conflictState : "none", allowPolicy: raw.allowPolicy === "allow_once" || raw.allowPolicy === "allow_always" ? raw.allowPolicy : "forbid", updatedAt: raw.updatedAt ?? nowIso(), }; } function normalizeProjectExecutionPolicyScope(input: { deviceId: string; folderKey?: string; projectId: string; }) { return { deviceId: trimToDefined(input.deviceId) ?? "", folderKey: trimToDefined(input.folderKey), projectId: trimToDefined(input.projectId) ?? "", }; } function findProjectExecutionPolicyInState( state: BossState, input: { deviceId: string; folderKey?: string; projectId: string; }, ) { const scope = normalizeProjectExecutionPolicyScope(input); if (scope.folderKey) { const folderMatch = state.projectExecutionPolicies.find( (policy) => policy.deviceId === scope.deviceId && policy.folderKey === scope.folderKey, ); if (folderMatch) { return folderMatch; } } return state.projectExecutionPolicies.find( (policy) => policy.deviceId === scope.deviceId && policy.projectId === scope.projectId, ); } function upsertProjectExecutionPolicyInState( state: BossState, input: Partial & { deviceId: string; projectId: string; }, ) { const scope = normalizeProjectExecutionPolicyScope(input); const existing = findProjectExecutionPolicyInState(state, scope); const next = normalizeProjectExecutionPolicy({ ...existing, ...input, ...scope, }); if (existing) { Object.assign(existing, next); return existing; } state.projectExecutionPolicies.unshift(next); return next; } 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 parseBackendOverride(value: unknown) { if (value === undefined || value === null) { return { kind: "clear" as const }; } if (value !== "claw-runtime") { return { kind: "invalid" as const }; } return { kind: "set" as const, value: "claw-runtime" as const }; } function parseBooleanControlOverride(value: unknown) { if (value === undefined || value === null) { return { kind: "clear" as const }; } if (typeof value !== "boolean") { return { kind: "invalid" as const }; } return { kind: "set" as const, value }; } function normalizeOrchestrationBackendOverride( value: unknown, ): OrchestrationBackendOverride | undefined { return value === "omx-team" ? "omx-team" : undefined; } 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 ?? "", requestedOrchestrationBackendId: normalizeOrchestrationBackendOverride( raw.requestedOrchestrationBackendId ?? fallback?.requestedOrchestrationBackendId, ), orchestrationBackendId: raw.orchestrationBackendId === "omx-team" || raw.orchestrationBackendId === "boss-native-orchestrator" ? raw.orchestrationBackendId : fallback?.orchestrationBackendId ?? "boss-native-orchestrator", orchestrationBackendLabel: trimToDefined(raw.orchestrationBackendLabel) ?? trimToDefined(fallback?.orchestrationBackendLabel) ?? (raw.orchestrationBackendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator"), orchestrationFallbackReason: trimToDefined(raw.orchestrationFallbackReason) ?? trimToDefined(fallback?.orchestrationFallbackReason), 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 ?? "", orchestrationBackendId: raw.orchestrationBackendId === "omx-team" || raw.orchestrationBackendId === "boss-native-orchestrator" ? raw.orchestrationBackendId : fallback?.orchestrationBackendId ?? "boss-native-orchestrator", orchestrationBackendLabel: trimToDefined(raw.orchestrationBackendLabel) ?? trimToDefined(fallback?.orchestrationBackendLabel) ?? (raw.orchestrationBackendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator"), 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-${slugifyWithHash(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 ?? ""; const recentAssistantMessages = ensureArray( raw.recentAssistantMessages as Partial[] | undefined, fallback?.recentAssistantMessages ?? [], ) .map((message) => normalizeObservedAssistantMessage(message)) .filter((message): message is DeviceObservedAssistantMessage => Boolean(message)); 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, ...(recentAssistantMessages.length > 0 ? { recentAssistantMessages } : {}), }; } function normalizeObservedAssistantMessage( raw: Partial | undefined, ): DeviceObservedAssistantMessage | undefined { const messageId = raw?.messageId?.trim(); const body = raw?.body?.trim(); if (!messageId || !body) { return undefined; } return { messageId, body, sentAt: raw?.sentAt ?? nowIso(), phase: raw?.phase?.trim() || undefined, }; } 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, }; } const validBossPermissions = new Set([ "device.view", "device.manage", "project.view", "thread.chat", "master_agent.ask", "master_agent.takeover", "computer.control", "skill.view", "skill.use", "skill.manage", "account.manage", "audit.view", ]); function normalizeBossPermissions(value: unknown): BossPermission[] { const source = Array.isArray(value) ? value : []; return dedupeStrings(source.filter((item): item is string => typeof item === "string")) .filter((item): item is BossPermission => validBossPermissions.has(item as BossPermission)); } function normalizeAccountDeviceGrant(raw: Partial): AccountDeviceGrant { const account = raw.account ?? ""; const deviceId = raw.deviceId ?? ""; const permissions = normalizeBossPermissions(raw.permissions); return { grantId: raw.grantId ?? `grant-device-${slugify(`${account}-${deviceId}-${permissions.join("-")}`)}`, account, deviceId, permissions, grantedBy: raw.grantedBy ?? PRIMARY_ADMIN_ACCOUNT, grantedAt: raw.grantedAt ?? nowIso(), expiresAt: trimToDefined(raw.expiresAt), note: trimToDefined(raw.note), }; } function normalizeAccountProjectGrant(raw: Partial): AccountProjectGrant { const account = raw.account ?? ""; const projectId = raw.projectId ?? ""; const deviceId = trimToDefined(raw.deviceId); const permissions = normalizeBossPermissions(raw.permissions); return { grantId: raw.grantId ?? `grant-project-${slugify(`${account}-${projectId}-${deviceId ?? "all"}-${permissions.join("-")}`)}`, account, projectId, deviceId, permissions, inheritFromDeviceGrant: raw.inheritFromDeviceGrant === true ? true : undefined, grantedBy: raw.grantedBy ?? PRIMARY_ADMIN_ACCOUNT, grantedAt: raw.grantedAt ?? nowIso(), expiresAt: trimToDefined(raw.expiresAt), note: trimToDefined(raw.note), }; } function normalizeAccountSkillGrant(raw: Partial): AccountSkillGrant { const account = raw.account ?? ""; const skillId = raw.skillId ?? ""; const deviceId = trimToDefined(raw.deviceId); const projectId = trimToDefined(raw.projectId); const permissions = normalizeBossPermissions(raw.permissions); return { grantId: raw.grantId ?? `grant-skill-${slugify(`${account}-${skillId}-${deviceId ?? "all"}-${projectId ?? "all"}-${permissions.join("-")}`)}`, account, skillId, deviceId, projectId, permissions, grantedBy: raw.grantedBy ?? PRIMARY_ADMIN_ACCOUNT, grantedAt: raw.grantedAt ?? nowIso(), expiresAt: trimToDefined(raw.expiresAt), note: trimToDefined(raw.note), }; } function normalizeSkillCatalogEntry(raw: Partial): SkillCatalogEntry { const name = raw.name ?? raw.skillId ?? "未命名 Skill"; const sourceType = raw.sourceType === "skillhub" || raw.sourceType === "local" || raw.sourceType === "gitea" ? raw.sourceType : "local"; return { skillId: raw.skillId ?? `skill-${slugify(name)}`, name, description: raw.description ?? "未提供说明", sourceType, sourceUrl: trimToDefined(raw.sourceUrl), version: trimToDefined(raw.version), checksum: trimToDefined(raw.checksum), category: trimToDefined(raw.category), updatedAt: raw.updatedAt ?? nowIso(), }; } function normalizeSkillLifecycleRequest(raw: Partial): SkillLifecycleRequest { const action: SkillLifecycleAction = raw.action === "install" || raw.action === "update" || raw.action === "uninstall" || raw.action === "rollback" || raw.action === "version_lock" ? raw.action : "install"; const status: SkillLifecycleRequestStatus = raw.status === "accepted" || raw.status === "running" || raw.status === "completed" || raw.status === "failed" || raw.status === "canceled" || raw.status === "pending" ? raw.status : "pending"; const requestedAt = raw.requestedAt ?? nowIso(); return { requestId: raw.requestId ?? randomToken("skill-request"), action, status, deviceId: raw.deviceId ?? "", skillId: trimToDefined(raw.skillId), sourceUrl: trimToDefined(raw.sourceUrl), trustedSource: trimToDefined(raw.trustedSource), trustedSourceId: trimToDefined(raw.trustedSourceId), checksum: trimToDefined(raw.checksum), expectedChecksum: trimToDefined(raw.expectedChecksum), targetVersion: trimToDefined(raw.targetVersion), rollbackToVersion: trimToDefined(raw.rollbackToVersion), lockedVersion: trimToDefined(raw.lockedVersion), requestedBy: raw.requestedBy ?? PRIMARY_ADMIN_ACCOUNT, requestedAt, claimedByDeviceId: trimToDefined(raw.claimedByDeviceId), claimedAt: trimToDefined(raw.claimedAt), completedAt: trimToDefined(raw.completedAt), updatedAt: raw.updatedAt ?? requestedAt, note: trimToDefined(raw.note), resultSummary: trimToDefined(raw.resultSummary), error: trimToDefined(raw.error), }; } function normalizePermissionAuditLog(raw: Partial): PermissionAuditLog { const action = raw.action === "grant.created" || raw.action === "grant.updated" || raw.action === "grant.revoked" || raw.action === "skill.assigned" || raw.action === "skill.revoked" || raw.action === "skill.lifecycle.requested" || raw.action === "skill.lifecycle.completed" || raw.action === "account.updated" || raw.action === "account.mfa_updated" || raw.action === "account.password_reset" || raw.action === "account.reclaimed" || raw.action === "company.updated" || raw.action === "company.assigned" || raw.action === "company.status_updated" || raw.action === "device.revoked" || raw.action === "risk.notification.created" || raw.action === "risk.notification.dispatched" || raw.action === "dialog_guard.intervention_required" || raw.action === "dialog_guard.intervention_resolved" || raw.action === "backup.snapshot_created" || raw.action === "backup.snapshot_verified" || raw.action === "backup.restore_previewed" || raw.action === "backup.restore_dry_run" || raw.action === "backup.snapshot_restored" || raw.action === "master_agent.task_retried" || raw.action === "task.authorized" || raw.action === "task.denied" ? raw.action : "grant.updated"; return { auditId: raw.auditId ?? randomToken("audit"), actorAccount: raw.actorAccount ?? PRIMARY_ADMIN_ACCOUNT, action, targetAccount: trimToDefined(raw.targetAccount), deviceId: trimToDefined(raw.deviceId), projectId: trimToDefined(raw.projectId), skillId: trimToDefined(raw.skillId), permissions: normalizeBossPermissions(raw.permissions), detail: trimToDefined(raw.detail), ipAddress: trimToDefined(raw.ipAddress), userAgent: trimToDefined(raw.userAgent), requestId: trimToDefined(raw.requestId), beforeJson: normalizeAuditSnapshot(raw.beforeJson), afterJson: normalizeAuditSnapshot(raw.afterJson), createdAt: raw.createdAt ?? nowIso(), }; } function normalizeDialogGuardInterventionAction( value: unknown, ): DialogGuardInterventionAction | undefined { return value === "allow_once" || value === "allow_for_device_dialog" || value === "deny" || value === "handled_on_device" || value === "cancel_task" ? value : undefined; } function normalizeDialogGuardIntervention( raw: Partial, ): DialogGuardIntervention { const createdAt = raw.createdAt ?? nowIso(); const availableActions = dedupeStrings( ensureArray(raw.availableActions, []) .map(normalizeDialogGuardInterventionAction) .filter((item): item is DialogGuardInterventionAction => Boolean(item)), ) as DialogGuardInterventionAction[]; const risk = raw.risk === "low" || raw.risk === "medium" || raw.risk === "high" ? raw.risk : "medium"; const decision = normalizeDialogGuardInterventionAction(raw.decision); return { interventionId: raw.interventionId ?? randomToken("dialog-intervention"), dialogId: trimToDefined(raw.dialogId) ?? randomToken("dialog"), requestId: trimToDefined(raw.requestId), taskId: trimToDefined(raw.taskId) ?? "", deviceId: trimToDefined(raw.deviceId) ?? "", projectId: trimToDefined(raw.projectId) ?? "master-agent", appName: trimToDefined(raw.appName), platform: trimToDefined(raw.platform), risk, summary: trimToDefined(raw.summary) ?? "桌面弹窗需要用户确认。", recommendedAction: normalizeDialogGuardInterventionAction(raw.recommendedAction), availableActions: availableActions.length > 0 ? availableActions : ["allow_once", "deny"], status: raw.status === "resolved" ? "resolved" : "pending", decision, decisionBy: trimToDefined(raw.decisionBy), decisionNote: trimToDefined(raw.decisionNote), createdAt, resolvedAt: trimToDefined(raw.resolvedAt), }; } function normalizeAdminCompany(raw: Partial): AdminCompany { const companyId = trimToDefined(raw.companyId)?.toLowerCase() ?? "default"; const createdAt = raw.createdAt ?? nowIso(); const planTier = raw.planTier === "enterprise" || raw.planTier === "standard" || raw.planTier === "trial" ? raw.planTier : undefined; return { companyId, name: trimToDefined(raw.name) ?? (companyId === "default" ? "默认公司" : companyId), ownerAccount: trimToDefined(raw.ownerAccount), successOwnerAccount: trimToDefined(raw.successOwnerAccount), planTier, contractExpiresAt: trimToDefined(raw.contractExpiresAt), status: raw.status === "disabled" ? "disabled" : "active", note: trimToDefined(raw.note), createdAt, updatedAt: raw.updatedAt ?? createdAt, }; } function normalizeAdminNotification(raw: Partial): AdminNotification { const createdAt = raw.createdAt ?? nowIso(); const severity: OpsSeverity = raw.severity === "critical" || raw.severity === "warning" || raw.severity === "info" ? raw.severity : "warning"; const status = raw.status === "acknowledged" || raw.status === "resolved" ? raw.status : "open"; return { notificationId: raw.notificationId ?? randomToken("admin-notification"), kind: "risk_sla_overdue", severity, companyId: trimToDefined(raw.companyId) ?? "default", riskId: trimToDefined(raw.riskId) ?? "", title: trimToDefined(raw.title) ?? "风险 SLA 已超时", body: trimToDefined(raw.body) ?? "", status, deliveryStatus: raw.deliveryStatus === "sent" || raw.deliveryStatus === "failed" || raw.deliveryStatus === "disabled" || raw.deliveryStatus === "pending" ? raw.deliveryStatus : undefined, deliveryTarget: trimToDefined(raw.deliveryTarget), deliveredAt: trimToDefined(raw.deliveredAt), deliveryError: trimToDefined(raw.deliveryError), createdAt, acknowledgedAt: trimToDefined(raw.acknowledgedAt), acknowledgedBy: trimToDefined(raw.acknowledgedBy), }; } function normalizeAdminRiskTimelineEvent(raw: Partial): AdminRiskTimelineEvent { return { eventId: trimToDefined(raw.eventId) ?? randomToken("risk-event"), riskId: trimToDefined(raw.riskId) ?? "", notificationId: trimToDefined(raw.notificationId), companyId: trimToDefined(raw.companyId), action: trimToDefined(raw.action) ?? "risk.updated", actorAccount: trimToDefined(raw.actorAccount) ?? "system", note: trimToDefined(raw.note), createdAt: raw.createdAt ?? nowIso(), }; } 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 fastModelOverride = trimToDefined(raw?.fastModelOverride); const deepModelOverride = trimToDefined(raw?.deepModelOverride); const promptOverride = trimToDefined(raw?.promptOverride); const backendOverride = raw?.backendOverride === "claw-runtime" ? raw.backendOverride : undefined; const takeoverEnabled = typeof raw?.takeoverEnabled === "boolean" ? raw.takeoverEnabled : undefined; const globalTakeoverEnabled = typeof raw?.globalTakeoverEnabled === "boolean" ? raw.globalTakeoverEnabled : undefined; if ( !modelOverride && !reasoningEffortOverride && !fastModelOverride && !deepModelOverride && !promptOverride && !backendOverride && takeoverEnabled === undefined && globalTakeoverEnabled === undefined ) { return undefined; } return { modelOverride, reasoningEffortOverride, fastModelOverride, deepModelOverride, promptOverride, backendOverride, takeoverEnabled, globalTakeoverEnabled, 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 normalizeStateSchemaMetadata(raw: Partial | undefined) { const rawVersion = typeof raw?.schemaVersion === "number" && Number.isFinite(raw.schemaVersion) ? Math.floor(raw.schemaVersion) : 0; const rawMigratedAt = trimToDefined(raw?.migratedAt); return { schemaVersion: CURRENT_BOSS_STATE_SCHEMA_VERSION, migratedAt: rawVersion >= CURRENT_BOSS_STATE_SCHEMA_VERSION && rawMigratedAt ? rawMigratedAt : nowIso(), }; } 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")}`; } function slugifyWithHash(value: string) { const base = slugify(value).slice(0, 40); const hash = createHash("sha1").update(value).digest("hex").slice(0, 8); return `${base || "item"}-${hash}`; } 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 "google_oauth": return "谷歌登录"; case "chatgpt_oauth": return "ChatGPT登录"; case "openai_api": return "OpenAI API"; case "aliyun_qwen_api": return "阿里百炼 Qwen"; case "deepseek_api": return "DeepSeek API"; case "minimax_api": return "MiniMax API"; case "glm_api": return "GLM API"; case "hyzq_api": return "环宇智擎 API"; case "custom_api": return "自定义 API"; default: return provider; } } export function aiProviderDefaultApiBaseUrl(provider: AiProvider) { switch (provider) { case "openai_api": return "https://api.openai.com/v1"; case "aliyun_qwen_api": return "https://dashscope.aliyuncs.com/compatible-mode/v1"; case "deepseek_api": return "https://api.deepseek.com"; case "minimax_api": return "https://api.minimaxi.com/v1"; case "glm_api": return "https://open.bigmodel.cn/api/paas/v4"; case "hyzq_api": return "https://api.hyzq2046.com/v1"; default: return undefined; } } export function aiProviderDefaultModel(provider: AiProvider) { switch (provider) { case "openai_api": return "gpt-5.4"; case "aliyun_qwen_api": return "qwen3.5-plus"; case "deepseek_api": return "deepseek-v4-pro"; case "minimax_api": return "MiniMax-M1"; case "glm_api": return "glm-4.5"; case "hyzq_api": return "gpt-5.4-mini"; default: return undefined; } } 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 normalizeApiBaseUrl(value?: string) { if (!value?.trim()) return undefined; return value.trim().replace(/\/+$/, ""); } export function isApiKeyProvider(provider: AiProvider) { return ( provider === "openai_api" || provider === "aliyun_qwen_api" || provider === "deepseek_api" || provider === "minimax_api" || provider === "glm_api" || provider === "hyzq_api" || provider === "custom_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, apiBaseUrl: normalized.apiBaseUrl, 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", apiBaseUrl: normalizeApiBaseUrl(process.env.OPENAI_API_BASE_URL), 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 { const normalizedBody = sanitizeUserVisibleMessageText(raw.body); const normalizedKind = raw.kind === "text" && raw.sender === "device" && isLikelyThreadProcessBody(normalizedBody) ? "thread_process" : raw.kind ?? "text"; return { id: raw.id ?? randomToken("msg"), sender: raw.sender ?? "master", senderLabel: raw.senderLabel ?? "主 Agent", body: normalizedBody ?? "", sentAt: raw.sentAt ?? nowIso(), account: raw.account?.trim() || undefined, kind: normalizedKind, externalMessageId: raw.externalMessageId?.trim() || undefined, controlTarget: raw.controlTarget?.trim() || undefined, attachments: Array.isArray(raw.attachments) ? raw.attachments.map((attachment) => normalizeMessageAttachment(attachment)) : undefined, forwardSource: raw.forwardSource, forwardBundle: raw.forwardBundle, executionProgress: raw.executionProgress && typeof raw.executionProgress === "object" ? normalizeExecutionProgressSnapshot(raw.executionProgress) : undefined, }; } const USER_VISIBLE_RUNTIME_FAILURE_MARKERS = [ "LOCAL_AGENT_CODEX_THREAD_BINDING_MISSING", "LOCAL_AGENT_CODEX_THREAD_BINDING_STALE", "LOCAL_AGENT_CODEX_THREAD_BINDING_MISMATCH", "LOCAL_AGENT_CODEX_WORKDIR_INVALID", "THREAD_ENVIRONMENT_INVALID", "LOCAL_AGENT_CODEX_THREAD_READ_ONLY", "CODEX_APP_SERVER_TURN_INTERRUPTED", "CODEX_APP_SERVER_TIMEOUT", "CODEX_APP_SERVER_STDIN_CLOSED", "CODEX_APP_SERVER_EXITED", MASTER_CODEX_NODE_OUTPUT_LEAKED, ]; function sanitizeUserVisibleMessageText(value: string | undefined | null) { const text = sanitizeSensitiveUserVisibleText(value); if (!text) { return undefined; } return replaceUserVisibleRuntimeFailureCodes(text); } function replaceUserVisibleRuntimeFailureCodes(value: string) { let next = value; for (const marker of USER_VISIBLE_RUNTIME_FAILURE_MARKERS) { if (!next.includes(marker)) continue; next = next.split(marker).join(buildFriendlyThreadExecutionError(marker)); } if (/执行失败[::]\s*fetch failed/i.test(next)) { next = next.replace(/fetch failed/gi, buildFriendlyThreadExecutionError("fetch failed")); } return next; } function normalizeExecutionProgressSnapshot(raw: Partial): ExecutionProgressSnapshot { const status = normalizeExecutionProgressStatus(raw.status); const runtimeKind = raw.runtimeKind === "codex-thread-runtime" || raw.runtimeKind === "browser-automation-runtime" || raw.runtimeKind === "computer-use-runtime" ? raw.runtimeKind : undefined; const controlPlatform = raw.controlPlatform === "macos" ? raw.controlPlatform : undefined; const computerUseProvider = raw.computerUseProvider === "boss-native-computer-use" || raw.computerUseProvider === "codex-computer-use" || raw.computerUseProvider === "cua-driver-computer-use" || raw.computerUseProvider === "openai-computer-use" ? raw.computerUseProvider : undefined; const rawControlMode = raw.controlMode === "native_remote_control" || raw.controlMode === "codex_thread" ? raw.controlMode : undefined; const nativeRemoteControl = rawControlMode === "native_remote_control" || raw.taskType === "browser_control" || raw.taskType === "desktop_control" || runtimeKind === "browser-automation-runtime" || runtimeKind === "computer-use-runtime"; const normalizedTaskType = raw.taskType === "browser_control" || raw.taskType === "desktop_control" || raw.taskType === "dispatch_execution" || raw.taskType === "conversation_reply" ? raw.taskType : runtimeKind === "browser-automation-runtime" ? "browser_control" : runtimeKind === "computer-use-runtime" ? "desktop_control" : "conversation_reply"; const taskShim: Pick = { taskType: normalizedTaskType, relayViaMasterAgent: false, }; const controlMode = rawControlMode ?? (nativeRemoteControl ? "native_remote_control" : undefined); const phase = normalizeMasterAgentTaskPhase(raw.phase, status); return { taskId: raw.taskId?.trim() || randomToken("mastertask"), projectId: raw.projectId?.trim() || "", targetProjectId: raw.targetProjectId?.trim() || undefined, taskType: normalizedTaskType, phase, controlMode, runtimeKind, controlPlatform, computerUseProvider, title: raw.title?.trim() || (nativeRemoteControl ? "远程控制进度" : "进度"), status: phaseToExecutionProgressStatus(phase, status), steps: normalizeExecutionProgressSteps( taskShim, phaseToExecutionProgressStatus(phase, status), phase, raw.steps, ), branch: nativeRemoteControl ? undefined : normalizeExecutionProgressBranch(raw.branch), artifacts: normalizeExecutionProgressArtifacts(raw.artifacts), agents: nativeRemoteControl ? undefined : normalizeExecutionProgressAgents(raw.agents), approvals: nativeRemoteControl ? undefined : normalizeExecutionProgressApprovals(raw.approvals), warnings: nativeRemoteControl ? undefined : normalizeExecutionProgressWarnings(raw.warnings), fileChanges: nativeRemoteControl ? undefined : normalizeExecutionProgressFileChanges(raw.fileChanges), threadStatus: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadStatus(raw.threadStatus), realtime: nativeRemoteControl ? undefined : normalizeExecutionProgressRealtime(raw.realtime), modelRoute: nativeRemoteControl ? undefined : normalizeExecutionProgressModelRoute(raw.modelRoute), tokenUsage: nativeRemoteControl ? undefined : normalizeExecutionProgressTokenUsage(raw.tokenUsage), accountStatus: nativeRemoteControl ? undefined : normalizeExecutionProgressAccountStatus(raw.accountStatus), modelVerification: nativeRemoteControl ? undefined : normalizeExecutionProgressModelVerification(raw.modelVerification), mcpServers: nativeRemoteControl ? undefined : normalizeExecutionProgressMcpServers(raw.mcpServers), remoteControl: nativeRemoteControl ? undefined : normalizeExecutionProgressRemoteControl(raw.remoteControl), windowsSandbox: nativeRemoteControl ? undefined : normalizeExecutionProgressWindowsSandbox(raw.windowsSandbox), threadGoal: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadGoal(raw.threadGoal), threadSettings: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadSettings(raw.threadSettings), compaction: nativeRemoteControl ? undefined : normalizeExecutionProgressCompaction(raw.compaction), threadCollaboration: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadCollaboration(raw.threadCollaboration), toolActivities: nativeRemoteControl ? undefined : normalizeExecutionProgressToolActivities(raw.toolActivities), reasoningSummary: nativeRemoteControl ? undefined : normalizeExecutionProgressReasoningSummary(raw.reasoningSummary), streamEvents: nativeRemoteControl ? undefined : normalizeExecutionProgressStreamEvents(raw.streamEvents), updatedAt: raw.updatedAt ?? nowIso(), }; } 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", projectUnderstanding: normalizeProjectUnderstanding(raw.projectUnderstanding, fallback?.projectUnderstanding), 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", lightDispatchReminderEnabled: raw.lightDispatchReminderEnabled ?? false, orchestrationBackendOverride: normalizeOrchestrationBackendOverride(raw.orchestrationBackendOverride), agentControls: normalizeProjectAgentControls(raw.agentControls), }; const maxUnreadCount = project.messages.reduce( (count, message) => count + (shouldCountAsUnreadMessage(message) ? 1 : 0), 0, ); project.unreadCount = Math.min(Math.max(0, project.unreadCount ?? 0), maxUnreadCount); project.groupMembers = ensureArray(raw.groupMembers, []).map((member) => normalizeGroupMember(member, projectId, project.threadMeta), ); normalizeProjectConversationShape(project); project.preview = sanitizeUserVisibleMessageText(project.preview) ?? project.preview; project.updatedAt = resolveProjectUpdatedAt(project, project.threadMeta.updatedAt); return project; } function normalizeProjectUnderstanding( raw: Partial | undefined, fallback?: ProjectUnderstandingSnapshot, ): ProjectUnderstandingSnapshot | undefined { if (!raw && !fallback) { return undefined; } const projectGoal = raw?.projectGoal?.trim() ?? fallback?.projectGoal?.trim() ?? ""; const currentProgress = raw?.currentProgress?.trim() ?? fallback?.currentProgress?.trim() ?? ""; const technicalArchitecture = raw?.technicalArchitecture?.trim() ?? fallback?.technicalArchitecture?.trim() ?? ""; const currentBlockers = raw?.currentBlockers?.trim() ?? fallback?.currentBlockers?.trim() ?? ""; const recommendedNextStep = raw?.recommendedNextStep?.trim() ?? fallback?.recommendedNextStep?.trim() ?? ""; if (!projectGoal && !currentProgress && !technicalArchitecture && !currentBlockers && !recommendedNextStep) { return undefined; } return { projectGoal, currentProgress, technicalArchitecture, currentBlockers, recommendedNextStep, sourceTaskId: raw?.sourceTaskId ?? fallback?.sourceTaskId ?? randomToken("mastertask"), updatedAt: raw?.updatedAt ?? fallback?.updatedAt ?? nowIso(), sourceKind: raw?.sourceKind ?? fallback?.sourceKind ?? "thread_sync", }; } function normalizeThreadStatusSourceKind(value?: ThreadStatusSourceKind): ThreadStatusSourceKind { return value === "device_import" || value === "full_sync" || value === "incremental_sync" ? value : "incremental_sync"; } function normalizeThreadProgressEventType(value?: ThreadProgressEventType): ThreadProgressEventType { return value === "phase_changed" || value === "progress_updated" || value === "blocker_added" || value === "blocker_resolved" || value === "next_step_changed" || value === "architecture_updated" || value === "handoff_ready" ? value : "progress_updated"; } function compareThreadStatusDocuments(a: ThreadStatusDocument, b: ThreadStatusDocument) { const updatedDelta = messageTimeValue(b.updatedAt) - messageTimeValue(a.updatedAt); if (updatedDelta !== 0) return updatedDelta; return b.documentId.localeCompare(a.documentId); } function compareThreadProgressEvents(a: ThreadProgressEvent, b: ThreadProgressEvent) { const createdDelta = messageTimeValue(b.createdAt) - messageTimeValue(a.createdAt); if (createdDelta !== 0) return createdDelta; return b.eventId.localeCompare(a.eventId); } function threadProgressEventKey(event: ThreadProgressEvent) { return `${event.projectId}:${event.threadId}`; } function normalizeThreadStatusDocument( raw: Partial, fallback?: ThreadStatusDocument, ): ThreadStatusDocument { const keyFiles = dedupeStrings( ensureArray(raw.keyFiles as string[] | undefined, fallback?.keyFiles ?? []).map((item) => item.trim(), ), ); const keyCommands = dedupeStrings( ensureArray(raw.keyCommands as string[] | undefined, fallback?.keyCommands ?? []).map((item) => item.trim(), ), ); return { documentId: raw.documentId ?? fallback?.documentId ?? randomToken("thread-status"), projectId: trimToDefined(raw.projectId ?? fallback?.projectId) ?? "", threadId: trimToDefined(raw.threadId ?? fallback?.threadId) ?? "", threadDisplayName: trimToDefined(raw.threadDisplayName ?? fallback?.threadDisplayName) ?? "", folderName: trimToDefined(raw.folderName ?? fallback?.folderName) ?? "", deviceId: trimToDefined(raw.deviceId ?? fallback?.deviceId) ?? "", projectGoal: raw.projectGoal?.trim() ?? fallback?.projectGoal ?? "", currentPhase: raw.currentPhase?.trim() ?? fallback?.currentPhase ?? "待整理", currentProgress: raw.currentProgress?.trim() ?? fallback?.currentProgress ?? "", technicalArchitecture: raw.technicalArchitecture?.trim() ?? fallback?.technicalArchitecture ?? "", currentBlockers: raw.currentBlockers?.trim() ?? fallback?.currentBlockers ?? "", recommendedNextStep: raw.recommendedNextStep?.trim() ?? fallback?.recommendedNextStep ?? "", keyFiles, keyCommands, updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(), sourceTaskId: trimToDefined(raw.sourceTaskId ?? fallback?.sourceTaskId) ?? randomToken("thread-status"), sourceKind: normalizeThreadStatusSourceKind(raw.sourceKind ?? fallback?.sourceKind), }; } function normalizeThreadProgressEvent( raw: Partial, fallback?: ThreadProgressEvent, ): ThreadProgressEvent { return { eventId: raw.eventId ?? fallback?.eventId ?? randomToken("thread-event"), projectId: trimToDefined(raw.projectId ?? fallback?.projectId) ?? "", threadId: trimToDefined(raw.threadId ?? fallback?.threadId) ?? "", threadDisplayName: trimToDefined(raw.threadDisplayName ?? fallback?.threadDisplayName) ?? "", deviceId: trimToDefined(raw.deviceId ?? fallback?.deviceId) ?? "", eventType: normalizeThreadProgressEventType(raw.eventType ?? fallback?.eventType), summary: raw.summary?.trim() ?? fallback?.summary ?? "线程状态更新", phase: trimToDefined(raw.phase ?? fallback?.phase), blockerDelta: trimToDefined(raw.blockerDelta ?? fallback?.blockerDelta), nextStepDelta: trimToDefined(raw.nextStepDelta ?? fallback?.nextStepDelta), createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(), sourceTaskId: trimToDefined(raw.sourceTaskId ?? fallback?.sourceTaskId) ?? randomToken("thread-event"), sourceMessageId: trimToDefined(raw.sourceMessageId ?? fallback?.sourceMessageId), }; } function normalizeTelegramIntegration( raw: Partial | undefined, fallback?: TelegramIntegrationState, ): TelegramIntegrationState { const base = fallback ?? cloneInitialState().telegramIntegration ?? { enabled: false, mode: "webhook" as const, dmPolicy: "allowlist" as const, allowFrom: [], groupPolicy: "allowlist" as const, groups: [], requireMentionInGroups: true, defaultProjectId: "master-agent", groupProjectRoutes: [], processedUpdateIds: [], }; return { enabled: raw?.enabled === true, mode: raw?.mode === "polling" ? "polling" : "webhook", botToken: trimToDefined(raw?.botToken ?? base.botToken), botUsername: trimToDefined(raw?.botUsername ?? base.botUsername), dmPolicy: raw?.dmPolicy === "open" || raw?.dmPolicy === "disabled" ? raw.dmPolicy : base.dmPolicy, allowFrom: ensureArray(raw?.allowFrom, base.allowFrom) .map((item) => String(item).trim()) .filter(Boolean), groupPolicy: raw?.groupPolicy === "open" || raw?.groupPolicy === "disabled" ? raw.groupPolicy : base.groupPolicy, groups: ensureArray(raw?.groups, base.groups) .map((item) => String(item).trim()) .filter(Boolean), requireMentionInGroups: raw?.requireMentionInGroups ?? base.requireMentionInGroups, defaultProjectId: trimToDefined(raw?.defaultProjectId ?? base.defaultProjectId) ?? "master-agent", groupProjectRoutes: normalizeTelegramGroupProjectRoutes(raw?.groupProjectRoutes, base.groupProjectRoutes), webhookSecret: trimToDefined(raw?.webhookSecret ?? base.webhookSecret), webhookUrl: trimToDefined(raw?.webhookUrl ?? base.webhookUrl), lastConfiguredAt: trimToDefined(raw?.lastConfiguredAt ?? base.lastConfiguredAt), lastConfiguredBy: trimToDefined(raw?.lastConfiguredBy ?? base.lastConfiguredBy), lastError: trimToDefined(raw?.lastError ?? base.lastError), processedUpdateIds: ensureArray(raw?.processedUpdateIds, base.processedUpdateIds) .map((value) => Number(value)) .filter((value) => Number.isFinite(value)), }; } function normalizeTelegramGroupProjectRoutes( raw: unknown, fallback: TelegramGroupProjectRoute[] = [], ): TelegramGroupProjectRoute[] { const source = Array.isArray(raw) ? raw : fallback; const routes: TelegramGroupProjectRoute[] = []; for (const item of source) { if (!item || typeof item !== "object") { continue; } const route = item as Partial; const chatId = trimToDefined(String(route.chatId ?? "")); const projectId = trimToDefined(String(route.projectId ?? "")); if (!chatId || !projectId) { continue; } const threadId = Number(route.threadId); routes.push({ chatId, ...(Number.isFinite(threadId) ? { threadId } : {}), projectId, ...(trimToDefined(route.label) ? { label: trimToDefined(route.label) } : {}), }); } return routes; } function buildHeartbeatProgressSummary(threadDisplayName: string) { return `检测到线程有新活动:${threadDisplayName}`; } function summarizeThreadReplyBody(body: string) { const normalized = body .replace(/\s+/g, " ") .trim(); if (!normalized) { return "线程状态更新"; } return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized; } function upsertThreadStatusDocumentInState( state: BossState, input: { projectId: string; threadId: string; threadDisplayName: string; folderName: string; deviceId: string; projectGoal: string; currentPhase: string; currentProgress: string; technicalArchitecture: string; currentBlockers: string; recommendedNextStep: string; keyFiles: string[]; keyCommands: string[]; updatedAt: string; sourceTaskId: string; sourceKind: ThreadStatusSourceKind; }, ) { const existing = state.threadStatusDocuments.find( (item) => item.projectId === input.projectId && item.threadId === input.threadId, ); const document = normalizeThreadStatusDocument(input, existing); if (existing) { Object.assign(existing, document); return existing; } state.threadStatusDocuments.unshift(document); return document; } function appendThreadProgressEventInState( state: BossState, input: Omit, ) { const event = normalizeThreadProgressEvent({ eventId: randomToken("thread-event"), ...input, }); state.threadProgressEvents.unshift(event); return event; } export function migrateBossState(raw: Partial | undefined): BossState { const base = cloneInitialState(); if (!raw) return syncDerivedState(base); const state: BossState = { ...normalizeStateSchemaMetadata(raw), conversationHistoryClearedAt: trimToDefined((raw as { conversationHistoryClearedAt?: string }).conversationHistoryClearedAt) ?? trimToDefined(base.conversationHistoryClearedAt), user: { ...base.user, ...raw.user, settings: { ...base.user.settings, ...(raw.user?.settings ?? {}), }, }, devices: ensureArray(raw.devices, base.devices).map((device, index) => { const fallback = (device.id ? base.devices.find((item) => item.id === device.id) : undefined) ?? base.devices[index % base.devices.length]; const lastSeenAt = trimToDefined(device.lastSeenAt) ?? fallback.lastSeenAt; return { ...fallback, ...device, companyId: trimToDefined(device.companyId) ?? trimToDefined(fallback.companyId), source: device.source ?? (device.id === PRIMARY_CODEX_NODE_ID ? "production" : "demo"), lastSeenAt, capabilities: normalizeDeviceCapabilities(device.capabilities ?? fallback.capabilities, lastSeenAt), preferredExecutionMode: normalizePreferredExecutionMode( device.preferredExecutionMode ?? fallback.preferredExecutionMode, ), revokedAt: trimToDefined(device.revokedAt), revokedBy: trimToDefined(device.revokedBy), revokeReason: trimToDefined(device.revokeReason), }; }), 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(PRIMARY_ADMIN_PASSWORD), displayName: account.displayName ?? "Boss 成员", role: account.role ?? "member", status: account.status === "disabled" ? "disabled" : "active", companyId: trimToDefined(account.companyId), 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, mfaRequired: account.mfaRequired === true, mfaSecret: trimToDefined(account.mfaSecret), mfaEnabledAt: trimToDefined(account.mfaEnabledAt), 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, })), adminCompanies: ensureArray(raw.adminCompanies, base.adminCompanies) .map(normalizeAdminCompany) .filter((company) => Boolean(company.companyId)), adminNotifications: ensureArray(raw.adminNotifications, base.adminNotifications) .map(normalizeAdminNotification) .filter((notification) => Boolean(notification.notificationId && notification.riskId)) .slice(0, 500), adminRiskTimeline: ensureArray(raw.adminRiskTimeline, base.adminRiskTimeline) .map(normalizeAdminRiskTimelineEvent) .filter((event) => Boolean(event.eventId && event.riskId)) .slice(0, 1000), accountDeviceGrants: ensureArray(raw.accountDeviceGrants, base.accountDeviceGrants) .map(normalizeAccountDeviceGrant) .filter((grant) => Boolean(grant.account && grant.deviceId && grant.permissions.length > 0)), accountProjectGrants: ensureArray(raw.accountProjectGrants, base.accountProjectGrants) .map(normalizeAccountProjectGrant) .filter((grant) => Boolean(grant.account && grant.projectId && grant.permissions.length > 0)), accountSkillGrants: ensureArray(raw.accountSkillGrants, base.accountSkillGrants) .map(normalizeAccountSkillGrant) .filter((grant) => Boolean(grant.account && grant.skillId && grant.permissions.length > 0)), skillCatalog: ensureArray(raw.skillCatalog, base.skillCatalog).map(normalizeSkillCatalogEntry), skillLifecycleRequests: ensureArray(raw.skillLifecycleRequests, base.skillLifecycleRequests) .map(normalizeSkillLifecycleRequest) .filter((request) => Boolean(request.deviceId && (request.skillId || request.sourceUrl || request.trustedSource || request.trustedSourceId)), ), permissionAuditLogs: ensureArray(raw.permissionAuditLogs, base.permissionAuditLogs) .map(normalizePermissionAuditLog) .slice(0, 500), dialogGuardInterventions: ensureArray( raw.dialogGuardInterventions, base.dialogGuardInterventions, ) .map(normalizeDialogGuardIntervention) .filter((intervention) => Boolean(intervention.interventionId && intervention.taskId && intervention.deviceId), ) .slice(0, 500), 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) => { const status: MasterAgentTaskStatus = task.status === "queued" || task.status === "running" || task.status === "needs_user_action" || task.status === "completed" || task.status === "failed" || task.status === "timed_out" || task.status === "canceled" ? task.status : "queued"; const phase = normalizeMasterAgentTaskPhase(task.phase, status); return { taskId: task.taskId ?? randomToken("mastertask"), projectId: task.projectId ?? "master-agent", taskType: task.taskType ?? "conversation_reply", requestMessageId: task.requestMessageId ?? "", requestText: task.requestText ?? "", executionPrompt: task.status === "queued" || task.status === "running" ? task.executionPrompt ?? task.requestText ?? "" : shouldBlockSensitiveMasterAgentOutput(task.executionPrompt) ? MASTER_CODEX_NODE_OUTPUT_LEAKED : task.executionPrompt ?? task.requestText ?? "", sourceMessageId: trimToDefined(task.sourceMessageId), sourceMessageBody: trimToDefined(task.sourceMessageBody), sourceMessageSentAt: trimToDefined(task.sourceMessageSentAt), requestedBy: task.requestedBy ?? "用户", requestedByAccount: task.requestedByAccount ?? "", authorizedDeviceIds: dedupeStrings(ensureArray(task.authorizedDeviceIds, [])), authorizedProjectIds: dedupeStrings(ensureArray(task.authorizedProjectIds, [])), authorizedSkillIds: dedupeStrings(ensureArray(task.authorizedSkillIds, [])), requiredPermissions: normalizeBossPermissions(task.requiredPermissions), 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, sourceProjectId: task.sourceProjectId, sourceThreadId: task.sourceThreadId, sourceThreadDisplayName: task.sourceThreadDisplayName, sourceCodexThreadRef: task.sourceCodexThreadRef, targetProjectId: task.targetProjectId, targetThreadId: task.targetThreadId, targetThreadDisplayName: task.targetThreadDisplayName, targetCodexThreadRef: task.targetCodexThreadRef, targetTurnId: task.targetTurnId, targetCodexTurnId: task.targetCodexTurnId, targetCodexFolderRef: task.targetCodexFolderRef, orchestrationBackendId: task.orchestrationBackendId === "omx-team" || task.orchestrationBackendId === "boss-native-orchestrator" ? task.orchestrationBackendId : undefined, orchestrationBackendLabel: task.orchestrationBackendLabel, deviceImportDraftId: task.deviceImportDraftId, deviceImportCandidateId: task.deviceImportCandidateId, deviceImportCandidateFolderName: task.deviceImportCandidateFolderName, maintenanceKind: task.maintenanceKind === "codex_remote_control" ? task.maintenanceKind : undefined, codexRemoteControlAction: task.codexRemoteControlAction === "start" || task.codexRemoteControlAction === "stop" ? task.codexRemoteControlAction : undefined, projectUnderstandingTargetProjectId: task.projectUnderstandingTargetProjectId, projectUnderstandingReason: task.projectUnderstandingReason === "heartbeat_activity" || task.projectUnderstandingReason === "thread_reply" ? task.projectUnderstandingReason : undefined, projectUnderstandingReplyProjectId: trimToDefined(task.projectUnderstandingReplyProjectId), projectUnderstandingNotifyOnCompletion: task.projectUnderstandingNotifyOnCompletion === true ? true : undefined, relayViaMasterAgent: task.relayViaMasterAgent === true ? true : undefined, mirrorBossUserMessageToCodexDesktop: task.mirrorBossUserMessageToCodexDesktop === true ? true : undefined, rollbackNumTurns: Number.isFinite(Number(task.rollbackNumTurns)) && Number(task.rollbackNumTurns) >= 1 ? Math.floor(Number(task.rollbackNumTurns)) : undefined, rollbackReason: trimToDefined(task.rollbackReason), compactReason: trimToDefined(task.compactReason), threadLifecycleAction: task.threadLifecycleAction === "archive" || task.threadLifecycleAction === "unarchive" ? task.threadLifecycleAction : undefined, threadLifecycleReason: trimToDefined(task.threadLifecycleReason), threadRenameName: trimToDefined(task.threadRenameName), threadRenameReason: trimToDefined(task.threadRenameReason), threadGoalObjective: trimToDefined(task.threadGoalObjective), threadGoalStatus: task.threadGoalStatus === "active" || task.threadGoalStatus === "paused" || task.threadGoalStatus === "blocked" || task.threadGoalStatus === "usageLimited" || task.threadGoalStatus === "budgetLimited" || task.threadGoalStatus === "complete" ? task.threadGoalStatus : undefined, threadGoalTokenBudget: Number.isFinite(Number(task.threadGoalTokenBudget)) && Number(task.threadGoalTokenBudget) > 0 ? Math.floor(Number(task.threadGoalTokenBudget)) : undefined, threadGoalReason: trimToDefined(task.threadGoalReason), threadMetadataGitInfo: normalizeThreadMetadataGitInfoPatch(task.threadMetadataGitInfo), threadMetadataReason: trimToDefined(task.threadMetadataReason), threadForkEphemeral: task.threadForkEphemeral === true, threadForkReason: trimToDefined(task.threadForkReason), intentCategory: task.intentCategory === "discussion_only" || task.intentCategory === "project_development" || task.intentCategory === "thread_collaboration" || task.intentCategory === "thread_rollback" || task.intentCategory === "thread_compact" || task.intentCategory === "thread_archive" || task.intentCategory === "thread_unarchive" || task.intentCategory === "thread_rename" || task.intentCategory === "thread_goal_sync" || task.intentCategory === "thread_metadata_sync" || task.intentCategory === "thread_fork" || task.intentCategory === "browser_control" || task.intentCategory === "desktop_control" ? task.intentCategory : undefined, runtimeKind: task.runtimeKind === "codex-thread-runtime" || task.runtimeKind === "browser-automation-runtime" || task.runtimeKind === "computer-use-runtime" ? task.runtimeKind : undefined, controlPlatform: task.controlPlatform === "macos" ? task.controlPlatform : undefined, computerUseProvider: task.computerUseProvider === "boss-native-computer-use" || task.computerUseProvider === "codex-computer-use" || task.computerUseProvider === "cua-driver-computer-use" || task.computerUseProvider === "openai-computer-use" ? task.computerUseProvider : undefined, riskLevel: task.riskLevel === "low" || task.riskLevel === "medium" || task.riskLevel === "high" ? task.riskLevel : undefined, confirmationPolicy: task.confirmationPolicy === "none" || task.confirmationPolicy === "light_confirm" || task.confirmationPolicy === "strong_confirm" ? task.confirmationPolicy : undefined, requiresUserConfirmation: task.requiresUserConfirmation === true ? true : undefined, confirmationScopeKey: trimToDefined(task.confirmationScopeKey), externalReplyTarget: task.externalReplyTarget?.provider === "telegram" && trimToDefined(task.externalReplyTarget.chatId) ? { provider: "telegram", chatId: trimToDefined(task.externalReplyTarget.chatId) ?? "", messageId: typeof task.externalReplyTarget.messageId === "number" ? task.externalReplyTarget.messageId : undefined, threadId: typeof task.externalReplyTarget.threadId === "number" ? task.externalReplyTarget.threadId : undefined, sessionKey: trimToDefined(task.externalReplyTarget.sessionKey), deliveredAt: trimToDefined(task.externalReplyTarget.deliveredAt), deliveryError: trimToDefined(task.externalReplyTarget.deliveryError), } : undefined, status, phase, requestedAt: task.requestedAt ?? nowIso(), claimedAt: trimToDefined(task.claimedAt), lastClaimedAt: trimToDefined(task.lastClaimedAt), leaseExpiresAt: trimToDefined(task.leaseExpiresAt), lastProgressAt: trimToDefined(task.lastProgressAt), lastErrorCode: trimToDefined(task.lastErrorCode) ?? phaseErrorCode(phase), recoverable: typeof task.recoverable === "boolean" ? task.recoverable : phase === "recoverable_failed", nextRetryAt: trimToDefined(task.nextRetryAt), attemptCount: normalizeOptionalNumber(task.attemptCount), maxAttempts: normalizeOptionalNumber(task.maxAttempts), completedAt: trimToDefined(task.completedAt), canceledAt: trimToDefined(task.canceledAt), canceledBy: trimToDefined(task.canceledBy), cancelReason: trimToDefined(task.cancelReason), lastErrorKind: trimToDefined(task.lastErrorKind), replyBody: task.replyBody, errorMessage: sanitizeSensitiveTaskFailureDetailForTransport(task.errorMessage) ?? task.errorMessage, requestId: task.requestId, targetUrl: trimToDefined(task.targetUrl), targetApp: trimToDefined(task.targetApp), }; }), 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)], ), ), threadStatusDocuments: ensureArray( raw.threadStatusDocuments as Partial[] | undefined, base.threadStatusDocuments, ).map((document, index) => normalizeThreadStatusDocument( document, base.threadStatusDocuments[index % Math.max(1, base.threadStatusDocuments.length)], ), ), threadProgressEvents: ensureArray( raw.threadProgressEvents as Partial[] | undefined, base.threadProgressEvents, ).map((event, index) => normalizeThreadProgressEvent( event, base.threadProgressEvents[index % Math.max(1, base.threadProgressEvents.length)], ), ), telegramIntegration: normalizeTelegramIntegration(raw.telegramIntegration, base.telegramIntegration), 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, message: sanitizeSensitiveUserVisibleText(log.message) ?? "已拦截内部执行日志,原始内容已隐藏。", detail: sanitizeSensitiveTaskFailureDetailForLog(log.detail), 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, []), })), projectExecutionPolicies: ensureArray( raw.projectExecutionPolicies, base.projectExecutionPolicies, ).map((policy) => normalizeProjectExecutionPolicy(policy)), }; if (!state.projects.some((project) => project.id === "master-agent")) { state.projects.unshift(base.projects[0]); } removeLegacyBossConsoleArtifacts(state); return syncDerivedState(state); } const normalizeState = migrateBossState; const LEGACY_BOSS_CONSOLE_PROJECT_ID = "boss-console"; const LEGACY_BOSS_CONSOLE_PROJECT_NAME = "Boss 移动控制台"; function isLegacyBossConsoleRef(value?: string | null) { const normalized = value?.trim(); return ( normalized === LEGACY_BOSS_CONSOLE_PROJECT_ID || normalized === LEGACY_BOSS_CONSOLE_PROJECT_NAME ); } function removeLegacyBossConsoleArtifacts(state: BossState) { state.projects = state.projects.filter((project) => !isLegacyBossConsoleRef(project.id)); state.devices = state.devices.map((device) => ({ ...device, projects: device.projects.filter((project) => !isLegacyBossConsoleRef(project)), })); state.masterAgentMemories = state.masterAgentMemories.filter( (memory) => !isLegacyBossConsoleRef(memory.projectId), ); state.userProjectAgentControls = state.userProjectAgentControls.filter( (item) => !isLegacyBossConsoleRef(item.projectId), ); state.threadContextSnapshots = state.threadContextSnapshots.filter( (snapshot) => !isLegacyBossConsoleRef(snapshot.projectId), ); state.threadHandoffPackages = state.threadHandoffPackages.filter( (item) => !isLegacyBossConsoleRef(item.projectId), ); state.threadContextAlerts = state.threadContextAlerts.filter( (item) => !isLegacyBossConsoleRef(item.projectId), ); state.threadStatusDocuments = state.threadStatusDocuments.filter( (item) => !isLegacyBossConsoleRef(item.projectId), ); state.threadProgressEvents = state.threadProgressEvents.filter( (item) => !isLegacyBossConsoleRef(item.projectId), ); state.opsFaults = state.opsFaults.filter((item) => !isLegacyBossConsoleRef(item.projectId)); state.masterAgentTasks = state.masterAgentTasks.filter( (task) => !isLegacyBossConsoleRef(task.projectId) && !isLegacyBossConsoleRef(task.targetProjectId) && !isLegacyBossConsoleRef(task.projectUnderstandingTargetProjectId), ); state.dispatchPlans = state.dispatchPlans.filter( (plan) => !isLegacyBossConsoleRef(plan.groupProjectId) && !(plan.targets ?? []).some((target) => isLegacyBossConsoleRef(target.projectId)) && !(plan.confirmedTargetProjectIds ?? []).some((projectId) => isLegacyBossConsoleRef(projectId), ), ); state.dispatchExecutions = state.dispatchExecutions.filter( (execution) => !isLegacyBossConsoleRef(execution.groupProjectId) && !isLegacyBossConsoleRef(execution.targetProjectId), ); } 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(); } const THREAD_PROCESS_PREFIXES = [ "我先", "我现在", "我会先", "我发现", "我准备", "接下来", "正在", "先看", "先读", "我把", "我再", "目前在", "现在在", "补一组", "处理一下", "先确认", "准备", "同步一下", "我这边已经", ]; const THREAD_PROCESS_CONTAINS_MARKERS = [ "我继续", "我已经在", "正在跑", "正在检查", "正在处理", "正在同步", "我会直接", "我先把", "先补", "再接", ]; const THREAD_PROCESS_NUMBERED_PREFIX = /^\d+[.)\u3001]\s*/; const THREAD_PROCESS_STRUCTURED_LINE_HINTS = [ "先", "再", "接下来", "然后", "检查", "确认", "处理", "同步", "补", "排查", "推进", "回你", "回传", "会把", "我会", ]; const THREAD_PROCESS_BLOCK_MARKERS = [ "失败", "报错", "错误", "阻塞", "不能", "无法", "崩溃", "超时", "exception", "error", "fatal", "结论", "最终", "总结", "已完成", "已经完成", "验证通过", "测试通过", "已修复", "修好了", "已部署", "已安装", "可以直接", ]; function isThreadProcessMessageKind(kind?: string) { return kind === "thread_process"; } function isExecutionProgressMessageKind(kind?: string) { return kind === "execution_progress"; } function isControlSummaryMessageKind(kind?: string) { return kind === "control_summary"; } function isPreviewableMessageKind(kind?: string) { return ( !isThreadProcessMessageKind(kind) && !isExecutionProgressMessageKind(kind) && !isControlSummaryMessageKind(kind) ); } function compactThreadProcessBody(body: string) { return body.replace(/\s+/g, " ").trim().toLowerCase(); } function containsThreadProcessMarker(body: string, markers: string[]) { return markers.some((marker) => body.includes(marker)); } function isLikelyStructuredThreadProcessBody(body?: string) { const lines = (body ?? "") .replace(/\r\n/g, "\n") .replace(/\r/g, "\n") .split("\n") .map((line) => compactThreadProcessBody(line)) .filter(Boolean); if (lines.length < 2) { return false; } const numberedLines = lines.filter((line) => THREAD_PROCESS_NUMBERED_PREFIX.test(line)); if (numberedLines.length < 2) { return false; } return containsThreadProcessMarker( numberedLines.join(" "), THREAD_PROCESS_STRUCTURED_LINE_HINTS, ); } function isLikelyThreadProcessBody(body?: string) { const normalized = compactThreadProcessBody(body ?? ""); if (!normalized) { return false; } if (isLikelyStructuredThreadProcessBody(body)) { return true; } if (containsThreadProcessMarker(normalized, THREAD_PROCESS_BLOCK_MARKERS)) { return false; } return ( THREAD_PROCESS_NUMBERED_PREFIX.test(normalized) || THREAD_PROCESS_PREFIXES.some((marker) => normalized.startsWith(marker)) || containsThreadProcessMarker(normalized, THREAD_PROCESS_CONTAINS_MARKERS) ); } function resolveObservedAssistantMessageKind(message: Pick): MessageKind { if (message.phase === "commentary") { return "thread_process"; } if (message.phase === "final_answer") { return "text"; } return isLikelyThreadProcessBody(message.body) ? "thread_process" : "text"; } function resolveConversationReplyMessageKind( task: Pick, message: Pick, ): MessageKind { if (task.relayViaMasterAgent) { return "text"; } return resolveObservedAssistantMessageKind(message); } function shouldCountAsUnreadMessage(message: Pick) { return message.sender !== "user" && isPreviewableMessageKind(message.kind); } function findLatestPreviewableMessage(project: Project) { const sortedMessages = [...project.messages].sort( (a, b) => messageTimeValue(b.sentAt) - messageTimeValue(a.sentAt), ); return sortedMessages.find((message) => isPreviewableMessageKind(message.kind)) ?? null; } function deriveProjectPreview( state: BossState, project: Project, options?: { emptyMessageFallback?: "preserve" | "clear" }, ) { 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 = findLatestPreviewableMessage(project); if (lastMessage) { return lastMessage.body; } if (project.messages.some((message) => isThreadProcessMessageKind(message.kind))) { return ""; } if (options?.emptyMessageFallback === "clear") { return ""; } return project.preview; } function updateMasterProjectSummary(state: BossState) { const masterProject = state.projects.find((project) => project.id === "master-agent"); if (!masterProject) return; const historyClearedAt = trimToDefined(state.conversationHistoryClearedAt); 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(" · ") || (historyClearedAt ? "" : "当前无高风险线程,主 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, body: sanitizeUserVisibleMessageText(message.body) ?? "已拦截内部执行日志,原始内容已隐藏。", }; project.messages.push(entry); if (shouldCountAsUnreadMessage(entry)) { project.unreadCount = Math.max(0, project.unreadCount ?? 0) + 1; } project.lastMessageAt = entry.sentAt; if (isPreviewableMessageKind(entry.kind)) { project.preview = entry.body; } return entry; } function safeExecutionProgressText(value: unknown, fallback = "") { const text = typeof value === "string" ? value.trim() : ""; if (!text) { return fallback; } const sanitized = sanitizeUserVisibleMessageText(text) ?.replace(/sk-[A-Za-z0-9_-]{8,}/g, "[redacted]") .replace(/(api[_-]?key|token|secret|password)\s*[=:]\s*[^ \n\r\t]+/gi, "$1=[redacted]") .trim(); return sanitized || fallback; } function safeExecutionProgressDiagnosticText(value: unknown, fallback = "") { const text = safeExecutionProgressText(value, fallback) .replace(/[A-Za-z]:\\[^\s]+/g, "[path]") .replace(/\/Users\/[^\s]+/g, "[path]") .trim(); return text || fallback; } function normalizeExecutionProgressStatus(status: unknown): ExecutionProgressStatus { return status === "running" || status === "completed" || status === "failed" || status === "queued" ? status : "queued"; } function normalizeExecutionProgressStepStatus(status: unknown): ExecutionProgressStepStatus { return status === "running" || status === "done" || status === "failed" || status === "pending" ? status : "pending"; } function normalizeOptionalNumber(value: unknown) { return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : undefined; } function normalizeExecutionProgressBranch(input?: Partial) { if (!input) { return undefined; } const githubCliStatus = input.githubCliStatus === "available" || input.githubCliStatus === "unavailable" || input.githubCliStatus === "unknown" ? input.githubCliStatus : undefined; const branch: ExecutionProgressBranchDetails = { additions: normalizeOptionalNumber(input.additions), deletions: normalizeOptionalNumber(input.deletions), changedFiles: normalizeOptionalNumber(input.changedFiles), gitStatus: safeExecutionProgressText(input.gitStatus), githubCliStatus, }; return Object.values(branch).some((value) => value !== undefined && value !== "") ? branch : undefined; } function normalizeExecutionProgressArtifacts(input?: ExecutionProgressInput["artifacts"]) { return (input ?? []) .map((artifact, index): ExecutionProgressArtifact | null => { const label = safeExecutionProgressText(artifact?.label); if (!label) { return null; } const kind = artifact?.kind === "image" || artifact?.kind === "link" || artifact?.kind === "log" || artifact?.kind === "file" ? artifact.kind : "file"; return { id: safeExecutionProgressText(artifact?.id) || `artifact-${index + 1}`, label, kind, path: safeExecutionProgressText(artifact?.path) || undefined, url: safeExecutionProgressText(artifact?.url) || undefined, }; }) .filter((artifact): artifact is ExecutionProgressArtifact => Boolean(artifact)) .slice(0, 12); } function normalizeExecutionProgressAgents(input?: ExecutionProgressInput["agents"]) { return (input ?? []) .map((agent): ExecutionProgressAgent | null => { const name = safeExecutionProgressText(agent?.name); if (!name) { return null; } return { name, role: safeExecutionProgressText(agent?.role) || undefined, status: safeExecutionProgressText(agent?.status) || undefined, }; }) .filter((agent): agent is ExecutionProgressAgent => Boolean(agent)) .slice(0, 8); } function normalizeExecutionProgressApprovals(input?: ExecutionProgressInput["approvals"]) { return (input ?? []) .map((approval, index): ExecutionProgressApproval | null => { const label = safeExecutionProgressText(approval?.label); if (!label) { return null; } return { id: safeExecutionProgressText(approval?.id) || `approval-${index + 1}`, kind: safeExecutionProgressText(approval?.kind) || undefined, label, status: safeExecutionProgressText(approval?.status) || undefined, riskLevel: safeExecutionProgressText(approval?.riskLevel) || undefined, detail: safeExecutionProgressText(approval?.detail) || undefined, }; }) .filter((approval): approval is ExecutionProgressApproval => Boolean(approval)) .slice(0, 8); } function normalizeExecutionProgressWarnings(input?: ExecutionProgressInput["warnings"]) { return (input ?? []) .map((warning, index): ExecutionProgressWarning | null => { const message = safeExecutionProgressText(warning?.message); if (!message) { return null; } return { id: safeExecutionProgressText(warning?.id) || `warning-${index + 1}`, message, severity: safeExecutionProgressText(warning?.severity) || "warning", }; }) .filter((warning): warning is ExecutionProgressWarning => Boolean(warning)) .slice(0, 6); } function normalizeExecutionProgressFileChanges(input?: ExecutionProgressInput["fileChanges"]) { return (input ?? []) .map((fileChange, index): ExecutionProgressFileChange | null => { const path = safeExecutionProgressText(fileChange?.path); if (!path) { return null; } return { id: safeExecutionProgressText(fileChange?.id) || `file-change-${index + 1}`, path, kind: safeExecutionProgressText(fileChange?.kind) || undefined, status: safeExecutionProgressText(fileChange?.status) || undefined, }; }) .filter((fileChange): fileChange is ExecutionProgressFileChange => Boolean(fileChange)) .slice(0, 12); } function normalizeExecutionProgressThreadStatus( input?: ExecutionProgressInput["threadStatus"], ): ExecutionProgressThreadStatus | undefined { if (!input) { return undefined; } const type = safeExecutionProgressText(input.type); if (!type) { return undefined; } const activeFlags = Array.isArray(input.activeFlags) ? input.activeFlags .map((flag) => safeExecutionProgressText(flag)) .filter(Boolean) .slice(0, 6) : []; return { type, ...(activeFlags.length > 0 ? { activeFlags } : {}), ...(input.waitingOnApproval === true || activeFlags.includes("waitingOnApproval") ? { waitingOnApproval: true } : {}), ...(input.waitingOnUserInput === true || activeFlags.includes("waitingOnUserInput") ? { waitingOnUserInput: true } : {}), }; } function normalizeExecutionProgressRealtime( input?: ExecutionProgressInput["realtime"], ): ExecutionProgressRealtime | undefined { if (!input) { return undefined; } const status = input.status === "started" || input.status === "streaming" || input.status === "closed" || input.status === "error" ? input.status : undefined; if (!status) { return undefined; } return { status, sessionId: safeExecutionProgressText(input.sessionId) || undefined, version: safeExecutionProgressText(input.version) || undefined, transcriptRole: safeExecutionProgressText(input.transcriptRole) || undefined, transcriptPreview: safeExecutionProgressText(input.transcriptPreview) || undefined, audioChunkCount: normalizeOptionalNumber(input.audioChunkCount), itemCount: normalizeOptionalNumber(input.itemCount), lastError: safeExecutionProgressText(input.lastError) || undefined, closeReason: safeExecutionProgressText(input.closeReason) || undefined, }; } function normalizeExecutionProgressModelRoute( input?: ExecutionProgressInput["modelRoute"], ): ExecutionProgressModelRoute | undefined { if (!input) { return undefined; } const fromModel = safeExecutionProgressText(input.fromModel); const toModel = safeExecutionProgressText(input.toModel); if (!fromModel || !toModel) { return undefined; } return { fromModel, toModel, reason: safeExecutionProgressText(input.reason) || undefined, }; } function normalizeExecutionProgressTokenUsage( input?: ExecutionProgressInput["tokenUsage"], ): ExecutionProgressTokenUsage | undefined { if (!input) { return undefined; } const totalTokens = normalizeOptionalNumber(input.totalTokens); if (totalTokens === undefined) { return undefined; } const modelContextWindow = normalizeOptionalNumber(input.modelContextWindow); const contextPercent = input.contextPercent !== undefined ? Math.max(0, Math.min(100, normalizeOptionalNumber(input.contextPercent) ?? 0)) : modelContextWindow && modelContextWindow > 0 ? Math.max(0, Math.min(100, Math.ceil((totalTokens / modelContextWindow) * 100))) : undefined; return { totalTokens, inputTokens: normalizeOptionalNumber(input.inputTokens), cachedInputTokens: normalizeOptionalNumber(input.cachedInputTokens), outputTokens: normalizeOptionalNumber(input.outputTokens), reasoningOutputTokens: normalizeOptionalNumber(input.reasoningOutputTokens), modelContextWindow, contextPercent, }; } function normalizeExecutionProgressAccountStatus( input?: ExecutionProgressInput["accountStatus"], ): ExecutionProgressAccountStatus | undefined { if (!input) { return undefined; } const status: ExecutionProgressAccountStatus = { authMode: safeExecutionProgressText(input.authMode) || undefined, planType: safeExecutionProgressText(input.planType) || undefined, limitId: safeExecutionProgressText(input.limitId) || undefined, limitName: safeExecutionProgressText(input.limitName) || undefined, usedPercent: normalizeOptionalNumber(input.usedPercent), windowDurationMins: normalizeOptionalNumber(input.windowDurationMins), resetsAt: normalizeOptionalNumber(input.resetsAt), rateLimitReachedType: safeExecutionProgressText(input.rateLimitReachedType) || undefined, creditsBalance: safeExecutionProgressText(input.creditsBalance) || undefined, hasCredits: typeof input.hasCredits === "boolean" ? input.hasCredits : undefined, unlimitedCredits: typeof input.unlimitedCredits === "boolean" ? input.unlimitedCredits : undefined, }; return Object.values(status).some((value) => value !== undefined && value !== "") ? status : undefined; } function normalizeExecutionProgressModelVerification( input?: ExecutionProgressInput["modelVerification"], ): ExecutionProgressModelVerification | undefined { if (!input) { return undefined; } const verifications = Array.isArray(input.verifications) ? input.verifications .map((verification) => safeExecutionProgressText(verification)) .filter(Boolean) .slice(0, 8) : []; return verifications.length > 0 ? { verifications } : undefined; } function normalizeExecutionProgressMcpServers(input?: ExecutionProgressInput["mcpServers"]) { return (input ?? []) .map((server): ExecutionProgressMcpServer | null => { const name = safeExecutionProgressText(server?.name); if (!name) { return null; } return { name, status: safeExecutionProgressText(server?.status) || undefined, error: safeExecutionProgressText(server?.error) || undefined, }; }) .filter((server): server is ExecutionProgressMcpServer => Boolean(server)) .slice(0, 6); } function normalizeExecutionProgressRemoteControl( input?: ExecutionProgressInput["remoteControl"], ): ExecutionProgressRemoteControl | undefined { if (!input) { return undefined; } const status = safeExecutionProgressText(input.status); if (!status) { return undefined; } return { status, serverName: safeExecutionProgressText(input.serverName) || undefined, environmentId: safeExecutionProgressText(input.environmentId) || undefined, }; } function normalizeExecutionProgressWindowsSandbox( input?: ExecutionProgressInput["windowsSandbox"], ): ExecutionProgressWindowsSandbox | undefined { if (!input) { return undefined; } const status = safeExecutionProgressText(input.status); const setupMode = safeExecutionProgressText(input.setupMode); const error = safeExecutionProgressDiagnosticText(input.error); const snapshot: ExecutionProgressWindowsSandbox = { status: status || undefined, setupMode: setupMode || undefined, error: error || undefined, }; return Object.values(snapshot).some((value) => value !== undefined && value !== "") ? snapshot : undefined; } function normalizeExecutionProgressThreadGoal( input?: ExecutionProgressInput["threadGoal"], ): ExecutionProgressThreadGoal | undefined { if (!input) { return undefined; } const status = safeExecutionProgressText(input.status); const objective = safeExecutionProgressText(input.objective); if (!status && !objective) { return undefined; } return { objective: objective || undefined, status: status || "active", tokenBudget: normalizeOptionalNumber(input.tokenBudget), tokensUsed: normalizeOptionalNumber(input.tokensUsed), timeUsedSeconds: normalizeOptionalNumber(input.timeUsedSeconds), }; } function normalizeExecutionProgressThreadSettings( input?: ExecutionProgressInput["threadSettings"], ): ExecutionProgressThreadSettings | undefined { if (!input) { return undefined; } const settings: ExecutionProgressThreadSettings = { model: safeExecutionProgressText(input.model) || undefined, modelProvider: safeExecutionProgressText(input.modelProvider) || undefined, approvalPolicy: safeExecutionProgressText(input.approvalPolicy) || undefined, approvalsReviewer: safeExecutionProgressText(input.approvalsReviewer) || undefined, sandboxPolicy: safeExecutionProgressText(input.sandboxPolicy) || undefined, permissionProfile: safeExecutionProgressText(input.permissionProfile) || undefined, serviceTier: safeExecutionProgressText(input.serviceTier) || undefined, effort: safeExecutionProgressText(input.effort) || undefined, summary: safeExecutionProgressText(input.summary) || undefined, collaborationMode: safeExecutionProgressText(input.collaborationMode) || undefined, personality: safeExecutionProgressText(input.personality) || undefined, }; return Object.values(settings).some((value) => value !== undefined && value !== "") ? settings : undefined; } function normalizeExecutionProgressCompaction( input?: ExecutionProgressInput["compaction"], ): ExecutionProgressCompaction | undefined { if (!input) { return undefined; } const status = safeExecutionProgressText(input.status); const message = safeExecutionProgressText(input.message); if (!status && !message) { return undefined; } return { status: status || "completed", message: message || undefined, }; } function normalizeExecutionProgressThreadCollaboration( input?: ExecutionProgressInput["threadCollaboration"], ): ExecutionProgressThreadCollaboration | undefined { if (!input) { return undefined; } const collaboration: ExecutionProgressThreadCollaboration = { tool: safeExecutionProgressText(input.tool) || undefined, status: safeExecutionProgressText(input.status) || "running", target: safeExecutionProgressText(input.target) || undefined, agentStatus: safeExecutionProgressText(input.agentStatus) || undefined, }; const receiverCount = normalizeOptionalNumber(input.receiverCount); if (receiverCount !== undefined) { collaboration.receiverCount = receiverCount; } return Object.values(collaboration).some((value) => value !== undefined && value !== "") ? collaboration : undefined; } function normalizeExecutionProgressToolActivities( input?: ExecutionProgressInput["toolActivities"], ): ExecutionProgressToolActivity[] | undefined { const activities = (input ?? []) .map((item): ExecutionProgressToolActivity | null => { const kind = safeExecutionProgressText(item.kind); const name = safeExecutionProgressText(item.name); if (!kind || !name) { return null; } return { kind, name, status: safeExecutionProgressText(item.status) || "running", detail: safeExecutionProgressText(item.detail) || undefined, }; }) .filter((item): item is ExecutionProgressToolActivity => Boolean(item)) .slice(0, 8); return activities.length > 0 ? activities : undefined; } function normalizeExecutionProgressReasoningSummary( input?: ExecutionProgressInput["reasoningSummary"], ): ExecutionProgressReasoningSummary | undefined { if (!input) { return undefined; } const summary = safeExecutionProgressText(input.summary); if (!summary) { return undefined; } return { status: safeExecutionProgressText(input.status) || "running", summary, }; } function normalizeStreamEventCount(value: unknown) { const count = normalizeOptionalNumber(value); return count === undefined ? undefined : Math.max(0, Math.min(9999, count)); } function normalizeExecutionProgressStreamEvents( input?: ExecutionProgressInput["streamEvents"], ): ExecutionProgressStreamEvents | undefined { if (!input) { return undefined; } const streamEvents: ExecutionProgressStreamEvents = { status: safeExecutionProgressText(input.status) || "streaming", agentDeltaCount: normalizeStreamEventCount(input.agentDeltaCount), planDeltaCount: normalizeStreamEventCount(input.planDeltaCount), reasoningDeltaCount: normalizeStreamEventCount(input.reasoningDeltaCount), toolProgressCount: normalizeStreamEventCount(input.toolProgressCount), commandOutputChunkCount: normalizeStreamEventCount(input.commandOutputChunkCount), terminalInteractionCount: normalizeStreamEventCount(input.terminalInteractionCount), fileOutputChunkCount: normalizeStreamEventCount(input.fileOutputChunkCount), }; const hasCount = [ streamEvents.agentDeltaCount, streamEvents.planDeltaCount, streamEvents.reasoningDeltaCount, streamEvents.toolProgressCount, streamEvents.commandOutputChunkCount, streamEvents.terminalInteractionCount, streamEvents.fileOutputChunkCount, ].some((count) => typeof count === "number" && count > 0); return hasCount ? streamEvents : undefined; } function defaultExecutionProgressStepTexts(task: Pick) { if (task.taskType === "device_maintenance") { return [ "接收设备维护指令", "确认设备权限和绑定状态", "执行本机维护动作", "回写维护结果", ]; } if (task.taskType === "browser_control") { return [ "接收远程控制指令", "连接目标电脑", "执行浏览器操作", "回写控制结果", ]; } if (task.taskType === "desktop_control") { return [ "接收远程控制指令", "连接目标电脑", "执行桌面操作", "回写控制结果", ]; } if (task.taskType === "dispatch_execution") { return [ "接收群聊下发任务", "定位目标 Codex 线程", "执行目标线程任务", "回写群聊和原线程", "更新计划文档和版本记录", ]; } return [ "接收对话任务", task.relayViaMasterAgent ? "确认主 Agent 托管意图" : "定位目标 Codex 线程", "写入 Codex 桌面线程记录", "等待目标线程回复", "回写 Boss 对话窗口", ]; } function deriveExecutionProgressStepStatuses(status: ExecutionProgressStatus, count: number) { return Array.from({ length: count }, (_, index): ExecutionProgressStepStatus => { if (status === "completed") { return "done"; } if (status === "failed") { return index === Math.min(count - 1, 3) ? "failed" : index < Math.min(count - 1, 3) ? "done" : "pending"; } if (status === "running") { return index <= 1 ? "done" : index === 2 ? "running" : "pending"; } return index === 0 ? "running" : "pending"; }); } function deriveExecutionProgressStepStatusesFromPhase( phase: MasterAgentTaskPhase | undefined, status: ExecutionProgressStatus, count: number, ) { if (!phase) { return deriveExecutionProgressStepStatuses(status, count); } if (phase === "completed") { return Array.from({ length: count }, () => "done" as const); } if ( phase === "recoverable_failed" || phase === "terminal_failed" || phase === "timed_out" || phase === "canceled" ) { const failedIndex = Math.min(count - 1, count >= 5 ? 3 : 2); return Array.from({ length: count }, (_, index): ExecutionProgressStepStatus => { if (index < failedIndex) return "done"; if (index === failedIndex) return "failed"; return "pending"; }); } const activeIndex = phase === "queued" ? 0 : phase === "claimed" || phase === "executor_starting" ? Math.min(1, count - 1) : phase === "turn_started" || phase === "awaiting_reply" ? Math.min(count >= 5 ? 3 : 2, count - 1) : phase === "completing" ? count - 1 : Math.min(1, count - 1); return Array.from({ length: count }, (_, index): ExecutionProgressStepStatus => { if (index < activeIndex) return "done"; if (index === activeIndex) return "running"; return "pending"; }); } function normalizeExecutionProgressSteps( task: Pick, status: ExecutionProgressStatus, phase?: MasterAgentTaskPhase, input?: ExecutionProgressInput["steps"], ) { const defaultTexts = defaultExecutionProgressStepTexts(task); const defaultStatuses = deriveExecutionProgressStepStatusesFromPhase(phase, status, defaultTexts.length); const sourceSteps = input && input.length > 0 ? input : defaultTexts.map((text, index) => ({ id: `step-${index + 1}`, text, status: defaultStatuses[index], })); return sourceSteps .map((step, index): ExecutionProgressStep | null => { const text = safeExecutionProgressText(step?.text, defaultTexts[index] ?? ""); if (!text) { return null; } return { id: safeExecutionProgressText(step?.id) || `step-${index + 1}`, text, status: normalizeExecutionProgressStepStatus(step?.status ?? defaultStatuses[index]), }; }) .filter((step): step is ExecutionProgressStep => Boolean(step)) .slice(0, 10); } function resolveTaskExecutionProgressProjectId(task: Pick) { if (task.taskType === "device_maintenance") { return task.projectId?.trim() || ""; } if (task.taskType === "browser_control" || task.taskType === "desktop_control") { return task.projectId?.trim() || ""; } if (task.taskType === "dispatch_execution") { return task.targetProjectId?.trim() || ""; } if (task.taskType === "conversation_reply" && task.projectId !== "master-agent") { return task.targetProjectId?.trim() || task.projectId; } return ""; } function shouldShowTaskExecutionProgress(task: Pick) { if (task.taskType === "device_maintenance") { return Boolean(task.projectId?.trim()); } if (task.taskType === "browser_control" || task.taskType === "desktop_control") { return Boolean(task.projectId?.trim()); } if (task.taskType === "dispatch_execution") { return Boolean(task.targetProjectId?.trim() && task.targetThreadId?.trim()); } return ( task.taskType === "conversation_reply" && task.projectId !== "master-agent" && Boolean(task.targetProjectId?.trim() && task.targetThreadId?.trim()) ); } function buildExecutionProgressSnapshot( task: MasterAgentTask, status: ExecutionProgressStatus, input?: ExecutionProgressInput, ): ExecutionProgressSnapshot | null { const projectId = resolveTaskExecutionProgressProjectId(task); if (!projectId || !shouldShowTaskExecutionProgress(task)) { return null; } const taskPhase = normalizeMasterAgentTaskPhase(input?.phase ?? task.phase, task.status); const normalizedStatus = phaseToExecutionProgressStatus( taskPhase, normalizeExecutionProgressStatus(status), ); const steps = normalizeExecutionProgressSteps(task, normalizedStatus, taskPhase, input?.steps); const nativeRemoteControl = task.taskType === "browser_control" || task.taskType === "desktop_control" || task.taskType === "device_maintenance"; const maintenanceControl = task.taskType === "device_maintenance"; return { taskId: task.taskId, projectId, targetProjectId: task.targetProjectId, taskType: task.taskType, phase: taskPhase, controlMode: nativeRemoteControl ? "native_remote_control" : "codex_thread", runtimeKind: task.runtimeKind, controlPlatform: task.controlPlatform, computerUseProvider: task.computerUseProvider, title: maintenanceControl ? "设备维护进度" : nativeRemoteControl ? "远程控制进度" : "进度", status: normalizedStatus, steps, branch: nativeRemoteControl ? undefined : normalizeExecutionProgressBranch(input?.branch), artifacts: normalizeExecutionProgressArtifacts(input?.artifacts), agents: nativeRemoteControl ? undefined : normalizeExecutionProgressAgents(input?.agents), approvals: nativeRemoteControl ? undefined : normalizeExecutionProgressApprovals(input?.approvals), warnings: nativeRemoteControl ? undefined : normalizeExecutionProgressWarnings(input?.warnings), fileChanges: nativeRemoteControl ? undefined : normalizeExecutionProgressFileChanges(input?.fileChanges), threadStatus: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadStatus(input?.threadStatus), realtime: nativeRemoteControl ? undefined : normalizeExecutionProgressRealtime(input?.realtime), modelRoute: nativeRemoteControl ? undefined : normalizeExecutionProgressModelRoute(input?.modelRoute), tokenUsage: nativeRemoteControl ? undefined : normalizeExecutionProgressTokenUsage(input?.tokenUsage), accountStatus: nativeRemoteControl ? undefined : normalizeExecutionProgressAccountStatus(input?.accountStatus), modelVerification: nativeRemoteControl ? undefined : normalizeExecutionProgressModelVerification(input?.modelVerification), mcpServers: nativeRemoteControl ? undefined : normalizeExecutionProgressMcpServers(input?.mcpServers), remoteControl: nativeRemoteControl ? undefined : normalizeExecutionProgressRemoteControl(input?.remoteControl), windowsSandbox: nativeRemoteControl ? undefined : normalizeExecutionProgressWindowsSandbox(input?.windowsSandbox), threadGoal: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadGoal(input?.threadGoal), threadSettings: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadSettings(input?.threadSettings), compaction: nativeRemoteControl ? undefined : normalizeExecutionProgressCompaction(input?.compaction), threadCollaboration: nativeRemoteControl ? undefined : normalizeExecutionProgressThreadCollaboration(input?.threadCollaboration), toolActivities: nativeRemoteControl ? undefined : normalizeExecutionProgressToolActivities(input?.toolActivities), reasoningSummary: nativeRemoteControl ? undefined : normalizeExecutionProgressReasoningSummary(input?.reasoningSummary), streamEvents: nativeRemoteControl ? undefined : normalizeExecutionProgressStreamEvents(input?.streamEvents), updatedAt: nowIso(), }; } function upsertTaskExecutionProgressMessageInState( state: BossState, task: MasterAgentTask, status: ExecutionProgressStatus, input?: ExecutionProgressInput, ) { const snapshot = buildExecutionProgressSnapshot(task, status, input); if (!snapshot) { return null; } const project = state.projects.find((item) => item.id === snapshot.projectId); if (!project) { return null; } const existing = project.messages.find((message) => message.executionProgress?.taskId === task.taskId); if (existing) { existing.sender = "master"; existing.senderLabel = task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent"; existing.kind = "execution_progress"; existing.body = snapshot.status === "failed" ? "执行进度:失败" : "执行进度"; existing.executionProgress = snapshot; existing.sentAt = snapshot.updatedAt; project.lastMessageAt = latestIsoTimestamp(project.lastMessageAt, snapshot.updatedAt) ?? snapshot.updatedAt; return existing; } return pushProjectLedgerMessage(state, snapshot.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", body: snapshot.status === "failed" ? "执行进度:失败" : "执行进度", kind: "execution_progress", executionProgress: snapshot, }); } const OBSERVED_ASSISTANT_DUPLICATE_WINDOW_MS = 5_000; function convertRecentMirroredThreadReplyToMaster( project: Project, input: { body: string; sentAt: string; senderLabel: string; kind: MessageKind; }, ) { const replyTime = messageTimeValue(input.sentAt); if (!replyTime) { return null; } const mirrored = [...project.messages].reverse().find((message) => { if (message.sender !== "device") return false; if (message.body !== input.body) return false; const messageTime = messageTimeValue(message.sentAt); if (!messageTime) return false; return Math.abs(replyTime - messageTime) <= OBSERVED_ASSISTANT_DUPLICATE_WINDOW_MS; }); if (!mirrored) { return null; } const countedBefore = shouldCountAsUnreadMessage(mirrored); mirrored.sender = "master"; mirrored.senderLabel = input.senderLabel; mirrored.kind = input.kind; mirrored.sentAt = input.sentAt; const countedAfter = shouldCountAsUnreadMessage(mirrored); if (countedBefore !== countedAfter) { project.unreadCount = Math.max( 0, (project.unreadCount ?? 0) + (countedAfter ? 1 : -1), ); } project.lastMessageAt = mirrored.sentAt; if (isPreviewableMessageKind(mirrored.kind)) { project.preview = mirrored.body; } return mirrored; } 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 shouldMirrorAppLogToMaster(payload: { source: "app_client" | "local_agent"; category: string; mirrorToMaster?: boolean; }) { if (!payload.mirrorToMaster) { return false; } // Local agent logs are operational telemetry. Showing them as chat messages // confuses demos and can expose backend execution details; keep them in logs. if (payload.source === "local_agent") { return false; } return true; } function buildMasterAgentLogReply(state: BossState, entry: AppLogEntry) { const device = state.devices.find((item) => item.id === entry.deviceId); 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 优化方向。"); } 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", status: "active", 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.status = "active"; 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 主节点 · ${PRIMARY_ADMIN_ACCOUNT} 已绑定`; } 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; if (!primary.accountIdentifier || primary.accountIdentifier === LEGACY_PRIMARY_ADMIN_ACCOUNT) { primary.accountIdentifier = PRIMARY_ADMIN_ACCOUNT; } if (!primary.displayName || primary.displayName.includes(LEGACY_PRIMARY_ADMIN_ACCOUNT)) { primary.displayName = `${PRIMARY_ADMIN_ACCOUNT} · 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), ); const visibleProjectIds = new Set(state.projects.map((project) => project.id)); const seenProjectExecutionPolicies = new Set(); state.projectExecutionPolicies = state.projectExecutionPolicies .map((policy) => normalizeProjectExecutionPolicy(policy)) .filter((policy) => visibleDeviceIds.has(policy.deviceId)) .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) .filter((policy) => { const key = `${policy.deviceId}:${policy.folderKey ?? policy.projectId}`; if (seenProjectExecutionPolicies.has(key)) { return false; } seenProjectExecutionPolicies.add(key); return true; }) .slice(0, 200); const threadStatusDocumentByThread = new Map(); const normalizedThreadStatusDocuments = state.threadStatusDocuments.map((document) => normalizeThreadStatusDocument(document), ); for (const document of normalizedThreadStatusDocuments .filter((item) => visibleProjectIds.has(item.projectId) && visibleDeviceIds.has(item.deviceId)) .sort(compareThreadStatusDocuments)) { const key = `${document.projectId}:${document.threadId}`; if (!threadStatusDocumentByThread.has(key)) { threadStatusDocumentByThread.set(key, document); } } state.threadStatusDocuments = [...threadStatusDocumentByThread.values()].slice(0, 80); const progressEventCounts = new Map(); const normalizedThreadProgressEvents = state.threadProgressEvents.map((event) => normalizeThreadProgressEvent(event), ); state.threadProgressEvents = normalizedThreadProgressEvents .filter((item) => visibleProjectIds.has(item.projectId) && visibleDeviceIds.has(item.deviceId)) .sort(compareThreadProgressEvents) .filter((item) => { const key = threadProgressEventKey(item); const nextCount = (progressEventCounts.get(key) ?? 0) + 1; if (nextCount > 20) { return false; } progressEventCounts.set(key, nextCount); return true; }) .slice(0, 400); 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); const nowMs = Date.now(); state.authSessions = state.authSessions .filter((session) => { if (!session.revokedAt) { return new Date(session.expiresAt).getTime() > nowMs; } const revokedAtMs = new Date(session.revokedAt).getTime(); return Number.isFinite(revokedAtMs) && nowMs - revokedAtMs < AUTH_REVOKED_SESSION_RETENTION_MS; }) .sort((left, right) => { const activeRank = Number(Boolean(left.revokedAt)) - Number(Boolean(right.revokedAt)); if (activeRank !== 0) return activeRank; return right.lastSeenAt.localeCompare(left.lastSeenAt); }) .slice(0, 50); 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() { const initialJson = JSON.stringify(syncDerivedState(cloneInitialState()), null, 2); await stateStore.ensure(initialJson); } 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 stateStore.readText(); try { const state = normalizeState(JSON.parse(raw) as Partial); lastPersistedStateText = JSON.stringify(state, null, 2); return state; } catch { const fallbackText = (await stateStore.readBackupText()) ?? 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); lastPersistedStateText = serialized; const persist = async () => { await ensureStateFile(); await stateStore.writeText(serialized); }; 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; try { return parseStateText(await stateStore.readText()); } catch { try { const backupText = await stateStore.readBackupText(); if (!backupText) throw new Error("NO_STATE_BACKUP"); return parseStateText(backupText); } 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 updateProjectOrchestrationBackendOverride(input: { projectId: string; requestedBy: string; orchestrationBackendOverride?: OrchestrationBackendOverride; }) { return mutateState((state) => { const project = state.projects.find((item) => item.id === input.projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } if (!project.isGroup) { throw new Error("PROJECT_NOT_GROUP_CHAT"); } requireDispatchActorSession(state, input.requestedBy); const nextOverride = input.orchestrationBackendOverride; if (project.orchestrationBackendOverride === nextOverride) { return project; } project.orchestrationBackendOverride = nextOverride; project.updatedAt = nowIso(); project.threadMeta.updatedAt = project.updatedAt; return project; }); } export async function updateProjectLightDispatchReminder(input: { projectId: string; requestedBy: string; lightDispatchReminderEnabled: boolean; }) { return mutateStateIfChanged((state) => { const project = state.projects.find((item) => item.id === input.projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } if (!project.isGroup) { throw new Error("PROJECT_NOT_GROUP_CHAT"); } requireDispatchActorSession(state, input.requestedBy); const nextValue = Boolean(input.lightDispatchReminderEnabled); if (Boolean(project.lightDispatchReminderEnabled) == nextValue) { return { result: project, changed: false }; } project.lightDispatchReminderEnabled = nextValue; project.updatedAt = nowIso(); project.threadMeta.updatedAt = project.updatedAt; return { result: project, changed: true }; }); } 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 ); } function resolveStoredProjectAgentControls( state: BossState, projectId: string, account?: string, ) { const scopedControls = findUserProjectAgentControls(state, projectId, account); if (scopedControls?.controls) { return scopedControls.controls; } return state.projects.find((project) => project.id === projectId)?.agentControls ?? null; } function applyDerivedTakeoverControls( state: BossState, projectId: string, account: string | undefined, controls: ProjectAgentControls | null, ): ProjectAgentControls | null { const normalized = controls ? { ...controls } : null; if (projectId === "master-agent") { return normalized; } const globalControls = resolveStoredProjectAgentControls(state, "master-agent", account); const explicitTakeover = normalized?.takeoverEnabled; const inheritedGlobalTakeover = globalControls?.globalTakeoverEnabled; const effectiveTakeoverEnabled = explicitTakeover !== undefined ? explicitTakeover : Boolean(inheritedGlobalTakeover); const takeoverInheritedFromGlobal = explicitTakeover === undefined && inheritedGlobalTakeover !== undefined; if (!normalized && !takeoverInheritedFromGlobal && !effectiveTakeoverEnabled) { return null; } return { ...(normalized ?? { updatedAt: globalControls?.updatedAt ?? nowIso() }), updatedAt: normalized?.updatedAt ?? globalControls?.updatedAt ?? nowIso(), effectiveTakeoverEnabled, takeoverInheritedFromGlobal, }; } export async function getProjectAgentControls(projectId: string, account?: string) { const projectExists = await hasPersistedProject(projectId); if (!projectExists) { return null; } const state = await readState(); const controls = resolveStoredProjectAgentControls(state, projectId, account); return applyDerivedTakeoverControls(state, projectId, account, controls); } function isPendingProjectUnderstandingSyncTask(task: MasterAgentTask, projectIds?: Set) { if (task.taskType !== "conversation_reply" || task.projectId !== "master-agent") { return false; } if (!task.projectUnderstandingTargetProjectId) { return false; } if (task.status !== "queued" && task.status !== "running") { return false; } return !projectIds || projectIds.has(task.projectUnderstandingTargetProjectId); } function clearPendingProjectUnderstandingSyncTasksInState(state: BossState, projectIds?: Set) { const clearedTaskIds: string[] = []; state.masterAgentTasks = state.masterAgentTasks.filter((task) => { if (!isPendingProjectUnderstandingSyncTask(task, projectIds)) { return true; } clearedTaskIds.push(task.taskId); return false; }); return clearedTaskIds; } export async function updateProjectAgentControls( projectId: string, payload: { modelOverride?: unknown; reasoningEffortOverride?: unknown; fastModelOverride?: unknown; deepModelOverride?: unknown; promptOverride?: unknown; backendOverride?: unknown; takeoverEnabled?: unknown; globalTakeoverEnabled?: unknown; }, account?: string, ) { const projectExists = await hasPersistedProject(projectId); if (!projectExists) { throw new Error("PROJECT_NOT_FOUND"); } 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 fastModelOverrideInput = Object.prototype.hasOwnProperty.call(payload, "fastModelOverride") ? parseControlTextOverride(payload.fastModelOverride) : { kind: "preserve" as const }; const deepModelOverrideInput = Object.prototype.hasOwnProperty.call(payload, "deepModelOverride") ? parseControlTextOverride(payload.deepModelOverride) : { kind: "preserve" as const }; const promptOverrideInput = Object.prototype.hasOwnProperty.call(payload, "promptOverride") ? parseControlTextOverride(payload.promptOverride) : { kind: "preserve" as const }; const backendOverrideInput = Object.prototype.hasOwnProperty.call(payload, "backendOverride") ? parseBackendOverride(payload.backendOverride) : { kind: "preserve" as const }; const takeoverEnabledInput = Object.prototype.hasOwnProperty.call(payload, "takeoverEnabled") ? parseBooleanControlOverride(payload.takeoverEnabled) : { kind: "preserve" as const }; const globalTakeoverEnabledInput = Object.prototype.hasOwnProperty.call(payload, "globalTakeoverEnabled") ? parseBooleanControlOverride(payload.globalTakeoverEnabled) : { 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 (fastModelOverrideInput.kind === "invalid") { throw new Error("INVALID_FAST_MODEL_OVERRIDE"); } if (deepModelOverrideInput.kind === "invalid") { throw new Error("INVALID_DEEP_MODEL_OVERRIDE"); } if (promptOverrideInput.kind === "invalid") { throw new Error("INVALID_PROMPT_OVERRIDE"); } if (backendOverrideInput.kind === "invalid") { throw new Error("INVALID_BACKEND_OVERRIDE"); } if (takeoverEnabledInput.kind === "invalid") { throw new Error("INVALID_TAKEOVER_ENABLED"); } if (globalTakeoverEnabledInput.kind === "invalid") { throw new Error("INVALID_GLOBAL_TAKEOVER_ENABLED"); } if (projectId !== "master-agent") { if ( modelOverrideInput.kind !== "preserve" || reasoningEffortInput.kind !== "preserve" || fastModelOverrideInput.kind !== "preserve" || deepModelOverrideInput.kind !== "preserve" || promptOverrideInput.kind !== "preserve" || backendOverrideInput.kind !== "preserve" || globalTakeoverEnabledInput.kind !== "preserve" ) { throw new Error("PROJECT_AGENT_CONTROLS_SCOPE_RESTRICTED"); } } else if (takeoverEnabledInput.kind !== "preserve") { throw new Error("MASTER_AGENT_TAKEOVER_SCOPE_RESTRICTED"); } let clearedProjectUnderstandingTaskIds: string[] = []; const result = await 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 fastModelOverride = fastModelOverrideInput.kind === "set" ? fastModelOverrideInput.value : fastModelOverrideInput.kind === "clear" ? undefined : currentControls?.fastModelOverride; const deepModelOverride = deepModelOverrideInput.kind === "set" ? deepModelOverrideInput.value : deepModelOverrideInput.kind === "clear" ? undefined : currentControls?.deepModelOverride; const promptOverride = promptOverrideInput.kind === "set" ? promptOverrideInput.value : promptOverrideInput.kind === "clear" ? undefined : currentControls?.promptOverride; const backendOverride = backendOverrideInput.kind === "set" ? backendOverrideInput.value : backendOverrideInput.kind === "clear" ? undefined : currentControls?.backendOverride; const takeoverEnabled = takeoverEnabledInput.kind === "set" ? takeoverEnabledInput.value : takeoverEnabledInput.kind === "clear" ? undefined : currentControls?.takeoverEnabled; const globalTakeoverEnabled = globalTakeoverEnabledInput.kind === "set" ? globalTakeoverEnabledInput.value : globalTakeoverEnabledInput.kind === "clear" ? undefined : currentControls?.globalTakeoverEnabled; const currentModelOverride = currentControls?.modelOverride; const currentReasoningEffortOverride = currentControls?.reasoningEffortOverride; const currentFastModelOverride = currentControls?.fastModelOverride; const currentDeepModelOverride = currentControls?.deepModelOverride; const currentPromptOverride = currentControls?.promptOverride; const currentBackendOverride = currentControls?.backendOverride; const currentTakeoverEnabled = currentControls?.takeoverEnabled; const currentGlobalTakeoverEnabled = currentControls?.globalTakeoverEnabled; const shouldClearProjectUnderstandingTasks = projectId === "master-agent" ? globalTakeoverEnabledInput.kind === "set" && globalTakeoverEnabledInput.value === false : takeoverEnabledInput.kind === "set" && takeoverEnabledInput.value === false; const nextClearedTaskIds = shouldClearProjectUnderstandingTasks ? clearPendingProjectUnderstandingSyncTasksInState( state, projectId === "master-agent" ? undefined : new Set([projectId]), ) : []; clearedProjectUnderstandingTaskIds = nextClearedTaskIds; if ( currentModelOverride === modelOverride && currentReasoningEffortOverride === reasoningEffortOverride && currentFastModelOverride === fastModelOverride && currentDeepModelOverride === deepModelOverride && currentPromptOverride === promptOverride && currentBackendOverride === backendOverride && currentTakeoverEnabled === takeoverEnabled && currentGlobalTakeoverEnabled === globalTakeoverEnabled ) { return { result: applyDerivedTakeoverControls( state, projectId, normalizedAccount ?? undefined, currentControls ?? null, ), changed: nextClearedTaskIds.length > 0, }; } const nextControls = { modelOverride, reasoningEffortOverride, fastModelOverride, deepModelOverride, promptOverride, backendOverride, takeoverEnabled, globalTakeoverEnabled, 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: applyDerivedTakeoverControls( state, projectId, normalizedAccount ?? undefined, normalizedControls, ), changed: true, }; }); if (projectId === "master-agent") { publishBossEvent("conversation.updated", { projectId, note: "project_agent_controls.updated" }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); for (const taskId of clearedProjectUnderstandingTaskIds) { publishBossEvent("master_agent.task.updated", { taskId, status: "cleared" }); } return result; } publishBossEvent("conversation.updated", { projectId, note: "project_agent_controls.updated" }); for (const taskId of clearedProjectUnderstandingTaskIds) { publishBossEvent("master_agent.task.updated", { taskId, status: "cleared", projectId }); } return result; } function projectOrchestrationRequestedBackendId(project: Project): OrchestrationBackendId { return project.orchestrationBackendOverride ?? "boss-native-orchestrator"; } async function buildProjectOrchestrationBackendState( project: Project, ): Promise { const requestedBackendId = projectOrchestrationRequestedBackendId(project); const omxSelection = await getOmxTeamBackendSelectionState(); const currentBackend = await selectOrchestrationBackend({ requestedBackendId, omx: omxSelection, }); const nativeBackend = await BOSS_NATIVE_ORCHESTRATOR.describe(); const omxBackend = await OMX_TEAM_BACKEND.describe(); const availableChoices: ProjectOrchestrationBackendChoice[] = [ { backendId: nativeBackend.backendId as OrchestrationBackendId, label: nativeBackend.label, selectable: true, current: currentBackend.backendId === nativeBackend.backendId, }, { backendId: omxBackend.backendId as OrchestrationBackendId, label: omxBackend.label, selectable: omxSelection.selectable, current: currentBackend.backendId === omxBackend.backendId, }, ]; return { projectId: project.id, requestedBackendId, currentBackendId: currentBackend.backendId as OrchestrationBackendId, currentBackendLabel: availableChoices.find((choice) => choice.backendId === currentBackend.backendId)?.label ?? nativeBackend.label, requestedBackendLabel: availableChoices.find((choice) => choice.backendId === requestedBackendId)?.label ?? nativeBackend.label, availableChoices, omxAvailability: omxSelection.availability, }; } export async function getProjectOrchestrationBackendState( projectId: string, ): Promise { const state = await readState(); const project = state.projects.find((item) => item.id === projectId); if (!project) { return null; } return buildProjectOrchestrationBackendState(project); } export async function updateProjectOrchestrationBackend( projectId: string, requestedBackendId: OrchestrationBackendId, ) { return mutateStateIfChanged(async (state) => { const project = state.projects.find((item) => item.id === projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } const nextOverride = requestedBackendId === "boss-native-orchestrator" ? undefined : "omx-team"; const currentRequestedBackendId = projectOrchestrationRequestedBackendId(project); if (currentRequestedBackendId === requestedBackendId && project.orchestrationBackendOverride === nextOverride) { return { result: project.orchestrationBackendOverride ?? null, changed: false }; } project.orchestrationBackendOverride = nextOverride; const updatedAt = nowIso(); project.updatedAt = updatedAt; project.threadMeta.updatedAt = updatedAt; return { result: project.orchestrationBackendOverride ?? null, changed: true }; }); } export async function getDevice(deviceId: string) { const state = await readState(); return state.devices.find((device) => device.id === deviceId) ?? null; } export function isDeviceRevoked(device?: Pick | null) { return Boolean(device?.revokedAt); } 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) { const result = await 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; }); publishBossEvent("storage.updated"); return result; } 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"); } const result = await mutateState((state) => { const policy: MasterAgentPromptPolicy = { globalPrompt, updatedBy: input.updatedBy?.trim() || undefined, updatedAt: nowIso(), }; state.masterAgentPromptPolicy = policy; return policy; }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); return result; } 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"); } const result = await 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; }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); return result; } export async function clearUserMasterPrompt(account: string) { const result = await mutateState((state) => { const before = state.userMasterPrompts.length; state.userMasterPrompts = state.userMasterPrompts.filter((item) => item.account !== account); return { cleared: before !== state.userMasterPrompts.length }; }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); return result; } 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"); } const result = await 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; }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); return result; } export async function updateUserMasterMemory( memoryId: string, account: string, patch: Partial< Pick< MasterAgentMemory, "scope" | "projectId" | "title" | "content" | "memoryType" | "tags" | "sourceMessageId" | "lastUsedAt" > >, ) { const result = await 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; }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); return result; } export async function archiveUserMasterMemory(memoryId: string, account: string) { const result = await 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; }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); return result; } 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) { const goal = await 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; }); publishBossEvent("conversation.updated", { projectId, note: "project_goals.updated" }); return goal; } export async function updateGoalText(projectId: string, goalId: string, text: string) { const goal = await 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; }); publishBossEvent("conversation.updated", { projectId, note: "project_goals.updated" }); return goal; } export async function createGoal(projectId: string, text: string) { const goal = await 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; }); publishBossEvent("conversation.updated", { projectId, note: "project_goals.updated" }); 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); } export function generateAuthAccountMfaCode(secret: string, at: Date = new Date()) { const counter = Math.floor(at.getTime() / 30_000); const counterBuffer = Buffer.alloc(8); counterBuffer.writeBigUInt64BE(BigInt(counter)); const digest = createHmac("sha1", secret).update(counterBuffer).digest(); const offset = digest[digest.length - 1] & 0x0f; const binary = ((digest[offset] & 0x7f) << 24) | ((digest[offset + 1] & 0xff) << 16) | ((digest[offset + 2] & 0xff) << 8) | (digest[offset + 3] & 0xff); return String(binary % 1_000_000).padStart(6, "0"); } function verifyAuthAccountMfaCode(secret: string | undefined, code: string | undefined) { const normalizedCode = code?.trim(); if (!secret || !normalizedCode) return false; const now = Date.now(); return [-1, 0, 1].some((step) => { const expected = generateAuthAccountMfaCode(secret, new Date(now + step * 30_000)); return expected === normalizedCode; }); } 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 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; const account = state.authAccounts.find((item) => item.account === session.account); if (account?.status === "disabled") return null; if (new Date(session.expiresAt).getTime() <= Date.now()) 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; const account = state.authAccounts.find((item) => item.account === session.account); if (account?.status === "disabled") { session.revokedAt = nowIso(); 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) => { 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 = [ session, ...state.authSessions.filter((item) => item.sessionId !== session.sessionId), ].slice(0, 20); 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", status: "active", 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.sessionId !== session.sessionId), ].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) { const state = await readState(); const session = activeAuthSession(state, token); return session ? { ...session } : null; } export async function restoreAuthSession(restoreToken?: string | null) { return mutateState((state) => { const session = activeAuthSessionByRestoreToken(state, restoreToken); if (!session) return null; session.lastSeenAt = nowIso(); session.sessionToken = randomBytes(24).toString("hex"); session.restoreToken = randomBytes(24).toString("hex"); 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(); } }); } export async function revokeAuthSessionById(params: { sessionId: string; actorAccount: string; actorRole: AuthRole; }) { return mutateState((state) => { const session = state.authSessions.find((item) => item.sessionId === params.sessionId); if (!session || session.revokedAt) { return null; } const canRevoke = params.actorRole === "highest_admin" || session.account === params.actorAccount; if (!canRevoke) { throw new Error("FORBIDDEN_AUTH_SESSION"); } session.revokedAt = nowIso(); return { ...session }; }); } export async function appendPermissionAuditLog(input: Omit) { return mutateState((state) => { const entry: PermissionAuditLog = normalizePermissionAuditLog({ ...input, auditId: randomToken("audit"), createdAt: nowIso(), }); state.permissionAuditLogs = [entry, ...state.permissionAuditLogs].slice(0, 500); return entry; }); } export async function resolveDialogGuardInterventionDecision(input: { interventionId: string; actorAccount: string; decision: DialogGuardInterventionAction; note?: string; }) { const result = await mutateState((state) => { const intervention = state.dialogGuardInterventions.find( (item) => item.interventionId === input.interventionId, ); if (!intervention) { throw new Error("DIALOG_GUARD_INTERVENTION_NOT_FOUND"); } if (intervention.status !== "pending") { throw new Error("DIALOG_GUARD_INTERVENTION_ALREADY_RESOLVED"); } if (!intervention.availableActions.includes(input.decision)) { throw new Error("DIALOG_GUARD_DECISION_NOT_AVAILABLE"); } const resolvedAt = nowIso(); intervention.status = "resolved"; intervention.decision = input.decision; intervention.decisionBy = input.actorAccount; intervention.decisionNote = trimToDefined(input.note); intervention.resolvedAt = resolvedAt; let taskStatus: MasterAgentTaskStatus | undefined; if (input.decision === "cancel_task") { const task = state.masterAgentTasks.find((item) => item.taskId === intervention.taskId); if (task && task.status === "needs_user_action") { task.status = "canceled"; task.completedAt = resolvedAt; task.canceledAt = resolvedAt; task.canceledBy = input.actorAccount; task.cancelReason = trimToDefined(input.note) ?? "Desktop dialog guard task canceled by user."; task.lastErrorKind = "user_canceled"; task.errorMessage = task.cancelReason; taskStatus = task.status; } } const audit = normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "dialog_guard.intervention_resolved", deviceId: intervention.deviceId, projectId: intervention.projectId, detail: `${input.decision}:${intervention.summary}`, requestId: intervention.requestId, createdAt: resolvedAt, afterJson: { interventionId: intervention.interventionId, dialogId: intervention.dialogId, decision: input.decision, status: intervention.status, }, }); state.permissionAuditLogs = [audit, ...state.permissionAuditLogs].slice(0, 500); return { intervention: { ...intervention }, taskStatus, }; }); publishBossEvent("desktop.dialog_guard.intervention_resolved", { interventionId: result.intervention.interventionId, dialogId: result.intervention.dialogId, taskId: result.intervention.taskId, deviceId: result.intervention.deviceId, projectId: result.intervention.projectId, requestId: result.intervention.requestId, appName: result.intervention.appName, platform: result.intervention.platform, risk: result.intervention.risk, summary: result.intervention.summary, status: result.intervention.status, decision: result.intervention.decision, }); if (result.taskStatus) { publishBossEvent("master_agent.task.updated", { taskId: result.intervention.taskId, deviceId: result.intervention.deviceId, status: result.taskStatus, }); } return result.intervention; } export async function saveAccountDeviceGrant( input: Omit & { grantId?: string; grantedAt?: string; auditMeta?: PermissionAuditMeta; }, ) { return mutateState((state) => { const grant = normalizeAccountDeviceGrant({ ...input, grantId: input.grantId ?? randomToken("grant-device"), grantedAt: input.grantedAt ?? nowIso(), }); state.accountDeviceGrants = [ grant, ...state.accountDeviceGrants.filter((item) => item.grantId !== grant.grantId), ]; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.grantedBy, action: "grant.created", targetAccount: input.account, deviceId: input.deviceId, permissions: grant.permissions, ...input.auditMeta, createdAt: grant.grantedAt, }), ); return grant; }); } export async function saveAccountProjectGrant( input: Omit & { grantId?: string; grantedAt?: string; auditMeta?: PermissionAuditMeta; }, ) { return mutateState((state) => { const grant = normalizeAccountProjectGrant({ ...input, grantId: input.grantId ?? randomToken("grant-project"), grantedAt: input.grantedAt ?? nowIso(), }); state.accountProjectGrants = [ grant, ...state.accountProjectGrants.filter((item) => item.grantId !== grant.grantId), ]; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.grantedBy, action: "grant.created", targetAccount: input.account, deviceId: input.deviceId, projectId: input.projectId, permissions: grant.permissions, ...input.auditMeta, createdAt: grant.grantedAt, }), ); return grant; }); } export async function saveAccountSkillGrant( input: Omit & { grantId?: string; grantedAt?: string; auditMeta?: PermissionAuditMeta; }, ) { return mutateState((state) => { const grant = normalizeAccountSkillGrant({ ...input, grantId: input.grantId ?? randomToken("grant-skill"), grantedAt: input.grantedAt ?? nowIso(), }); state.accountSkillGrants = [ grant, ...state.accountSkillGrants.filter((item) => item.grantId !== grant.grantId), ]; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.grantedBy, action: "skill.assigned", targetAccount: input.account, deviceId: input.deviceId, projectId: input.projectId, skillId: input.skillId, permissions: grant.permissions, ...input.auditMeta, createdAt: grant.grantedAt, }), ); return grant; }); } export async function createSkillLifecycleRequest( input: Omit & { requestId?: string; status?: SkillLifecycleRequestStatus; requestedAt?: string; updatedAt?: string; auditMeta?: PermissionAuditMeta; }, ) { return mutateState((state) => { const requestedAt = input.requestedAt ?? nowIso(); const request = normalizeSkillLifecycleRequest({ ...input, requestId: input.requestId ?? randomToken("skill-request"), status: input.status ?? "pending", requestedAt, updatedAt: input.updatedAt ?? requestedAt, }); state.skillLifecycleRequests = [ request, ...state.skillLifecycleRequests.filter((item) => item.requestId !== request.requestId), ]; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.requestedBy, action: "skill.lifecycle.requested", deviceId: input.deviceId, skillId: input.skillId, detail: `${request.action}${request.sourceUrl ? `:${request.sourceUrl}` : ""}`, ...input.auditMeta, createdAt: request.requestedAt, }), ); return request; }); } export async function claimNextSkillLifecycleRequest(deviceId: string) { const request = await mutateState((state) => { const device = state.devices.find((item) => item.id === deviceId); if (!device || isDeviceRevoked(device)) { return null; } const next = state.skillLifecycleRequests .filter((item) => item.deviceId === deviceId && item.status === "pending") .sort((left, right) => left.requestedAt.localeCompare(right.requestedAt))[0]; if (!next) { return null; } const now = nowIso(); next.status = "running"; next.claimedByDeviceId = deviceId; next.claimedAt = now; next.updatedAt = now; return { ...next }; }); if (request) { publishBossEvent("devices.skills.updated", { deviceId, status: request.status, note: request.requestId, }); } return request; } export async function completeSkillLifecycleRequest(payload: { requestId: string; deviceId: string; status: "completed" | "failed"; resultSummary?: string; error?: string; }) { const request = await mutateState((state) => { const next = state.skillLifecycleRequests.find((item) => item.requestId === payload.requestId); if (!next) { throw new Error("SKILL_LIFECYCLE_REQUEST_NOT_FOUND"); } if (next.deviceId !== payload.deviceId) { throw new Error("SKILL_LIFECYCLE_DEVICE_MISMATCH"); } const now = nowIso(); next.status = payload.status; next.completedAt = now; next.updatedAt = now; next.resultSummary = payload.resultSummary?.trim() || undefined; next.error = payload.error?.trim() || undefined; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: next.requestedBy, action: "skill.lifecycle.completed", deviceId: next.deviceId, skillId: next.skillId, detail: `${next.action}:${next.status}${next.error ? `:${next.error}` : ""}`, createdAt: now, }), ); return { ...next }; }); publishBossEvent("devices.skills.updated", { deviceId: payload.deviceId, status: request.status, note: request.requestId, }); return request; } export async function revokeAccessGrant(grantId: string, actorAccount: string, auditMeta?: PermissionAuditMeta) { return mutateState((state) => { const deviceGrant = state.accountDeviceGrants.find((item) => item.grantId === grantId); const projectGrant = state.accountProjectGrants.find((item) => item.grantId === grantId); const skillGrant = state.accountSkillGrants.find((item) => item.grantId === grantId); const target = deviceGrant ?? projectGrant ?? skillGrant; state.accountDeviceGrants = state.accountDeviceGrants.filter((item) => item.grantId !== grantId); state.accountProjectGrants = state.accountProjectGrants.filter((item) => item.grantId !== grantId); state.accountSkillGrants = state.accountSkillGrants.filter((item) => item.grantId !== grantId); if (target) { state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount, action: skillGrant ? "skill.revoked" : "grant.revoked", targetAccount: target.account, deviceId: "deviceId" in target ? target.deviceId : undefined, projectId: "projectId" in target ? target.projectId : undefined, skillId: "skillId" in target ? target.skillId : undefined, permissions: target.permissions, ...auditMeta, createdAt: nowIso(), }), ); } return target ?? null; }); } 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; apiBaseUrl?: string; apiKey?: string; enabled?: boolean; setActive?: boolean; loginStatusNote?: string; }) { const result = await 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 providerChanged = Boolean(existing && existing.provider !== payload.provider); const defaultModel = aiProviderDefaultModel(payload.provider); 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, apiBaseUrl: isApiKeyProvider(payload.provider) ? normalizeApiBaseUrl(payload.apiBaseUrl) ?? (!providerChanged ? existing?.apiBaseUrl : undefined) ?? aiProviderDefaultApiBaseUrl(payload.provider) : undefined, 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")) { if (!aiAccountCanGenerate(next)) { next.isActive = false; } else { setActiveAiAccountInState(state, next.accountId, existing ? "手动更新 AI 账号配置" : "新增 AI 账号并设为当前主控"); } } else if (next.isActive && !aiAccountCanGenerate(next)) { next.isActive = false; const fallback = sortAiAccounts(state.aiAccounts).find((item) => item.accountId !== next.accountId && aiAccountCanGenerate(item), ); if (fallback) { setActiveAiAccountInState(state, fallback.accountId, `当前主控 ${next.label} 暂不可用,自动切换`); } } return buildAiAccountSummary(next); }); publishBossEvent("ai_accounts.updated"); return result; } export async function deleteAiAccount(accountId: string) { if (accountId === ENV_OPENAI_ACCOUNT_ID) { throw new Error("ENV_AI_ACCOUNT_READ_ONLY"); } const result = await 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; }); publishBossEvent("ai_accounts.updated"); return result; } export async function activateAiAccount(accountId: string, reason: string) { if (accountId === ENV_OPENAI_ACCOUNT_ID) { const state = await readState(); const result = { 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, }, }; publishBossEvent("ai_accounts.updated"); return result; } const result = await mutateState((state) => { const target = state.aiAccounts.find((item) => item.accountId === accountId); if (!target) { throw new Error("AI_ACCOUNT_NOT_FOUND"); } if (!aiAccountCanGenerate(target)) { throw new Error("AI_ACCOUNT_NOT_READY_FOR_ACTIVATION"); } setActiveAiAccountInState(state, accountId, reason); return { activeIdentity: getMasterIdentitySummaryFromState(state), }; }); publishBossEvent("ai_accounts.updated"); return result; } 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 }, ); } }); publishBossEvent("ai_accounts.updated"); } export async function getMasterAgentRuntimeAccount() { const state = await readState(); return resolveMasterAgentRuntimeAccountFromState(state); } export function resolveMasterAgentRuntimeAccountFromState(state: BossState) { 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; sourceMessageId?: string; sourceMessageBody?: string; sourceMessageSentAt?: string; requestedBy: string; requestedByAccount: string; authorizedDeviceIds?: string[]; authorizedProjectIds?: string[]; authorizedSkillIds?: string[]; requiredPermissions?: BossPermission[]; deviceId: string; accountId?: string; accountLabel?: string; attachmentId?: string; attachmentFileName?: string; attachmentDownloadToken?: string; attachmentDownloadExpiresAt?: string; attachmentDownloadUrl?: string; attachmentTextExcerpt?: string; deviceImportDraftId?: string; dispatchExecutionId?: string; sourceProjectId?: string; sourceThreadId?: string; sourceThreadDisplayName?: string; sourceCodexThreadRef?: string; targetProjectId?: string; targetThreadId?: string; targetThreadDisplayName?: string; targetCodexThreadRef?: string; targetTurnId?: string; targetCodexTurnId?: string; targetCodexFolderRef?: string; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: string; deviceImportCandidateId?: string; deviceImportCandidateFolderName?: string; maintenanceKind?: DeviceMaintenanceKind; codexRemoteControlAction?: CodexRemoteControlAction; projectUnderstandingTargetProjectId?: string; projectUnderstandingReason?: "heartbeat_activity" | "thread_reply"; projectUnderstandingReplyProjectId?: string; projectUnderstandingNotifyOnCompletion?: boolean; relayViaMasterAgent?: boolean; mirrorBossUserMessageToCodexDesktop?: boolean; rollbackNumTurns?: number; rollbackReason?: string; compactReason?: string; threadLifecycleAction?: "archive" | "unarchive"; threadLifecycleReason?: string; threadRenameName?: string; threadRenameReason?: string; threadGoalObjective?: string; threadGoalStatus?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete"; threadGoalTokenBudget?: number; threadGoalReason?: string; threadMetadataGitInfo?: ThreadMetadataGitInfoPatch; threadMetadataReason?: string; threadForkEphemeral?: boolean; threadForkReason?: string; intentCategory?: ComputerControlIntentCategory; runtimeKind?: ComputerControlRuntimeKind; controlPlatform?: ComputerControlPlatform; computerUseProvider?: ComputerUseProvider; riskLevel?: ComputerControlRiskLevel; confirmationPolicy?: ComputerControlConfirmationPolicy; requiresUserConfirmation?: boolean; confirmationScopeKey?: string; externalReplyTarget?: ExternalReplyTarget; }) { 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, sourceMessageId: payload.sourceMessageId, sourceMessageBody: payload.sourceMessageBody, sourceMessageSentAt: payload.sourceMessageSentAt, requestedBy: payload.requestedBy, requestedByAccount: payload.requestedByAccount, authorizedDeviceIds: payload.authorizedDeviceIds, authorizedProjectIds: payload.authorizedProjectIds, authorizedSkillIds: payload.authorizedSkillIds, requiredPermissions: payload.requiredPermissions, 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, sourceProjectId: payload.sourceProjectId, sourceThreadId: payload.sourceThreadId, sourceThreadDisplayName: payload.sourceThreadDisplayName, sourceCodexThreadRef: payload.sourceCodexThreadRef, targetProjectId: payload.targetProjectId, targetThreadId: payload.targetThreadId, targetThreadDisplayName: payload.targetThreadDisplayName, targetCodexThreadRef: payload.targetCodexThreadRef, targetTurnId: payload.targetTurnId, targetCodexTurnId: payload.targetCodexTurnId, targetCodexFolderRef: payload.targetCodexFolderRef, orchestrationBackendId: payload.orchestrationBackendId, orchestrationBackendLabel: payload.orchestrationBackendLabel, deviceImportCandidateId: payload.deviceImportCandidateId, deviceImportCandidateFolderName: payload.deviceImportCandidateFolderName, maintenanceKind: payload.maintenanceKind === "codex_remote_control" ? payload.maintenanceKind : undefined, codexRemoteControlAction: payload.codexRemoteControlAction === "start" || payload.codexRemoteControlAction === "stop" ? payload.codexRemoteControlAction : undefined, projectUnderstandingTargetProjectId: payload.projectUnderstandingTargetProjectId, projectUnderstandingReason: payload.projectUnderstandingReason, projectUnderstandingReplyProjectId: payload.projectUnderstandingReplyProjectId, projectUnderstandingNotifyOnCompletion: payload.projectUnderstandingNotifyOnCompletion === true ? true : undefined, relayViaMasterAgent: payload.relayViaMasterAgent === true ? true : undefined, mirrorBossUserMessageToCodexDesktop: payload.mirrorBossUserMessageToCodexDesktop === true ? true : undefined, rollbackNumTurns: Number.isFinite(Number(payload.rollbackNumTurns)) && Number(payload.rollbackNumTurns) >= 1 ? Math.floor(Number(payload.rollbackNumTurns)) : undefined, rollbackReason: trimToDefined(payload.rollbackReason), compactReason: trimToDefined(payload.compactReason), threadLifecycleAction: payload.threadLifecycleAction === "archive" || payload.threadLifecycleAction === "unarchive" ? payload.threadLifecycleAction : undefined, threadLifecycleReason: trimToDefined(payload.threadLifecycleReason), threadRenameName: trimToDefined(payload.threadRenameName), threadRenameReason: trimToDefined(payload.threadRenameReason), threadGoalObjective: trimToDefined(payload.threadGoalObjective), threadGoalStatus: payload.threadGoalStatus === "active" || payload.threadGoalStatus === "paused" || payload.threadGoalStatus === "blocked" || payload.threadGoalStatus === "usageLimited" || payload.threadGoalStatus === "budgetLimited" || payload.threadGoalStatus === "complete" ? payload.threadGoalStatus : undefined, threadGoalTokenBudget: Number.isFinite(Number(payload.threadGoalTokenBudget)) && Number(payload.threadGoalTokenBudget) > 0 ? Math.floor(Number(payload.threadGoalTokenBudget)) : undefined, threadGoalReason: trimToDefined(payload.threadGoalReason), threadMetadataGitInfo: normalizeThreadMetadataGitInfoPatch(payload.threadMetadataGitInfo), threadMetadataReason: trimToDefined(payload.threadMetadataReason), threadForkEphemeral: payload.threadForkEphemeral === true, threadForkReason: trimToDefined(payload.threadForkReason), intentCategory: payload.intentCategory, runtimeKind: payload.runtimeKind, controlPlatform: payload.controlPlatform, computerUseProvider: payload.computerUseProvider, riskLevel: payload.riskLevel, confirmationPolicy: payload.confirmationPolicy, requiresUserConfirmation: payload.requiresUserConfirmation === true ? true : undefined, confirmationScopeKey: payload.confirmationScopeKey, externalReplyTarget: payload.externalReplyTarget, status: "queued", requestedAt: nowIso(), attemptCount: 0, maxAttempts: defaultMasterAgentTaskMaxAttempts(payload.taskType ?? "conversation_reply"), }; state.masterAgentTasks.unshift(task); upsertTaskExecutionProgressMessageInState(state, task, "queued"); return task; }); publishBossEvent("master_agent.task.updated", { taskId: task.taskId, deviceId: task.deviceId, status: task.status, }); const progressProjectId = resolveTaskExecutionProgressProjectId(task); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } return task; } export async function queueCodexRemoteControlTask(input: { deviceId: string; action: CodexRemoteControlAction; requestedBy: string; requestedByAccount: string; reason?: string; auditMeta?: PermissionAuditMeta; }) { if (input.action !== "start" && input.action !== "stop") { throw new Error("CODEX_REMOTE_CONTROL_ACTION_INVALID"); } const actionLabel = input.action === "start" ? "启动" : "停止"; const reason = trimToDefined(input.reason) ?? `${actionLabel} Codex Remote Control。`; const task = await queueMasterAgentTask({ projectId: "master-agent", taskType: "device_maintenance", requestMessageId: randomToken("msg-maintenance"), requestText: reason, executionPrompt: `设备维护:${actionLabel} Codex Remote Control。`, requestedBy: input.requestedBy, requestedByAccount: input.requestedByAccount, authorizedDeviceIds: [input.deviceId], requiredPermissions: ["computer.control"], deviceId: input.deviceId, maintenanceKind: "codex_remote_control", codexRemoteControlAction: input.action, runtimeKind: "codex-thread-runtime", confirmationPolicy: "strong_confirm", requiresUserConfirmation: true, confirmationScopeKey: `device:${input.deviceId}:codex_remote_control`, }); await appendPermissionAuditLog({ actorAccount: input.requestedByAccount, action: "task.authorized", deviceId: input.deviceId, permissions: ["computer.control"], detail: `codex_remote_control:${input.action}`, ipAddress: input.auditMeta?.ipAddress, userAgent: input.auditMeta?.userAgent, requestId: input.auditMeta?.requestId, afterJson: { taskId: task.taskId, maintenanceKind: "codex_remote_control", codexRemoteControlAction: input.action, }, }); return task; } export async function createDispatchPlan(input: { groupProjectId: string; requestMessageId: string; requestedBy: string; summary?: string; targets: DispatchPlanTarget[]; requestedOrchestrationBackendId?: OrchestrationBackendOverride; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: string; orchestrationFallbackReason?: string; }) { return mutateState((state) => { return upsertDispatchPlanInState(state, input); }); } function upsertDispatchPlanInState( state: BossState, input: { groupProjectId: string; requestMessageId: string; requestedBy: string; summary?: string; targets: DispatchPlanTarget[]; requestedOrchestrationBackendId?: OrchestrationBackendOverride; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: string; orchestrationFallbackReason?: string; }, ) { const groupProjectId = input.groupProjectId.trim(); const requestMessageId = input.requestMessageId.trim(); const requestedBy = input.requestedBy.trim(); const summary = input.summary?.trim() ?? ""; const requestedOrchestrationBackendId = input.requestedOrchestrationBackendId; const orchestrationBackendId = input.orchestrationBackendId ?? "boss-native-orchestrator"; const orchestrationBackendLabel = trimToDefined(input.orchestrationBackendLabel) ?? (orchestrationBackendId === "omx-team" ? "OMX Team Runtime" : "Boss Native Orchestrator"); const orchestrationFallbackReason = trimToDefined(input.orchestrationFallbackReason); 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 (!canOwnDispatchPlans(groupProject)) 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) && existing.requestedOrchestrationBackendId === requestedOrchestrationBackendId && existing.orchestrationBackendId === orchestrationBackendId && existing.orchestrationBackendLabel === orchestrationBackendLabel && existing.orchestrationFallbackReason === orchestrationFallbackReason; 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, requestedOrchestrationBackendId, orchestrationBackendId, orchestrationBackendLabel, orchestrationFallbackReason, 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 canOwnDispatchPlans(project: Project) { return project.isGroup || project.id === "master-agent"; } 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 (!canOwnDispatchPlans(groupProject)) 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"); } for (const execution of existingExecutions) { execution.orchestrationBackendId = execution.orchestrationBackendId ?? plan.orchestrationBackendId; execution.orchestrationBackendLabel = execution.orchestrationBackendLabel ?? plan.orchestrationBackendLabel; } 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, orchestrationBackendId: plan.orchestrationBackendId, orchestrationBackendLabel: plan.orchestrationBackendLabel, 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 代码块,不要输出额外解释。", `群聊名称:${input.groupProject.name}`, `目标线程:${input.target.threadDisplayName}`, "当前群聊请求:", requestText.trim(), "主 Agent 推荐摘要:", input.plan.summary.trim(), ].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.orchestrationBackendId = existing.orchestrationBackendId ?? execution.orchestrationBackendId; existing.orchestrationBackendLabel = existing.orchestrationBackendLabel ?? execution.orchestrationBackendLabel; 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, orchestrationBackendId: execution.orchestrationBackendId, orchestrationBackendLabel: execution.orchestrationBackendLabel, status: "queued", requestedAt: nowIso(), attemptCount: 0, maxAttempts: defaultMasterAgentTaskMaxAttempts("dispatch_execution"), }; state.masterAgentTasks.unshift(task); upsertTaskExecutionProgressMessageInState(state, task, "queued"); return task; } function ensureDispatchExecutionTasksInState( state: BossState, plan: DispatchPlan, executions: DispatchExecution[], ) { return executions.map((execution) => ensureDispatchExecutionTaskInState(state, plan, execution)); } function validateDispatchExecutionTarget( state: BossState, target: DispatchPlanTarget, ) { const project = state.projects.find((item) => item.id === target.projectId); if (!project || project.isGroup) { throw new Error("DISPATCH_TARGET_PROJECT_NOT_FOUND"); } if (!project.threadMeta.codexThreadRef?.trim()) { throw new Error("DISPATCH_TARGET_THREAD_BINDING_REQUIRED"); } const device = state.devices.find((item) => item.id === target.deviceId); if (!device || device.status !== "online") { throw new Error("DISPATCH_TARGET_DEVICE_OFFLINE"); } } export async function confirmDispatchPlanAndCreateExecutions(input: { groupProjectId: string; planId: string; confirmedBy: string; approvedTargetProjectIds: string[]; rememberLightReminder?: boolean; }) { 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 (!canOwnDispatchPlans(groupProject)) throw new Error("PROJECT_NOT_GROUP_CHAT"); if (input.rememberLightReminder && !groupProject.lightDispatchReminderEnabled) { groupProject.lightDispatchReminderEnabled = true; groupProject.updatedAt = nowIso(); groupProject.threadMeta.updatedAt = groupProject.updatedAt; } 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"); } for (const execution of existingExecutions) { execution.orchestrationBackendId = execution.orchestrationBackendId ?? plan.orchestrationBackendId; execution.orchestrationBackendLabel = execution.orchestrationBackendLabel ?? plan.orchestrationBackendLabel; } 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"); } targets.forEach((target) => validateDispatchExecutionTarget(state, target)); 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, orchestrationBackendId: plan.orchestrationBackendId, orchestrationBackendLabel: plan.orchestrationBackendLabel, 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; failureReason?: 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 targetProject = state.projects.find((item) => item.id === payload.targetProjectId); const threadTitle = payload.targetThreadDisplayName?.trim() || targetProject?.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 mirroredThreadResult: 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", }); mirroredThreadResult = pushProjectLedgerMessage(state, payload.targetProjectId, { 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: payload.failureReason?.trim() ? `${threadTitle} 执行失败:${buildFriendlyThreadExecutionError(payload.failureReason)}` : `${threadTitle} 执行失败,请稍后重试。`, kind: "text", }); } execution.status = payload.status; execution.completedAt = nowIso(); execution.completedByDeviceId = payload.completedByDeviceId; execution.resultMessageId = mirroredResult?.id ?? execution.resultMessageId; return { execution: { ...execution }, mirroredResult, mirroredThreadResult, 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 }); publishBossEvent("project.messages.updated", { projectId: payload.targetProjectId }); publishBossEvent("conversation.updated", { projectId: payload.targetProjectId }); 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; } function isCliWriteTask(task: MasterAgentTask) { if (task.taskType === "dispatch_execution") { return true; } if (task.taskType !== "conversation_reply") { return false; } if (task.projectId === "master-agent") { return false; } return true; } function canRunWriteTaskThroughGui(device: Device | undefined, task: MasterAgentTask) { if (!device || device.status !== "online" || isDeviceRevoked(device)) { return false; } const appServerHealth = resolveCodexAppServerHealth(device); const capabilities = device.capabilities; const hasGuiExecutionChannel = Boolean( appServerHealth !== "unavailable" || capabilities?.gui?.connected, ); if (!hasGuiExecutionChannel) { return false; } return Boolean( task.targetCodexThreadRef || task.targetThreadId || task.targetCodexFolderRef || task.targetProjectId, ); } export type CodexAppServerHealth = "available" | "degraded" | "unavailable"; export function resolveCodexAppServerHealth(device: Device | undefined): CodexAppServerHealth { if (!device || device.status !== "online" || isDeviceRevoked(device)) { return "unavailable"; } const capability = device.capabilities?.codexAppServer; if (!capability?.connected) { return "unavailable"; } const lastSeenAtMs = Date.parse(capability.lastSeenAt ?? device.lastSeenAt ?? ""); if (Number.isFinite(lastSeenAtMs) && Date.now() - lastSeenAtMs > 2 * 60 * 1000) { return "degraded"; } const metadata = capability.metadata; const errors = metadata && typeof metadata === "object" && Array.isArray((metadata as { errors?: unknown }).errors) ? ((metadata as { errors?: unknown[] }).errors ?? []) : []; const driftLevel = metadata && typeof metadata === "object" && typeof (metadata as { protocolDriftSummary?: { driftLevel?: unknown } }).protocolDriftSummary?.driftLevel === "string" ? (metadata as { protocolDriftSummary?: { driftLevel?: string } }).protocolDriftSummary?.driftLevel : undefined; if (errors.length > 0 || driftLevel === "warning" || driftLevel === "error") { return "degraded"; } return "available"; } function resolveProjectConflictScopeForTask( state: BossState, task: MasterAgentTask, ): { project?: Project; projectId: string; folderKey?: string; deviceId: string } | null { const projectId = task.targetProjectId ?? task.projectId; const project = state.projects.find((item) => item.id === projectId); if (project?.isGroup) { return null; } const folderRef = trimToDefined(task.targetCodexFolderRef) ?? trimToDefined(project?.threadMeta.codexFolderRef ?? project?.threadMeta.folderName); const folderKey = project ? buildProjectFolderKey(project) : task.deviceId && folderRef ? `${task.deviceId}:${folderRef.toLowerCase()}` : undefined; if (!project && !folderKey) { return null; } return { project, projectId: project?.id ?? projectId, folderKey, deviceId: task.deviceId, }; } const STALE_RUNNING_CONVERSATION_REPLY_MS = 15 * 60 * 1000; const STALE_QUEUED_CONVERSATION_REPLY_MS = 60 * 60 * 1000; const MASTER_AGENT_TASK_DEFAULT_LEASE_MS = 30 * 60 * 1000; const MASTER_AGENT_TASK_CONVERSATION_LEASE_MS = STALE_RUNNING_CONVERSATION_REPLY_MS; const MASTER_AGENT_TASK_DEFAULT_MAX_ATTEMPTS = 3; function defaultMasterAgentTaskMaxAttempts(taskType: MasterAgentTaskType) { return taskType === "conversation_reply" ? 2 : MASTER_AGENT_TASK_DEFAULT_MAX_ATTEMPTS; } function isTerminalMasterAgentTaskStatus(status: MasterAgentTaskStatus) { return status === "completed" || status === "failed" || status === "timed_out" || status === "canceled"; } function isMasterAgentTaskPhase(value: unknown): value is MasterAgentTaskPhase { return ( value === "queued" || value === "claimed" || value === "executor_starting" || value === "turn_started" || value === "awaiting_reply" || value === "completing" || value === "completed" || value === "recoverable_failed" || value === "terminal_failed" || value === "timed_out" || value === "canceled" || value === "needs_user_action" ); } function defaultMasterAgentTaskPhase(status: MasterAgentTaskStatus): MasterAgentTaskPhase { switch (status) { case "queued": return "queued"; case "running": return "claimed"; case "needs_user_action": return "needs_user_action"; case "completed": return "completed"; case "timed_out": return "timed_out"; case "canceled": return "canceled"; case "failed": return "terminal_failed"; default: return "queued"; } } function normalizeMasterAgentTaskPhase( phase: unknown, status: MasterAgentTaskStatus, ): MasterAgentTaskPhase { return isMasterAgentTaskPhase(phase) ? phase : defaultMasterAgentTaskPhase(status); } function phaseToExecutionProgressStatus( phase: MasterAgentTaskPhase, fallback: ExecutionProgressStatus, ): ExecutionProgressStatus { if (phase === "completed") return "completed"; if ( phase === "recoverable_failed" || phase === "terminal_failed" || phase === "timed_out" || phase === "canceled" ) { return "failed"; } if (phase === "queued") return "queued"; return fallback === "completed" || fallback === "failed" ? fallback : "running"; } function phaseErrorCode(phase: MasterAgentTaskPhase) { if (phase === "recoverable_failed") return "RECOVERABLE_TASK_FAILURE"; if (phase === "terminal_failed") return "TERMINAL_TASK_FAILURE"; if (phase === "timed_out") return "TASK_TIMED_OUT"; if (phase === "canceled") return "TASK_CANCELED"; return undefined; } function codexModelChannelAccountSummarySignedIn(device: Device) { const metadata = device.capabilities?.codexAppServer?.metadata; const accountSummary = metadata && typeof metadata === "object" ? (metadata.accountSummary as { signedIn?: unknown } | undefined) : undefined; return accountSummary?.signedIn !== false; } function hasUsableCodexModelChannelInState(device: Device | undefined) { if (!device || device.status !== "online" || isDeviceRevoked(device)) { return false; } const capabilities = device.capabilities; const hasCodexTransport = Boolean( resolveCodexAppServerHealth(device) !== "unavailable" || capabilities?.cli?.connected || capabilities?.gui?.connected, ); return hasCodexTransport && codexModelChannelAccountSummarySignedIn(device); } function isMasterCodexNodeTask(task: MasterAgentTask, state: BossState) { if (task.taskType !== "conversation_reply") { return false; } if (task.accountId?.startsWith("master-codex-device-")) { return true; } const account = task.accountId ? state.aiAccounts.find((item) => item.accountId === task.accountId) : undefined; return account?.provider === "master_codex_node"; } function isTaskAuthorizedForDevice(task: MasterAgentTask, deviceId: string) { return ( !task.authorizedDeviceIds || task.authorizedDeviceIds.length === 0 || task.authorizedDeviceIds.includes(deviceId) ); } function sortMasterCodexFailoverDevices(left: Device, right: Device) { return (right.lastSeenAt ?? "").localeCompare(left.lastSeenAt ?? ""); } function findNextMasterCodexNodeCandidateForFailedTask( state: BossState, task: MasterAgentTask, failedDeviceId: string, ) { const excludedDeviceIds = new Set([failedDeviceId, ...(task.modelChannelAttemptedDeviceIds ?? [])]); const seenDeviceIds = new Set(); const configuredCandidates = state.aiAccounts .filter((account) => { const deviceId = account.nodeId?.trim(); if ( !account.enabled || account.provider !== "master_codex_node" || (account.status !== "ready" && account.status !== "degraded") || !deviceId || excludedDeviceIds.has(deviceId) || !isTaskAuthorizedForDevice(task, deviceId) ) { return false; } const device = state.devices.find((item) => item.id === deviceId); return hasUsableCodexModelChannelInState(device); }) .sort((left, right) => { if (left.isActive !== right.isActive) { return left.isActive ? -1 : 1; } return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? ""); }); const configured = configuredCandidates[0]; if (configured?.nodeId) { return { deviceId: configured.nodeId, accountId: configured.accountId, accountLabel: configured.label, }; } for (const account of configuredCandidates) { if (account.nodeId) { seenDeviceIds.add(account.nodeId); } } const device = state.devices .filter((item) => { if ( excludedDeviceIds.has(item.id) || seenDeviceIds.has(item.id) || !isTaskAuthorizedForDevice(task, item.id) ) { return false; } return hasUsableCodexModelChannelInState(item); }) .sort(sortMasterCodexFailoverDevices)[0]; if (!device) { return null; } return { deviceId: device.id, accountId: `master-codex-device-${device.id}`, accountLabel: `Codex · ${device.name || device.id}`, }; } function masterAgentTaskLeaseMs(task: MasterAgentTask) { return task.taskType === "conversation_reply" ? MASTER_AGENT_TASK_CONVERSATION_LEASE_MS : MASTER_AGENT_TASK_DEFAULT_LEASE_MS; } function taskLeaseExpired(task: MasterAgentTask, nowMs = Date.now()) { if (task.status !== "running") { return false; } const leaseExpiresAtMs = Date.parse(task.leaseExpiresAt ?? ""); if (Number.isFinite(leaseExpiresAtMs)) { return leaseExpiresAtMs <= nowMs; } const claimedAtMs = Date.parse(task.claimedAt ?? ""); if (!Number.isFinite(claimedAtMs)) { return true; } return nowMs - claimedAtMs >= masterAgentTaskLeaseMs(task); } function taskCanRetryAfterExpiredLease(task: MasterAgentTask) { if ( task.phase === "turn_started" || task.phase === "awaiting_reply" || task.phase === "completing" ) { return false; } const maxAttempts = task.maxAttempts ?? defaultMasterAgentTaskMaxAttempts(task.taskType); return (task.attemptCount ?? 0) < maxAttempts; } function requeueRecoverableMasterAgentTaskInState(task: MasterAgentTask, now: string, reason: string) { task.status = "queued"; task.phase = "recoverable_failed"; task.leaseExpiresAt = undefined; task.claimedAt = undefined; task.lastClaimedAt = task.lastClaimedAt ?? now; task.lastProgressAt = now; task.lastErrorKind = "recoverable_lease_timeout"; task.lastErrorCode = "RECOVERABLE_LEASE_TIMEOUT"; task.recoverable = true; task.nextRetryAt = now; task.errorMessage = reason; } function isRecoverableRuntimeFailureMessage(message?: string) { const normalized = message?.trim().toUpperCase() ?? ""; if (!normalized) { return false; } return ( normalized.includes("CODEX_APP_SERVER_TURN_INTERRUPTED") || normalized.includes("CODEX_APP_SERVER_TIMEOUT") || normalized.includes("CODEX_APP_SERVER_STDIN_CLOSED") || normalized.includes("CODEX_APP_SERVER_EXITED") || normalized.includes("UND_ERR_SOCKET") || normalized.includes("ECONNRESET") || normalized.includes("ETIMEDOUT") || normalized.includes("FETCH FAILED") ); } function canAutoRetryRecoverableRuntimeFailure(task: MasterAgentTask, errorMessage?: string) { if (task.taskType !== "conversation_reply") { return false; } if (!isRecoverableRuntimeFailureMessage(errorMessage)) { return false; } const maxAttempts = task.maxAttempts ?? defaultMasterAgentTaskMaxAttempts(task.taskType); return (task.attemptCount ?? 0) < maxAttempts; } function requeueRecoverableRuntimeFailureInState( task: MasterAgentTask, now: string, errorMessage?: string, ) { task.status = "queued"; task.phase = "recoverable_failed"; task.leaseExpiresAt = undefined; task.claimedAt = undefined; task.lastProgressAt = now; task.completedAt = undefined; task.canceledAt = undefined; task.canceledBy = undefined; task.cancelReason = undefined; task.lastErrorKind = "recoverable_runtime_failure"; task.lastErrorCode = "RECOVERABLE_RUNTIME_FAILURE"; task.recoverable = true; task.nextRetryAt = now; task.errorMessage = errorMessage?.trim() || "RECOVERABLE_RUNTIME_FAILURE"; task.replyBody = undefined; task.requestId = undefined; } function expireMasterAgentTaskInState(task: MasterAgentTask, now: string, reason: string) { task.status = "timed_out"; task.phase = "timed_out"; task.completedAt = now; task.leaseExpiresAt = undefined; task.lastErrorKind = "lease_timeout"; task.lastErrorCode = "TASK_TIMED_OUT"; task.recoverable = false; task.errorMessage = reason; } function isStaleRunningConversationReplyTask(task: MasterAgentTask, nowMs = Date.now()) { if (task.taskType !== "conversation_reply" || task.status !== "running") { return false; } const lastActivityMs = Date.parse(task.lastProgressAt ?? task.claimedAt ?? ""); if (!Number.isFinite(lastActivityMs)) { return true; } return nowMs - lastActivityMs >= STALE_RUNNING_CONVERSATION_REPLY_MS; } function isStaleQueuedConversationReplyTask(task: MasterAgentTask, nowMs = Date.now()) { if (task.taskType !== "conversation_reply" || task.status !== "queued") { return false; } const requestedAtMs = Date.parse(task.requestedAt ?? ""); if (!Number.isFinite(requestedAtMs)) { return false; } return nowMs - requestedAtMs >= STALE_QUEUED_CONVERSATION_REPLY_MS; } function buildControlTaskCompletionUserSummary(task: MasterAgentTask) { const controlLabel = task.taskType === "browser_control" ? "浏览器控制" : "桌面控制"; const target = task.targetUrl?.trim() || task.targetApp?.trim(); const requestText = task.requestText?.trim(); const lines = [`任务小结:${controlLabel}已完成。`]; if (requestText) { lines.push(`执行内容:${requestText}`); } if (target) { lines.push(`执行结果:已定位到 ${target}`); } else if (task.replyBody?.trim()) { lines.push(`执行结果:${task.replyBody.trim()}`); } return lines.join(";"); } function hasRecentProjectReplyDuplicate( project: Project | undefined, input: { body?: string; senderLabel?: string; at?: string; windowMs?: number }, ) { const body = input.body?.trim(); const senderLabel = input.senderLabel?.trim(); if (!project || !body || !senderLabel) { return false; } const atMs = Date.parse(input.at ?? ""); const referenceMs = Number.isFinite(atMs) ? atMs : Date.now(); const windowMs = input.windowMs ?? 5 * 60 * 1000; return project.messages.some((message) => { if (message.kind === "execution_progress") { return false; } if (message.body.trim() !== body || message.senderLabel.trim() !== senderLabel) { return false; } const sentAtMs = Date.parse(message.sentAt); if (!Number.isFinite(sentAtMs)) { return false; } return Math.abs(referenceMs - sentAtMs) <= windowMs; }); } function conversationReplyDuplicateWindowMs(task: Pick) { const requestedAtMs = messageTimeValue(task.requestedAt); const completedAtMs = messageTimeValue(task.completedAt); if (!requestedAtMs || !completedAtMs) { return 5 * 60 * 1000; } return Math.max(5 * 60 * 1000, Math.abs(completedAtMs - requestedAtMs) + 60 * 1000); } function stripThreadProcessPrefix(body: string, prefix: string) { const candidate = body.trimStart(); const trimmedPrefix = prefix.trim(); if (!candidate || !trimmedPrefix) { return body; } if (!candidate.startsWith(trimmedPrefix)) { return body; } return candidate.slice(trimmedPrefix.length).trimStart(); } function stripAlreadyMirroredThreadProcessPrefix( project: Project | undefined, input: { body?: string; senderLabel?: string; requestedAt?: string; completedAt?: string; }, ) { const originalBody = input.body?.trim() ?? ""; if (!project || !originalBody) { return originalBody; } const senderLabel = input.senderLabel?.trim(); const requestedAtMs = messageTimeValue(input.requestedAt); const completedAtMs = messageTimeValue(input.completedAt); const lowerBoundMs = requestedAtMs ? requestedAtMs - 30 * 1000 : completedAtMs ? completedAtMs - 30 * 60 * 1000 : 0; const upperBoundMs = completedAtMs ? completedAtMs + 60 * 1000 : Date.now() + 60 * 1000; const processMessages = project.messages .filter((message) => { if (!isThreadProcessMessageKind(message.kind)) { return false; } if (senderLabel && message.senderLabel.trim() !== senderLabel) { return false; } const sentAtMs = messageTimeValue(message.sentAt); if (!sentAtMs) { return false; } return sentAtMs >= lowerBoundMs && sentAtMs <= upperBoundMs; }) .sort((a, b) => messageTimeValue(a.sentAt) - messageTimeValue(b.sentAt)); let remainingBody = originalBody; let strippedAny = false; for (const message of processMessages) { const nextBody = stripThreadProcessPrefix(remainingBody, message.body); if (nextBody !== remainingBody) { remainingBody = nextBody; strippedAny = true; } } return strippedAny ? remainingBody.trim() : originalBody; } async function sweepExpiredMasterAgentTasksForDevice(deviceId: string, nowMs = Date.now()) { const expired: MasterAgentTask[] = []; await mutateStateIfChanged(async (state) => { let changed = false; const now = new Date(nowMs).toISOString(); for (const task of state.masterAgentTasks) { const staleQueued = isStaleQueuedConversationReplyTask(task, nowMs); const expiredRunning = taskLeaseExpired(task, nowMs); if (task.deviceId !== deviceId || (!expiredRunning && !staleQueued)) { continue; } if (expiredRunning && taskCanRetryAfterExpiredLease(task)) { requeueRecoverableMasterAgentTaskInState( task, now, "Master Agent task lease expired before Codex turn started; queued for safe retry.", ); upsertTaskExecutionProgressMessageInState(state, task, "failed", { phase: task.phase }); expired.push({ ...task }); changed = true; continue; } expireMasterAgentTaskInState( task, now, staleQueued ? "Master Agent task expired before being claimed." : `Master Agent task timed out after ${task.attemptCount ?? 0} attempts.`, ); upsertTaskExecutionProgressMessageInState(state, task, "failed", { phase: task.phase }); expired.push({ ...task }); changed = true; } return { result: expired, changed }; }); for (const task of expired) { publishBossEvent("master_agent.task.updated", { taskId: task.taskId, deviceId: task.deviceId, status: task.status, }); const progressProjectId = resolveTaskExecutionProgressProjectId(task); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } } return expired; } export async function claimNextMasterAgentTask(deviceId: string) { const nowMs = Date.now(); await sweepExpiredMasterAgentTasksForDevice(deviceId, nowMs); const snapshot = await readState(); const queued = snapshot.masterAgentTasks.find( (item) => item.deviceId === deviceId && item.status === "queued", ); const staleRunning = snapshot.masterAgentTasks.find( (item) => item.deviceId === deviceId && item.status === "running" && (taskLeaseExpired(item, nowMs) || isStaleRunningConversationReplyTask(item, nowMs)) && taskCanRetryAfterExpiredLease(item), ); const claimable = queued ?? staleRunning; if (!claimable) { return null; } const device = snapshot.devices.find((item) => item.id === deviceId); if (!device || isDeviceRevoked(device)) { return null; } const claimableViaGui = canRunWriteTaskThroughGui(device, claimable); if (device?.preferredExecutionMode === "gui" && isCliWriteTask(claimable) && !claimableViaGui) { return null; } if (isCliWriteTask(claimable)) { const scope = resolveProjectConflictScopeForTask(snapshot, claimable); const externalActivityAt = scope?.project?.threadMeta.lastObservedCodexActivityAt; const claimActivityAt = nowIso(); let conflictActivityAt = externalActivityAt; if (scope) { const existingPolicy = findProjectExecutionPolicyInState(snapshot, scope); const fallbackPolicy = existingPolicy ?? snapshot.projectExecutionPolicies.find( (policy) => policy.deviceId === deviceId && policy.projectId === scope.projectId, ); const policyActivityAt = fallbackPolicy?.recentExternalActivityAt; conflictActivityAt = policyActivityAt ?? externalActivityAt; if ( fallbackPolicy?.conflictState === "blocked" && fallbackPolicy.allowPolicy === "forbid" && (!policyActivityAt || hasRecentThreadConversationExternalActivity({ activityAt: claimActivityAt, externalActivityAt: policyActivityAt, })) ) { return null; } } if (scope && conflictActivityAt) { const conflict = await detectProjectExecutionConflict({ deviceId, folderKey: scope.folderKey, projectId: scope.projectId, executionMode: "cli", activityAt: claimActivityAt, externalActivityAt: conflictActivityAt, }); if (conflict.blocked) { return null; } } } let attachmentProjectId: string | undefined; let dispatchExecutionProjectId: string | undefined; const task = await mutateState((state) => { const next = state.masterAgentTasks.find((item) => { if (item.taskId !== claimable.taskId || item.deviceId !== deviceId) { return false; } return ( item.status === "queued" || ( item.status === "running" && (taskLeaseExpired(item) || isStaleRunningConversationReplyTask(item)) && taskCanRetryAfterExpiredLease(item) ) ); }); if (!next) return null; const previousClaimedAt = next.claimedAt; const claimedAt = nowIso(); next.status = "running"; next.phase = "claimed"; next.lastClaimedAt = previousClaimedAt; next.claimedAt = claimedAt; next.lastProgressAt = claimedAt; next.attemptCount = (next.attemptCount ?? 0) + 1; next.maxAttempts = next.maxAttempts ?? defaultMasterAgentTaskMaxAttempts(next.taskType); next.leaseExpiresAt = new Date(Date.now() + masterAgentTaskLeaseMs(next)).toISOString(); next.completedAt = undefined; next.canceledAt = undefined; next.canceledBy = undefined; next.cancelReason = undefined; next.lastErrorKind = undefined; next.lastErrorCode = undefined; next.recoverable = false; next.nextRetryAt = undefined; next.errorMessage = undefined; 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; } } if (isCliWriteTask(next)) { const scope = resolveProjectConflictScopeForTask(state, next); if (scope) { const existing = findProjectExecutionPolicyInState(state, scope); upsertProjectExecutionPolicyInState(state, { ...existing, ...scope, allowPolicy: existing?.allowPolicy ?? "forbid", conflictState: existing?.conflictState ?? "none", activeCliExecution: true, updatedAt: nowIso(), }); } } upsertTaskExecutionProgressMessageInState(state, next, "running"); 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 }); } const progressProjectId = resolveTaskExecutionProgressProjectId(task); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } } return task; } export async function cancelMasterAgentTask(input: { taskId: string; actorAccount: string; reason?: string; }) { const result = await mutateState((state) => { const task = state.masterAgentTasks.find((item) => item.taskId === input.taskId); if (!task) { throw new Error("MASTER_AGENT_TASK_NOT_FOUND"); } if (isTerminalMasterAgentTaskStatus(task.status)) { return { ...task }; } const now = nowIso(); task.status = "canceled"; task.phase = "canceled"; task.completedAt = now; task.canceledAt = now; task.canceledBy = input.actorAccount; task.cancelReason = input.reason?.trim() || undefined; task.leaseExpiresAt = undefined; task.lastErrorKind = "user_canceled"; task.lastErrorCode = "TASK_CANCELED"; task.recoverable = false; task.errorMessage = task.cancelReason ?? "Task canceled by user."; if (task.dispatchExecutionId) { const execution = state.dispatchExecutions.find( (item) => item.executionId === task.dispatchExecutionId, ); if (execution && execution.status !== "completed" && execution.status !== "failed") { execution.status = "failed"; execution.completedAt = now; } } upsertTaskExecutionProgressMessageInState(state, task, "failed"); return { ...task }; }); publishBossEvent("master_agent.task.updated", { taskId: result.taskId, deviceId: result.deviceId, status: result.status, }); const progressProjectId = resolveTaskExecutionProgressProjectId(result); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } return result; } export function canRetryMasterAgentTaskSafely(task: MasterAgentTask) { return ( task.recoverable === true && ( task.phase === "queued" || task.phase === "claimed" || task.phase === "executor_starting" || task.phase === "recoverable_failed" ) && task.status !== "completed" && task.status !== "canceled" && task.status !== "timed_out" ); } export async function retryRecoverableMasterAgentTask(input: { taskId: string; actorAccount: string; reason?: string; }) { const result = await mutateState((state) => { const task = state.masterAgentTasks.find((item) => item.taskId === input.taskId); if (!task) { throw new Error("MASTER_AGENT_TASK_NOT_FOUND"); } if (!canRetryMasterAgentTaskSafely(task)) { throw new Error("MASTER_AGENT_TASK_RETRY_UNSAFE"); } const retriedAt = nowIso(); task.status = "queued"; task.phase = "queued"; task.claimedAt = undefined; task.lastClaimedAt = undefined; task.leaseExpiresAt = undefined; task.lastProgressAt = retriedAt; task.completedAt = undefined; task.canceledAt = undefined; task.canceledBy = undefined; task.cancelReason = undefined; task.lastErrorKind = undefined; task.lastErrorCode = undefined; task.errorMessage = undefined; task.recoverable = false; task.nextRetryAt = undefined; upsertTaskExecutionProgressMessageInState(state, task, "queued", { phase: "queued" }); state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "master_agent.task_retried", projectId: task.projectId, deviceId: task.deviceId, detail: input.reason?.trim() || `重试任务:${task.taskId}`, requestId: task.taskId, createdAt: retriedAt, afterJson: { taskId: task.taskId, phase: task.phase, status: task.status, }, }), ); state.permissionAuditLogs = state.permissionAuditLogs.slice(0, 500); return { ...task }; }); publishBossEvent("master_agent.task.updated", { taskId: result.taskId, deviceId: result.deviceId, status: result.status, }); const progressProjectId = resolveTaskExecutionProgressProjectId(result); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } return result; } export async function updateMasterAgentTaskProgress(payload: { taskId: string; deviceId: string; status?: "queued" | "running"; phase?: MasterAgentTaskPhase; executionProgress?: ExecutionProgressInput; requestId?: string; }) { 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"); } if (isTerminalMasterAgentTaskStatus(task.status)) { return { ...task }; } const progressStatus = payload.status === "queued" ? "queued" : "running"; if (task.status === "queued" && progressStatus === "running") { task.status = "running"; task.claimedAt = task.claimedAt ?? nowIso(); task.lastClaimedAt = task.lastClaimedAt ?? task.claimedAt; task.attemptCount = task.attemptCount ?? 1; task.maxAttempts = task.maxAttempts ?? defaultMasterAgentTaskMaxAttempts(task.taskType); task.leaseExpiresAt = task.leaseExpiresAt ?? new Date(Date.now() + masterAgentTaskLeaseMs(task)).toISOString(); } const phase = normalizeMasterAgentTaskPhase( payload.phase ?? payload.executionProgress?.phase ?? task.phase, task.status, ); const progressedAt = nowIso(); task.phase = phase; task.lastProgressAt = progressedAt; if (task.status === "running") { task.leaseExpiresAt = new Date(Date.now() + masterAgentTaskLeaseMs(task)).toISOString(); } if (phase === "recoverable_failed" || phase === "terminal_failed") { task.lastErrorCode = phaseErrorCode(phase); task.recoverable = phase === "recoverable_failed"; } task.requestId = payload.requestId?.trim() || task.requestId; upsertTaskExecutionProgressMessageInState( state, task, phaseToExecutionProgressStatus(phase, progressStatus), { ...payload.executionProgress, phase, }, ); return { ...task }; }); publishBossEvent("master_agent.task.updated", { taskId: result.taskId, deviceId: result.deviceId, status: result.status, }); const progressProjectId = resolveTaskExecutionProgressProjectId(result); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } return result; } export async function completeMasterAgentTask(payload: { taskId: string; deviceId: string; status: "completed" | "failed" | "needs_user_action"; kind?: string; replyBody?: string; errorMessage?: string; requestId?: string; dialogId?: string; appName?: string; platform?: string; risk?: ComputerControlRiskLevel; summary?: string; recommendedAction?: DialogGuardInterventionAction; availableActions?: DialogGuardInterventionAction[]; dispatchExecutionId?: string; targetProjectId?: string; targetThreadId?: string; targetUrl?: string; targetApp?: string; computerUseProvider?: ComputerUseProvider; rawThreadReply?: string; executionProgress?: ExecutionProgressInput; dispatchPlan?: { summary?: string; targets: DispatchPlanTarget[]; requestedOrchestrationBackendId?: OrchestrationBackendOverride; orchestrationBackendId?: OrchestrationBackendId; orchestrationBackendLabel?: string; orchestrationFallbackReason?: string; }; }) { 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"); } if (isTerminalMasterAgentTaskStatus(task.status)) { return { ...task, dispatchPlan: undefined, dispatchExecution: undefined, dialogGuardIntervention: undefined, }; } if (payload.status === "needs_user_action") { if (payload.kind !== "dialog_intervention_required") { throw new Error("MASTER_AGENT_USER_ACTION_KIND_UNSUPPORTED"); } const createdAt = nowIso(); const dialogId = payload.dialogId?.trim(); const summary = payload.summary?.trim(); if (!dialogId || !summary) { throw new Error("DIALOG_GUARD_INTERVENTION_REQUIRED_FIELDS_MISSING"); } const existing = state.dialogGuardInterventions.find( (item) => item.taskId === task.taskId && item.deviceId === payload.deviceId && (payload.requestId ? item.requestId === payload.requestId : item.dialogId === dialogId), ); const intervention = normalizeDialogGuardIntervention({ ...(existing ?? {}), interventionId: existing?.interventionId ?? randomToken("dialog-intervention"), dialogId, requestId: payload.requestId, taskId: task.taskId, deviceId: payload.deviceId, projectId: task.projectId, appName: payload.appName, platform: payload.platform, risk: payload.risk ?? task.riskLevel ?? "medium", summary, recommendedAction: payload.recommendedAction, availableActions: payload.availableActions, status: "pending", createdAt: existing?.createdAt ?? createdAt, }); task.status = "needs_user_action"; task.phase = "needs_user_action"; task.requestId = payload.requestId; task.lastProgressAt = createdAt; task.targetApp = payload.appName?.trim() || task.targetApp; if (existing) { Object.assign(existing, intervention); } else { state.dialogGuardInterventions.unshift(intervention); state.dialogGuardInterventions = state.dialogGuardInterventions.slice(0, 500); } const audit = normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: task.requestedByAccount || PRIMARY_ADMIN_ACCOUNT, action: "dialog_guard.intervention_required", deviceId: payload.deviceId, projectId: task.projectId, detail: `${intervention.risk}:${intervention.summary}`, requestId: intervention.requestId, createdAt, afterJson: { interventionId: intervention.interventionId, dialogId: intervention.dialogId, taskId: task.taskId, availableActions: intervention.availableActions, }, }); state.permissionAuditLogs = [audit, ...state.permissionAuditLogs].slice(0, 500); return { ...task, dialogGuardIntervention: { ...intervention }, dispatchPlan: undefined, dispatchExecution: undefined, }; } if (payload.status === "failed" && isMasterCodexNodeTask(task, state)) { const failover = findNextMasterCodexNodeCandidateForFailedTask(state, task, payload.deviceId); if (failover) { const failedAt = nowIso(); const failedAccount = task.accountId ? state.aiAccounts.find((item) => item.accountId === task.accountId) : undefined; if (failedAccount?.provider === "master_codex_node") { failedAccount.status = "degraded"; failedAccount.lastError = payload.errorMessage?.trim() || "MASTER_CODEX_NODE_EXEC_FAILED"; failedAccount.lastValidatedAt = failedAt; failedAccount.updatedAt = failedAt; } task.status = "queued"; task.phase = "queued"; task.deviceId = failover.deviceId; task.accountId = failover.accountId; task.accountLabel = failover.accountLabel; task.modelChannelAttemptedDeviceIds = [ ...new Set([...(task.modelChannelAttemptedDeviceIds ?? []), payload.deviceId]), ]; task.completedAt = undefined; task.canceledAt = undefined; task.canceledBy = undefined; task.cancelReason = undefined; task.leaseExpiresAt = undefined; task.claimedAt = undefined; task.lastClaimedAt = undefined; task.lastProgressAt = nowIso(); task.lastErrorKind = "model_channel_failover"; task.lastErrorCode = "MODEL_CHANNEL_FAILOVER"; task.recoverable = true; task.nextRetryAt = undefined; task.errorMessage = payload.errorMessage?.trim() || "MASTER_CODEX_NODE_EXEC_FAILED"; task.replyBody = undefined; task.requestId = undefined; upsertTaskExecutionProgressMessageInState(state, task, "queued", payload.executionProgress); return { ...task, dispatchPlan: undefined, dispatchExecution: undefined, dialogGuardIntervention: undefined, }; } } if ( payload.status === "failed" && canAutoRetryRecoverableRuntimeFailure(task, payload.errorMessage) ) { const retryAt = nowIso(); requeueRecoverableRuntimeFailureInState(task, retryAt, payload.errorMessage); upsertTaskExecutionProgressMessageInState(state, task, "failed", { ...payload.executionProgress, phase: task.phase, }); return { ...task, dispatchPlan: undefined, dispatchExecution: undefined, dialogGuardIntervention: undefined, }; } task.status = payload.status; task.phase = payload.status === "completed" ? "completed" : "terminal_failed"; task.completedAt = nowIso(); task.leaseExpiresAt = undefined; task.lastProgressAt = task.completedAt; task.replyBody = payload.replyBody?.trim() || undefined; task.errorMessage = payload.errorMessage?.trim() || undefined; task.lastErrorKind = payload.status === "failed" ? "runtime_failed" : undefined; task.lastErrorCode = payload.status === "failed" ? "RUNTIME_FAILED" : undefined; task.recoverable = false; task.nextRetryAt = undefined; task.requestId = payload.requestId; task.targetUrl = payload.targetUrl?.trim() || undefined; task.targetApp = payload.targetApp?.trim() || undefined; if ( payload.computerUseProvider === "boss-native-computer-use" || payload.computerUseProvider === "codex-computer-use" || payload.computerUseProvider === "cua-driver-computer-use" || payload.computerUseProvider === "openai-computer-use" ) { task.computerUseProvider = payload.computerUseProvider; } upsertTaskExecutionProgressMessageInState(state, task, payload.status, payload.executionProgress); 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, requestedOrchestrationBackendId: payload.dispatchPlan.requestedOrchestrationBackendId, orchestrationBackendId: payload.dispatchPlan.orchestrationBackendId, orchestrationBackendLabel: payload.dispatchPlan.orchestrationBackendLabel, orchestrationFallbackReason: payload.dispatchPlan.orchestrationFallbackReason, }); } } 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(), failureReason: payload.errorMessage?.trim(), }); } else if (!attachmentProjectId && payload.status === "completed" && task.replyBody) { const isDeviceImportUnderstanding = task.taskType === "conversation_reply" && task.projectId === "master-agent" && Boolean(task.deviceImportDraftId && task.deviceImportCandidateId); const isProjectUnderstandingSync = task.taskType === "conversation_reply" && task.projectId === "master-agent" && Boolean(task.projectUnderstandingTargetProjectId); const isThreadConversationReply = task.taskType === "conversation_reply" && task.projectId !== "master-agent" && Boolean(task.targetProjectId && task.targetThreadId); if (isDeviceImportUnderstanding) { const draft = state.deviceImportDrafts.find((item) => item.draftId === task.deviceImportDraftId); if (draft) { publishBossEvent("devices.updated", { deviceId: draft.deviceId }); } } else if (isProjectUnderstandingSync) { const understanding = parseStructuredProjectUnderstandingReply(task); if (understanding && task.projectUnderstandingTargetProjectId) { const targetProject = state.projects.find((item) => item.id === task.projectUnderstandingTargetProjectId); const previousUnderstanding = targetProject?.projectUnderstanding; applyProjectUnderstandingSnapshotInState(state, { projectId: task.projectUnderstandingTargetProjectId, account: task.requestedByAccount, snapshot: understanding.snapshot, sourceMessageId: task.requestMessageId, sourceKind: "thread_sync", }); const versionRecordAppended = appendProjectVersionFromUnderstandingSyncInState(state, { projectId: task.projectUnderstandingTargetProjectId, versionRecord: understanding.versionRecord, updatedAt: understanding.snapshot.updatedAt, }); if ( targetProject && shouldAnnounceProjectUnderstandingUpdate(previousUnderstanding, understanding.snapshot) ) { const projectDisplayName = targetProject.threadMeta.threadDisplayName?.trim() || targetProject.name; if (!task.projectUnderstandingNotifyOnCompletion || !task.projectUnderstandingReplyProjectId) { pushProjectLedgerMessage(state, "master-agent", { sender: "master", senderLabel: "主 Agent", account: task.requestedByAccount, body: buildProjectUnderstandingUpdateDigest(projectDisplayName, understanding.snapshot), kind: "system_notice", }); if ( understanding.snapshot.recommendedNextStep?.trim() && previousUnderstanding?.recommendedNextStep !== understanding.snapshot.recommendedNextStep ) { pushProjectLedgerMessage(state, "master-agent", { sender: "master", senderLabel: "主 Agent", account: task.requestedByAccount, body: buildProjectUnderstandingNextStepNotice(projectDisplayName, understanding.snapshot), kind: "system_notice", }); pushProjectLedgerMessage(state, "master-agent", { sender: "master", senderLabel: "主 Agent", account: task.requestedByAccount, body: buildProjectUnderstandingCollaborationNotice(projectDisplayName, understanding.snapshot), kind: "system_notice", }); } publishBossEvent("project.messages.updated", { projectId: "master-agent" }); publishBossEvent("conversation.updated", { projectId: "master-agent" }); } } if (task.projectUnderstandingNotifyOnCompletion && task.projectUnderstandingReplyProjectId) { const replyProject = state.projects.find( (item) => item.id === task.projectUnderstandingReplyProjectId, ); if (replyProject) { const threadTitle = targetProject?.threadMeta.threadDisplayName?.trim() || targetProject?.name || "当前线程"; pushProjectLedgerMessage(state, replyProject.id, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", account: task.requestedByAccount, body: `《${threadTitle}》的项目目标和版本记录已同步完成,顶部入口已更新。`, kind: "system_notice", }); publishBossEvent("project.messages.updated", { projectId: replyProject.id }); publishBossEvent("conversation.updated", { projectId: replyProject.id }); } } if (versionRecordAppended) { publishBossEvent("conversation.updated", { projectId: task.projectUnderstandingTargetProjectId, note: "project_versions.updated" }); } publishBossEvent("conversation.updated", { projectId: task.projectUnderstandingTargetProjectId, note: "project_goals.updated" }); publishBossEvent("conversation.updated", { projectId: task.projectUnderstandingTargetProjectId }); } } else if (isThreadConversationReply) { const threadProject = state.projects.find( (item) => item.id === (task.targetProjectId ?? task.projectId), ); const device = state.devices.find((item) => item.id === payload.deviceId); const replySender = task.relayViaMasterAgent ? "master" : "device"; const replySenderLabel = task.relayViaMasterAgent ? task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent" : task.targetThreadDisplayName?.trim() || threadProject?.threadMeta.threadDisplayName || device?.name || "线程"; const displayReplyBody = stripAlreadyMirroredThreadProcessPrefix(threadProject, { body: task.replyBody, senderLabel: replySenderLabel, requestedAt: task.requestedAt, completedAt: task.completedAt, }); const replyKind = resolveConversationReplyMessageKind(task, { body: displayReplyBody, phase: undefined, }); const shouldKeepExistingPreview = replyKind === "thread_process" && Boolean(threadProject?.messages.some((message) => !isThreadProcessMessageKind(message.kind))); const previousPreview = threadProject?.preview; const convertedMirroredReply = displayReplyBody && task.relayViaMasterAgent && threadProject ? convertRecentMirroredThreadReplyToMaster(threadProject, { body: displayReplyBody, sentAt: task.completedAt ?? nowIso(), senderLabel: replySenderLabel, kind: replyKind, }) : null; const duplicateRecentReply = hasRecentProjectReplyDuplicate(threadProject, { body: displayReplyBody, senderLabel: replySenderLabel, at: task.completedAt, windowMs: conversationReplyDuplicateWindowMs(task), }); if (displayReplyBody && !convertedMirroredReply && !duplicateRecentReply) { pushProjectLedgerMessage(state, threadProject?.id ?? task.projectId, { sender: replySender, senderLabel: replySenderLabel, body: displayReplyBody, kind: replyKind, }); } if (threadProject && shouldKeepExistingPreview && previousPreview?.trim()) { threadProject.preview = previousPreview; } } else if (task.taskType === "browser_control" || task.taskType === "desktop_control") { pushProjectLedgerMessage(state, task.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", account: task.requestedByAccount, body: task.replyBody, kind: "control_summary", controlTarget: payload.targetUrl?.trim() || payload.targetApp?.trim() || task.targetUrl || task.targetApp, }); pushProjectLedgerMessage(state, task.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", account: task.requestedByAccount, body: buildControlTaskCompletionUserSummary(task), kind: "text", }); } else { pushProjectLedgerMessage(state, task.projectId, { sender: "master", senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent", account: task.requestedByAccount, body: task.replyBody, kind: "text", }); autoCaptureMasterAgentMemoriesInState(state, { account: task.requestedByAccount, requestText: task.requestText, replyText: task.replyBody, sourceMessageId: task.requestMessageId, }); } const conversationSummaryUpdate = applyProjectSummaryFromConversationReplyInState(state, task); if (conversationSummaryUpdate) { if (conversationSummaryUpdate.versionRecordAppended) { publishBossEvent("conversation.updated", { projectId: conversationSummaryUpdate.projectId, note: "project_versions.updated", }); } publishBossEvent("conversation.updated", { projectId: conversationSummaryUpdate.projectId, note: "project_goals.updated", }); publishBossEvent("conversation.updated", { projectId: conversationSummaryUpdate.projectId }); } } else if (!attachmentProjectId && payload.status === "failed") { const isThreadConversationReply = task.taskType === "conversation_reply" && task.projectId !== "master-agent" && Boolean(task.targetProjectId && task.targetThreadId); const isNativeRemoteControl = task.taskType === "browser_control" || task.taskType === "desktop_control"; pushProjectLedgerMessage(state, task.projectId, { sender: isNativeRemoteControl ? "master" : "ops", senderLabel: isNativeRemoteControl ? task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent" : isThreadConversationReply ? task.relayViaMasterAgent ? "主 Agent Relay" : "线程执行失败" : task.accountLabel ? `主 Agent Relay · ${task.accountLabel}` : "主 Agent Relay", account: task.requestedByAccount, body: isNativeRemoteControl ? buildControlTaskFailureUserSummary(task) : isThreadConversationReply ? task.relayViaMasterAgent ? `主 Agent 转述失败:${task.targetThreadDisplayName ?? "当前线程"} 暂时无法返回结果,${buildFriendlyThreadExecutionError(task.errorMessage)}` : `${task.targetThreadDisplayName ?? "当前线程"} 执行失败:${buildFriendlyThreadExecutionError(task.errorMessage)}` : `Master Codex Node 执行失败:${buildFriendlyThreadExecutionError(task.errorMessage)}`, kind: "text", }); } if (isCliWriteTask(task)) { const scope = resolveProjectConflictScopeForTask(state, task); if (scope) { const policy = findProjectExecutionPolicyInState(state, scope); if (policy) { if (policy.allowPolicy === "allow_once") { policy.allowPolicy = "forbid"; policy.conflictState = "blocked"; } policy.activeCliExecution = false; policy.updatedAt = nowIso(); } } } return { ...task, dispatchPlan: createdDispatchPlan ? { ...createdDispatchPlan } : undefined, dispatchExecution: dispatchExecutionResult?.execution, dialogGuardIntervention: undefined, }; }); if (result.status === "needs_user_action" && result.dialogGuardIntervention) { publishBossEvent("master_agent.task.updated", { taskId: result.taskId, deviceId: result.deviceId, status: result.status, }); publishBossEvent("desktop.dialog_guard.intervention_required", { interventionId: result.dialogGuardIntervention.interventionId, dialogId: result.dialogGuardIntervention.dialogId, taskId: result.taskId, deviceId: result.deviceId, projectId: result.projectId, requestId: result.dialogGuardIntervention.requestId, appName: result.dialogGuardIntervention.appName, platform: result.dialogGuardIntervention.platform, risk: result.dialogGuardIntervention.risk, summary: result.dialogGuardIntervention.summary, recommendedAction: result.dialogGuardIntervention.recommendedAction, availableActions: result.dialogGuardIntervention.availableActions, status: result.dialogGuardIntervention.status, }); return result; } 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 }); if (result.dispatchExecution?.targetProjectId) { publishBossEvent("project.messages.updated", { projectId: result.dispatchExecution.targetProjectId }); publishBossEvent("conversation.updated", { projectId: result.dispatchExecution.targetProjectId }); } 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 upsertAuthAccountByAdmin(input: { account: string; displayName?: string; role: AuthRole; password?: string; verificationEmail?: string; companyId?: string; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const account = input.account.trim(); if (!account) { throw new Error("ACCOUNT_REQUIRED"); } let existing = state.authAccounts.find((item) => item.account === account); if (!existing && !input.password?.trim()) { throw new Error("PASSWORD_REQUIRED"); } const now = nowIso(); if (!existing) { existing = { id: `account-${slugify(account)}`, account, passwordHash: hashPassword(input.password ?? ""), displayName: input.displayName?.trim() || account, role: input.role, status: "active", companyId: trimToDefined(input.companyId), verificationEmail: input.verificationEmail?.trim() || (isLikelyEmailAccount(account) ? account : undefined), createdAt: now, updatedAt: now, }; state.authAccounts.push(existing); } else { existing.displayName = input.displayName?.trim() || existing.displayName || account; existing.role = input.role; existing.companyId = trimToDefined(input.companyId) ?? existing.companyId; existing.verificationEmail = input.verificationEmail?.trim() || existing.verificationEmail; if (input.password?.trim()) { existing.passwordHash = hashPassword(input.password); state.authSessions = state.authSessions.filter((session) => session.account !== account); } existing.updatedAt = now; } state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "account.updated", targetAccount: account, permissions: ["account.manage"], ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } export async function upsertAdminCompanyByAdmin(input: { companyId: string; name?: string; ownerAccount?: string; successOwnerAccount?: string; planTier?: AdminCompany["planTier"]; contractExpiresAt?: string; note?: string; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const companyId = input.companyId.trim().toLowerCase(); if (!companyId) { throw new Error("COMPANY_ID_REQUIRED"); } const now = nowIso(); let existing = state.adminCompanies.find((company) => company.companyId === companyId); if (!existing) { existing = { companyId, name: input.name?.trim() || companyId, ownerAccount: trimToDefined(input.ownerAccount), successOwnerAccount: trimToDefined(input.successOwnerAccount), planTier: input.planTier, contractExpiresAt: trimToDefined(input.contractExpiresAt), status: "active", note: trimToDefined(input.note), createdAt: now, updatedAt: now, }; state.adminCompanies.push(existing); } else { existing.name = input.name?.trim() || existing.name; existing.ownerAccount = trimToDefined(input.ownerAccount) ?? existing.ownerAccount; existing.successOwnerAccount = trimToDefined(input.successOwnerAccount) ?? existing.successOwnerAccount; existing.planTier = input.planTier ?? existing.planTier; existing.contractExpiresAt = trimToDefined(input.contractExpiresAt) ?? existing.contractExpiresAt; existing.note = trimToDefined(input.note) ?? existing.note; existing.status = "active"; existing.updatedAt = now; } state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "company.updated", targetAccount: input.ownerAccount, permissions: ["account.manage"], detail: `company:${companyId}`, ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } export async function setAuthAccountMfaRequiredByAdmin(input: { account: string; required: boolean; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const account = input.account.trim(); const existing = state.authAccounts.find((item) => item.account === account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } if (existing.role === "highest_admin") { throw new Error("CANNOT_CHANGE_HIGHEST_ADMIN_MFA"); } const now = nowIso(); existing.mfaRequired = input.required; if (input.required && !existing.mfaSecret) { existing.mfaSecret = randomBytes(20).toString("hex"); existing.mfaEnabledAt = now; } if (!input.required) { existing.mfaEnabledAt = undefined; } existing.updatedAt = now; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "account.mfa_updated", targetAccount: account, permissions: ["account.manage"], detail: `mfa_required:${input.required}`, ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } export async function setAdminCompanyStatusByAdmin(input: { companyId: string; status: AdminCompany["status"]; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const companyId = input.companyId.trim().toLowerCase(); const existing = state.adminCompanies.find((company) => company.companyId === companyId); if (!existing) { throw new Error("COMPANY_NOT_FOUND"); } const now = nowIso(); existing.status = input.status; existing.updatedAt = now; let disabledAccounts = 0; if (input.status === "disabled") { for (const account of state.authAccounts) { if (account.companyId !== companyId || account.role === "highest_admin") { continue; } if (account.status !== "disabled") { disabledAccounts += 1; } account.status = "disabled"; account.updatedAt = now; } for (const session of state.authSessions) { const account = state.authAccounts.find((item) => item.account === session.account); if (account?.companyId === companyId && !session.revokedAt) { session.revokedAt = now; } } } state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "company.status_updated", permissions: ["account.manage"], detail: `company:${companyId};status:${input.status};disabled_accounts:${disabledAccounts}`, ...input.auditMeta, createdAt: now, }), ); return { company: { ...existing }, disabledAccounts }; }); } export async function assignAccountCompanyByAdmin(input: { account: string; companyId: string; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const account = input.account.trim(); const companyId = input.companyId.trim().toLowerCase(); const existing = state.authAccounts.find((item) => item.account === account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } if (!state.adminCompanies.some((company) => company.companyId === companyId)) { throw new Error("COMPANY_NOT_FOUND"); } const now = nowIso(); existing.companyId = companyId; existing.updatedAt = now; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "company.assigned", targetAccount: account, permissions: ["account.manage"], detail: `company:${companyId}`, ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } export async function assignDeviceCompanyByAdmin(input: { deviceId: string; companyId: string; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const deviceId = input.deviceId.trim(); const companyId = input.companyId.trim().toLowerCase(); const existing = state.devices.find((item) => item.id === deviceId); if (!existing) { throw new Error("DEVICE_NOT_FOUND"); } if (!state.adminCompanies.some((company) => company.companyId === companyId)) { throw new Error("COMPANY_NOT_FOUND"); } const now = nowIso(); existing.companyId = companyId; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "company.assigned", deviceId, permissions: ["account.manage"], detail: `company:${companyId}`, ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } export async function bulkImportAuthAccountsByAdmin(input: { companyId: string; accounts: Array<{ account?: string; displayName?: string; role?: AuthRole; password?: string; verificationEmail?: string; }>; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const companyId = input.companyId.trim().toLowerCase(); if (!state.adminCompanies.some((company) => company.companyId === companyId)) { throw new Error("COMPANY_NOT_FOUND"); } const now = nowIso(); const imported: AuthAccount[] = []; for (const item of input.accounts) { const account = item.account?.trim() ?? ""; const role = item.role === "admin" ? "admin" : "member"; if (!account) { continue; } let existing = state.authAccounts.find((authAccount) => authAccount.account === account); if (!existing) { existing = { id: `account-${slugify(account)}`, account, passwordHash: hashPassword(item.password?.trim() || randomToken("password")), displayName: item.displayName?.trim() || account, role, status: "active", companyId, verificationEmail: item.verificationEmail?.trim() || (isLikelyEmailAccount(account) ? account : undefined), createdAt: now, updatedAt: now, }; state.authAccounts.push(existing); } else { existing.displayName = item.displayName?.trim() || existing.displayName || account; existing.role = role; existing.companyId = companyId; existing.verificationEmail = item.verificationEmail?.trim() || existing.verificationEmail; if (item.password?.trim()) { existing.passwordHash = hashPassword(item.password); state.authSessions = state.authSessions.filter((session) => session.account !== account); } existing.updatedAt = now; } imported.push({ ...existing }); state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "account.updated", targetAccount: account, permissions: ["account.manage"], detail: `bulk_import:${companyId}`, ...input.auditMeta, createdAt: now, }), ); } return imported; }); } export interface BulkImportAccountPreviewRow { account: string; displayName?: string; role: AuthRole; operation: "create" | "update"; valid: boolean; reason?: string; } export async function previewBulkImportAuthAccountsByAdmin(input: { companyId: string; accounts: Array<{ account?: string; displayName?: string; role?: AuthRole; password?: string; verificationEmail?: string; }>; }) { const state = await readState(); const companyId = input.companyId.trim().toLowerCase(); if (!state.adminCompanies.some((company) => company.companyId === companyId)) { throw new Error("COMPANY_NOT_FOUND"); } const rows: BulkImportAccountPreviewRow[] = input.accounts.map((item) => { const account = item.account?.trim() ?? ""; const role: AuthRole = item.role === "admin" ? "admin" : "member"; const existing = state.authAccounts.find((authAccount) => authAccount.account === account); return { account, displayName: trimToDefined(item.displayName), role, operation: existing ? "update" : "create", valid: Boolean(account), reason: account ? undefined : "ACCOUNT_REQUIRED", }; }); return { companyId, rows, summary: { create: rows.filter((row) => row.valid && row.operation === "create").length, update: rows.filter((row) => row.valid && row.operation === "update").length, invalid: rows.filter((row) => !row.valid).length, }, }; } export async function resetAuthAccountPasswordByAdmin(input: { account: string; password: string; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const account = input.account.trim(); const password = input.password.trim(); if (!account) { throw new Error("ACCOUNT_REQUIRED"); } if (!password) { throw new Error("PASSWORD_REQUIRED"); } const existing = state.authAccounts.find((item) => item.account === account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } if (existing.role === "highest_admin") { throw new Error("CANNOT_RESET_HIGHEST_ADMIN"); } const now = nowIso(); const activeSessions = state.authSessions.filter((session) => session.account === account && !session.revokedAt).length; existing.passwordHash = hashPassword(password); existing.failedLoginAttempts = 0; existing.lockedUntil = undefined; existing.updatedAt = now; let revokedSessions = 0; for (const session of state.authSessions) { if (session.account === account && !session.revokedAt) { session.revokedAt = now; revokedSessions += 1; } } state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "account.password_reset", targetAccount: account, permissions: ["account.manage"], beforeJson: { account, status: existing.status ?? "active", activeSessions, }, afterJson: { account, status: existing.status ?? "active", revokedSessions, }, ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } export async function reclaimAuthAccountByAdmin(input: { account: string; reason?: string; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const account = input.account.trim(); const existing = state.authAccounts.find((item) => item.account === account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } if (existing.role === "highest_admin") { throw new Error("CANNOT_RECLAIM_HIGHEST_ADMIN"); } const now = nowIso(); existing.status = "disabled"; existing.updatedAt = now; for (const session of state.authSessions) { if (session.account === account && !session.revokedAt) { session.revokedAt = now; } } const before = { deviceGrants: state.accountDeviceGrants.length, projectGrants: state.accountProjectGrants.length, skillGrants: state.accountSkillGrants.length, }; state.accountDeviceGrants = state.accountDeviceGrants.filter((grant) => grant.account !== account); state.accountProjectGrants = state.accountProjectGrants.filter((grant) => grant.account !== account); state.accountSkillGrants = state.accountSkillGrants.filter((grant) => grant.account !== account); const removedGrants = { deviceGrants: before.deviceGrants - state.accountDeviceGrants.length, projectGrants: before.projectGrants - state.accountProjectGrants.length, skillGrants: before.skillGrants - state.accountSkillGrants.length, }; state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "account.reclaimed", targetAccount: account, permissions: ["account.manage"], detail: input.reason?.trim() || "account_reclaimed", ...input.auditMeta, createdAt: now, }), ); return { account: { ...existing }, removedGrants }; }); } export async function setAuthAccountStatusByAdmin(input: { account: string; status: AuthAccountStatus; actorAccount: string; auditMeta?: PermissionAuditMeta; }) { return mutateState((state) => { const account = input.account.trim(); const existing = state.authAccounts.find((item) => item.account === account); if (!existing) { throw new Error("ACCOUNT_NOT_FOUND"); } if (existing.role === "highest_admin" && input.status === "disabled") { throw new Error("CANNOT_DISABLE_HIGHEST_ADMIN"); } const now = nowIso(); existing.status = input.status; existing.updatedAt = now; if (input.status === "disabled") { for (const session of state.authSessions) { if (session.account === account && !session.revokedAt) { session.revokedAt = now; } } } state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "account.updated", targetAccount: account, permissions: ["account.manage"], detail: `status:${input.status}`, ...input.auditMeta, createdAt: now, }), ); return { ...existing }; }); } 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; mfaCode?: 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.status === "disabled") { throw new Error("ACCOUNT_DISABLED"); } 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"); } if (!consumeVerificationCode(state, params.account, "login", params.code)) { registerLoginFailure(existing); throw new Error("INVALID_VERIFICATION_CODE"); } } if (existing.mfaRequired && !verifyAuthAccountMfaCode(existing.mfaSecret, params.mfaCode)) { registerLoginFailure(existing); throw new Error("MFA_CODE_REQUIRED_OR_INVALID"); } 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.sessionId !== session.sessionId), ].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; device.status = "offline"; device.revokedAt = undefined; device.revokedBy = undefined; device.revokeReason = undefined; } 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: DeviceUpdatePayload) { const device = await mutateState((state) => { const nextDevice = state.devices.find((item) => item.id === deviceId); if (!nextDevice) throw new Error("DEVICE_NOT_FOUND"); const nextLastSeenAt = nowIso(); 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.capabilities = normalizeDeviceCapabilities( refreshDeviceCapabilityLastSeenAt( payload.capabilities ? { gui: payload.capabilities.gui ?? nextDevice.capabilities?.gui, cli: payload.capabilities.cli ?? nextDevice.capabilities?.cli, browserAutomation: payload.capabilities.browserAutomation ?? nextDevice.capabilities?.browserAutomation, computerUse: payload.capabilities.computerUse ?? nextDevice.capabilities?.computerUse, codexAppServer: payload.capabilities.codexAppServer ?? nextDevice.capabilities?.codexAppServer, } : nextDevice.capabilities, nextLastSeenAt, Boolean(payload.capabilities), ), nextLastSeenAt, ); if (payload.preferredExecutionMode !== undefined) { nextDevice.preferredExecutionMode = normalizePreferredExecutionMode(payload.preferredExecutionMode); } nextDevice.lastSeenAt = nextLastSeenAt; return nextDevice; }); publishBossEvent("devices.updated", { deviceId }); return device; } export async function revokeDeviceByAdmin(input: { deviceId: string; actorAccount: string; reason?: string; auditMeta?: PermissionAuditMeta; }) { const device = await mutateState((state) => { const deviceId = input.deviceId.trim(); const nextDevice = state.devices.find((item) => item.id === deviceId); if (!nextDevice) throw new Error("DEVICE_NOT_FOUND"); const now = nowIso(); const beforeJson: PermissionAuditSnapshot = { id: nextDevice.id, status: nextDevice.status, account: nextDevice.account, companyId: nextDevice.companyId, revokedAt: nextDevice.revokedAt, }; nextDevice.status = "offline"; nextDevice.revokedAt = nextDevice.revokedAt ?? now; nextDevice.revokedBy = input.actorAccount; nextDevice.revokeReason = input.reason?.trim() || undefined; nextDevice.token = undefined; nextDevice.lastSeenAt = now; nextDevice.capabilities = normalizeDeviceCapabilities({ gui: nextDevice.capabilities?.gui ? { ...nextDevice.capabilities.gui, connected: false, lastSeenAt: now } : undefined, cli: nextDevice.capabilities?.cli ? { ...nextDevice.capabilities.cli, connected: false, lastSeenAt: now } : undefined, browserAutomation: nextDevice.capabilities?.browserAutomation ? { ...nextDevice.capabilities.browserAutomation, connected: false, lastSeenAt: now } : undefined, computerUse: nextDevice.capabilities?.computerUse ? { ...nextDevice.capabilities.computerUse, connected: false, lastSeenAt: now } : undefined, codexAppServer: nextDevice.capabilities?.codexAppServer ? { ...nextDevice.capabilities.codexAppServer, connected: false, lastSeenAt: now } : undefined, }, now); state.deviceEnrollments = state.deviceEnrollments.map((enrollment) => enrollment.deviceId === deviceId ? { ...enrollment, status: "expired", expiresAt: now, } : enrollment, ); state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "device.revoked", deviceId, permissions: ["device.manage"], detail: nextDevice.revokeReason, ...input.auditMeta, beforeJson, afterJson: { id: nextDevice.id, status: nextDevice.status, account: nextDevice.account, companyId: nextDevice.companyId, revokedAt: nextDevice.revokedAt, revokedBy: nextDevice.revokedBy, }, createdAt: now, }), ); return { ...nextDevice }; }); publishBossEvent("devices.updated", { deviceId: device.id }); return device; } export async function applyProjectConflictDecision(input: { deviceId: string; folderKey?: string; projectId: string; decision: ProjectConflictAllowPolicy; }) { return mutateState((state) => { const policy = upsertProjectExecutionPolicyInState(state, { deviceId: input.deviceId, folderKey: input.folderKey, projectId: input.projectId, allowPolicy: input.decision, conflictState: input.decision === "forbid" ? "blocked" : "warning", activeCliExecution: false, updatedAt: nowIso(), }); return normalizeProjectExecutionPolicy(policy); }); } export async function detectProjectExecutionConflict(input: { deviceId: string; folderKey?: string; projectId: string; executionMode: DeviceExecutionMode; activityAt: string; externalActivityAt?: string; }) { let result!: { blocked: boolean; policy: ProjectExecutionPolicy; }; await mutateState((state) => { const scope = normalizeProjectExecutionPolicyScope(input); const existingPolicy = findProjectExecutionPolicyInState(state, scope); const hasConflict = input.executionMode === "cli" && hasRecentThreadConversationExternalActivity({ activityAt: input.activityAt, externalActivityAt: input.externalActivityAt, }); if (!hasConflict) { const clearedPolicy = existingPolicy ? upsertProjectExecutionPolicyInState(state, { ...existingPolicy, ...scope, allowPolicy: existingPolicy.allowPolicy ?? "forbid", conflictState: "none", activeCliExecution: false, recentExternalActivityAt: undefined, updatedAt: nowIso(), }) : null; result = { blocked: false, policy: normalizeProjectExecutionPolicy( clearedPolicy ?? { ...existingPolicy, ...scope, allowPolicy: existingPolicy?.allowPolicy ?? "forbid", conflictState: "none", activeCliExecution: false, recentExternalActivityAt: undefined, updatedAt: existingPolicy?.updatedAt ?? nowIso(), }, ), }; return; } if (existingPolicy?.allowPolicy === "allow_always") { const policy = upsertProjectExecutionPolicyInState(state, { ...existingPolicy, ...scope, allowPolicy: "allow_always", conflictState: "warning", activeCliExecution: true, recentExternalActivityAt: input.externalActivityAt, updatedAt: nowIso(), }); result = { blocked: false, policy: normalizeProjectExecutionPolicy(policy) }; return; } if (existingPolicy?.allowPolicy === "allow_once") { const policy = upsertProjectExecutionPolicyInState(state, { ...existingPolicy, ...scope, allowPolicy: "allow_once", conflictState: "warning", activeCliExecution: true, recentExternalActivityAt: input.externalActivityAt, updatedAt: nowIso(), }); result = { blocked: false, policy: normalizeProjectExecutionPolicy(policy) }; return; } const blockedPolicy = upsertProjectExecutionPolicyInState(state, { ...existingPolicy, ...scope, allowPolicy: "forbid", conflictState: "blocked", activeCliExecution: true, recentExternalActivityAt: input.externalActivityAt, updatedAt: nowIso(), }); result = { blocked: true, policy: normalizeProjectExecutionPolicy(blockedPolicy) }; }); return result; } 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 || isDeviceRevoked(device)) { return false; } 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[]; capabilities?: DeviceCapabilitiesInput; preferredExecutionMode?: DeviceExecutionMode; endpoint?: string; projectCandidates?: Array<{ folderName: string; folderRef?: string; threadId: string; threadDisplayName: string; codexFolderRef?: string; codexThreadRef?: string; lastActiveAt?: string; suggestedImport?: boolean; recentAssistantMessages?: Array>; }>; }) { const result = await mutateState((state) => { let conversationRefreshRequired = false; const messageRefreshProjectIds = new Set(); const conversationRefreshProjectIds = new Set(); const projectUnderstandingSyncRequests: Array<{ projectId: string; observedActivityAt: string; reason: "heartbeat_activity"; }> = []; const existingDevice = state.devices.find((item) => item.id === payload.deviceId) ?? null; if (isDeviceRevoked(existingDevice)) { throw new Error("DEVICE_REVOKED"); } 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, recentAssistantMessages: candidate.recentAssistantMessages as DeviceObservedAssistantMessage[] | undefined, }), ); 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, capabilities: normalizeDeviceCapabilities(payload.capabilities, nowIso()), preferredExecutionMode: normalizePreferredExecutionMode(payload.preferredExecutionMode), }; state.devices.push(device); } else { if (device.token && payload.token && device.token !== payload.token && !claimedEnrollment) { throw new Error("DEVICE_TOKEN_MISMATCH"); } const nextLastSeenAt = nowIso(); 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 = nextLastSeenAt; device.endpoint = payload.endpoint ?? device.endpoint; device.token = claimedEnrollment?.token ?? payload.token ?? device.token; device.capabilities = normalizeDeviceCapabilities( refreshDeviceCapabilityLastSeenAt( payload.capabilities ?? device.capabilities, nextLastSeenAt, Boolean(payload.capabilities), ), nextLastSeenAt, ); if (device.preferredExecutionMode === undefined && payload.preferredExecutionMode !== undefined) { device.preferredExecutionMode = normalizePreferredExecutionMode(payload.preferredExecutionMode); } } 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; conversationRefreshRequired = true; } } } let draft = upsertDeviceImportDraftFromHeartbeat(state, { deviceId: payload.deviceId, enrollmentId: claimedEnrollment?.enrollmentId, candidates: normalizedCandidates, }); for (const candidate of normalizedCandidates) { const matchingProject = state.projects.find( (project) => !project.isGroup && project.deviceIds.includes(payload.deviceId) && ((candidate.codexThreadRef && project.threadMeta.codexThreadRef === candidate.codexThreadRef) || project.threadMeta.threadId === candidate.threadId), ); if (!matchingProject) { continue; } const previousObservedAt = matchingProject.threadMeta.lastObservedCodexActivityAt; matchingProject.threadMeta.lastObservedCodexActivityAt = latestIsoTimestamp( previousObservedAt, candidate.lastActiveAt, ) ?? candidate.lastActiveAt; const previousObservedTs = Date.parse(previousObservedAt ?? "1970-01-01T00:00:00.000Z"); const nextObservedTs = Date.parse(candidate.lastActiveAt); const hasNewObservedActivity = Number.isFinite(nextObservedTs) && (!Number.isFinite(previousObservedTs) || nextObservedTs > previousObservedTs); if ((candidate.recentAssistantMessages ?? []).length > 0) { const nextPreview = deriveProjectPreview(state, matchingProject, { emptyMessageFallback: "clear", }); if (matchingProject.preview !== nextPreview) { matchingProject.preview = nextPreview; conversationRefreshProjectIds.add(matchingProject.id); } } if (hasNewObservedActivity) { conversationRefreshProjectIds.add(matchingProject.id); appendThreadProgressEventInState(state, { projectId: matchingProject.id, threadId: matchingProject.threadMeta.threadId, threadDisplayName: matchingProject.threadMeta.threadDisplayName, deviceId: matchingProject.deviceIds[0] ?? payload.deviceId, eventType: "progress_updated", summary: buildHeartbeatProgressSummary(candidate.threadDisplayName), createdAt: candidate.lastActiveAt, sourceTaskId: `heartbeat-${candidate.candidateId}`, }); const folderKey = buildProjectFolderKey(matchingProject); const activePolicy = folderKey ? findProjectExecutionPolicyInState(state, { deviceId: payload.deviceId, folderKey, projectId: matchingProject.id, }) : undefined; const hasRunningCliTask = state.masterAgentTasks.some((task) => { if (task.deviceId !== payload.deviceId || task.status !== "running") { return false; } if (!isCliWriteTask(task)) { return false; } const taskScope = resolveProjectConflictScopeForTask(state, task); if (!taskScope) { return false; } if (taskScope.projectId !== matchingProject.id) { return false; } if (!folderKey) { return true; } return taskScope.folderKey === folderKey; }); if (activePolicy?.activeCliExecution || hasRunningCliTask) { const allowPolicy = activePolicy?.allowPolicy ?? "forbid"; const conflictState = allowPolicy === "allow_always" ? "warning" : "blocked"; upsertProjectExecutionPolicyInState(state, { ...activePolicy, deviceId: payload.deviceId, folderKey, projectId: matchingProject.id, allowPolicy, conflictState, activeCliExecution: true, recentExternalActivityAt: candidate.lastActiveAt, updatedAt: nowIso(), }); } } if (shouldQueueProjectUnderstandingSync(matchingProject, candidate.lastActiveAt, state, "heartbeat_activity")) { projectUnderstandingSyncRequests.push({ projectId: matchingProject.id, observedActivityAt: candidate.lastActiveAt, reason: "heartbeat_activity", }); } } 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; conversationRefreshRequired = true; } } return { device, token: claimedEnrollment?.token ?? device.token, pairingStatus: claimedEnrollment?.status, importDraft: draft, projectUnderstandingSyncRequests, conversationRefreshRequired, messageRefreshProjectIds: [...messageRefreshProjectIds], conversationRefreshProjectIds: [...conversationRefreshProjectIds], }; }); for (const request of result.projectUnderstandingSyncRequests ?? []) { await queueProjectUnderstandingSyncTask(request); } publishBossEvent("devices.updated", { deviceId: payload.deviceId }); for (const projectId of result.messageRefreshProjectIds ?? []) { publishBossEvent("project.messages.updated", { projectId }); publishBossEvent("conversation.updated", { projectId }); } for (const projectId of result.conversationRefreshProjectIds ?? []) { if (result.messageRefreshProjectIds?.includes(projectId)) { continue; } publishBossEvent("conversation.updated", { projectId }); } if (result.conversationRefreshRequired) { publishBossEvent("conversation.updated", { deviceId: payload.deviceId, note: "device_import.updated" }); } 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.draft.status === "applied" && input.draft.resolutionId && sameStringSet(input.draft.selectedCandidateIds, resolveAutoSyncCandidateIds(input.draft)) ) { 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 }; } function parseStructuredProjectUnderstandingReply( task: Pick, ): { snapshot: ProjectUnderstandingSnapshot; versionRecord: string } | null { const replyBody = task.replyBody?.trim(); if (!replyBody) { return null; } const fencedMatch = replyBody.match(/```(?:json)?\s*([\s\S]*?)```/i); const inlineJsonMatch = replyBody.match(/\{[\s\S]*\}/); const jsonCandidate = fencedMatch?.[1]?.trim() ?? inlineJsonMatch?.[0]?.trim() ?? replyBody; let parsed: ProjectUnderstandingSyncReply | null = null; try { parsed = JSON.parse(jsonCandidate); } catch { return parseLabeledProjectUnderstandingReply(task); } const projectGoal = parsed?.projectGoal?.trim() ?? ""; const currentProgress = parsed?.currentProgress?.trim() ?? ""; const technicalArchitecture = parsed?.technicalArchitecture?.trim() ?? ""; const currentBlockers = parsed?.currentBlockers?.trim() ?? ""; const recommendedNextStep = parsed?.recommendedNextStep?.trim() ?? ""; const versionRecord = parsed?.versionRecord?.trim() ?? ""; if ( !projectGoal && !currentProgress && !technicalArchitecture && !currentBlockers && !recommendedNextStep && !versionRecord ) { return null; } return { snapshot: { projectGoal, currentProgress, technicalArchitecture, currentBlockers, recommendedNextStep, sourceTaskId: task.taskId, updatedAt: task.completedAt ?? task.requestedAt, sourceKind: "thread_sync", }, versionRecord, }; } function parseLabeledProjectUnderstandingReply( task: Pick, ): { snapshot: ProjectUnderstandingSnapshot; versionRecord: string } | null { const replyBody = task.replyBody?.trim(); if (!replyBody) { return null; } const projectGoal = extractLabeledProjectSummaryValue(replyBody, ["项目目标", "目标"]); const currentProgress = extractLabeledProjectSummaryValue(replyBody, ["当前进度", "当前进展", "进度"]); const technicalArchitecture = extractLabeledProjectSummaryValue(replyBody, [ "技术架构", "架构说明", "架构", "技术方案", ]); const currentBlockers = extractLabeledProjectSummaryValue(replyBody, [ "当前阻塞", "阻塞说明", "阻塞", "风险", ]); const recommendedNextStep = extractLabeledProjectSummaryValue(replyBody, [ "建议下一步", "推荐下一步", "下一步建议", "下一步", ]); const versionRecord = extractLabeledProjectSummaryValue(replyBody, [ "版本记录", "版本迭代记录", "版本摘要", "迭代记录", ]); if ( !projectGoal && !currentProgress && !technicalArchitecture && !currentBlockers && !recommendedNextStep && !versionRecord ) { return null; } return { snapshot: { projectGoal, currentProgress, technicalArchitecture, currentBlockers, recommendedNextStep, sourceTaskId: task.taskId, updatedAt: task.completedAt ?? task.requestedAt, sourceKind: "thread_sync", }, versionRecord, }; } function extractLabeledProjectSummaryValue(text: string, labels: string[]) { const escapedLabels = labels.map((label) => escapeRegExp(label)).join("|"); const pattern = new RegExp( `^\\s*(?:[-*•]\\s*)?(?:\\d+[.、]\\s*)?(?:${escapedLabels})\\s*[::]\\s*(.+?)\\s*$`, "i", ); for (const line of text.split(/\r?\n/)) { const match = line.match(pattern); const value = match?.[1]?.trim(); if (value) { return value; } } return ""; } function escapeRegExp(value: string) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function appendProjectVersionFromUnderstandingSyncInState( state: BossState, input: { projectId: string; versionRecord: string; updatedAt: string; }, ) { const versionRecord = input.versionRecord.trim(); if (!versionRecord) { return false; } const project = state.projects.find((item) => item.id === input.projectId); if (!project) { return false; } if (project.versions.some((entry) => entry.summary === versionRecord)) { return false; } project.versions.unshift({ version: `同步更新 ${input.updatedAt.slice(0, 10)}`, summary: versionRecord, createdAt: input.updatedAt, }); return true; } function shouldRequestProjectSummarySyncFromConversationText(text?: string | null) { const normalized = text?.trim(); if (!normalized) { return false; } const mentionsGoal = /项目目标|目标/.test(normalized); const mentionsVersion = /版本记录|版本迭代|版本/.test(normalized); const mentionsReviewOrSync = /核对|确认|同步|更新|刷新|整理|汇总|总结|梳理|概括|回写/.test(normalized); return mentionsReviewOrSync && (mentionsGoal || mentionsVersion); } function resolveProjectSummaryTargetFromConversationTask(task: MasterAgentTask) { if (task.projectUnderstandingTargetProjectId) { return null; } if (task.taskType !== "conversation_reply" || task.relayViaMasterAgent !== true) { return null; } const targetProjectId = (task.targetProjectId ?? task.projectId).trim(); if (!targetProjectId || targetProjectId === "master-agent") { return null; } if (!shouldRequestProjectSummarySyncFromConversationText(task.requestText)) { return null; } return targetProjectId; } function applyProjectSummaryFromConversationReplyInState(state: BossState, task: MasterAgentTask) { const targetProjectId = resolveProjectSummaryTargetFromConversationTask(task); if (!targetProjectId) { return null; } const understanding = parseStructuredProjectUnderstandingReply(task); if (!understanding) { return null; } const project = state.projects.find((item) => item.id === targetProjectId); if (!project) { return null; } applyProjectUnderstandingSnapshotInState(state, { projectId: targetProjectId, account: task.requestedByAccount, snapshot: understanding.snapshot, sourceMessageId: task.requestMessageId, sourceKind: "thread_sync", }); const versionRecordAppended = appendProjectVersionFromUnderstandingSyncInState(state, { projectId: targetProjectId, versionRecord: understanding.versionRecord, updatedAt: understanding.snapshot.updatedAt, }); return { projectId: targetProjectId, versionRecordAppended, }; } function applyProjectUnderstandingSnapshotInState( state: BossState, input: { projectId: string; account: string; snapshot: ProjectUnderstandingSnapshot; sourceMessageId?: string; sourceKind: ProjectUnderstandingSnapshot["sourceKind"]; }, ) { const project = state.projects.find((item) => item.id === input.projectId); if (!project) { return null; } const snapshot: ProjectUnderstandingSnapshot = { ...input.snapshot, sourceKind: input.sourceKind, }; project.projectUnderstanding = snapshot; upsertThreadStatusDocumentInState(state, { projectId: project.id, threadId: project.threadMeta.threadId, threadDisplayName: project.threadMeta.threadDisplayName, folderName: project.threadMeta.folderName, deviceId: project.deviceIds[0] ?? state.user.boundDeviceId ?? PRIMARY_CODEX_NODE_ID, projectGoal: snapshot.projectGoal, currentPhase: input.sourceKind === "device_import" ? "导入理解" : "全量理解", currentProgress: snapshot.currentProgress, technicalArchitecture: snapshot.technicalArchitecture, currentBlockers: snapshot.currentBlockers, recommendedNextStep: snapshot.recommendedNextStep, keyFiles: [], keyCommands: [], updatedAt: snapshot.updatedAt, sourceTaskId: snapshot.sourceTaskId, sourceKind: input.sourceKind === "device_import" ? "device_import" : "full_sync", }); project.threadMeta.lastProjectUnderstandingSyncedAt = snapshot.updatedAt; project.threadMeta.lastObservedCodexActivityAt = latestIsoTimestamp(project.threadMeta.lastObservedCodexActivityAt, snapshot.updatedAt) ?? snapshot.updatedAt; const tags = [project.name, project.threadMeta.threadDisplayName].filter(Boolean); if (snapshot.projectGoal) { upsertAutoMasterMemoryInState(state, { account: input.account, scope: "project", projectId: project.id, title: `项目目标 · ${project.name}`, content: snapshot.projectGoal, memoryType: "project_progress", tags: [...tags, "项目目标"], sourceMessageId: input.sourceMessageId, }); } if (snapshot.currentProgress) { upsertAutoMasterMemoryInState(state, { account: input.account, scope: "project", projectId: project.id, title: `项目进度 · ${project.name}`, content: snapshot.currentProgress, memoryType: "project_progress", tags: [...tags, "项目进度"], sourceMessageId: input.sourceMessageId, }); } if (snapshot.technicalArchitecture) { upsertAutoMasterMemoryInState(state, { account: input.account, scope: "project", projectId: project.id, title: `技术架构 · ${project.name}`, content: snapshot.technicalArchitecture, memoryType: "research_note", tags: [...tags, "技术架构"], sourceMessageId: input.sourceMessageId, }); } if (snapshot.currentBlockers) { upsertAutoMasterMemoryInState(state, { account: input.account, scope: "project", projectId: project.id, title: `当前阻塞 · ${project.name}`, content: snapshot.currentBlockers, memoryType: "blocking_issue", tags: [...tags, "阻塞"], sourceMessageId: input.sourceMessageId, }); } if (snapshot.recommendedNextStep) { upsertAutoMasterMemoryInState(state, { account: input.account, scope: "project", projectId: project.id, title: `下一步建议 · ${project.name}`, content: snapshot.recommendedNextStep, memoryType: "workflow_rule", tags: [...tags, "下一步"], sourceMessageId: input.sourceMessageId, }); } return snapshot; } function shouldAnnounceProjectUnderstandingUpdate( previous: ProjectUnderstandingSnapshot | undefined, next: ProjectUnderstandingSnapshot, ) { if (!previous) { return true; } return ( previous.projectGoal !== next.projectGoal || previous.currentProgress !== next.currentProgress || previous.technicalArchitecture !== next.technicalArchitecture || previous.currentBlockers !== next.currentBlockers || previous.recommendedNextStep !== next.recommendedNextStep ); } function buildProjectUnderstandingUpdateDigest(projectName: string, snapshot: ProjectUnderstandingSnapshot) { return [ `已同步项目理解:${projectName}`, snapshot.currentProgress ? `进度:${snapshot.currentProgress}` : undefined, snapshot.currentBlockers ? `阻塞:${snapshot.currentBlockers}` : undefined, snapshot.recommendedNextStep ? `下一步:${snapshot.recommendedNextStep}` : undefined, ] .filter(Boolean) .join("\n"); } function buildProjectUnderstandingNextStepNotice(projectName: string, snapshot: ProjectUnderstandingSnapshot) { return [`建议下一步推进:${projectName}`, snapshot.recommendedNextStep?.trim() || ""] .filter(Boolean) .join("\n"); } function buildProjectUnderstandingCollaborationNotice(projectName: string, snapshot: ProjectUnderstandingSnapshot) { return [ `主 Agent 已同步:${projectName}`, "你仍然可以继续直接控制线程开发,主 Agent 只是在旁边协同推进。", "不会中断你继续直接控制线程开发。", snapshot.recommendedNextStep?.trim() ? `如果需要主 Agent 协助推进,建议下一步:${snapshot.recommendedNextStep.trim()}` : undefined, ] .filter(Boolean) .join("\n"); } function buildFriendlyThreadExecutionError(errorMessage?: string) { const message = errorMessage?.trim(); if (!message) { return "线程执行失败,请稍后重试。"; } if (message.includes("LOCAL_AGENT_CODEX_THREAD_BINDING_MISSING")) { return "线程绑定缺失,请重新导入或重新绑定该线程后再试。"; } if (message.includes("LOCAL_AGENT_CODEX_THREAD_BINDING_STALE")) { return "线程绑定已失效,请重新导入或重新绑定该线程后再试。"; } if ( message.includes("LOCAL_AGENT_CODEX_THREAD_BINDING_MISMATCH") || message.includes("LOCAL_AGENT_CODEX_WORKDIR_INVALID") || message.includes("THREAD_ENVIRONMENT_INVALID") ) { return "线程环境异常,请重新绑定到正确项目或工作目录后再试。"; } if (message.includes(MASTER_CODEX_NODE_OUTPUT_LEAKED)) { return "主 Agent 执行节点返回了内部启动日志,已拦截;我会改用更稳的执行链路重试。"; } if (message.includes("LOCAL_AGENT_CODEX_THREAD_READ_ONLY")) { return "线程当前处于只读环境,无法继续执行,请切换到可写线程后再试。"; } if (message.includes("CODEX_APP_SERVER_TURN_INTERRUPTED")) { return "Codex 桌面线程本轮被中断,系统会优先自动重试;如果多次失败,请确认 Codex 桌面端没有手动停止或切换线程。"; } if (message.includes("CODEX_APP_SERVER_TIMEOUT")) { return "Codex 桌面线程响应超时,系统会优先自动重试;如果多次失败,请确认 Codex 桌面端在线且网络稳定。"; } if (message.includes("CODEX_APP_SERVER_STDIN_CLOSED") || message.includes("CODEX_APP_SERVER_EXITED")) { return "Codex App Server 连接中断,系统会优先自动重连并重试;如果多次失败,请重启 Codex 或 boss-agent。"; } if ( message.toLowerCase().includes("fetch failed") || message.toLowerCase().includes("econnreset") || message.toLowerCase().includes("etimedout") ) { return "执行链路网络连接不稳定,系统会优先自动重试;如果多次失败,请检查电脑和服务器网络。"; } return message; } function buildFriendlyControlExecutionError(errorMessage?: string) { const message = errorMessage?.trim().toLowerCase() ?? ""; if (!message) { return "目标电脑暂时没有返回执行结果,请确认设备在线后重试。"; } if (message.includes("timeout") || message.includes("timed out") || message.includes("超时")) { return "目标电脑响应超时,请确认 boss-agent 在线、网络稳定后重试。"; } if ( message.includes("permission denied") || message.includes("authentication") || message.includes("not permitted") || message.includes("unauthorized") || message.includes("权限") ) { return "目标电脑权限未就绪,请确认 boss-agent 已获得辅助功能和屏幕录制权限后重试。"; } if ( message.includes("fetch failed") || message.includes("connection refused") || message.includes("econnrefused") || message.includes("network") || message.includes("offline") || message.includes("断网") ) { return "目标电脑连接暂时不可用,请确认设备在线后重试。"; } if (message.includes("chrome") || message.includes("safari") || message.includes("browser")) { return "浏览器没有按预期完成操作,请确认目标电脑浏览器可正常打开后重试。"; } return "目标电脑本次没有完成操作,请确认设备在线和权限状态后重试。"; } function buildControlTaskFailureUserSummary(task: MasterAgentTask) { const controlLabel = task.taskType === "browser_control" ? "浏览器控制" : "桌面控制"; const requestText = task.requestText?.trim(); const lines = [`远程控制未完成:${buildFriendlyControlExecutionError(task.errorMessage)}`]; if (requestText) { lines.push(`本次指令:${requestText}`); } lines.push(`类型:${controlLabel}`); return lines.join(";"); } function shouldQueueProjectUnderstandingSync( project: Project, observedActivityAt: string, state: BossState, reason: "heartbeat_activity" | "thread_reply" = "heartbeat_activity", ) { if (!isDispatchableThreadProject(project)) { return false; } const takeoverControls = applyDerivedTakeoverControls( state, project.id, state.user.account, resolveStoredProjectAgentControls(state, project.id, state.user.account), ); if (takeoverControls?.effectiveTakeoverEnabled !== true) { return false; } const observedTs = Date.parse(observedActivityAt); if (!Number.isFinite(observedTs)) { return false; } const latestWatermark = Date.parse( project.threadMeta.lastProjectUnderstandingRequestedAt ?? project.threadMeta.lastProjectUnderstandingSyncedAt ?? project.projectUnderstanding?.updatedAt ?? "1970-01-01T00:00:00.000Z", ); if (Number.isFinite(latestWatermark) && observedTs <= latestWatermark) { return false; } const hasThreadStatusDocument = state.threadStatusDocuments.some( (item) => item.projectId === project.id && item.threadId === project.threadMeta.threadId, ); if (reason === "thread_reply" && hasThreadStatusDocument) { return false; } if (project.projectUnderstanding && hasThreadStatusDocument) { const lastSyncedTs = Date.parse( project.threadMeta.lastProjectUnderstandingSyncedAt ?? project.projectUnderstanding.updatedAt ?? "1970-01-01T00:00:00.000Z", ); const understandingLooksThin = !project.projectUnderstanding.currentProgress?.trim() || !project.projectUnderstanding.recommendedNextStep?.trim(); if ( !understandingLooksThin && Number.isFinite(lastSyncedTs) && observedTs - lastSyncedTs < THREAD_STATUS_FULL_SYNC_INTERVAL_MS ) { return false; } } return !state.masterAgentTasks.some( (task) => task.taskType === "conversation_reply" && task.projectId === "master-agent" && task.projectUnderstandingTargetProjectId === project.id && (task.status === "queued" || task.status === "running"), ); } function buildProjectUnderstandingSyncPrompt(project: Project, reason: "heartbeat_activity" | "thread_reply") { return [ "你正在向主 Agent 同步当前项目状态。", `项目名称:${project.name}`, `线程名称:${project.threadMeta.threadDisplayName}`, `文件夹:${project.threadMeta.folderName}`, `同步原因:${reason === "heartbeat_activity" ? "检测到线程有新活动" : "线程刚刚产生了新的执行结果"}`, "", "先基于当前项目本地可见的开发文档和实际代码进行汇总,再回答。", "优先检查 README、docs、架构文档、版本记录和最近改动的关键代码文件;不要只依赖当前对话残留上下文。", "如果文档与代码不一致,以当前代码和最新开发文档为准。", "", "只输出 JSON,不要输出解释性文字或 Markdown。", "JSON 结构固定为:", '{ "projectGoal": "一句中文目标", "currentProgress": "一句中文进度", "technicalArchitecture": "一句中文架构说明", "currentBlockers": "一句中文阻塞说明", "recommendedNextStep": "一句中文建议动作", "versionRecord": "一句中文版本记录摘要" }', "", "要求:", "1. 只写当前项目最重要、对主 Agent 接手有帮助的事实。", "2. 不要重复内部字段、线程编号、目录路径、设备 ID。", "3. 如果某个字段暂时不清楚,填空字符串。", "4. versionRecord 只写本次同步最值得写入版本记录的一条变化;如果没有,填空字符串。", "5. 不要把全局 OTA 可用状态、设备在线状态、账号状态或运行时噪音当作项目目标/版本记录;除非它就是该线程本次实际交付的版本变化。", "6. 不要输出解释、注意事项、文档旧值对比或下一步清单,只输出上述 JSON。", ].join("\n"); } async function queueProjectUnderstandingSyncTask(input: { projectId: string; observedActivityAt: string; reason: "heartbeat_activity" | "thread_reply"; replyProjectId?: string; notifyOnCompletion?: boolean; requestedByAccount?: string; }, options?: { force?: boolean }) { const state = await readState(); const project = state.projects.find((item) => item.id === input.projectId); if (!project) { return null; } const existingTask = state.masterAgentTasks.find( (task) => task.projectId === "master-agent" && task.projectUnderstandingTargetProjectId === project.id && (task.status === "queued" || task.status === "running"), ); if (existingTask) { if (input.replyProjectId || input.notifyOnCompletion === true) { await mutateStateIfChanged((freshState) => { const freshTask = freshState.masterAgentTasks.find((item) => item.taskId === existingTask.taskId); if (!freshTask) { return { result: null, changed: false }; } let changed = false; const replyProjectId = input.replyProjectId?.trim(); if (replyProjectId && freshTask.projectUnderstandingReplyProjectId !== replyProjectId) { freshTask.projectUnderstandingReplyProjectId = replyProjectId; changed = true; } if ( input.notifyOnCompletion === true && freshTask.projectUnderstandingNotifyOnCompletion !== true ) { freshTask.projectUnderstandingNotifyOnCompletion = true; changed = true; } return { result: null, changed }; }); } return existingTask; } if ( options?.force !== true && !shouldQueueProjectUnderstandingSync(project, input.observedActivityAt, state, input.reason) ) { return null; } const requestedByAccount = input.requestedByAccount?.trim() || state.user.account || project.deviceIds[0] || PRIMARY_ADMIN_ACCOUNT; const task = await queueMasterAgentTask({ projectId: "master-agent", taskType: "conversation_reply", requestMessageId: randomToken("project-understanding"), requestText: `请同步项目《${project.name}》当前目标与进度`, executionPrompt: buildProjectUnderstandingSyncPrompt(project, input.reason), requestedBy: requestedByAccount, requestedByAccount, deviceId: project.deviceIds[0] || state.user.boundDeviceId || "mac-studio", targetProjectId: project.id, targetThreadId: project.threadMeta.threadId, targetThreadDisplayName: project.threadMeta.threadDisplayName, targetCodexThreadRef: project.threadMeta.codexThreadRef, targetCodexFolderRef: project.threadMeta.codexFolderRef, projectUnderstandingTargetProjectId: project.id, projectUnderstandingReason: input.reason, projectUnderstandingReplyProjectId: input.replyProjectId?.trim() || undefined, projectUnderstandingNotifyOnCompletion: input.notifyOnCompletion === true, }); await mutateStateIfChanged((freshState) => { const freshProject = freshState.projects.find((item) => item.id === input.projectId); if (!freshProject) { return { result: null, changed: false }; } freshProject.threadMeta.lastProjectUnderstandingRequestedAt = task.requestedAt; freshProject.threadMeta.lastObservedCodexActivityAt = latestIsoTimestamp( freshProject.threadMeta.lastObservedCodexActivityAt, input.observedActivityAt, ) ?? input.observedActivityAt; return { result: null, changed: true }; }); return task; } export async function forceProjectUnderstandingSyncTask(input: { projectId: string; observedActivityAt: string; reason: "heartbeat_activity" | "thread_reply"; }) { return queueProjectUnderstandingSyncTask(input, { force: true }); } 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() ? slugifyWithHash(`${device.id}-${candidate.codexFolderRef}-${candidate.codexThreadRef}`) : slugifyWithHash(`${device.id}-${candidate.folderName}-${candidate.threadId}`); const now = nowIso(); const importedActivityAt = candidate.lastActiveAt || now; return normalizeProject({ id: projectId, name: candidate.threadDisplayName, pinned: false, systemPinned: false, deviceIds: [device.id], preview: `已从 ${device.name} 导入线程 ${candidate.threadDisplayName}`, updatedAt: now, lastMessageAt: importedActivityAt, 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: importedActivityAt, 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", "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.threadMeta.lastObservedCodexActivityAt = candidate.lastActiveAt; targetProject.updatedAt = nowIso(); 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 shouldMirrorToMaster = shouldMirrorAppLogToMaster(payload); const sanitizedMessage = sanitizeSensitiveUserVisibleText(payload.message) ?? "已拦截内部执行日志,原始内容已隐藏。"; const sanitizedDetail = sanitizeSensitiveTaskFailureDetailForLog(payload.detail); const entry: AppLogEntry = { logId: randomToken("applog"), deviceId: payload.deviceId, projectId: payload.projectId, level: payload.level, source: payload.source, category: payload.category, message: sanitizedMessage, detail: sanitizedDetail, mirroredToProject: shouldMirrorToMaster, createdAt: nowIso(), }; state.appLogs.unshift(entry); let mirroredProjectId: string | undefined; if (shouldMirrorToMaster) { 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}] ${entry.message}${entry.detail ? `\n${entry.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 directProject = state.projects.find((item) => item.id === projectId); const folderProjects = directProject ? [] : state.projects.filter((item) => buildProjectFolderKey(item) === projectId); if (!directProject && folderProjects.length === 0) throw new Error("PROJECT_NOT_FOUND"); if (action === "toggle_pin") { if (directProject) { if (directProject.systemPinned) { throw new Error("MASTER_PROJECT_PIN_LOCKED"); } directProject.pinned = !directProject.pinned; } else { const folderLocked = folderProjects.some((item) => item.systemPinned); if (folderLocked) { throw new Error("MASTER_PROJECT_PIN_LOCKED"); } const folderPinned = folderProjects.some((item) => item.pinned || item.systemPinned); for (const item of folderProjects) { item.pinned = !folderPinned; } } } if (action === "mark_read") { if (directProject) { directProject.unreadCount = 0; } else { for (const item of folderProjects) { item.unreadCount = 0; } } } return directProject ?? folderProjects[0]; }); publishBossEvent("conversation.updated", { projectId }); return project; } function buildProjectFolderKey(project: Project) { if (project.id === "master-agent" || project.isGroup) return undefined; const deviceId = project.deviceIds[0]; const folderRef = (project.threadMeta.codexFolderRef?.trim() || project.threadMeta.folderName.trim()).toLowerCase(); if (!deviceId || !folderRef) return undefined; return `${deviceId}:${folderRef}`; } 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}个线程`; } type AppendProjectMessagePayload = { projectId: string; sender?: MessageSender; senderLabel?: string; body?: string; account?: string; kind?: MessageKind; attachments?: MessageAttachment[]; }; function appendProjectMessageInState( state: BossState, project: Project, payload: Omit, ) { 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 fallbackBody = 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" ? "已登记视频证据上传请求,等待对象存储通道接入。" : "已提交消息。"; const normalizedBody = sanitizeUserVisibleMessageText(body ?? fallbackBody) ?? fallbackBody; const message: Message = { id: randomToken("msg"), sender: payload.sender ?? "user", senderLabel: payload.senderLabel ?? "你", account: payload.account?.trim() || undefined, body: normalizedBody, sentAt: nowIso(), kind: payload.kind ?? "text", attachments: payload.attachments?.map((attachment) => normalizeMessageAttachment(attachment)), }; project.messages.push(message); project.unreadCount = 0; project.lastMessageAt = message.sentAt; if (isPreviewableMessageKind(message.kind)) { project.preview = message.body; } const shouldTrackThreadProgress = payload.sender === "device" && (payload.kind ?? "text") === "text" && isDispatchableThreadProject(project) && Boolean(project.threadMeta.codexThreadRef?.trim()); if (shouldTrackThreadProgress) { project.threadMeta.lastObservedCodexActivityAt = latestIsoTimestamp( project.threadMeta.lastObservedCodexActivityAt, message.sentAt, ) ?? message.sentAt; appendThreadProgressEventInState(state, { projectId: project.id, threadId: project.threadMeta.threadId, threadDisplayName: project.threadMeta.threadDisplayName, deviceId: project.deviceIds[0] ?? project.id, eventType: "progress_updated", summary: summarizeThreadReplyBody(message.body), phase: project.projectUnderstanding ? "增量同步" : "线程回复", createdAt: message.sentAt, sourceTaskId: message.id, sourceMessageId: message.id, }); } return { message, shouldQueueUnderstandingSync: shouldTrackThreadProgress && shouldQueueProjectUnderstandingSync(project, message.sentAt, state, "thread_reply"), }; } export async function appendProjectMessages(payload: { projectId: string; messages: Array>; }) { const result = await mutateState((state) => { const project = state.projects.find((item) => item.id === payload.projectId); if (!project) throw new Error("PROJECT_NOT_FOUND"); const appended = payload.messages.map((messagePayload) => appendProjectMessageInState(state, project, messagePayload), ); return { messages: appended.map((item) => item.message), shouldQueueUnderstandingSync: appended.some((item) => item.shouldQueueUnderstandingSync), }; }); if (result.shouldQueueUnderstandingSync) { await queueProjectUnderstandingSyncTask({ projectId: payload.projectId, observedActivityAt: result.messages.at(-1)?.sentAt ?? nowIso(), reason: "thread_reply", }); } publishBossEvent("project.messages.updated", { projectId: payload.projectId }); publishBossEvent("conversation.updated", { projectId: payload.projectId }); return result.messages; } export async function appendProjectMessage(payload: AppendProjectMessagePayload) { const [message] = await appendProjectMessages({ projectId: payload.projectId, messages: [ { sender: payload.sender, senderLabel: payload.senderLabel, account: payload.account, body: payload.body, kind: payload.kind, attachments: payload.attachments, }, ], }); if (!message) { throw new Error("MESSAGE_NOT_CREATED"); } return message; } export async function deleteProjectMessage(payload: { projectId: string; messageId: string; }) { const result = await mutateState((state) => { const project = state.projects.find((item) => item.id === payload.projectId); if (!project) { throw new Error("PROJECT_NOT_FOUND"); } const messageIndex = project.messages.findIndex((message) => message.id === payload.messageId); if (messageIndex < 0) { throw new Error("MESSAGE_NOT_FOUND"); } const [deletedMessage] = project.messages.splice(messageIndex, 1); if (deletedMessage && shouldCountAsUnreadMessage(deletedMessage)) { project.unreadCount = Math.max(0, (project.unreadCount ?? 0) - 1); } state.threadProgressEvents = state.threadProgressEvents.filter( (event) => event.sourceMessageId !== payload.messageId, ); return { projectId: project.id, deletedMessage, remainingCount: project.messages.length, }; }); publishBossEvent("project.messages.updated", { projectId: payload.projectId }); publishBossEvent("conversation.updated", { projectId: payload.projectId }); return result; } export async function requestProjectUnderstandingSyncForProject(input: { projectId: string; observedActivityAt?: string; reason?: "heartbeat_activity" | "thread_reply"; replyProjectId?: string; notifyOnCompletion?: boolean; requestedByAccount?: string; }) { return queueProjectUnderstandingSyncTask( { projectId: input.projectId, observedActivityAt: input.observedActivityAt ?? nowIso(), reason: input.reason ?? "thread_reply", replyProjectId: input.replyProjectId, notifyOnCompletion: input.notifyOnCompletion, requestedByAccount: input.requestedByAccount, }, { force: true }, ); } export function hasProjectSummarySyncNotifyPreferenceInState( state: BossState, account: string, ) { return state.masterAgentMemories.some( (memory) => memory.account === account && memory.scope === "global" && memory.memoryType === "workflow_rule" && /项目摘要同步完成提醒/.test(memory.title) && /同步项目目标和版本记录/.test(memory.content), ); } export async function rememberProjectSummarySyncNotifyPreference(input: { account: string; sourceMessageId?: string; }) { await mutateState((state) => { upsertAutoMasterMemoryInState(state, { account: input.account, scope: "global", title: "项目摘要同步完成提醒", content: "当我让主 Agent 同步项目目标和版本记录时,同步完成后要在发起请求的会话里主动回我一条确认消息,并说明已写回顶部入口。", memoryType: "workflow_rule", tags: ["项目摘要", "回执", "工作方式"], sourceMessageId: input.sourceMessageId, }); }); publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" }); } 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("settings.updated"); 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) { const ticket = await 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; }); publishBossEvent("project.context_risk.updated", { status: "approved", note: ticketId, }); return ticket; } export async function verifyRepairTicket(ticketId: string) { const ticket = await 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; }); publishBossEvent("project.context_risk.updated", { status: "verified", note: ticketId, }); return ticket; } export type AdminRiskAction = "ack" | "resolve" | "create_repair_ticket" | "assign_owner" | "set_sla"; function autoRecoverMasterAgentTasksInState( state: BossState, input: { actorAccount: string; recoveredAt: string }, ) { const recovered: MasterAgentTask[] = []; const recoveredAtMs = Date.parse(input.recoveredAt); const nowMs = Number.isFinite(recoveredAtMs) ? recoveredAtMs : Date.now(); for (const task of state.masterAgentTasks) { if (!isMasterAgentTaskAutoRecoverable(task, nowMs)) { continue; } const slaRow = buildMasterAgentTaskSlaRow(state, task, new Date(nowMs)); task.status = "queued"; task.phase = "queued"; task.claimedAt = undefined; task.lastClaimedAt = undefined; task.leaseExpiresAt = undefined; task.lastProgressAt = input.recoveredAt; task.completedAt = undefined; task.canceledAt = undefined; task.canceledBy = undefined; task.cancelReason = undefined; task.lastErrorKind = undefined; task.lastErrorCode = undefined; task.errorMessage = undefined; task.recoverable = false; task.nextRetryAt = undefined; upsertTaskExecutionProgressMessageInState(state, task, "queued", { phase: "queued" }); recovered.push({ ...task }); state.adminRiskTimeline.unshift( normalizeAdminRiskTimelineEvent({ riskId: slaRow.riskId, notificationId: slaRow.notificationId, companyId: slaRow.companyId, action: "task.auto_recovery_requeued", actorAccount: input.actorAccount, note: `自动恢复任务:${task.taskId}`, createdAt: input.recoveredAt, }), ); state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "master_agent.task_retried", projectId: task.projectId, deviceId: task.deviceId, detail: `后台 SLA 扫描自动重排队:${task.taskId}`, requestId: task.taskId, createdAt: input.recoveredAt, afterJson: { taskId: task.taskId, phase: task.phase, status: task.status, source: "admin_risk_scan", }, }), ); } if (recovered.length > 0) { state.adminRiskTimeline = state.adminRiskTimeline.slice(0, 1000); state.permissionAuditLogs = state.permissionAuditLogs.slice(0, 500); } return recovered; } export async function scanAdminRiskNotifications(input: { actorAccount: string; now?: string; }) { const result = await mutateState((state) => { const scannedAt = input.now ? new Date(input.now) : new Date(); const openFaultKeys = new Set( state.opsFaults .filter((fault) => fault.status !== "resolved") .map((fault) => `${fault.faultKey}:${fault.nodeId}:${fault.serviceName}`), ); const createdFaults = buildOperationalRiskFaultDrafts(state, scannedAt).filter((fault) => { const key = `${fault.faultKey}:${fault.nodeId}:${fault.serviceName}`; if (openFaultKeys.has(key)) return false; openFaultKeys.add(key); return true; }); if (createdFaults.length > 0) { state.opsFaults = [...createdFaults, ...state.opsFaults].slice(0, 500); state.adminRiskTimeline = [ ...createdFaults.map((fault) => normalizeAdminRiskTimelineEvent({ riskId: `ops-fault:${fault.faultId}`, action: "risk.created", actorAccount: input.actorAccount, note: fault.summary, createdAt: scannedAt.toISOString(), }), ), ...state.adminRiskTimeline, ].slice(0, 1000); } const drafts = buildRiskSlaNotificationDrafts(state, scannedAt); const existingIds = new Set(state.adminNotifications.map((item) => item.notificationId)); const created = drafts .filter((draft) => !existingIds.has(draft.notificationId)) .map(normalizeAdminNotification); const autoRecovered = autoRecoverMasterAgentTasksInState(state, { actorAccount: input.actorAccount, recoveredAt: scannedAt.toISOString(), }); if (created.length > 0) { state.adminNotifications = [...created, ...state.adminNotifications].slice(0, 500); state.adminRiskTimeline = [ ...created.map((notification) => normalizeAdminRiskTimelineEvent({ riskId: notification.riskId, notificationId: notification.notificationId, companyId: notification.companyId, action: "notification_created", actorAccount: input.actorAccount, note: notification.title, createdAt: scannedAt.toISOString(), }), ), ...state.adminRiskTimeline, ].slice(0, 1000); state.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "risk.notification.created", permissions: ["audit.view"], detail: `created:${created.length}`, createdAt: nowIso(), }), ); } return { createdFaults, created, autoRecovered, notifications: state.adminNotifications .filter((notification) => notification.status === "open") .sort((left, right) => right.createdAt.localeCompare(left.createdAt)), }; }); if (result.created.length > 0 || result.createdFaults.length > 0 || result.autoRecovered.length > 0) { publishBossEvent("project.context_risk.updated", { status: "risk_notification_created", note: `notifications:${result.created.length};faults:${result.createdFaults.length};autoRecovered:${result.autoRecovered.length}`, }); } for (const task of result.autoRecovered) { publishBossEvent("master_agent.task.updated", { taskId: task.taskId, deviceId: task.deviceId, status: task.status, }); const progressProjectId = resolveTaskExecutionProgressProjectId(task); if (progressProjectId) { publishBossEvent("project.messages.updated", { projectId: progressProjectId }); publishBossEvent("conversation.updated", { projectId: progressProjectId }); } } return result; } export async function dispatchAdminRiskNotifications(input: { actorAccount: string; max?: number; }) { const state = await readState(); const candidates = state.adminNotifications .filter((notification) => notification.status === "open" && notification.deliveryStatus !== "sent" && notification.deliveryStatus !== "disabled", ) .slice(0, input.max ?? 20); const results: AdminNotificationDeliveryResult[] = []; for (const notification of candidates) { results.push(await deliverAdminNotification(state, notification)); } if (results.length === 0) { return { results: [] }; } await mutateState((nextState) => { const byId = new Map(results.map((result) => [result.notificationId, result])); for (const notification of nextState.adminNotifications) { const result = byId.get(notification.notificationId); if (!result) continue; notification.deliveryStatus = result.status; notification.deliveryTarget = result.target; notification.deliveredAt = result.deliveredAt; notification.deliveryError = result.status === "failed" ? result.message : undefined; nextState.adminRiskTimeline.unshift( normalizeAdminRiskTimelineEvent({ riskId: notification.riskId, notificationId: notification.notificationId, companyId: notification.companyId, action: `notification_dispatch_${result.status}`, actorAccount: input.actorAccount, note: result.message, createdAt: result.deliveredAt, }), ); } nextState.adminRiskTimeline = nextState.adminRiskTimeline.slice(0, 1000); nextState.permissionAuditLogs.unshift( normalizePermissionAuditLog({ auditId: randomToken("audit"), actorAccount: input.actorAccount, action: "risk.notification.dispatched", permissions: ["audit.view"], detail: `dispatched:${results.length}`, createdAt: nowIso(), }), ); }); return { results }; } export async function handleAdminRiskAction(input: { riskId: string; action: AdminRiskAction; actorAccount: string; ownerAccount?: string; slaDueAt?: string; note?: string; }) { const result = await mutateState((state) => { const now = nowIso(); const [kind, targetId] = input.riskId.split(":", 2); if (!targetId) { throw new Error("RISK_ID_INVALID"); } if (kind === "ops-fault") { const fault = state.opsFaults.find((item) => item.faultId === targetId); if (!fault) { throw new Error("RISK_TARGET_NOT_FOUND"); } if (input.action === "ack") { fault.status = "acked"; fault.lastSeenAt = now; return { fault: { ...fault } }; } if (input.action === "resolve") { fault.status = "resolved"; fault.lastSeenAt = now; return { fault: { ...fault } }; } if (input.action === "create_repair_ticket") { const existing = state.opsRepairTickets.find((ticket) => ticket.faultId === fault.faultId); if (existing) { return { fault: { ...fault }, ticket: { ...existing } }; } const ticket: OpsRepairTicket = { ticketId: randomToken("repair-ticket"), faultId: fault.faultId, title: `修复 ${fault.faultKey}`, approvalStatus: "pending", executionStatus: "queued", requestedBy: input.actorAccount, targetNodeId: fault.nodeId, actionSummary: fault.suggestedNextAction || fault.summary, createdAt: now, updatedAt: now, }; state.opsRepairTickets.unshift(ticket); fault.status = "repairing"; fault.lastSeenAt = now; return { fault: { ...fault }, ticket: { ...ticket } }; } if (input.action === "assign_owner") { const ownerAccount = trimToDefined(input.ownerAccount); if (!ownerAccount) throw new Error("RISK_OWNER_REQUIRED"); fault.ownerAccount = ownerAccount; fault.riskNote = trimToDefined(input.note) ?? fault.riskNote; fault.lastSeenAt = now; return { fault: { ...fault } }; } if (input.action === "set_sla") { const slaDueAt = trimToDefined(input.slaDueAt); if (!slaDueAt) throw new Error("RISK_SLA_REQUIRED"); fault.slaDueAt = slaDueAt; fault.riskNote = trimToDefined(input.note) ?? fault.riskNote; fault.lastSeenAt = now; return { fault: { ...fault } }; } } if (kind === "thread-alert") { const alert = state.threadContextAlerts.find((item) => item.alertId === targetId); if (!alert) { throw new Error("RISK_TARGET_NOT_FOUND"); } if (input.action === "ack") { alert.alertStatus = "acked"; return { alert: { ...alert } }; } if (input.action === "resolve") { alert.alertStatus = "resolved"; alert.resolvedAt = now; return { alert: { ...alert } }; } if (input.action === "assign_owner") { const ownerAccount = trimToDefined(input.ownerAccount); if (!ownerAccount) throw new Error("RISK_OWNER_REQUIRED"); alert.ownerAccount = ownerAccount; alert.riskNote = trimToDefined(input.note) ?? alert.riskNote; return { alert: { ...alert } }; } if (input.action === "set_sla") { const slaDueAt = trimToDefined(input.slaDueAt); if (!slaDueAt) throw new Error("RISK_SLA_REQUIRED"); alert.slaDueAt = slaDueAt; alert.riskNote = trimToDefined(input.note) ?? alert.riskNote; return { alert: { ...alert } }; } } throw new Error("RISK_ACTION_UNSUPPORTED"); }); await mutateState((state) => { const now = nowIso(); for (const notification of state.adminNotifications) { if (notification.riskId !== input.riskId || notification.status === "resolved") { continue; } if (input.action === "ack") { notification.status = "acknowledged"; notification.acknowledgedAt = now; notification.acknowledgedBy = input.actorAccount; } if (input.action === "resolve") { notification.status = "resolved"; } } state.adminRiskTimeline.unshift( normalizeAdminRiskTimelineEvent({ riskId: input.riskId, notificationId: state.adminNotifications.find((notification) => notification.riskId === input.riskId)?.notificationId, companyId: state.adminNotifications.find((notification) => notification.riskId === input.riskId)?.companyId, action: `risk_${input.action}`, actorAccount: input.actorAccount, note: input.note || input.ownerAccount || input.slaDueAt, createdAt: now, }), ); state.adminRiskTimeline = state.adminRiskTimeline.slice(0, 1000); }); const eventPayload = { status: input.action === "ack" ? "acked" : input.action === "resolve" ? "resolved" : input.action === "assign_owner" ? "assigned" : input.action === "set_sla" ? "sla_updated" : "repair_ticket_created", note: input.riskId, projectId: result.fault?.projectId ?? result.alert?.projectId, deviceId: result.fault?.nodeId, }; publishBossEvent("project.context_risk.updated", eventPayload); return result; }