Files
boss/src/lib/boss-data.ts

9192 lines
312 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { existsSync, readFileSync, statSync } from "node:fs";
import { promises as fs } from "node:fs";
import path from "node:path";
import { publishBossEvent } from "@/lib/boss-events";
import type { VerificationDeliveryMode } from "@/lib/boss-mail";
import { getFixedVerificationCode, getVerificationDeliveryMode } from "@/lib/boss-mail";
import { getPublishedOtaAsset } from "@/lib/boss-ota";
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";
export type DeviceStatus = "online" | "abnormal" | "offline";
export type DeviceSource = "production" | "demo";
export type GoalState = "pending" | "completed";
export type MessageSender = "master" | "device" | "user" | "ops" | "audit";
// Forwarding uses a structured contract so the route can distinguish
// single-message forwarding, bundle forwarding, and the legacy notice shape.
export type MessageKind =
| "text"
| "system_notice"
| "voice_intent"
| "image_intent"
| "video_intent"
| "forward_notice"
| "forward_single"
| "forward_bundle"
| "attachment"
| "analysis_card";
export type AttachmentKind = "image" | "video" | "pdf" | "text" | "office" | "binary";
export type AttachmentStorageBackend = "server_file" | "aliyun_oss";
export type AttachmentAnalysisState =
| "not_applicable"
| "queued_auto"
| "ready_manual"
| "processing"
| "completed"
| "failed";
export interface MessageAttachment {
attachmentId: string;
fileName: string;
mimeType: string;
fileSizeBytes: number;
attachmentKind: AttachmentKind;
storageBackend: AttachmentStorageBackend;
storagePath: string;
storageSnapshot?: {
provider: "aliyun_oss";
accessKeyId: string;
accessKeySecretEncrypted: string;
bucket: string;
endpoint: string;
region: string;
prefix?: string;
};
previewAvailable: boolean;
uploadedAt: string;
uploadedBy: string;
analysisState: AttachmentAnalysisState;
analysisSummary?: string;
analysisCardId?: string;
}
export interface ForwardSource {
sourceProjectId: string;
sourceProjectName: string;
sourceThreadId?: string;
sourceThreadTitle?: string;
sourceMessageId: string;
forwardedBy: string;
forwardedAt: string;
}
export interface ForwardBundleItem {
messageId: string;
senderLabel: string;
body: string;
kind: string;
sentAt: string;
}
export interface ForwardBundlePayload {
sourceProjectId: string;
sourceProjectName: string;
sourceThreadId?: string;
sourceThreadTitle?: string;
itemCount: number;
startedAt: string;
endedAt: string;
items: ForwardBundleItem[];
}
export type ContextBudgetLevel = "safe" | "watch" | "urgent" | "critical";
export type ThreadState =
| "idle"
| "running"
| "waiting_input"
| "waiting_approval"
| "context_watch"
| "context_urgent"
| "compacted"
| "handoff_pending"
| "completed"
| "failed";
export type RiskLevel = "low" | "medium" | "high";
export type AlertStatus = "opened" | "acked" | "resolved";
export type HandoffStatus = "draft" | "ready" | "consumed" | "expired";
export type OpsSeverity = "info" | "warning" | "critical";
export type OpsStatus = "opened" | "acked" | "repairing" | "resolved";
export type ApprovalStatus = "pending" | "approved" | "escalated";
export type ExecutionStatus = "queued" | "running" | "verified" | "failed";
export type VerificationStatus = "passed" | "failed" | "watching";
export type EnrollmentStatus = "ready" | "claimed" | "expired";
export type AuditTrigger =
| "milestone"
| "merge_candidate"
| "failure_escalation"
| "scheduled_check"
| "human_request";
export type AuditType = "software" | "hardware" | "multimodal" | "chief";
export type AuditResultStatus =
| "completed"
| "failed"
| "needs_retry"
| "needs_human_review";
export type AuditDecision = "pass" | "fail" | "warning" | "inconclusive";
export type CapabilityLeaseMode = "exclusive" | "shared_read" | "shared_stream";
export type AuthRole = "member" | "admin" | "highest_admin";
export type LoginMethod = "password" | "code";
export type OtaUpdateStatus = "available" | "scheduled" | "applied" | "skipped";
export type OtaLogStatus = "checked" | "applied" | "skipped";
export type AppLogLevel = "info" | "warn" | "error";
export type AiProvider = "master_codex_node" | "openai_api" | "aliyun_qwen_api";
export type AiAccountRole = "primary" | "backup" | "api_fallback";
export type AiAccountStatus = "ready" | "needs_login" | "needs_api_key" | "degraded" | "disabled";
export type MasterAgentTaskStatus = "queued" | "running" | "completed" | "failed";
export type MasterAgentTaskType =
| "conversation_reply"
| "attachment_analysis"
| "group_dispatch_plan"
| "dispatch_execution"
| "device_import_resolution";
export type DispatchPlanStatus =
| "pending_user_confirmation"
| "approved"
| "rejected"
| "dispatched";
export type DispatchExecutionStatus = "queued" | "running" | "completed" | "failed";
export type ReasoningEffort = "low" | "medium" | "high";
export type OrchestrationBackendId = import("@/lib/execution/orchestration-backend").OrchestrationBackendId;
export type OrchestrationBackendOverride = "omx-team";
export interface UserSettings {
liveUpdates: boolean;
showRiskBadges: boolean;
confirmDangerousActions: boolean;
preferredEntryPoint: "conversations" | "devices" | "me";
}
export interface UserProfile {
id: string;
name: string;
avatar: string;
account: string;
verificationEmail?: string;
role: AuthRole;
roleLabel: string;
accountType: string;
qrCodeValue: string;
boundCodexNodeId?: string;
boundCodexNodeLabel?: string;
boundDeviceId?: string;
boundAt?: string;
version: string;
otaVersion?: string;
otaSummary?: string[];
hasOta: boolean;
settings: UserSettings;
}
export interface Device {
id: string;
name: string;
avatar: string;
account: string;
source: DeviceSource;
status: DeviceStatus;
projects: string[];
quota5h: number;
quota7d: number;
lastSeenAt: string;
endpoint?: string;
token?: string;
note?: string;
}
export interface Message {
id: string;
sender: MessageSender;
senderLabel: string;
body: string;
sentAt: string;
kind?: MessageKind;
attachments?: MessageAttachment[];
forwardSource?: ForwardSource;
forwardBundle?: ForwardBundlePayload;
}
export interface UserAttachmentStorageConfig {
account: string;
mode: "server_file" | "oss";
ossProvider?: "aliyun_oss";
aliyunOss?: {
enabled: boolean;
accessKeyId: string;
accessKeySecretEncrypted: string;
bucket: string;
endpoint: string;
region: string;
prefix?: string;
};
updatedAt: string;
validatedAt?: string;
}
export interface MasterAgentPromptPolicy {
globalPrompt: string;
updatedAt: string;
updatedBy?: string;
}
export interface UserMasterPrompt {
account: string;
content: string;
updatedAt: string;
}
export type MasterMemoryScope = "global" | "project";
export type MasterMemoryType =
| "user_preference"
| "project_progress"
| "decision"
| "risk"
| "blocking_issue"
| "research_note"
| "workflow_rule";
export interface MasterAgentMemory {
memoryId: string;
account: string;
scope: MasterMemoryScope;
projectId?: string;
title: string;
content: string;
memoryType: MasterMemoryType;
tags: string[];
sourceMessageId?: string;
createdAt: string;
updatedAt: string;
lastUsedAt?: string;
archived: boolean;
}
export interface GoalItem {
id: string;
text: string;
state: GoalState;
note: string;
completedAt?: string;
completedBy?: string;
}
export interface VersionEntry {
version: string;
summary: string;
createdAt: string;
}
export interface ThreadConversationMeta {
projectId: string;
threadId: string;
threadDisplayName: string;
folderName: string;
activityIconCount: number;
updatedAt: string;
lastObservedCodexActivityAt?: string;
lastProjectUnderstandingRequestedAt?: string;
lastProjectUnderstandingSyncedAt?: string;
codexThreadRef?: string;
codexFolderRef?: string;
}
export interface GroupConversationMember {
projectId: string;
deviceId: string;
threadId: string;
threadDisplayName: string;
folderName: string;
}
export interface Project {
id: string;
name: string;
pinned: boolean;
systemPinned?: boolean;
deviceIds: string[];
preview: string;
updatedAt: string;
lastMessageAt: string;
isGroup: boolean;
threadMeta: ThreadConversationMeta;
groupMembers: GroupConversationMember[];
createdByAgent: boolean;
collaborationMode: "development" | "approval_required";
approvalState: "not_required" | "pending_agent" | "pending_user" | "approved" | "rejected";
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<Project, "isGroup" | "collaborationMode" | "approvalState" | "lightDispatchReminderEnabled">,
) {
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;
promptOverride?: string;
backendOverride?: "claw-runtime";
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;
}
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;
verificationEmail?: string;
codexNodeId?: string;
codexNodeLabel?: string;
primaryDeviceId?: string;
isPrimary?: boolean;
failedLoginAttempts?: number;
lockedUntil?: string;
lastLoginAt?: string;
lastLoginMethod?: LoginMethod;
createdAt: string;
updatedAt: string;
}
export interface AuthSession {
sessionId: string;
sessionToken: string;
restoreToken: string;
account: string;
role: AuthRole;
displayName: string;
loginMethod: LoginMethod;
createdAt: string;
expiresAt: string;
lastSeenAt: string;
revokedAt?: string;
}
export interface AiAccount {
accountId: string;
label: string;
role: AiAccountRole;
provider: AiProvider;
displayName: string;
accountIdentifier?: string;
nodeId?: string;
nodeLabel?: string;
model?: string;
apiKey?: string;
apiKeyMasked?: string;
enabled: boolean;
isActive: boolean;
status: AiAccountStatus;
loginStatusNote?: string;
lastValidatedAt?: string;
lastUsedAt?: string;
lastError?: string;
lastSwitchedAt?: string;
switchReason?: string;
createdAt: string;
updatedAt: string;
}
export interface AiAccountSwitchRecord {
switchId: string;
fromAccountId?: string;
fromLabel?: string;
toAccountId: string;
toLabel: string;
role: AiAccountRole;
switchedAt: string;
reason: string;
}
export interface AiAccountSummary {
accountId: string;
label: string;
role: AiAccountRole;
roleLabel: string;
provider: AiProvider;
providerLabel: string;
displayName: string;
accountIdentifier?: string;
nodeId?: string;
nodeLabel?: string;
model?: string;
enabled: boolean;
isActive: boolean;
canGenerate: boolean;
status: AiAccountStatus;
statusLabel: string;
loginStatusNote?: string;
apiKeyConfigured: boolean;
apiKeyMasked?: string;
lastValidatedAt?: string;
lastUsedAt?: string;
lastError?: string;
lastSwitchedAt?: string;
switchReason?: string;
createdAt: string;
updatedAt: string;
isEnvironmentFallback?: boolean;
}
export interface MasterIdentitySummary {
accountId?: string;
label: string;
role: AiAccountRole;
roleLabel: string;
provider: AiProvider;
providerLabel: string;
displayName: string;
nodeLabel?: string;
model?: string;
status: AiAccountStatus;
statusLabel: string;
canGenerate: boolean;
switchReason?: string;
lastSwitchedAt?: string;
note?: string;
isEnvironmentFallback?: boolean;
}
export interface MasterAgentTask {
taskId: string;
projectId: string;
taskType: MasterAgentTaskType;
requestMessageId: string;
requestText: string;
executionPrompt: string;
requestedBy: string;
requestedByAccount: string;
deviceId: string;
accountId?: string;
accountLabel?: string;
attachmentId?: string;
attachmentFileName?: string;
attachmentDownloadToken?: string;
attachmentDownloadExpiresAt?: string;
attachmentDownloadUrl?: string;
attachmentTextExcerpt?: string;
dispatchExecutionId?: string;
targetProjectId?: string;
targetThreadId?: string;
targetThreadDisplayName?: string;
targetCodexThreadRef?: string;
targetCodexFolderRef?: string;
orchestrationBackendId?: OrchestrationBackendId;
orchestrationBackendLabel?: string;
deviceImportDraftId?: string;
deviceImportCandidateId?: string;
deviceImportCandidateFolderName?: string;
projectUnderstandingTargetProjectId?: string;
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply";
status: MasterAgentTaskStatus;
requestedAt: string;
claimedAt?: string;
completedAt?: string;
replyBody?: string;
errorMessage?: string;
requestId?: string;
}
export interface OtaUpdate {
releaseId: string;
version: string;
currentVersion: string;
channel: "stable" | "beta";
packageType: "web_console" | "android_shell";
status: OtaUpdateStatus;
summary: string[];
targetScope: string;
requiredRole: AuthRole;
publishedAt: string;
packageFileName?: string;
packageSizeBytes?: number;
packageSha256?: string;
downloadUrl?: string;
assetUpdatedAt?: string;
}
export interface OtaUpdateLog {
logId: string;
releaseId: string;
version: string;
status: OtaLogStatus;
triggeredBy: string;
triggeredAt: string;
completedAt?: string;
note: string;
}
export interface DeviceSkill {
skillId: string;
deviceId: string;
name: string;
description: string;
path: string;
invocation: string;
category: string;
updatedAt: string;
}
export interface AppLogEntry {
logId: string;
deviceId: string;
projectId?: string;
level: AppLogLevel;
source: "app_client" | "local_agent";
category: string;
message: string;
detail?: string;
mirroredToProject: boolean;
createdAt: string;
}
export interface ThreadContextSnapshot {
snapshotId: string;
projectId: string;
taskId: string;
threadId: string;
title: string;
summary: string;
nodeId: string;
workerId: string;
sourceKind: "codex_app_server" | "codex_sdk" | "worker_estimator";
status: ThreadState;
contextBudgetRemainingPct: number;
contextBudgetLevel: ContextBudgetLevel;
compactionExpectedAt?: string;
mustFinishBeforeCompaction: boolean;
estimatedRemainingTurns: number;
estimatedRemainingLargeMessages: number;
lastCompactionAt?: string;
compactionCount: number;
patchPending: boolean;
testsPending: boolean;
evidencePending: boolean;
checklist: string[];
capturedAt: string;
}
export interface ThreadHandoffPackage {
handoffPackageId: string;
projectId: string;
taskId: string;
fromThreadId: string;
toThreadId: string;
packageStatus: HandoffStatus;
summaryText: string;
openQuestions: string[];
criticalFiles: string[];
criticalCommands: string[];
criticalTests: string[];
criticalArtifacts: string[];
decisionLinks: string[];
createdAt: string;
readyAt?: string;
consumedAt?: string;
}
export interface ThreadContextAlert {
alertId: string;
threadId: string;
projectId: string;
alertType:
| "context_watch"
| "context_urgent"
| "context_critical"
| "compaction_risk"
| "handoff_missing";
alertStatus: AlertStatus;
openedAt: string;
resolvedAt?: string;
summary: string;
masterActions: string[];
}
export interface DeviceEnrollment {
enrollmentId: string;
deviceId: string;
label: string;
pairingCode: string;
token: string;
status: EnrollmentStatus;
note: string;
createdAt: string;
expiresAt: string;
claimedAt?: string;
claimedDeviceId?: string;
}
export interface OpsFault {
faultId: string;
faultKey: string;
severity: OpsSeverity;
status: OpsStatus;
nodeId: string;
serviceName: string;
projectId?: string;
threadRef?: string;
traceId: string;
runbookId: string;
firstSeenAt: string;
lastSeenAt: string;
summary: string;
suggestedNextAction: string;
autoRepairable: boolean;
}
export interface OpsRepairTicket {
ticketId: string;
faultId: string;
title: string;
approvalStatus: ApprovalStatus;
executionStatus: ExecutionStatus;
requestedBy: string;
approvedBy?: string;
targetNodeId: string;
actionSummary: string;
resultSummary?: string;
createdAt: string;
updatedAt: string;
}
export interface OpsRepairVerification {
verificationId: string;
ticketId: string;
verifier: string;
status: VerificationStatus;
summary: string;
verifiedAt: string;
}
export interface CapabilityRequirement {
capabilityType: string;
mode: CapabilityLeaseMode;
}
export interface Capability {
capabilityId: string;
providerId: string;
nodeId: string;
capabilityType: string;
displayName: string;
status: "online" | "offline" | "busy";
healthStatus: "healthy" | "warning" | "critical";
leaseMode: CapabilityLeaseMode;
preemptible: boolean;
supportedActions: string[];
evidenceModes: string[];
}
export interface AuditTaskRequest {
protocolVersion: string;
auditRequestId: string;
projectId: string;
projectName: string;
taskId: string;
sourceThreadRef: string;
trigger: AuditTrigger;
auditType: AuditType;
priority: number;
objective: string;
systemContextSummary: string;
acceptanceCriteria: string[];
riskFocus: string[];
evidenceRefs: string[];
artifactRefs: string[];
capabilityRequirements: CapabilityRequirement[];
timeBudgetSeconds: number;
responseMode: string;
createdAt: string;
metadataJson: Record<string, unknown>;
}
export interface AuditTaskResult {
auditRequestId: string;
auditType: AuditType;
status: AuditResultStatus;
decision: AuditDecision;
confidence: number;
summary: string;
findings: string[];
requiredActions: string[];
usedCapabilities: string[];
artifactRefs: string[];
timeline: string[];
durationMs: number;
completedAt: string;
}
export interface BossState {
user: UserProfile;
devices: Device[];
projects: Project[];
verificationCodes: VerificationCode[];
verificationDispatches: VerificationDispatch[];
authAccounts: AuthAccount[];
authSessions: AuthSession[];
aiAccounts: AiAccount[];
aiAccountSwitchHistory: AiAccountSwitchRecord[];
masterAgentTasks: MasterAgentTask[];
dispatchPlans: DispatchPlan[];
dispatchExecutions: DispatchExecution[];
deviceImportDrafts: DeviceImportDraft[];
deviceImportResolutions: DeviceImportResolution[];
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[];
}
function detectRuntimeRoot(startDir: string) {
let current = startDir;
while (true) {
if (existsSync(path.join(current, "package.json")) && existsSync(path.join(current, "src", "app"))) {
return current;
}
const parent = path.dirname(current);
if (parent === current) {
return startDir;
}
current = parent;
}
}
function resolveRuntimeRoot() {
if (process.env.BOSS_RUNTIME_ROOT?.trim()) {
return path.resolve(process.env.BOSS_RUNTIME_ROOT);
}
if (process.env.BOSS_STATE_FILE?.trim()) {
return path.dirname(path.dirname(path.resolve(process.env.BOSS_STATE_FILE)));
}
return detectRuntimeRoot(/* turbopackIgnore: true */ process.cwd());
}
const runtimeRoot = resolveRuntimeRoot();
const dataFile = process.env.BOSS_STATE_FILE
? path.resolve(process.env.BOSS_STATE_FILE)
: path.join(runtimeRoot, "data", "boss-state.json");
const dataDir = path.dirname(dataFile);
const backupFile = `${dataFile}.bak`;
const publishedApkPath = path.join(runtimeRoot, "public", "downloads", "boss-android-latest.apk");
const publishedApkMetaPath = path.join(runtimeRoot, "public", "downloads", "boss-android-latest.json");
const defaultSettings: UserSettings = {
liveUpdates: true,
showRiskBadges: true,
confirmDangerousActions: true,
preferredEntryPoint: "conversations",
};
const PRIMARY_ADMIN_ACCOUNT = "17600003315";
const PRIMARY_ADMIN_PASSWORD = "boss123456";
const PRIMARY_ADMIN_VERIFICATION_EMAIL = "verify@boss.hyzq.net";
const PRIMARY_CODEX_NODE_ID = "mac-studio";
const PRIMARY_CODEX_NODE_LABEL = "本机 Codex · Mac Studio";
const VERIFICATION_SEND_COOLDOWN_MS = 60_000;
const VERIFICATION_SEND_WINDOW_MS = 15 * 60_000;
const VERIFICATION_SEND_WINDOW_LIMIT = 5;
export const AUTH_SESSION_TTL_MS = 30 * 24 * 60 * 60_000;
const AUTH_LOGIN_LOCK_THRESHOLD = 5;
const AUTH_LOGIN_LOCK_MS = 10 * 60_000;
const ENV_OPENAI_ACCOUNT_ID = "env-openai-api";
function baseThreadChecklist(labels: string[]) {
return labels;
}
const initialState: BossState = {
user: {
id: "user-boss-admin",
name: "Boss 超级管理员",
avatar: "17",
account: PRIMARY_ADMIN_ACCOUNT,
verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL,
role: "highest_admin",
roleLabel: "最高管理员",
accountType: "最高管理员 · 本机 Codex 已绑定",
qrCodeValue: `boss://user/${PRIMARY_ADMIN_ACCOUNT}`,
boundCodexNodeId: PRIMARY_CODEX_NODE_ID,
boundCodexNodeLabel: PRIMARY_CODEX_NODE_LABEL,
boundDeviceId: PRIMARY_CODEX_NODE_ID,
boundAt: "2026-03-26T09:00:00+08:00",
version: "1.4.0",
otaVersion: "v1.4.1",
otaSummary: ["新增登录会话守卫", "新增验证码防重放", "开放 APK 下载 OTA 包元数据"],
hasOta: true,
settings: defaultSettings,
},
devices: [
{
id: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: PRIMARY_ADMIN_ACCOUNT,
source: "production",
status: "online",
projects: ["硬件审计协作"],
quota5h: 68,
quota7d: 81,
lastSeenAt: "2026-03-25T11:52:00+08:00",
endpoint: "mac://kris.local",
token: "boss-mac-studio-token",
note: "本机 Codex 主节点 · 17600003315 已绑定",
},
{
id: "win-gpu-01",
name: "Windows GPU",
avatar: "W",
account: "kris.plus.gpu",
source: "demo",
status: "abnormal",
projects: ["硬件审计协作"],
quota5h: 31,
quota7d: 46,
lastSeenAt: "2026-03-25T11:40:00+08:00",
endpoint: "win://gpu.local",
token: "boss-win-gpu-token",
note: "摄像头证据通道偶发抖动",
},
{
id: "cloud-backup",
name: "Cloud Backup",
avatar: "C",
account: "kris.plus.backup",
source: "demo",
status: "offline",
projects: [],
quota5h: 92,
quota7d: 95,
lastSeenAt: "2026-03-25T08:15:00+08:00",
endpoint: "cloud://standby",
token: "boss-cloud-backup-token",
note: "standby 节点",
},
],
projects: [
{
id: "master-agent",
name: "主 Agent",
pinned: true,
systemPinned: true,
deviceIds: ["mac-studio"],
preview: "已汇总当前活跃项目,优先收尾 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: [],
authAccounts: [
{
id: "account-17600003315",
account: PRIMARY_ADMIN_ACCOUNT,
passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD),
displayName: "Boss 超级管理员",
role: "highest_admin",
verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL,
codexNodeId: PRIMARY_CODEX_NODE_ID,
codexNodeLabel: PRIMARY_CODEX_NODE_LABEL,
primaryDeviceId: PRIMARY_CODEX_NODE_ID,
isPrimary: true,
createdAt: "2026-03-25T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
},
],
authSessions: [],
aiAccounts: [
{
accountId: "master-codex-primary",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "17600003315 · Master Codex Node",
accountIdentifier: PRIMARY_ADMIN_ACCOUNT,
nodeId: PRIMARY_CODEX_NODE_ID,
nodeLabel: PRIMARY_CODEX_NODE_LABEL,
enabled: true,
isActive: true,
status: "ready",
loginStatusNote: "已绑定本机 Codex可通过 local-agent relay 执行主 Agent 对话。",
createdAt: "2026-03-26T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
lastSwitchedAt: "2026-03-26T09:00:00+08:00",
switchReason: "默认主控身份预留位",
},
{
accountId: "master-codex-backup",
label: "备用 GPT",
role: "backup",
provider: "master_codex_node",
displayName: "备用 Master Codex Node",
enabled: false,
isActive: false,
status: "disabled",
loginStatusNote: "备用节点未启用。",
createdAt: "2026-03-26T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
},
{
accountId: "openai-api-fallback",
label: "API 容灾",
role: "api_fallback",
provider: "openai_api",
displayName: "OpenAI API",
model: "gpt-5.4",
enabled: true,
isActive: false,
status: "needs_api_key",
loginStatusNote: "配置 OpenAI API Key 后,可直接为主 Agent 生成真实回复。",
createdAt: "2026-03-26T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
},
],
aiAccountSwitchHistory: [
{
switchId: "aiswitch-seed-primary",
toAccountId: "master-codex-primary",
toLabel: "主 GPT",
role: "primary",
switchedAt: "2026-03-26T09:00:00+08:00",
reason: "初始化默认主控身份",
},
],
userAttachmentStorageConfigs: [
{
account: PRIMARY_ADMIN_ACCOUNT,
mode: "server_file",
updatedAt: nowIso(),
},
],
masterAgentPromptPolicy: null,
userMasterPrompts: [],
masterAgentMemories: [],
userProjectAgentControls: [],
masterAgentTasks: [],
dispatchPlans: [],
dispatchExecutions: [],
deviceImportDrafts: [],
deviceImportResolutions: [],
otaUpdates: [
{
releaseId: "ota_140_to_141",
version: "v2.0.0",
currentVersion: "1.4.0",
channel: "stable",
packageType: "android_shell",
status: "available",
summary: ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"],
targetScope: "Boss Android 原生客户端与 Web 控制台",
requiredRole: "highest_admin",
publishedAt: "2026-03-26T11:20:00+08:00",
packageFileName: "boss-android-latest.apk",
downloadUrl: "/api/v1/user/ota/package",
},
],
otaUpdateLogs: [
{
logId: "otalog_seed_130",
releaseId: "ota_seed_check",
version: "1.4.0",
status: "checked",
triggeredBy: "Boss 超级管理员",
triggeredAt: "2026-03-26T09:05:00+08:00",
note: "本机 Codex 已切到 17600003315等待管理员决定是否升级到 v1.4.1。",
},
],
deviceSkills: [],
appLogs: [],
threadContextSnapshots: [
{
snapshotId: "snapshot-master-main",
projectId: "master-agent",
taskId: "task-master-summary",
threadId: "thread-master-main",
title: "主控汇总线程",
summary: "正在整理 3 个项目状态、2 条未关闭告警和 1 个待交接线程。",
nodeId: "mac-studio",
workerId: "worker-mac-master",
sourceKind: "worker_estimator",
status: "running",
contextBudgetRemainingPct: 71,
contextBudgetLevel: "safe",
compactionExpectedAt: "2026-03-25T13:20:00+08:00",
mustFinishBeforeCompaction: false,
estimatedRemainingTurns: 18,
estimatedRemainingLargeMessages: 6,
lastCompactionAt: "2026-03-25T09:48:00+08:00",
compactionCount: 1,
patchPending: false,
testsPending: false,
evidencePending: false,
checklist: baseThreadChecklist(["持续刷新阶段摘要", "复核风险排序", "回写交接文档"]),
capturedAt: "2026-03-25T12:06:00+08:00",
},
{
snapshotId: "snapshot-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"],
},
],
threadStatusDocuments: [],
threadProgressEvents: [],
};
const levelPriority: Record<ContextBudgetLevel, number> = {
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<ThreadConversationMeta> | 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<GroupConversationMember>,
fallbackProjectId: string,
fallbackThreadMeta: ThreadConversationMeta,
): GroupConversationMember {
return {
projectId: raw.projectId ?? fallbackProjectId,
deviceId: raw.deviceId ?? "",
threadId: raw.threadId ?? fallbackThreadMeta.threadId,
threadDisplayName: raw.threadDisplayName ?? fallbackThreadMeta.threadDisplayName,
folderName: raw.folderName ?? fallbackThreadMeta.folderName,
};
}
function trimToDefined(value?: string) {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
function parseControlTextOverride(value: unknown) {
if (value === undefined || value === null) {
return { kind: "clear" as const };
}
if (typeof value !== "string") {
return { kind: "invalid" as const };
}
const trimmed = value.trim();
return trimmed ? { kind: "set" as const, value: trimmed } : { kind: "clear" as const };
}
function parseReasoningEffortOverride(value: unknown) {
if (value === undefined || value === null) {
return { kind: "clear" as const };
}
if (!isReasoningEffort(value)) {
return { kind: "invalid" as const };
}
return { kind: "set" as const, value };
}
function 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 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<DispatchPlanTarget>,
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<DispatchPlan>, fallback?: DispatchPlan): DispatchPlan {
const fallbackTargets = fallback?.targets ?? [];
const targets = ensureArray(raw.targets as Partial<DispatchPlanTarget>[] | 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<DispatchExecution>,
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<DeviceImportCandidate>,
fallback?: DeviceImportCandidate,
): DeviceImportCandidate {
const deviceId = raw.deviceId ?? fallback?.deviceId ?? "";
const folderName = raw.folderName ?? fallback?.folderName ?? "";
const threadId = raw.threadId ?? fallback?.threadId ?? "";
return {
candidateId:
raw.candidateId ??
fallback?.candidateId ??
buildDeviceImportCandidateId({
deviceId,
folderName,
threadId,
codexFolderRef: raw.codexFolderRef ?? fallback?.codexFolderRef,
codexThreadRef: raw.codexThreadRef ?? fallback?.codexThreadRef,
}),
deviceId,
folderName,
folderRef: raw.folderRef ?? fallback?.folderRef,
threadId,
threadDisplayName: raw.threadDisplayName ?? fallback?.threadDisplayName ?? threadId,
codexFolderRef: raw.codexFolderRef ?? fallback?.codexFolderRef,
codexThreadRef: raw.codexThreadRef ?? fallback?.codexThreadRef,
lastActiveAt: raw.lastActiveAt ?? fallback?.lastActiveAt ?? nowIso(),
suggestedImport: raw.suggestedImport ?? fallback?.suggestedImport ?? true,
};
}
function normalizeDeviceImportDraft(
raw: Partial<DeviceImportDraft>,
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<DeviceImportCandidate>[] | 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<DeviceImportResolution>,
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<DeviceImportResolutionItem>[] | undefined,
fallbackItems,
).map((item, index) => ({
candidateId: item.candidateId ?? fallbackItems[index % Math.max(1, fallbackItems.length)]?.candidateId ?? "",
action:
item.action ??
fallbackItems[index % Math.max(1, fallbackItems.length)]?.action ??
"skip",
threadDisplayName:
item.threadDisplayName ??
fallbackItems[index % Math.max(1, fallbackItems.length)]?.threadDisplayName ??
"",
folderName:
item.folderName ??
fallbackItems[index % Math.max(1, fallbackItems.length)]?.folderName ??
"",
targetProjectId:
item.targetProjectId ??
fallbackItems[index % Math.max(1, fallbackItems.length)]?.targetProjectId,
reason:
item.reason ??
fallbackItems[index % Math.max(1, fallbackItems.length)]?.reason ??
"",
})),
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
appliedAt: raw.appliedAt ?? fallback?.appliedAt,
appliedBy: raw.appliedBy ?? fallback?.appliedBy,
};
}
function dedupeStrings(values: string[]) {
return [...new Set(values.filter((value) => Boolean(value)))];
}
function dedupeGroupMembers(members: GroupConversationMember[]) {
const seen = new Set<string>();
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<string>;
},
) {
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<ProjectAgentControls> | undefined,
): ProjectAgentControls | undefined {
const modelOverride = trimToDefined(raw?.modelOverride);
const reasoningEffortOverride = isReasoningEffort(raw?.reasoningEffortOverride)
? raw.reasoningEffortOverride
: undefined;
const promptOverride = trimToDefined(raw?.promptOverride);
const backendOverride = raw?.backendOverride === "claw-runtime" ? raw.backendOverride : undefined;
if (!modelOverride && !reasoningEffortOverride && !promptOverride && !backendOverride) {
return undefined;
}
return {
modelOverride,
reasoningEffortOverride,
promptOverride,
backendOverride,
updatedAt: raw?.updatedAt ?? nowIso(),
};
}
function isReasoningEffort(value: unknown): value is ReasoningEffort {
return value === "low" || value === "medium" || value === "high";
}
function resolveProjectUpdatedAt(project: Pick<Project, "updatedAt" | "lastMessageAt" | "threadMeta">, latestActivityAt?: string) {
return latestIsoTimestamp(
project.updatedAt,
project.lastMessageAt,
project.threadMeta.updatedAt,
latestActivityAt,
);
}
function latestIsoTimestamp(...values: Array<string | undefined>) {
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<T>(value: T[] | undefined, fallback: T[]) {
return Array.isArray(value) ? value : fallback;
}
function deriveLevelFromPercent(percent: number): ContextBudgetLevel {
if (percent < 25) return "critical";
if (percent < 40) return "urgent";
if (percent < 60) return "watch";
return "safe";
}
function deriveRiskFromSnapshots(snapshots: ThreadContextSnapshot[]) {
const top = [...snapshots].sort(compareSnapshotsForRisk)[0];
if (!top) return "low" as RiskLevel;
if (top.contextBudgetLevel === "critical") return "high" as RiskLevel;
if (top.contextBudgetLevel === "urgent" || top.mustFinishBeforeCompaction) {
return "high" as RiskLevel;
}
if (top.contextBudgetLevel === "watch") return "medium" as RiskLevel;
return "low" as RiskLevel;
}
function compareSnapshotsForRisk(a: ThreadContextSnapshot, b: ThreadContextSnapshot) {
if (a.mustFinishBeforeCompaction !== b.mustFinishBeforeCompaction) {
return a.mustFinishBeforeCompaction ? -1 : 1;
}
if (levelPriority[a.contextBudgetLevel] !== levelPriority[b.contextBudgetLevel]) {
return levelPriority[a.contextBudgetLevel] - levelPriority[b.contextBudgetLevel];
}
if ((a.compactionExpectedAt ?? "") !== (b.compactionExpectedAt ?? "")) {
return (a.compactionExpectedAt ?? "").localeCompare(b.compactionExpectedAt ?? "");
}
return (b.capturedAt ?? "").localeCompare(a.capturedAt ?? "");
}
function messageTimeValue(value?: string) {
if (!value) return 0;
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? 0 : parsed;
}
function randomToken(prefix: string) {
return `${prefix}-${randomBytes(4).toString("hex")}`;
}
function randomDigits(length: number) {
return Array.from({ length }, () => Math.floor(Math.random() * 10)).join("");
}
function slugify(value: string) {
return value
.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fa5]+/gi, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 48) || `item-${randomBytes(2).toString("hex")}`;
}
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<AiAccountRole, number> = {
primary: 0,
backup: 1,
api_fallback: 2,
};
export function aiRoleLabel(role: AiAccountRole) {
switch (role) {
case "primary":
return "主 GPT";
case "backup":
return "备用 GPT";
case "api_fallback":
return "API 容灾";
default:
return role;
}
}
export function aiProviderLabel(provider: AiProvider) {
switch (provider) {
case "master_codex_node":
return "Master Codex Node / ChatGPT Plus 节点";
case "openai_api":
return "OpenAI API";
case "aliyun_qwen_api":
return "阿里百炼 Qwen";
default:
return provider;
}
}
export function aiStatusLabel(status: AiAccountStatus) {
switch (status) {
case "ready":
return "可用";
case "needs_login":
return "待登录";
case "needs_api_key":
return "待配置 Key";
case "degraded":
return "异常";
case "disabled":
return "已停用";
default:
return status;
}
}
function maskApiKey(value?: string) {
if (!value?.trim()) return undefined;
const trimmed = value.trim();
if (trimmed.length <= 8) return "已配置";
return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
}
function isApiKeyProvider(provider: AiProvider) {
return provider === "openai_api" || provider === "aliyun_qwen_api";
}
function deriveAiAccountStatus(account: AiAccount): AiAccountStatus {
if (!account.enabled) return "disabled";
if (isApiKeyProvider(account.provider)) {
if (!account.apiKey?.trim()) return "needs_api_key";
return account.status === "disabled" ? "ready" : account.status;
}
return account.status === "disabled" ? "needs_login" : account.status;
}
function aiAccountCanGenerate(account: AiAccount) {
if (!account.enabled) return false;
if (account.provider === "master_codex_node") {
return Boolean(account.nodeId?.trim());
}
return Boolean(account.apiKey?.trim());
}
function sortAiAccounts(accounts: AiAccount[]) {
return [...accounts].sort((a, b) => {
if (a.isActive !== b.isActive) {
return a.isActive ? -1 : 1;
}
if (aiRolePriority[a.role] !== aiRolePriority[b.role]) {
return aiRolePriority[a.role] - aiRolePriority[b.role];
}
return (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "");
});
}
function normalizeAiAccount(account: AiAccount) {
const apiKeyMasked = account.apiKeyMasked ?? maskApiKey(account.apiKey);
const status = deriveAiAccountStatus({
...account,
apiKeyMasked,
});
return {
...account,
apiKeyMasked,
status,
} satisfies AiAccount;
}
function buildAiAccountSummary(account: AiAccount, options?: { isEnvironmentFallback?: boolean }) {
const normalized = normalizeAiAccount(account);
return {
accountId: normalized.accountId,
label: normalized.label,
role: normalized.role,
roleLabel: aiRoleLabel(normalized.role),
provider: normalized.provider,
providerLabel: aiProviderLabel(normalized.provider),
displayName: normalized.displayName,
accountIdentifier: normalized.accountIdentifier,
nodeId: normalized.nodeId,
nodeLabel: normalized.nodeLabel,
model: normalized.model,
enabled: normalized.enabled,
isActive: normalized.isActive,
canGenerate: aiAccountCanGenerate(normalized),
status: normalized.status,
statusLabel: aiStatusLabel(normalized.status),
loginStatusNote: normalized.loginStatusNote,
apiKeyConfigured: Boolean(normalized.apiKey?.trim()),
apiKeyMasked: normalized.apiKeyMasked,
lastValidatedAt: normalized.lastValidatedAt,
lastUsedAt: normalized.lastUsedAt,
lastError: normalized.lastError,
lastSwitchedAt: normalized.lastSwitchedAt,
switchReason: normalized.switchReason,
createdAt: normalized.createdAt,
updatedAt: normalized.updatedAt,
isEnvironmentFallback: options?.isEnvironmentFallback ?? false,
} satisfies AiAccountSummary;
}
function getEnvOpenAiAccount() {
const apiKey = process.env.OPENAI_API_KEY?.trim();
if (!apiKey) return null;
const createdAt = nowIso();
return normalizeAiAccount({
accountId: ENV_OPENAI_ACCOUNT_ID,
label: "API 容灾",
role: "api_fallback",
provider: "openai_api",
displayName: "环境变量 OpenAI API",
model: process.env.OPENAI_MODEL?.trim() || "gpt-5.4",
apiKey,
apiKeyMasked: maskApiKey(apiKey),
enabled: true,
isActive: false,
status: "ready",
loginStatusNote: "来自服务器环境变量 OPENAI_API_KEY。",
createdAt,
updatedAt: createdAt,
} satisfies AiAccount);
}
function resolveActiveAiAccount(state: BossState) {
const normalized = sortAiAccounts(state.aiAccounts.map(normalizeAiAccount));
const activeConfigured = normalized.find((account) => account.isActive);
const runnable = normalized.find(aiAccountCanGenerate);
const envAccount = getEnvOpenAiAccount();
if (activeConfigured && activeConfigured.enabled) {
return { account: activeConfigured, isEnvironmentFallback: false };
}
if (runnable) {
return { account: runnable, isEnvironmentFallback: false };
}
if (envAccount) {
return { account: envAccount, isEnvironmentFallback: true };
}
return {
account: activeConfigured ?? normalized[0] ?? envAccount ?? null,
isEnvironmentFallback: false,
};
}
export function getAiAccountSummariesFromState(state: BossState) {
const accounts = sortAiAccounts(state.aiAccounts.map(normalizeAiAccount)).map((account) =>
buildAiAccountSummary(account),
);
const envAccount = getEnvOpenAiAccount();
if (envAccount) {
accounts.push(buildAiAccountSummary(envAccount, { isEnvironmentFallback: true }));
}
return sortAiAccounts(
accounts.map((account) => ({
accountId: account.accountId,
label: account.label,
role: account.role,
provider: account.provider,
displayName: account.displayName,
accountIdentifier: account.accountIdentifier,
nodeId: account.nodeId,
nodeLabel: account.nodeLabel,
model: account.model,
enabled: account.enabled,
isActive: account.isActive,
status: account.status,
loginStatusNote: account.loginStatusNote,
apiKeyMasked: account.apiKeyMasked,
createdAt: account.createdAt,
updatedAt: account.updatedAt,
lastValidatedAt: account.lastValidatedAt,
lastUsedAt: account.lastUsedAt,
lastError: account.lastError,
lastSwitchedAt: account.lastSwitchedAt,
switchReason: account.switchReason,
apiKey: account.apiKeyConfigured ? "configured" : "",
} as AiAccount)),
).map((account) =>
buildAiAccountSummary(
account.accountId === ENV_OPENAI_ACCOUNT_ID ? (envAccount ?? account) : account,
{ isEnvironmentFallback: account.accountId === ENV_OPENAI_ACCOUNT_ID },
),
);
}
export function getMasterIdentitySummaryFromState(state: BossState): MasterIdentitySummary {
const resolved = resolveActiveAiAccount(state);
if (!resolved.account) {
return {
label: "API 容灾",
role: "api_fallback",
roleLabel: aiRoleLabel("api_fallback"),
provider: "openai_api",
providerLabel: aiProviderLabel("openai_api"),
displayName: "未配置 AI 账号",
status: "needs_api_key",
statusLabel: aiStatusLabel("needs_api_key"),
canGenerate: false,
note: "请到“我的 > AI 账号”至少配置一个可用的 Master Codex Node、OpenAI API 或阿里百炼 Qwen 账号。",
};
}
const summary = buildAiAccountSummary(resolved.account, {
isEnvironmentFallback: resolved.isEnvironmentFallback,
});
return {
accountId: summary.accountId,
label: summary.label,
role: summary.role,
roleLabel: summary.roleLabel,
provider: summary.provider,
providerLabel: summary.providerLabel,
displayName: summary.displayName,
nodeLabel: summary.nodeLabel,
model: summary.model,
status: summary.status,
statusLabel: summary.statusLabel,
canGenerate: summary.canGenerate,
switchReason: summary.switchReason,
lastSwitchedAt: summary.lastSwitchedAt,
note:
summary.loginStatusNote ??
(summary.canGenerate
? "当前账号可直接生成主 Agent 回复。"
: "当前账号只完成了身份占位,还没有接通真实生成能力。"),
isEnvironmentFallback: summary.isEnvironmentFallback,
};
}
function normalizeMessage(raw: Partial<Message>): Message {
return {
id: raw.id ?? randomToken("msg"),
sender: raw.sender ?? "master",
senderLabel: raw.senderLabel ?? "主 Agent",
body: raw.body ?? "",
sentAt: raw.sentAt ?? nowIso(),
kind: raw.kind ?? "text",
attachments: Array.isArray(raw.attachments)
? raw.attachments.map((attachment) => normalizeMessageAttachment(attachment))
: undefined,
forwardSource: raw.forwardSource,
forwardBundle: raw.forwardBundle,
};
}
function normalizeMessageAttachment(raw: Partial<MessageAttachment>): 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<UserAttachmentStorageConfig>,
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<MasterAgentPromptPolicy> | 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<UserMasterPrompt>,
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<UserProjectAgentControls>,
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<MasterAgentMemory>,
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<Project>, 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),
};
project.groupMembers = ensureArray(raw.groupMembers, []).map((member) =>
normalizeGroupMember(member, projectId, project.threadMeta),
);
normalizeProjectConversationShape(project);
project.updatedAt = resolveProjectUpdatedAt(project, project.threadMeta.updatedAt);
return project;
}
function normalizeProjectUnderstanding(
raw: Partial<ProjectUnderstandingSnapshot> | 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<ThreadStatusDocument>,
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<ThreadProgressEvent>,
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 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<ThreadProgressEvent, "eventId">,
) {
const event = normalizeThreadProgressEvent({
eventId: randomToken("thread-event"),
...input,
});
state.threadProgressEvents.unshift(event);
return event;
}
function normalizeState(raw: Partial<BossState> | undefined): BossState {
const base = cloneInitialState();
if (!raw) return syncDerivedState(base);
const state: BossState = {
user: {
...base.user,
...raw.user,
settings: {
...base.user.settings,
...(raw.user?.settings ?? {}),
},
},
devices: ensureArray(raw.devices, base.devices).map((device, index) => ({
...base.devices[index % base.devices.length],
...device,
source:
device.source ??
(device.id === PRIMARY_CODEX_NODE_ID ? "production" : "demo"),
})),
projects: ensureArray(raw.projects, base.projects).map((project, index) =>
normalizeProject(project, base.projects[index % base.projects.length]),
),
verificationCodes: ensureArray(raw.verificationCodes, base.verificationCodes),
verificationDispatches: ensureArray(raw.verificationDispatches, base.verificationDispatches).map(
(dispatch) => ({
dispatchId: dispatch.dispatchId ?? randomToken("vdispatch"),
account: dispatch.account ?? "",
purpose: dispatch.purpose ?? "login",
deliveryMode: dispatch.deliveryMode ?? getVerificationDeliveryMode(),
requestedAt: dispatch.requestedAt ?? nowIso(),
status: dispatch.status ?? "requested",
note: dispatch.note ?? "",
}),
),
authAccounts: ensureArray(raw.authAccounts, base.authAccounts).map((account) => ({
id: account.id ?? `account-${slugify(account.account ?? "unknown")}`,
account: account.account ?? "",
passwordHash: account.passwordHash ?? hashPassword("boss123456"),
displayName: account.displayName ?? "Boss 成员",
role: account.role ?? "member",
verificationEmail: account.verificationEmail,
codexNodeId: account.codexNodeId,
codexNodeLabel: account.codexNodeLabel,
primaryDeviceId: account.primaryDeviceId,
isPrimary: account.isPrimary ?? false,
failedLoginAttempts: account.failedLoginAttempts ?? 0,
lockedUntil: account.lockedUntil,
lastLoginAt: account.lastLoginAt,
lastLoginMethod: account.lastLoginMethod,
createdAt: account.createdAt ?? nowIso(),
updatedAt: account.updatedAt ?? account.createdAt ?? nowIso(),
})),
authSessions: ensureArray(raw.authSessions, base.authSessions).map((session) => ({
sessionId: session.sessionId ?? randomToken("session"),
sessionToken: session.sessionToken ?? randomBytes(24).toString("hex"),
restoreToken: session.restoreToken ?? randomBytes(24).toString("hex"),
account: session.account ?? "",
role: session.role ?? "member",
displayName: session.displayName ?? "Boss 成员",
loginMethod: session.loginMethod ?? "password",
createdAt: session.createdAt ?? nowIso(),
expiresAt: session.expiresAt ?? new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(),
lastSeenAt: session.lastSeenAt ?? session.createdAt ?? nowIso(),
revokedAt: session.revokedAt,
})),
aiAccounts: ensureArray(raw.aiAccounts, base.aiAccounts).map((account, index) => {
const fallback = base.aiAccounts[index % base.aiAccounts.length];
return {
...fallback,
...account,
accountId: account.accountId ?? fallback.accountId ?? `ai-${randomToken("account")}`,
label: account.label ?? fallback.label ?? "AI 账号",
role: account.role ?? fallback.role ?? "api_fallback",
provider: account.provider ?? fallback.provider ?? "openai_api",
displayName: account.displayName ?? fallback.displayName ?? "未命名 AI",
model: account.model ?? fallback.model,
apiKey: account.apiKey,
apiKeyMasked: account.apiKeyMasked ?? fallback.apiKeyMasked,
enabled: account.enabled ?? fallback.enabled ?? true,
isActive: account.isActive ?? fallback.isActive ?? false,
status: account.status ?? fallback.status ?? "needs_api_key",
loginStatusNote: account.loginStatusNote ?? fallback.loginStatusNote,
createdAt: account.createdAt ?? nowIso(),
updatedAt: account.updatedAt ?? account.createdAt ?? nowIso(),
} satisfies AiAccount;
}),
aiAccountSwitchHistory: ensureArray(
raw.aiAccountSwitchHistory,
base.aiAccountSwitchHistory,
).map((item) => ({
switchId: item.switchId ?? randomToken("aiswitch"),
fromAccountId: item.fromAccountId,
fromLabel: item.fromLabel,
toAccountId: item.toAccountId ?? "",
toLabel: item.toLabel ?? "AI 账号",
role: item.role ?? "api_fallback",
switchedAt: item.switchedAt ?? nowIso(),
reason: item.reason ?? "未注明切换原因",
})),
masterAgentTasks: ensureArray(raw.masterAgentTasks, base.masterAgentTasks).map((task) => ({
taskId: task.taskId ?? randomToken("mastertask"),
projectId: task.projectId ?? "master-agent",
taskType: task.taskType ?? "conversation_reply",
requestMessageId: task.requestMessageId ?? "",
requestText: task.requestText ?? "",
executionPrompt: task.executionPrompt ?? task.requestText ?? "",
requestedBy: task.requestedBy ?? "用户",
requestedByAccount: task.requestedByAccount ?? "",
deviceId: task.deviceId ?? PRIMARY_CODEX_NODE_ID,
accountId: task.accountId,
accountLabel: task.accountLabel,
attachmentId: task.attachmentId,
attachmentFileName: task.attachmentFileName,
attachmentDownloadToken: task.attachmentDownloadToken,
attachmentDownloadExpiresAt: task.attachmentDownloadExpiresAt,
attachmentDownloadUrl: task.attachmentDownloadUrl,
attachmentTextExcerpt: task.attachmentTextExcerpt,
dispatchExecutionId: task.dispatchExecutionId,
targetProjectId: task.targetProjectId,
targetThreadId: task.targetThreadId,
targetThreadDisplayName: task.targetThreadDisplayName,
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,
projectUnderstandingTargetProjectId: task.projectUnderstandingTargetProjectId,
projectUnderstandingReason:
task.projectUnderstandingReason === "heartbeat_activity" || task.projectUnderstandingReason === "thread_reply"
? task.projectUnderstandingReason
: undefined,
status: task.status ?? "queued",
requestedAt: task.requestedAt ?? nowIso(),
claimedAt: task.claimedAt,
completedAt: task.completedAt,
replyBody: task.replyBody,
errorMessage: task.errorMessage,
requestId: task.requestId,
})),
dispatchPlans: ensureArray(raw.dispatchPlans, base.dispatchPlans).map((plan, index) =>
normalizeDispatchPlan(plan, base.dispatchPlans[index % Math.max(1, base.dispatchPlans.length)]),
),
dispatchExecutions: ensureArray(raw.dispatchExecutions, base.dispatchExecutions).map((execution, index) =>
normalizeDispatchExecution(
execution,
base.dispatchExecutions[index % Math.max(1, base.dispatchExecutions.length)],
),
),
deviceImportDrafts: ensureArray(raw.deviceImportDrafts, base.deviceImportDrafts).map((draft, index) =>
normalizeDeviceImportDraft(
draft,
base.deviceImportDrafts[index % Math.max(1, base.deviceImportDrafts.length)],
),
),
deviceImportResolutions: ensureArray(
raw.deviceImportResolutions,
base.deviceImportResolutions,
).map((resolution, index) =>
normalizeDeviceImportResolution(
resolution,
base.deviceImportResolutions[index % Math.max(1, base.deviceImportResolutions.length)],
),
),
threadStatusDocuments: ensureArray(
raw.threadStatusDocuments as Partial<ThreadStatusDocument>[] | undefined,
base.threadStatusDocuments,
).map((document, index) =>
normalizeThreadStatusDocument(
document,
base.threadStatusDocuments[index % Math.max(1, base.threadStatusDocuments.length)],
),
),
threadProgressEvents: ensureArray(
raw.threadProgressEvents as Partial<ThreadProgressEvent>[] | undefined,
base.threadProgressEvents,
).map((event, index) =>
normalizeThreadProgressEvent(
event,
base.threadProgressEvents[index % Math.max(1, base.threadProgressEvents.length)],
),
),
otaUpdates: ensureArray(raw.otaUpdates, base.otaUpdates).map((update, index) => ({
...base.otaUpdates[index % base.otaUpdates.length],
...update,
summary: ensureArray(update.summary, base.otaUpdates[index % base.otaUpdates.length].summary),
})),
otaUpdateLogs: ensureArray(raw.otaUpdateLogs, base.otaUpdateLogs).map((log, index) => ({
...base.otaUpdateLogs[index % base.otaUpdateLogs.length],
...log,
})),
deviceSkills: ensureArray(raw.deviceSkills, base.deviceSkills).map((skill) => ({
...skill,
invocation: skill.invocation ?? `$${skill.name}`,
category: skill.category ?? "本机技能",
updatedAt: skill.updatedAt ?? nowIso(),
})),
appLogs: ensureArray(raw.appLogs, base.appLogs).map((log) => ({
...log,
mirroredToProject: log.mirroredToProject ?? false,
createdAt: log.createdAt ?? nowIso(),
})),
userAttachmentStorageConfigs: ensureArray(
raw.userAttachmentStorageConfigs,
base.userAttachmentStorageConfigs,
).map((config, index) =>
normalizeAttachmentStorageConfig(
config,
base.userAttachmentStorageConfigs[index % base.userAttachmentStorageConfigs.length],
),
),
masterAgentPromptPolicy: normalizeMasterAgentPromptPolicy(
raw.masterAgentPromptPolicy,
base.masterAgentPromptPolicy,
),
userMasterPrompts: ensureArray(raw.userMasterPrompts, base.userMasterPrompts).map(
(prompt, index) =>
normalizeUserMasterPrompt(
prompt,
base.userMasterPrompts[index % Math.max(1, base.userMasterPrompts.length)],
),
),
masterAgentMemories: ensureArray(raw.masterAgentMemories, base.masterAgentMemories).map(
(memory, index) =>
normalizeUserMasterMemory(
memory,
base.masterAgentMemories[index % Math.max(1, base.masterAgentMemories.length)],
),
),
userProjectAgentControls: ensureArray(
raw.userProjectAgentControls,
base.userProjectAgentControls,
)
.map((controls, index) =>
normalizeUserProjectAgentControls(
controls,
base.userProjectAgentControls[index % Math.max(1, base.userProjectAgentControls.length)],
),
)
.filter((item): item is UserProjectAgentControls => Boolean(item)),
threadContextSnapshots: ensureArray(raw.threadContextSnapshots, base.threadContextSnapshots).map(
(snapshot, index) => ({
...base.threadContextSnapshots[index % base.threadContextSnapshots.length],
...snapshot,
contextBudgetLevel:
snapshot.contextBudgetLevel ??
deriveLevelFromPercent(snapshot.contextBudgetRemainingPct ?? 100),
checklist: ensureArray(snapshot.checklist, base.threadContextSnapshots[index % base.threadContextSnapshots.length].checklist),
}),
),
threadHandoffPackages: ensureArray(raw.threadHandoffPackages, base.threadHandoffPackages).map(
(item) => ({
...item,
openQuestions: ensureArray(item.openQuestions, []),
criticalFiles: ensureArray(item.criticalFiles, []),
criticalCommands: ensureArray(item.criticalCommands, []),
criticalTests: ensureArray(item.criticalTests, []),
criticalArtifacts: ensureArray(item.criticalArtifacts, []),
decisionLinks: ensureArray(item.decisionLinks, []),
}),
),
threadContextAlerts: ensureArray(raw.threadContextAlerts, base.threadContextAlerts).map((item) => ({
...item,
masterActions: ensureArray(item.masterActions, []),
})),
deviceEnrollments: ensureArray(raw.deviceEnrollments, base.deviceEnrollments),
opsFaults: ensureArray(raw.opsFaults, base.opsFaults),
opsRepairTickets: ensureArray(raw.opsRepairTickets, base.opsRepairTickets),
opsRepairVerifications: ensureArray(raw.opsRepairVerifications, base.opsRepairVerifications),
auditRequests: ensureArray(raw.auditRequests, base.auditRequests).map((item) => ({
...item,
acceptanceCriteria: ensureArray(item.acceptanceCriteria, []),
riskFocus: ensureArray(item.riskFocus, []),
evidenceRefs: ensureArray(item.evidenceRefs, []),
artifactRefs: ensureArray(item.artifactRefs, []),
capabilityRequirements: ensureArray(item.capabilityRequirements, []),
metadataJson: item.metadataJson ?? {},
})),
auditResults: ensureArray(raw.auditResults, base.auditResults).map((item) => ({
...item,
findings: ensureArray(item.findings, []),
requiredActions: ensureArray(item.requiredActions, []),
usedCapabilities: ensureArray(item.usedCapabilities, []),
artifactRefs: ensureArray(item.artifactRefs, []),
timeline: ensureArray(item.timeline, []),
})),
capabilities: ensureArray(raw.capabilities, base.capabilities).map((item) => ({
...item,
supportedActions: ensureArray(item.supportedActions, []),
evidenceModes: ensureArray(item.evidenceModes, []),
})),
};
if (!state.projects.some((project) => project.id === "master-agent")) {
state.projects.unshift(base.projects[0]);
}
removeLegacyBossConsoleArtifacts(state);
return syncDerivedState(state);
}
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();
}
function deriveProjectPreview(state: BossState, project: Project) {
if (project.id === "master-agent") {
return project.preview;
}
const relevantSnapshots = state.threadContextSnapshots
.filter((snapshot) => snapshot.projectId === project.id)
.sort(compareSnapshotsForRisk);
const topSnapshot = relevantSnapshots[0];
if (topSnapshot && (topSnapshot.contextBudgetLevel !== "safe" || topSnapshot.mustFinishBeforeCompaction)) {
return `${topSnapshot.title} · ${topSnapshot.contextBudgetLevel} ${topSnapshot.contextBudgetRemainingPct}% · ${topSnapshot.summary}`;
}
const latestAudit = state.auditResults
.map((result) => ({
result,
request: state.auditRequests.find((request) => request.auditRequestId === result.auditRequestId),
}))
.filter(
(item): item is { result: AuditTaskResult; request: AuditTaskRequest } =>
Boolean(item.request && item.request.projectId === project.id),
)
.sort((a, b) => messageTimeValue(b.result.completedAt) - messageTimeValue(a.result.completedAt))[0];
if (latestAudit) {
return `审计 · ${latestAudit.result.summary}`;
}
const openFault = state.opsFaults
.filter((fault) => fault.projectId === project.id && fault.status !== "resolved")
.sort((a, b) => messageTimeValue(b.lastSeenAt) - messageTimeValue(a.lastSeenAt))[0];
if (openFault) {
return `运维 · ${openFault.summary}`;
}
const lastMessage = [...project.messages].sort(
(a, b) => messageTimeValue(b.sentAt) - messageTimeValue(a.sentAt),
)[0];
if (lastMessage) {
return lastMessage.body;
}
return project.preview;
}
function updateMasterProjectSummary(state: BossState) {
const masterProject = state.projects.find((project) => project.id === "master-agent");
if (!masterProject) return;
const riskThreads = [...state.threadContextSnapshots]
.filter(
(snapshot) =>
snapshot.projectId !== "master-agent" &&
(snapshot.contextBudgetLevel !== "safe" || snapshot.mustFinishBeforeCompaction),
)
.sort(compareSnapshotsForRisk);
const openFaults = state.opsFaults.filter((fault) => fault.status !== "resolved");
const pendingAudits = state.auditRequests.filter(
(request) =>
!state.auditResults.some((result) => result.auditRequestId === request.auditRequestId),
);
const summaryParts = [];
if (riskThreads[0]) {
summaryParts.push(
`${riskThreads[0].title} ${riskThreads[0].contextBudgetLevel} ${riskThreads[0].contextBudgetRemainingPct}%`,
);
}
if (openFaults[0]) {
summaryParts.push(`运维 ${openFaults[0].faultKey}`);
}
if (pendingAudits.length > 0) {
summaryParts.push(`审计待处理 ${pendingAudits.length}`);
}
masterProject.preview =
summaryParts.join(" · ") ||
"当前无高风险线程,主 Agent 正在维护项目总结、运维账本和交接文档。";
masterProject.riskLevel = riskThreads[0]
? deriveRiskFromSnapshots([riskThreads[0]])
: openFaults.some((fault) => fault.severity === "critical")
? "high"
: "low";
const masterSnapshot = state.threadContextSnapshots.find(
(snapshot) => snapshot.projectId === "master-agent",
);
masterProject.contextBudgetPct = masterSnapshot?.contextBudgetRemainingPct;
masterProject.contextBudgetLabel = masterSnapshot
? `${masterSnapshot.contextBudgetRemainingPct}%`
: undefined;
masterProject.lastMessageAt = latestProjectTimestamp(state, "master-agent");
masterProject.updatedAt = masterProject.lastMessageAt;
const summaryMessage = masterProject.messages.find((message) => message.id === "master-summary");
if (summaryMessage) {
summaryMessage.body = masterProject.preview;
summaryMessage.sentAt = masterProject.lastMessageAt;
}
}
function firstAvailableOta(state: BossState) {
return state.otaUpdates.find((item) => item.status === "available" || item.status === "scheduled") ?? null;
}
function readPublishedOtaAsset() {
if (!existsSync(publishedApkPath)) {
return null;
}
const stats = statSync(publishedApkPath);
type PublishedOtaMetadata = {
fileName?: string;
urlPath?: string;
sizeBytes?: number;
updatedAt?: string;
sha256?: string;
versionName?: string;
};
let metadata: PublishedOtaMetadata | null = null;
if (existsSync(publishedApkMetaPath)) {
try {
metadata = JSON.parse(readFileSync(publishedApkMetaPath, "utf8")) as PublishedOtaMetadata;
} catch {
metadata = null;
}
}
return {
fileName: metadata?.fileName ?? path.basename(publishedApkPath),
downloadUrl: metadata?.urlPath ?? "/api/v1/user/ota/package",
sizeBytes: metadata?.sizeBytes ?? stats.size,
assetUpdatedAt: metadata?.updatedAt ?? stats.mtime.toISOString(),
packageSha256: metadata?.sha256,
versionName: metadata?.versionName,
};
}
function syncPublishedOtaAsset(state: BossState) {
const asset = readPublishedOtaAsset();
for (const release of state.otaUpdates) {
if (release.packageType !== "android_shell") continue;
release.currentVersion = state.user.version;
if (!asset) {
release.packageFileName = undefined;
release.packageSizeBytes = undefined;
release.packageSha256 = undefined;
release.downloadUrl = undefined;
release.assetUpdatedAt = undefined;
continue;
}
release.packageFileName = asset.fileName;
release.packageSizeBytes = asset.sizeBytes;
release.packageSha256 = asset.packageSha256;
release.downloadUrl = asset.downloadUrl;
release.assetUpdatedAt = asset.assetUpdatedAt;
if (asset.versionName) {
release.version = asset.versionName.startsWith("v")
? asset.versionName
: `v${asset.versionName}`;
}
}
}
function isProductionDevice(device: Device) {
return device.source === "production";
}
function pushProjectLedgerMessage(
state: BossState,
projectId: string,
message: Omit<Message, "id" | "sentAt"> & { sentAt?: string },
) {
const project = state.projects.find((item) => item.id === projectId);
if (!project) return null;
const entry: Message = {
id: randomToken("msg"),
sentAt: message.sentAt ?? nowIso(),
...message,
};
project.messages.push(entry);
project.lastMessageAt = entry.sentAt;
project.preview = entry.body;
return entry;
}
function shouldAutoReplyToMirroredLog(entry: AppLogEntry) {
if (entry.level !== "info") return true;
return (
entry.category.startsWith("apk.") ||
entry.category.startsWith("ota.") ||
entry.category.startsWith("runtime.") ||
entry.category.startsWith("chat.message_failed")
);
}
function buildMasterAgentLogReply(state: BossState, entry: AppLogEntry) {
const device = state.devices.find((item) => item.id === entry.deviceId);
const availableOta = firstAvailableOta(state);
const lines = [
`已收到 ${device?.name ?? entry.deviceId}${entry.level === "error" ? "错误" : "实时"}日志:${entry.category}`,
`现象:${entry.message}${entry.detail ? `,附加信息:${entry.detail}` : ""}`,
];
if (entry.level === "error") {
lines.push("我会优先沿着这条报错路径继续收口,并尽量把 patch、验证和 APK 调整一起推进。");
} else {
lines.push("我会把这条最新日志并入当前调度判断,继续对齐主 Agent 对话和 APK 优化方向。");
}
if (availableOta?.downloadUrl) {
lines.push(`当前可验证的 OTA 包是 ${availableOta.version},下载地址已经就绪。`);
}
lines.push("继续把目标、复现步骤或预期结果发给我,我会结合最新日志继续推进。");
return lines.join("");
}
function ensurePrimaryAdminBinding(state: BossState) {
state.user.id = "user-boss-admin";
state.user.name = "Boss 超级管理员";
state.user.avatar = "17";
state.user.account = PRIMARY_ADMIN_ACCOUNT;
state.user.verificationEmail = PRIMARY_ADMIN_VERIFICATION_EMAIL;
state.user.role = "highest_admin";
state.user.roleLabel = "最高管理员";
state.user.accountType = "最高管理员 · 本机 Codex 已绑定";
state.user.qrCodeValue = `boss://user/${PRIMARY_ADMIN_ACCOUNT}`;
state.user.boundCodexNodeId = PRIMARY_CODEX_NODE_ID;
state.user.boundCodexNodeLabel = PRIMARY_CODEX_NODE_LABEL;
state.user.boundDeviceId = PRIMARY_CODEX_NODE_ID;
state.user.boundAt = state.user.boundAt ?? "2026-03-26T09:00:00+08:00";
if (!state.user.version || state.user.version === "1.2.7" || state.user.version === "1.3.0") {
state.user.version = "1.4.0";
}
let adminAccount = state.authAccounts.find((item) => item.account === PRIMARY_ADMIN_ACCOUNT);
if (!adminAccount) {
adminAccount = {
id: `account-${PRIMARY_ADMIN_ACCOUNT}`,
account: PRIMARY_ADMIN_ACCOUNT,
passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD),
displayName: "Boss 超级管理员",
role: "highest_admin",
verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL,
codexNodeId: PRIMARY_CODEX_NODE_ID,
codexNodeLabel: PRIMARY_CODEX_NODE_LABEL,
primaryDeviceId: PRIMARY_CODEX_NODE_ID,
isPrimary: true,
createdAt: nowIso(),
updatedAt: nowIso(),
};
state.authAccounts.unshift(adminAccount);
} else {
adminAccount.displayName = "Boss 超级管理员";
adminAccount.role = "highest_admin";
adminAccount.verificationEmail = PRIMARY_ADMIN_VERIFICATION_EMAIL;
adminAccount.codexNodeId = PRIMARY_CODEX_NODE_ID;
adminAccount.codexNodeLabel = PRIMARY_CODEX_NODE_LABEL;
adminAccount.primaryDeviceId = PRIMARY_CODEX_NODE_ID;
adminAccount.isPrimary = true;
}
for (const account of state.authAccounts) {
if (account.account !== PRIMARY_ADMIN_ACCOUNT) {
account.isPrimary = false;
}
}
const primaryDevice = state.devices.find((item) => item.id === PRIMARY_CODEX_NODE_ID);
if (primaryDevice) {
primaryDevice.account = PRIMARY_ADMIN_ACCOUNT;
primaryDevice.note = "本机 Codex 主节点 · 17600003315 已绑定";
}
const seededOta = state.otaUpdates.find((item) =>
item.releaseId === "ota_130_to_131" || item.releaseId === "ota_140_to_141",
);
if (seededOta) {
seededOta.releaseId = "ota_140_to_141";
seededOta.version = "v2.0.0";
seededOta.currentVersion = state.user.version;
seededOta.channel = "stable";
seededOta.packageType = "android_shell";
seededOta.status = seededOta.status === "applied" ? "applied" : "available";
seededOta.summary = ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"];
seededOta.targetScope = "Boss Android 原生客户端与 Web 控制台";
seededOta.requiredRole = "highest_admin";
seededOta.publishedAt = seededOta.publishedAt || nowIso();
seededOta.packageFileName = seededOta.packageFileName || "boss-android-latest.apk";
seededOta.downloadUrl = seededOta.downloadUrl || "/api/v1/user/ota/package";
} else {
state.otaUpdates.push({
releaseId: "ota_140_to_141",
version: "v2.0.0",
currentVersion: state.user.version,
channel: "stable",
packageType: "android_shell",
status: "available",
summary: ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"],
targetScope: "Boss Android 原生客户端与 Web 控制台",
requiredRole: "highest_admin",
publishedAt: nowIso(),
packageFileName: "boss-android-latest.apk",
downloadUrl: "/api/v1/user/ota/package",
});
}
}
function ensureDefaultAiAccounts(state: BossState) {
const defaults = cloneInitialState().aiAccounts;
for (const fallback of defaults) {
if (!state.aiAccounts.some((item) => item.accountId === fallback.accountId)) {
state.aiAccounts.push(fallback);
}
}
const primary = state.aiAccounts.find((item) => item.accountId === "master-codex-primary");
if (primary) {
primary.role = "primary";
primary.provider = "master_codex_node";
primary.enabled = true;
primary.nodeId = primary.nodeId || PRIMARY_CODEX_NODE_ID;
primary.nodeLabel = primary.nodeLabel || PRIMARY_CODEX_NODE_LABEL;
primary.displayName = primary.displayName || "17600003315 · Master Codex Node";
if (primary.status !== "degraded") {
primary.status = "ready";
}
if (!primary.loginStatusNote || primary.loginStatusNote.includes("还未接通")) {
primary.loginStatusNote = "已绑定本机 Codex可通过 local-agent relay 执行主 Agent 对话。";
}
}
const fallbackApi = state.aiAccounts.find((item) => item.accountId === "openai-api-fallback");
if (fallbackApi) {
fallbackApi.role = "api_fallback";
fallbackApi.provider = "openai_api";
fallbackApi.model = fallbackApi.model || "gpt-5.4";
if (!fallbackApi.loginStatusNote) {
fallbackApi.loginStatusNote = "配置 OpenAI API Key 后,可直接为主 Agent 生成真实回复。";
}
}
}
function syncUserOtaState(state: BossState) {
const available = firstAvailableOta(state);
state.user.hasOta = Boolean(available);
state.user.otaVersion = available?.version;
state.user.otaSummary = available?.summary ?? [];
}
function syncDerivedState(input: BossState) {
const state = input;
ensurePrimaryAdminBinding(state);
ensureDefaultAiAccounts(state);
syncPublishedOtaAsset(state);
syncUserOtaState(state);
state.aiAccounts = sortAiAccounts(state.aiAccounts).slice(0, 24);
state.aiAccountSwitchHistory = state.aiAccountSwitchHistory
.sort((a, b) => b.switchedAt.localeCompare(a.switchedAt))
.slice(0, 40);
state.masterAgentTasks = state.masterAgentTasks
.sort((a, b) => b.requestedAt.localeCompare(a.requestedAt))
.slice(0, 80);
state.dispatchPlans = state.dispatchPlans
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
.slice(0, 80);
state.dispatchExecutions = state.dispatchExecutions
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
.slice(0, 160);
state.deviceImportDrafts = state.deviceImportDrafts
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
.slice(0, 40);
state.deviceImportResolutions = state.deviceImportResolutions
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
.slice(0, 80);
state.devices = state.devices.filter(isProductionDevice);
const visibleDeviceIds = new Set(state.devices.map((device) => device.id));
state.threadContextSnapshots = state.threadContextSnapshots.filter((item) =>
visibleDeviceIds.has(item.nodeId),
);
const visibleThreadIds = new Set(state.threadContextSnapshots.map((item) => item.threadId));
state.threadHandoffPackages = state.threadHandoffPackages.filter((item) =>
visibleThreadIds.has(item.fromThreadId),
);
state.threadContextAlerts = state.threadContextAlerts.filter((item) =>
visibleThreadIds.has(item.threadId),
);
state.deviceEnrollments = state.deviceEnrollments.filter((item) => visibleDeviceIds.has(item.deviceId));
state.deviceImportDrafts = state.deviceImportDrafts.filter((item) =>
visibleDeviceIds.has(item.deviceId),
);
const visibleImportDraftIds = new Set(state.deviceImportDrafts.map((item) => item.draftId));
state.deviceImportResolutions = state.deviceImportResolutions.filter(
(item) => visibleDeviceIds.has(item.deviceId) && visibleImportDraftIds.has(item.draftId),
);
const visibleProjectIds = new Set(state.projects.map((project) => project.id));
const threadStatusDocumentByThread = new Map<string, ThreadStatusDocument>();
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<string, number>();
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);
state.authSessions = state.authSessions
.filter((session) => !session.revokedAt && new Date(session.expiresAt).getTime() > Date.now())
.slice(0, 20);
state.opsFaults = state.opsFaults.filter((item) => visibleDeviceIds.has(item.nodeId));
const visibleFaultIds = new Set(state.opsFaults.map((item) => item.faultId));
state.opsRepairTickets = state.opsRepairTickets.filter((item) => visibleFaultIds.has(item.faultId));
const visibleTicketIds = new Set(state.opsRepairTickets.map((item) => item.ticketId));
state.opsRepairVerifications = state.opsRepairVerifications.filter((item) =>
visibleTicketIds.has(item.ticketId),
);
state.auditRequests = state.auditRequests.filter((item) => {
const sourceNodeId = item.sourceThreadRef.split(":")[0];
return visibleDeviceIds.has(sourceNodeId);
});
const visibleAuditIds = new Set(state.auditRequests.map((item) => item.auditRequestId));
state.auditResults = state.auditResults.filter((item) => visibleAuditIds.has(item.auditRequestId));
state.capabilities = state.capabilities.filter((item) => visibleDeviceIds.has(item.nodeId));
for (const project of state.projects) {
project.deviceIds = project.deviceIds.filter((deviceId) => visibleDeviceIds.has(deviceId));
const projectSnapshots = state.threadContextSnapshots
.filter((snapshot) => snapshot.projectId === project.id)
.sort(compareSnapshotsForRisk);
normalizeProjectConversationShape(project, { allowedDeviceIds: visibleDeviceIds });
project.riskLevel = deriveRiskFromSnapshots(projectSnapshots);
if (project.isGroup) {
project.contextBudgetPct = undefined;
project.contextBudgetLabel = undefined;
} else {
const topSnapshot = projectSnapshots[0];
project.contextBudgetPct = topSnapshot?.contextBudgetRemainingPct;
project.contextBudgetLabel = topSnapshot
? `${topSnapshot.contextBudgetRemainingPct}%`
: undefined;
}
project.lastMessageAt = latestProjectTimestamp(state, project.id);
project.updatedAt = resolveProjectUpdatedAt(project, project.lastMessageAt);
project.preview = deriveProjectPreview(state, project);
project.unreadCount = Math.max(0, project.unreadCount ?? 0);
}
updateMasterProjectSummary(state);
return state;
}
async function ensureStateFile() {
await fs.mkdir(dataDir, { recursive: true });
try {
await fs.access(dataFile);
} catch {
const initialJson = JSON.stringify(syncDerivedState(cloneInitialState()), null, 2);
await fs.writeFile(dataFile, initialJson, "utf8");
await fs.writeFile(backupFile, initialJson, "utf8");
}
}
let stateWriteQueue: Promise<void> = Promise.resolve();
let lastPersistedStateText: string | null = null;
let stateMutationQueue: Promise<unknown> = Promise.resolve();
export async function readState(): Promise<BossState> {
await ensureStateFile();
const raw = await fs.readFile(dataFile, "utf8");
try {
const state = normalizeState(JSON.parse(raw) as Partial<BossState>);
lastPersistedStateText = JSON.stringify(state, null, 2);
return state;
} catch {
const fallbackText =
(await fs.readFile(backupFile, "utf8").catch(() => null)) ??
lastPersistedStateText ??
JSON.stringify(syncDerivedState(cloneInitialState()), null, 2);
const state = normalizeState(JSON.parse(fallbackText) as Partial<BossState>);
lastPersistedStateText = JSON.stringify(state, null, 2);
return state;
}
}
export async function writeState(state: BossState) {
const nextState = syncDerivedState(state);
const serialized = JSON.stringify(nextState, null, 2);
const tempFile = `${dataFile}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
lastPersistedStateText = serialized;
const persist = async () => {
await ensureStateFile();
await fs.writeFile(tempFile, serialized, "utf8");
await fs.rename(tempFile, dataFile);
await fs.writeFile(backupFile, serialized, "utf8");
};
stateWriteQueue = stateWriteQueue.then(persist, persist);
await stateWriteQueue;
}
async function mutateState<T>(mutator: (state: BossState) => Promise<T> | 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<T>(
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<BossState>;
const tryRead = async (filePath: string) => {
const text = await fs.readFile(filePath, "utf8");
return parseStateText(text);
};
try {
return await tryRead(dataFile);
} catch {
try {
return await tryRead(backupFile);
} catch {
if (lastPersistedStateText) {
return parseStateText(lastPersistedStateText);
}
return JSON.parse(JSON.stringify(syncDerivedState(cloneInitialState()))) as Partial<BossState>;
}
}
}
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
);
}
export async function getProjectAgentControls(projectId: string, account?: string) {
if (projectId !== "master-agent") {
return null;
}
const state = await readState();
const scopedControls = findUserProjectAgentControls(state, projectId, account);
if (scopedControls?.controls) {
return scopedControls.controls;
}
return state.projects.find((project) => project.id === projectId)?.agentControls ?? null;
}
export async function updateProjectAgentControls(
projectId: string,
payload: {
modelOverride?: unknown;
reasoningEffortOverride?: unknown;
promptOverride?: unknown;
backendOverride?: unknown;
},
account?: string,
) {
if (projectId !== "master-agent") {
throw new Error("MASTER_AGENT_CONTROLS_SCOPE_RESTRICTED");
}
const modelOverrideInput = Object.prototype.hasOwnProperty.call(payload, "modelOverride")
? parseControlTextOverride(payload.modelOverride)
: { kind: "preserve" as const };
const reasoningEffortInput = Object.prototype.hasOwnProperty.call(payload, "reasoningEffortOverride")
? parseReasoningEffortOverride(payload.reasoningEffortOverride)
: { kind: "preserve" as const };
const promptOverrideInput = Object.prototype.hasOwnProperty.call(payload, "promptOverride")
? parseControlTextOverride(payload.promptOverride)
: { kind: "preserve" as const };
const backendOverrideInput = Object.prototype.hasOwnProperty.call(payload, "backendOverride")
? parseBackendOverride(payload.backendOverride)
: { kind: "preserve" as const };
if (modelOverrideInput.kind === "invalid") {
throw new Error("INVALID_MODEL_OVERRIDE");
}
if (reasoningEffortInput.kind === "invalid") {
throw new Error("INVALID_REASONING_EFFORT_OVERRIDE");
}
if (promptOverrideInput.kind === "invalid") {
throw new Error("INVALID_PROMPT_OVERRIDE");
}
if (backendOverrideInput.kind === "invalid") {
throw new Error("INVALID_BACKEND_OVERRIDE");
}
return mutateStateIfChanged((state) => {
const project = state.projects.find((item) => item.id === projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
const normalizedAccount = trimToDefined(account);
const currentEntry = findUserProjectAgentControls(state, projectId, normalizedAccount ?? undefined);
const currentControls = currentEntry?.controls ?? project.agentControls;
const modelOverride =
modelOverrideInput.kind === "set"
? modelOverrideInput.value
: modelOverrideInput.kind === "clear"
? undefined
: currentControls?.modelOverride;
const reasoningEffortOverride =
reasoningEffortInput.kind === "set"
? reasoningEffortInput.value
: reasoningEffortInput.kind === "clear"
? undefined
: currentControls?.reasoningEffortOverride;
const promptOverride =
promptOverrideInput.kind === "set"
? promptOverrideInput.value
: promptOverrideInput.kind === "clear"
? undefined
: currentControls?.promptOverride;
const backendOverride =
backendOverrideInput.kind === "set"
? backendOverrideInput.value
: backendOverrideInput.kind === "clear"
? undefined
: currentControls?.backendOverride;
const currentModelOverride = currentControls?.modelOverride;
const currentReasoningEffortOverride = currentControls?.reasoningEffortOverride;
const currentPromptOverride = currentControls?.promptOverride;
const currentBackendOverride = currentControls?.backendOverride;
if (
currentModelOverride === modelOverride &&
currentReasoningEffortOverride === reasoningEffortOverride &&
currentPromptOverride === promptOverride &&
currentBackendOverride === backendOverride
) {
return { result: currentControls, changed: false };
}
const nextControls = {
modelOverride,
reasoningEffortOverride,
promptOverride,
backendOverride,
updatedAt: nowIso(),
} satisfies ProjectAgentControls;
const normalizedControls = normalizeProjectAgentControls(nextControls) ?? null;
if (normalizedAccount) {
state.userProjectAgentControls = state.userProjectAgentControls.filter(
(item) => !(item.projectId === projectId && item.account === normalizedAccount),
);
if (normalizedControls) {
state.userProjectAgentControls.unshift({
account: normalizedAccount,
projectId,
controls: normalizedControls,
});
}
} else {
project.agentControls = normalizedControls ?? undefined;
}
project.threadMeta.updatedAt = nextControls.updatedAt;
project.updatedAt = nextControls.updatedAt;
return { result: normalizedControls, changed: true };
});
}
function projectOrchestrationRequestedBackendId(project: Project): OrchestrationBackendId {
return project.orchestrationBackendOverride ?? "boss-native-orchestrator";
}
async function buildProjectOrchestrationBackendState(
project: Project,
): Promise<ProjectOrchestrationBackendState> {
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<ProjectOrchestrationBackendState | null> {
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 async function getAttachmentStorageConfig(account: string) {
const state = await readState();
return (
state.userAttachmentStorageConfigs.find((item) => item.account === account) ?? {
account,
mode: "server_file" as const,
updatedAt: nowIso(),
}
);
}
export async function upsertAttachmentStorageConfig(config: UserAttachmentStorageConfig) {
return mutateState((state) => {
const index = state.userAttachmentStorageConfigs.findIndex(
(item) => item.account === config.account,
);
if (index >= 0) {
state.userAttachmentStorageConfigs[index] = config;
} else {
state.userAttachmentStorageConfigs.push(config);
}
return config;
});
}
export async function getMasterAgentPromptPolicy() {
const state = await readState();
return state.masterAgentPromptPolicy ?? null;
}
export async function updateMasterAgentPromptPolicy(input: {
globalPrompt: string;
updatedBy?: string;
}) {
const globalPrompt = input.globalPrompt.trim();
if (!globalPrompt) {
throw new Error("MASTER_AGENT_PROMPT_REQUIRED");
}
return mutateState((state) => {
const policy: MasterAgentPromptPolicy = {
globalPrompt,
updatedBy: input.updatedBy?.trim() || undefined,
updatedAt: nowIso(),
};
state.masterAgentPromptPolicy = policy;
return policy;
});
}
export async function getUserMasterPrompt(account: string) {
const state = await readState();
return state.userMasterPrompts.find((item) => item.account === account) ?? null;
}
export async function updateUserMasterPrompt(account: string, content: string) {
const trimmedContent = content.trim();
if (!trimmedContent) {
throw new Error("USER_MASTER_PROMPT_REQUIRED");
}
return mutateState((state) => {
const next: UserMasterPrompt = {
account,
content: trimmedContent,
updatedAt: nowIso(),
};
const existing = state.userMasterPrompts.find((item) => item.account === account);
if (existing) {
Object.assign(existing, next);
} else {
state.userMasterPrompts.unshift(next);
}
return next;
});
}
export async function clearUserMasterPrompt(account: string) {
return mutateState((state) => {
const before = state.userMasterPrompts.length;
state.userMasterPrompts = state.userMasterPrompts.filter((item) => item.account !== account);
return { cleared: before !== state.userMasterPrompts.length };
});
}
export async function listUserMasterMemories(
account: string,
options?: { includeArchived?: boolean; scope?: MasterMemoryScope; projectId?: string },
) {
const state = await readState();
const includeArchived = options?.includeArchived ?? false;
return [...state.masterAgentMemories]
.filter((memory) => {
if (memory.account !== account) return false;
if (!includeArchived && memory.archived) return false;
if (options?.scope && memory.scope !== options.scope) return false;
if (options?.projectId && memory.projectId !== options.projectId) return false;
return true;
})
.sort((a, b) => {
const timeDiff =
messageTimeValue(b.lastUsedAt ?? b.updatedAt ?? b.createdAt) -
messageTimeValue(a.lastUsedAt ?? a.updatedAt ?? a.createdAt);
if (timeDiff !== 0) return timeDiff;
return b.memoryId.localeCompare(a.memoryId);
});
}
export async function createUserMasterMemory(input: {
account: string;
scope: MasterMemoryScope;
projectId?: string;
title: string;
content: string;
memoryType: MasterMemoryType;
tags?: string[];
sourceMessageId?: string;
}) {
const title = input.title.trim();
const content = input.content.trim();
if (!title) {
throw new Error("USER_MASTER_MEMORY_TITLE_REQUIRED");
}
if (!content) {
throw new Error("USER_MASTER_MEMORY_CONTENT_REQUIRED");
}
if (input.scope === "project" && !input.projectId?.trim()) {
throw new Error("USER_MASTER_MEMORY_PROJECT_ID_REQUIRED");
}
return mutateState((state) => {
const now = nowIso();
const memory: MasterAgentMemory = {
memoryId: randomToken("memory"),
account: input.account,
scope: input.scope,
projectId: input.scope === "project" ? input.projectId?.trim() : undefined,
title,
content,
memoryType: input.memoryType,
tags: normalizeMasterMemoryTags(input.tags),
sourceMessageId: input.sourceMessageId,
createdAt: now,
updatedAt: now,
lastUsedAt: now,
archived: false,
};
state.masterAgentMemories.unshift(memory);
return memory;
});
}
export async function updateUserMasterMemory(
memoryId: string,
account: string,
patch: Partial<
Pick<
MasterAgentMemory,
"scope" | "projectId" | "title" | "content" | "memoryType" | "tags" | "sourceMessageId" | "lastUsedAt"
>
>,
) {
return mutateState((state) => {
const memory = state.masterAgentMemories.find(
(item) => item.memoryId === memoryId && item.account === account,
);
if (!memory) {
throw new Error("USER_MASTER_MEMORY_NOT_FOUND");
}
if (patch.scope) {
memory.scope = patch.scope;
}
if (memory.scope === "project" && patch.projectId !== undefined) {
memory.projectId = patch.projectId.trim() || undefined;
}
if (memory.scope !== "project") {
memory.projectId = undefined;
}
if (patch.title !== undefined) {
const title = patch.title.trim();
if (!title) throw new Error("USER_MASTER_MEMORY_TITLE_REQUIRED");
memory.title = title;
}
if (patch.content !== undefined) {
const content = patch.content.trim();
if (!content) throw new Error("USER_MASTER_MEMORY_CONTENT_REQUIRED");
memory.content = content;
}
if (patch.memoryType) {
memory.memoryType = patch.memoryType;
}
if (patch.tags) {
memory.tags = normalizeMasterMemoryTags(patch.tags);
}
if (patch.sourceMessageId !== undefined) {
memory.sourceMessageId = patch.sourceMessageId;
}
if (patch.lastUsedAt !== undefined) {
memory.lastUsedAt = patch.lastUsedAt;
}
memory.updatedAt = nowIso();
return memory;
});
}
export async function archiveUserMasterMemory(memoryId: string, account: string) {
return mutateState((state) => {
const memory = state.masterAgentMemories.find(
(item) => item.memoryId === memoryId && item.account === account,
);
if (!memory) {
throw new Error("USER_MASTER_MEMORY_NOT_FOUND");
}
memory.archived = true;
memory.updatedAt = nowIso();
return memory;
});
}
export async function touchUserMasterMemories(memoryIds: string[], account: string) {
const normalizedIds = Array.from(new Set(memoryIds.map((value) => value.trim()).filter(Boolean)));
if (normalizedIds.length === 0) {
return [];
}
return mutateState((state) => {
const now = nowIso();
const touched: MasterAgentMemory[] = [];
for (const memory of state.masterAgentMemories) {
if (memory.account !== account) continue;
if (!normalizedIds.includes(memory.memoryId)) continue;
memory.lastUsedAt = now;
touched.push(memory);
}
return touched;
});
}
function normalizeAutoMemoryText(value: string | undefined) {
return (value ?? "")
.replace(/\s+/g, " ")
.replace(/[。;;!]+$/g, "")
.trim();
}
function isLowValueAutoMemoryText(text: string) {
const normalized = normalizeAutoMemoryText(text);
if (!normalized) {
return true;
}
if (normalized.length < 10) {
return true;
}
if (/^(好的|收到|明白|继续|先这样|可以|行|没问题|辛苦了|谢谢|了解|嗯嗯)$/i.test(normalized)) {
return true;
}
if (/(马上|稍后|回头|等下|一会|临时|先看下|先试试|先这样)/i.test(normalized)) {
return true;
}
return false;
}
function inferAutoMemoryType(text: string): MasterMemoryType | null {
if (!text.trim()) return null;
if (/(微信|wechat|中文回复|中文沟通|UI风格|交互风格|偏好|习惯|默认)/i.test(text)) {
return "user_preference";
}
if (/(规则|约束|优先|先.*再|必须|不要|需要|流程|逻辑)/i.test(text)) {
return "workflow_rule";
}
if (/(阻塞|卡住|失败|异常|报错|问题|bug|未打通)/i.test(text)) {
return "blocking_issue";
}
if (/(风险|隐患|告警)/i.test(text)) {
return "risk";
}
if (/(决定|改成|采用|统一|确定|方案)/i.test(text)) {
return "decision";
}
if (/(调研|研究|结论)/i.test(text)) {
return "research_note";
}
if (/(进度|完成|已接通|已打通|上线|当前.*状态|回归|发布)/i.test(text)) {
return "project_progress";
}
return null;
}
function inferProjectAutoMemoryType(text: string): Exclude<MasterMemoryType, "user_preference"> | null {
if (!text.trim()) return null;
if (/(阻塞|卡住|失败|异常|报错|问题|bug|未打通)/i.test(text)) {
return "blocking_issue";
}
if (/(风险|隐患|告警)/i.test(text)) {
return "risk";
}
if (/(决定|改成|采用|统一|确定|方案)/i.test(text)) {
return "decision";
}
if (/(调研|研究|结论)/i.test(text)) {
return "research_note";
}
if (/(进度|完成|已接通|已打通|上线|当前.*状态|回归|发布)/i.test(text)) {
return "project_progress";
}
if (/(规则|约束|优先|先.*再|必须|不要|需要|流程|逻辑)/i.test(text)) {
return "workflow_rule";
}
return null;
}
function buildAutoMemoryTitle(memoryType: MasterMemoryType, label?: string) {
const typeLabel =
memoryType === "user_preference"
? "偏好"
: memoryType === "workflow_rule"
? "工作规则"
: memoryType === "blocking_issue"
? "阻塞"
: memoryType === "risk"
? "风险"
: memoryType === "decision"
? "决策"
: memoryType === "research_note"
? "调研结论"
: "项目进度";
return label ? `${label} · ${typeLabel}` : typeLabel;
}
function detectReferencedProjectForMemory(state: BossState, text: string) {
const lowered = text.toLowerCase();
const candidates = state.projects
.filter((project) => project.id !== "master-agent")
.flatMap((project) => {
const rawAliases = [
project.id,
project.name,
project.threadMeta.folderName,
project.threadMeta.threadDisplayName,
]
.map((value) => value.trim())
.filter(Boolean);
const aliases = Array.from(
new Set(
rawAliases.flatMap((alias) => {
const normalized = alias.trim();
if (!normalized) {
return [];
}
const tokenCandidates = normalized
.split(/[\s\-_/]+/)
.map((token) => token.trim())
.filter((token) => token.length >= 3);
return [normalized, ...tokenCandidates];
}),
),
);
return aliases.map((alias) => ({
projectId: project.id,
projectName: project.name,
alias,
}));
})
.sort((left, right) => right.alias.length - left.alias.length);
return candidates.find((candidate) => lowered.includes(candidate.alias.toLowerCase())) ?? null;
}
function upsertAutoMasterMemoryInState(
state: BossState,
input: {
account: string;
scope: MasterMemoryScope;
projectId?: string;
title: string;
content: string;
memoryType: MasterMemoryType;
tags: string[];
sourceMessageId?: string;
},
) {
const now = nowIso();
const existing = state.masterAgentMemories.find(
(memory) =>
memory.account === input.account &&
memory.scope === input.scope &&
(memory.projectId ?? undefined) === (input.projectId ?? undefined) &&
memory.title === input.title,
);
if (existing) {
existing.content = input.content;
existing.memoryType = input.memoryType;
existing.tags = normalizeMasterMemoryTags(input.tags);
existing.sourceMessageId = input.sourceMessageId ?? existing.sourceMessageId;
existing.archived = false;
existing.updatedAt = now;
existing.lastUsedAt = now;
return existing;
}
const memory: MasterAgentMemory = {
memoryId: randomToken("memory"),
account: input.account,
scope: input.scope,
projectId: input.scope === "project" ? input.projectId : undefined,
title: input.title,
content: input.content,
memoryType: input.memoryType,
tags: normalizeMasterMemoryTags(input.tags),
sourceMessageId: input.sourceMessageId,
createdAt: now,
updatedAt: now,
lastUsedAt: now,
archived: false,
};
state.masterAgentMemories.unshift(memory);
return memory;
}
function autoCaptureMasterAgentMemoriesInState(
state: BossState,
input: {
account: string;
requestText: string;
replyText: string;
sourceMessageId?: string;
},
) {
const requestText = normalizeAutoMemoryText(input.requestText);
const replyText = normalizeAutoMemoryText(input.replyText);
if (!requestText && !replyText) {
return [];
}
const createdOrUpdated: MasterAgentMemory[] = [];
const combined = [requestText, replyText].filter(Boolean).join(" ");
const preferenceCandidate = isLowValueAutoMemoryText(requestText) ? "" : requestText;
const projectCandidate = isLowValueAutoMemoryText(replyText) ? combined : replyText || combined;
const preferenceType = inferAutoMemoryType(preferenceCandidate);
if (
preferenceCandidate &&
(preferenceType === "user_preference" || preferenceType === "workflow_rule")
) {
createdOrUpdated.push(
upsertAutoMasterMemoryInState(state, {
account: input.account,
scope: "global",
title: buildAutoMemoryTitle(preferenceType),
content: preferenceCandidate,
memoryType: preferenceType,
tags: preferenceType === "user_preference" ? ["用户偏好"] : ["工作方式"],
sourceMessageId: input.sourceMessageId,
}),
);
}
const referencedProject = detectReferencedProjectForMemory(state, combined);
const projectType = inferProjectAutoMemoryType(projectCandidate) ?? inferProjectAutoMemoryType(combined);
if (referencedProject && projectType && !isLowValueAutoMemoryText(projectCandidate)) {
createdOrUpdated.push(
upsertAutoMasterMemoryInState(state, {
account: input.account,
scope: "project",
projectId: referencedProject.projectId,
title: buildAutoMemoryTitle(projectType, referencedProject.projectName),
content: projectCandidate,
memoryType: projectType,
tags: [referencedProject.projectName, referencedProject.alias],
sourceMessageId: input.sourceMessageId,
}),
);
}
return createdOrUpdated;
}
function preferredDeviceForAccount(
state: BossState,
account: string,
preferredDeviceId?: string,
) {
if (preferredDeviceId) {
const preferred = state.devices.find(
(device) =>
device.id === preferredDeviceId &&
device.source === "production" &&
device.account === account,
);
if (preferred) {
return preferred;
}
}
return (
state.devices.find(
(device) => device.source === "production" && device.account === account,
) ?? null
);
}
export function getPreferredDeviceIdForAccountFromState(
state: BossState,
account: string,
preferredDeviceId?: string,
) {
return preferredDeviceForAccount(state, account, preferredDeviceId)?.id;
}
export async function getPreferredDeviceIdForAccount(
account: string,
preferredDeviceId?: string,
) {
const state = await readState();
return getPreferredDeviceIdForAccountFromState(state, account, preferredDeviceId);
}
export async function toggleGoal(projectId: string, goalId: string) {
return mutateState((state) => {
const project = state.projects.find((item) => item.id === projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
const goal = project.goals.find((item) => item.id === goalId);
if (!goal) throw new Error("GOAL_NOT_FOUND");
if (goal.state === "completed") {
goal.state = "pending";
goal.completedAt = undefined;
goal.completedBy = undefined;
goal.note = "重新打开 · 等待继续执行";
} else {
goal.state = "completed";
goal.completedAt = nowIso();
goal.completedBy = "用户 / 主 Agent";
goal.note = "已完成 · 由用户或主 Agent 标记";
}
project.lastMessageAt = nowIso();
return goal;
});
}
export async function updateGoalText(projectId: string, goalId: string, text: string) {
return mutateState((state) => {
const project = state.projects.find((item) => item.id === projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
const goal = project.goals.find((item) => item.id === goalId);
if (!goal) throw new Error("GOAL_NOT_FOUND");
if (!text.trim()) throw new Error("GOAL_TEXT_REQUIRED");
goal.text = text.trim();
goal.note = "已编辑 · 主 Agent 将据此重排后续任务";
project.lastMessageAt = nowIso();
return goal;
});
}
export async function createGoal(projectId: string, text: string) {
return mutateState((state) => {
const project = state.projects.find((item) => item.id === projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
if (!text.trim()) throw new Error("GOAL_TEXT_REQUIRED");
const goal: GoalItem = {
id: randomToken("goal"),
text: text.trim(),
state: "pending",
note: "新建目标 · 等待主 Agent 安排优先级",
};
project.goals.unshift(goal);
project.lastMessageAt = nowIso();
return goal;
});
}
export async function issueVerificationCode(
account: string,
purpose: VerificationCode["purpose"],
) {
return mutateState((state) => {
validateVerificationPurpose(state, account, purpose);
validateVerificationSendWindow(state, account, purpose);
const code =
getVerificationDeliveryMode() === "email" ? randomDigits(6) : getFixedVerificationCode();
const record: VerificationCode = {
id: `${purpose}-${Date.now()}`,
account,
purpose,
code,
createdAt: nowIso(),
expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),
};
state.verificationCodes = [
record,
...state.verificationCodes.filter(
(item) => !(item.account === account && item.purpose === purpose),
),
].slice(0, 30);
recordVerificationDispatch(
state,
account,
purpose,
getVerificationDeliveryMode(),
"requested",
"验证码请求已创建,等待投递结果。",
);
return record;
});
}
export function hashPassword(password: string) {
const normalized = password.normalize("NFKC");
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(normalized, `boss:${salt}`, 64).toString("hex");
return `scrypt$${salt}$${hash}`;
}
function hashPasswordLegacy(password: string) {
return createHash("sha256").update(`boss:${password.normalize("NFKC")}`).digest("hex");
}
function verifyPasswordHash(password: string, passwordHash: string) {
const normalized = password.normalize("NFKC");
if (!passwordHash.startsWith("scrypt$")) {
return passwordHash === hashPasswordLegacy(normalized);
}
const [, salt, expectedHash] = passwordHash.split("$");
if (!salt || !expectedHash) return false;
const expectedBuffer = Buffer.from(expectedHash, "hex");
const actualBuffer = scryptSync(normalized, `boss:${salt}`, expectedBuffer.length);
return expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer);
}
function recordVerificationDispatch(
state: BossState,
account: string,
purpose: VerificationCode["purpose"],
deliveryMode: VerificationDeliveryMode,
status: VerificationDispatch["status"],
note: string,
) {
state.verificationDispatches.unshift({
dispatchId: randomToken("vdispatch"),
account,
purpose,
deliveryMode,
requestedAt: nowIso(),
status,
note,
});
}
function validateVerificationSendWindow(
state: BossState,
account: string,
purpose: VerificationCode["purpose"],
) {
const relevant = state.verificationDispatches
.filter((item) => item.account === account && item.purpose === purpose)
.sort((a, b) => b.requestedAt.localeCompare(a.requestedAt));
const latest = relevant[0];
if (latest && Date.now() - new Date(latest.requestedAt).getTime() < VERIFICATION_SEND_COOLDOWN_MS) {
throw new Error("VERIFICATION_CODE_COOLDOWN");
}
const recentCount = relevant.filter(
(item) => Date.now() - new Date(item.requestedAt).getTime() < VERIFICATION_SEND_WINDOW_MS,
).length;
if (recentCount >= VERIFICATION_SEND_WINDOW_LIMIT) {
throw new Error("VERIFICATION_RATE_LIMITED");
}
}
function validateVerificationPurpose(
state: BossState,
account: string,
purpose: VerificationCode["purpose"],
) {
const existing = state.authAccounts.find((item) => item.account === account);
if (purpose === "register") {
if (existing) {
throw new Error("ACCOUNT_ALREADY_EXISTS");
}
return;
}
if (!existing) {
throw new Error("ACCOUNT_NOT_FOUND");
}
}
function findValidCodeIndex(
state: BossState,
account: string,
purpose: VerificationCode["purpose"],
code: string,
) {
const index = state.verificationCodes.findIndex(
(item) => item.account === account && item.purpose === purpose && item.code === code,
);
if (index < 0) return null;
const record = state.verificationCodes[index];
if (new Date(record.expiresAt).getTime() <= Date.now()) {
return null;
}
return index;
}
function consumeVerificationCode(
state: BossState,
account: string,
purpose: VerificationCode["purpose"],
code: string,
) {
const index = findValidCodeIndex(state, account, purpose, code);
if (index === null) return false;
if (index >= 0) {
state.verificationCodes.splice(index, 1);
}
return true;
}
function shouldAcceptDirectFixedVerificationCode(
purpose: VerificationCode["purpose"],
code?: string,
) {
if (purpose !== "login") return false;
if (getVerificationDeliveryMode() !== "fixed") return false;
return code?.trim() === getFixedVerificationCode();
}
function isLikelyEmailAccount(account: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(account);
}
function verificationRecipientForAccount(state: BossState, account: string) {
if (isLikelyEmailAccount(account)) {
return account;
}
return state.authAccounts.find((item) => item.account === account)?.verificationEmail ?? null;
}
function registerLoginFailure(account: AuthAccount) {
const attempts = (account.failedLoginAttempts ?? 0) + 1;
account.failedLoginAttempts = attempts;
account.updatedAt = nowIso();
if (attempts >= AUTH_LOGIN_LOCK_THRESHOLD) {
account.lockedUntil = new Date(Date.now() + AUTH_LOGIN_LOCK_MS).toISOString();
account.failedLoginAttempts = 0;
}
}
function clearLoginFailure(account: AuthAccount) {
account.failedLoginAttempts = 0;
account.lockedUntil = undefined;
}
function activeAuthSession(state: BossState, token?: string | null) {
if (!token) return null;
const session = state.authSessions.find((item) => item.sessionToken === token);
if (!session || session.revokedAt) return null;
if (new Date(session.expiresAt).getTime() <= Date.now()) {
session.revokedAt = nowIso();
return null;
}
return session;
}
function activeAuthSessionByRestoreToken(state: BossState, restoreToken?: string | null) {
if (!restoreToken) return null;
const session = state.authSessions.find((item) => item.restoreToken === restoreToken);
if (!session || session.revokedAt) return null;
if (new Date(session.expiresAt).getTime() <= Date.now()) {
session.revokedAt = nowIso();
return null;
}
return session;
}
export async function createAuthSession(params: {
account: string;
role: AuthRole;
displayName: string;
loginMethod: LoginMethod;
}) {
return mutateState((state) => {
state.authSessions = state.authSessions.filter((session) => session.account !== params.account);
const createdAt = nowIso();
const session: AuthSession = {
sessionId: randomToken("session"),
sessionToken: randomBytes(24).toString("hex"),
restoreToken: randomBytes(24).toString("hex"),
account: params.account,
role: params.role,
displayName: params.displayName,
loginMethod: params.loginMethod,
createdAt,
expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(),
lastSeenAt: createdAt,
};
state.authSessions.unshift(session);
return session;
});
}
export async function createPrimaryAdminSession() {
return mutateState((state) => {
let existing = state.authAccounts.find((item) => item.account === PRIMARY_ADMIN_ACCOUNT);
if (!existing) {
existing = {
id: `account-${PRIMARY_ADMIN_ACCOUNT}`,
account: PRIMARY_ADMIN_ACCOUNT,
passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD),
displayName: "Boss 超级管理员",
role: "highest_admin",
verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL,
codexNodeId: PRIMARY_CODEX_NODE_ID,
codexNodeLabel: PRIMARY_CODEX_NODE_LABEL,
primaryDeviceId: PRIMARY_CODEX_NODE_ID,
isPrimary: true,
createdAt: nowIso(),
updatedAt: nowIso(),
};
state.authAccounts.unshift(existing);
}
clearLoginFailure(existing);
existing.updatedAt = nowIso();
existing.lastLoginAt = nowIso();
existing.lastLoginMethod = "password";
const session = {
sessionId: randomToken("session"),
sessionToken: randomBytes(24).toString("hex"),
restoreToken: randomBytes(24).toString("hex"),
account: existing.account,
role: existing.role,
displayName: existing.displayName,
loginMethod: "password" as const,
createdAt: nowIso(),
expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(),
lastSeenAt: nowIso(),
} satisfies AuthSession;
state.authSessions = [
session,
...state.authSessions.filter((item) => item.account !== existing.account),
].slice(0, 20);
return {
account: existing.account,
role: existing.role,
displayName: existing.displayName,
loginMethod: session.loginMethod,
sessionToken: session.sessionToken,
restoreToken: session.restoreToken,
sessionExpiresAt: session.expiresAt,
};
});
}
export async function getVerificationDeliveryTarget(account: string) {
const state = await readState();
return verificationRecipientForAccount(state, account);
}
export async function getAuthSession(token?: string | null) {
return mutateState((state) => {
const session = activeAuthSession(state, token);
if (!session) return null;
session.lastSeenAt = nowIso();
return { ...session };
});
}
export async function restoreAuthSession(restoreToken?: string | null) {
return mutateState((state) => {
const session = activeAuthSessionByRestoreToken(state, restoreToken);
if (!session) return null;
session.lastSeenAt = nowIso();
return { ...session };
});
}
export async function revokeAuthSession(token?: string | null) {
if (!token) return;
await mutateState((state) => {
const session = state.authSessions.find((item) => item.sessionToken === token);
if (session && !session.revokedAt) {
session.revokedAt = nowIso();
}
});
}
function setActiveAiAccountInState(
state: BossState,
accountId: string,
reason: string,
options?: { preserveLastSwitchAt?: boolean },
) {
const currentActive = state.aiAccounts.find((item) => item.isActive);
const nextActive = state.aiAccounts.find((item) => item.accountId === accountId);
if (!nextActive) {
throw new Error("AI_ACCOUNT_NOT_FOUND");
}
const switchedAt = nowIso();
for (const account of state.aiAccounts) {
account.isActive = account.accountId === accountId;
account.updatedAt = switchedAt;
if (account.accountId === accountId) {
account.lastSwitchedAt = options?.preserveLastSwitchAt ? account.lastSwitchedAt : switchedAt;
account.switchReason = reason;
}
}
if (currentActive?.accountId !== nextActive.accountId || reason !== nextActive.switchReason) {
state.aiAccountSwitchHistory.unshift({
switchId: randomToken("aiswitch"),
fromAccountId: currentActive?.accountId,
fromLabel: currentActive?.label,
toAccountId: nextActive.accountId,
toLabel: nextActive.label,
role: nextActive.role,
switchedAt,
reason,
});
}
return nextActive;
}
export async function listAiAccounts() {
const state = await readState();
return {
accounts: getAiAccountSummariesFromState(state),
activeIdentity: getMasterIdentitySummaryFromState(state),
switchHistory: [...state.aiAccountSwitchHistory].sort((a, b) =>
b.switchedAt.localeCompare(a.switchedAt),
),
};
}
export async function getAiAccount(accountId: string) {
const state = await readState();
const account = state.aiAccounts.find((item) => item.accountId === accountId);
if (!account) return null;
return buildAiAccountSummary(account);
}
export async function getRuntimeAiAccountById(accountId: string) {
if (accountId === ENV_OPENAI_ACCOUNT_ID) {
return getEnvOpenAiAccount();
}
const state = await readState();
return state.aiAccounts.find((item) => item.accountId === accountId) ?? null;
}
export async function saveAiAccount(payload: {
accountId?: string;
label: string;
role: AiAccountRole;
provider: AiProvider;
displayName: string;
accountIdentifier?: string;
nodeId?: string;
nodeLabel?: string;
model?: string;
apiKey?: string;
enabled?: boolean;
setActive?: boolean;
loginStatusNote?: string;
}) {
return mutateState((state) => {
const existing = payload.accountId
? state.aiAccounts.find((item) => item.accountId === payload.accountId)
: null;
const accountId =
existing?.accountId ??
payload.accountId?.trim() ??
`ai-${slugify(`${payload.label}-${payload.displayName}`)}`;
const defaultModel =
payload.provider === "aliyun_qwen_api"
? "qwen3.5-plus"
: payload.provider === "openai_api"
? "gpt-5.4"
: undefined;
const next: AiAccount = normalizeAiAccount({
accountId,
label: payload.label.trim() || aiRoleLabel(payload.role),
role: payload.role,
provider: payload.provider,
displayName: payload.displayName.trim() || "未命名 AI",
accountIdentifier: payload.accountIdentifier?.trim() || undefined,
nodeId: payload.nodeId?.trim() || undefined,
nodeLabel: payload.nodeLabel?.trim() || undefined,
model: payload.model?.trim() || defaultModel,
apiKey:
isApiKeyProvider(payload.provider)
? payload.apiKey?.trim()
? payload.apiKey.trim()
: existing?.apiKey
: undefined,
apiKeyMasked:
isApiKeyProvider(payload.provider)
? maskApiKey(payload.apiKey?.trim() || existing?.apiKey)
: undefined,
enabled: payload.enabled ?? existing?.enabled ?? true,
isActive: existing?.isActive ?? false,
status:
isApiKeyProvider(payload.provider)
? payload.apiKey?.trim() || existing?.apiKey
? existing?.status === "degraded"
? "degraded"
: "ready"
: "needs_api_key"
: payload.nodeId?.trim() || existing?.nodeId
? existing?.status === "degraded"
? "degraded"
: "ready"
: "needs_login",
loginStatusNote: payload.loginStatusNote?.trim() || existing?.loginStatusNote,
lastValidatedAt: existing?.lastValidatedAt,
lastUsedAt: existing?.lastUsedAt,
lastError: existing?.lastError,
lastSwitchedAt: existing?.lastSwitchedAt,
switchReason: existing?.switchReason,
createdAt: existing?.createdAt ?? nowIso(),
updatedAt: nowIso(),
});
if (existing) {
const index = state.aiAccounts.findIndex((item) => item.accountId === existing.accountId);
state.aiAccounts[index] = next;
} else {
state.aiAccounts.unshift(next);
}
if (payload.setActive ?? (!existing && next.role === "primary")) {
setActiveAiAccountInState(state, next.accountId, existing ? "手动更新 AI 账号配置" : "新增 AI 账号并设为当前主控");
}
return buildAiAccountSummary(next);
});
}
export async function deleteAiAccount(accountId: string) {
if (accountId === ENV_OPENAI_ACCOUNT_ID) {
throw new Error("ENV_AI_ACCOUNT_READ_ONLY");
}
return mutateState((state) => {
const target = state.aiAccounts.find((item) => item.accountId === accountId);
if (!target) {
throw new Error("AI_ACCOUNT_NOT_FOUND");
}
state.aiAccounts = state.aiAccounts.filter((item) => item.accountId !== accountId);
if (target.isActive) {
const fallback = sortAiAccounts(state.aiAccounts).find((item) => item.enabled);
if (fallback) {
setActiveAiAccountInState(state, fallback.accountId, `删除 ${target.label} 后自动切换`);
}
}
return true;
});
}
export async function activateAiAccount(accountId: string, reason: string) {
if (accountId === ENV_OPENAI_ACCOUNT_ID) {
const state = await readState();
return {
activeIdentity: {
...getMasterIdentitySummaryFromState(state),
accountId: ENV_OPENAI_ACCOUNT_ID,
label: "API 容灾",
role: "api_fallback" as const,
roleLabel: aiRoleLabel("api_fallback"),
provider: "openai_api" as const,
providerLabel: aiProviderLabel("openai_api"),
isEnvironmentFallback: true,
},
};
}
return mutateState((state) => {
setActiveAiAccountInState(state, accountId, reason);
return {
activeIdentity: getMasterIdentitySummaryFromState(state),
};
});
}
export async function updateAiAccountHealth(params: {
accountId: string;
status: AiAccountStatus;
lastError?: string;
lastValidatedAt?: string;
lastUsedAt?: string;
switchReason?: string;
activate?: boolean;
}) {
if (params.accountId === ENV_OPENAI_ACCOUNT_ID) {
return;
}
await mutateState((state) => {
const account = state.aiAccounts.find((item) => item.accountId === params.accountId);
if (!account) {
throw new Error("AI_ACCOUNT_NOT_FOUND");
}
account.status = params.status;
account.lastError = params.lastError;
account.lastValidatedAt = params.lastValidatedAt ?? account.lastValidatedAt;
account.lastUsedAt = params.lastUsedAt ?? account.lastUsedAt;
account.updatedAt = nowIso();
if (params.switchReason) {
account.switchReason = params.switchReason;
account.lastSwitchedAt = nowIso();
}
if (params.activate) {
setActiveAiAccountInState(
state,
account.accountId,
params.switchReason ?? "AI 账号状态更新后设为当前主控",
{ preserveLastSwitchAt: true },
);
}
});
}
export async function getMasterAgentRuntimeAccount() {
const state = await readState();
const resolved = resolveActiveAiAccount(state);
if (!resolved.account) {
return null;
}
return {
account: resolved.account,
summary: getMasterIdentitySummaryFromState(state),
isEnvironmentFallback: resolved.isEnvironmentFallback,
};
}
export async function queueMasterAgentTask(payload: {
taskId?: string;
projectId?: string;
taskType?: MasterAgentTaskType;
requestMessageId: string;
requestText: string;
executionPrompt: string;
requestedBy: string;
requestedByAccount: string;
deviceId: string;
accountId?: string;
accountLabel?: string;
attachmentId?: string;
attachmentFileName?: string;
attachmentDownloadToken?: string;
attachmentDownloadExpiresAt?: string;
attachmentDownloadUrl?: string;
attachmentTextExcerpt?: string;
deviceImportDraftId?: string;
dispatchExecutionId?: string;
targetProjectId?: string;
targetThreadId?: string;
targetThreadDisplayName?: string;
targetCodexThreadRef?: string;
targetCodexFolderRef?: string;
orchestrationBackendId?: OrchestrationBackendId;
orchestrationBackendLabel?: string;
deviceImportCandidateId?: string;
deviceImportCandidateFolderName?: string;
projectUnderstandingTargetProjectId?: string;
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply";
}) {
const task = await mutateState((state) => {
const task: MasterAgentTask = {
taskId: payload.taskId ?? randomToken("mastertask"),
projectId: payload.projectId ?? "master-agent",
taskType: payload.taskType ?? "conversation_reply",
requestMessageId: payload.requestMessageId,
requestText: payload.requestText,
executionPrompt: payload.executionPrompt,
requestedBy: payload.requestedBy,
requestedByAccount: payload.requestedByAccount,
deviceId: payload.deviceId,
accountId: payload.accountId,
accountLabel: payload.accountLabel,
attachmentId: payload.attachmentId,
attachmentFileName: payload.attachmentFileName,
attachmentDownloadToken: payload.attachmentDownloadToken,
attachmentDownloadExpiresAt: payload.attachmentDownloadExpiresAt,
attachmentDownloadUrl: payload.attachmentDownloadUrl,
attachmentTextExcerpt: payload.attachmentTextExcerpt,
deviceImportDraftId: payload.deviceImportDraftId,
dispatchExecutionId: payload.dispatchExecutionId,
targetProjectId: payload.targetProjectId,
targetThreadId: payload.targetThreadId,
targetThreadDisplayName: payload.targetThreadDisplayName,
targetCodexThreadRef: payload.targetCodexThreadRef,
targetCodexFolderRef: payload.targetCodexFolderRef,
orchestrationBackendId: payload.orchestrationBackendId,
orchestrationBackendLabel: payload.orchestrationBackendLabel,
deviceImportCandidateId: payload.deviceImportCandidateId,
deviceImportCandidateFolderName: payload.deviceImportCandidateFolderName,
projectUnderstandingTargetProjectId: payload.projectUnderstandingTargetProjectId,
projectUnderstandingReason: payload.projectUnderstandingReason,
status: "queued",
requestedAt: nowIso(),
};
state.masterAgentTasks.unshift(task);
return task;
});
publishBossEvent("master_agent.task.updated", {
taskId: task.taskId,
deviceId: task.deviceId,
status: task.status,
});
return task;
}
export async function createDispatchPlan(input: {
groupProjectId: string;
requestMessageId: string;
requestedBy: string;
summary?: string;
targets: DispatchPlanTarget[];
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(),
};
state.masterAgentTasks.unshift(task);
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;
}
export async function claimNextMasterAgentTask(deviceId: string) {
let attachmentProjectId: string | undefined;
let dispatchExecutionProjectId: string | undefined;
const task = await mutateState((state) => {
const next = state.masterAgentTasks.find(
(item) => item.deviceId === deviceId && item.status === "queued",
);
if (!next) return null;
next.status = "running";
next.claimedAt = nowIso();
if (next.taskType === "attachment_analysis" && next.attachmentId) {
const project = state.projects.find((item) => item.id === next.projectId);
const match = project ? findProjectAttachment(project, next.attachmentId) : null;
if (match) {
match.attachment.analysisState = "processing";
match.attachment.analysisSummary = undefined;
match.attachment.analysisCardId = undefined;
attachmentProjectId = next.projectId;
}
}
if (next.taskType === "dispatch_execution" && next.dispatchExecutionId) {
const execution = state.dispatchExecutions.find(
(item) => item.executionId === next.dispatchExecutionId,
);
if (execution && execution.status === "queued") {
execution.status = "running";
dispatchExecutionProjectId = execution.groupProjectId;
}
}
return { ...next };
});
if (task) {
publishBossEvent("master_agent.task.updated", {
taskId: task.taskId,
deviceId: task.deviceId,
status: task.status,
});
if (attachmentProjectId) {
publishBossEvent("project.messages.updated", { projectId: attachmentProjectId });
publishBossEvent("conversation.updated", { projectId: attachmentProjectId });
}
if (dispatchExecutionProjectId) {
publishBossEvent("conversation.updated", { projectId: dispatchExecutionProjectId });
}
}
return task;
}
export async function completeMasterAgentTask(payload: {
taskId: string;
deviceId: string;
status: "completed" | "failed";
replyBody?: string;
errorMessage?: string;
requestId?: string;
dispatchExecutionId?: string;
targetProjectId?: string;
targetThreadId?: string;
rawThreadReply?: string;
dispatchPlan?: {
summary?: string;
targets: DispatchPlanTarget[];
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");
}
task.status = payload.status;
task.completedAt = nowIso();
task.replyBody = payload.replyBody?.trim() || undefined;
task.errorMessage = payload.errorMessage?.trim() || undefined;
task.requestId = payload.requestId;
const linkedAccount = task.accountId
? state.aiAccounts.find((item) => item.accountId === task.accountId)
: undefined;
if (linkedAccount) {
linkedAccount.updatedAt = task.completedAt;
linkedAccount.lastUsedAt = task.completedAt;
linkedAccount.lastValidatedAt = task.completedAt;
linkedAccount.lastError = task.errorMessage;
linkedAccount.status = payload.status === "completed" ? "ready" : "degraded";
if (!linkedAccount.isActive) {
setActiveAiAccountInState(
state,
linkedAccount.accountId,
payload.status === "completed"
? "Master Codex Node 完成回复后自动切回当前主控"
: "Master Codex Node 失败后记录当前主控身份",
{ preserveLastSwitchAt: true },
);
}
}
let attachmentProjectId: string | undefined;
let createdDispatchPlan: DispatchPlan | undefined;
let dispatchExecutionResult:
| ReturnType<typeof appendDispatchExecutionResultInState>
| 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,
sourceMessageId: task.requestMessageId,
sourceKind: "thread_sync",
});
if (
targetProject &&
shouldAnnounceProjectUnderstandingUpdate(previousUnderstanding, understanding)
) {
const projectDisplayName =
targetProject.threadMeta.threadDisplayName?.trim() || targetProject.name;
pushProjectLedgerMessage(state, "master-agent", {
sender: "master",
senderLabel: "主 Agent",
body: buildProjectUnderstandingUpdateDigest(projectDisplayName, understanding),
kind: "system_notice",
});
if (
understanding.recommendedNextStep?.trim() &&
previousUnderstanding?.recommendedNextStep !== understanding.recommendedNextStep
) {
pushProjectLedgerMessage(state, "master-agent", {
sender: "master",
senderLabel: "主 Agent",
body: buildProjectUnderstandingNextStepNotice(projectDisplayName, understanding),
kind: "system_notice",
});
pushProjectLedgerMessage(state, "master-agent", {
sender: "master",
senderLabel: "主 Agent",
body: buildProjectUnderstandingCollaborationNotice(projectDisplayName, understanding),
kind: "system_notice",
});
}
publishBossEvent("project.messages.updated", { projectId: "master-agent" });
publishBossEvent("conversation.updated", { projectId: "master-agent" });
}
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);
pushProjectLedgerMessage(state, threadProject?.id ?? task.projectId, {
sender: "device",
senderLabel:
task.targetThreadDisplayName?.trim() ||
threadProject?.threadMeta.threadDisplayName ||
device?.name ||
"线程",
body: task.replyBody,
kind: "text",
});
} else {
pushProjectLedgerMessage(state, task.projectId, {
sender: "master",
senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent",
body: task.replyBody,
kind: "text",
});
autoCaptureMasterAgentMemoriesInState(state, {
account: task.requestedByAccount,
requestText: task.requestText,
replyText: task.replyBody,
sourceMessageId: task.requestMessageId,
});
}
} else if (!attachmentProjectId && payload.status === "failed") {
const isThreadConversationReply =
task.taskType === "conversation_reply" &&
task.projectId !== "master-agent" &&
Boolean(task.targetProjectId && task.targetThreadId);
pushProjectLedgerMessage(state, task.projectId, {
sender: "ops",
senderLabel: isThreadConversationReply
? "线程执行失败"
: task.accountLabel
? `主 Agent Relay · ${task.accountLabel}`
: "主 Agent Relay",
body: isThreadConversationReply
? `${task.targetThreadDisplayName ?? "当前线程"} 执行失败:${buildFriendlyThreadExecutionError(task.errorMessage)}`
: `Master Codex Node 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`,
kind: "text",
});
}
return {
...task,
dispatchPlan: createdDispatchPlan ? { ...createdDispatchPlan } : undefined,
dispatchExecution: dispatchExecutionResult?.execution,
};
});
publishBossEvent("master_agent.task.updated", {
taskId: result.taskId,
deviceId: result.deviceId,
status: result.status,
});
publishBossEvent("project.messages.updated", { projectId: result.projectId });
publishBossEvent("conversation.updated", { projectId: result.projectId });
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 registerAccount(account: string, password: string, code: string) {
await mutateState((state) => {
if (!consumeVerificationCode(state, account, "register", code)) {
throw new Error("INVALID_VERIFICATION_CODE");
}
if (state.authAccounts.some((item) => item.account === account)) {
throw new Error("ACCOUNT_ALREADY_EXISTS");
}
state.authAccounts.push({
id: `account-${slugify(account)}`,
account,
passwordHash: hashPassword(password),
displayName: account,
role: "member",
verificationEmail: isLikelyEmailAccount(account) ? account : undefined,
createdAt: nowIso(),
updatedAt: nowIso(),
});
});
}
export async function loginAccount(params: {
account: string;
password?: string;
code?: string;
method?: LoginMethod;
}) {
return mutateState((state) => {
const method = params.method ?? (params.password?.trim() ? "password" : "code");
const existing = state.authAccounts.find((item) => item.account === params.account);
if (!existing) {
throw new Error("ACCOUNT_NOT_FOUND");
}
if (existing.lockedUntil && new Date(existing.lockedUntil).getTime() > Date.now()) {
throw new Error("LOGIN_TEMPORARILY_LOCKED");
}
if (method === "password") {
if (!params.password?.trim()) {
throw new Error("PASSWORD_REQUIRED");
}
if (!verifyPasswordHash(params.password, existing.passwordHash)) {
registerLoginFailure(existing);
throw new Error("INVALID_ACCOUNT_OR_PASSWORD");
}
if (!existing.passwordHash.startsWith("scrypt$")) {
existing.passwordHash = hashPassword(params.password);
}
} else {
if (!params.code?.trim()) {
throw new Error("VERIFICATION_CODE_REQUIRED");
}
const directFixedCode = shouldAcceptDirectFixedVerificationCode("login", params.code);
if (!directFixedCode && !consumeVerificationCode(state, params.account, "login", params.code)) {
registerLoginFailure(existing);
throw new Error("INVALID_VERIFICATION_CODE");
}
}
clearLoginFailure(existing);
existing.updatedAt = nowIso();
existing.lastLoginAt = nowIso();
existing.lastLoginMethod = method;
const session = {
sessionId: randomToken("session"),
sessionToken: randomBytes(24).toString("hex"),
restoreToken: randomBytes(24).toString("hex"),
account: existing.account,
role: existing.role,
displayName: existing.displayName,
loginMethod: method,
createdAt: nowIso(),
expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(),
lastSeenAt: nowIso(),
} satisfies AuthSession;
state.authSessions = [
session,
...state.authSessions.filter((item) => item.account !== existing.account),
].slice(0, 20);
return {
account: existing.account,
role: existing.role,
displayName: existing.displayName,
loginMethod: method,
sessionToken: session.sessionToken,
restoreToken: session.restoreToken,
sessionExpiresAt: session.expiresAt,
};
});
}
export async function resetAccountPassword(account: string, password: string, code: string) {
await mutateState((state) => {
if (!consumeVerificationCode(state, account, "forgot-password", code)) {
throw new Error("INVALID_VERIFICATION_CODE");
}
const existing = state.authAccounts.find((item) => item.account === account);
if (!existing) {
throw new Error("ACCOUNT_NOT_FOUND");
}
existing.passwordHash = hashPassword(password);
clearLoginFailure(existing);
existing.updatedAt = nowIso();
state.authSessions = state.authSessions.filter((item) => item.account !== account);
});
}
function masterActionsForSnapshot(snapshot: ThreadContextSnapshot) {
const actions = new Set<string>();
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 = "线程预算已恢复到 safehandoff 风险暂时解除。";
}
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<ThreadContextSnapshot, "snapshotId" | "workerId" | "contextBudgetLevel"> & {
contextBudgetLevel?: ContextBudgetLevel;
},
) {
const normalized = await mutateState((state) => {
const snapshot: ThreadContextSnapshot = {
snapshotId: randomToken("snapshot"),
workerId,
contextBudgetLevel:
payload.contextBudgetLevel ??
deriveLevelFromPercent(payload.contextBudgetRemainingPct),
...payload,
};
state.threadContextSnapshots = [
snapshot,
...state.threadContextSnapshots.filter((item) => item.threadId !== payload.threadId),
];
upsertThreadAlert(state, snapshot);
ensureHandoffPackage(state, snapshot);
return snapshot;
});
publishBossEvent("project.context_risk.updated", {
projectId: normalized.projectId,
deviceId: normalized.nodeId,
note: normalized.threadId,
});
publishBossEvent("conversation.updated", {
projectId: normalized.projectId,
deviceId: normalized.nodeId,
});
return {
accepted: true,
thread_id: normalized.threadId,
context_budget_level: normalized.contextBudgetLevel,
next_required_report_in_seconds: nextReportSeconds(normalized.contextBudgetLevel),
master_actions: masterActionsForSnapshot(normalized),
};
}
export async function createDeviceEnrollment(payload: {
name: string;
avatar: string;
account: string;
endpoint?: string;
projects: string[];
note?: string;
}) {
const { device, enrollment, deviceId } = await mutateState((state) => {
const nextDeviceId = slugify(payload.name);
const token = randomToken("boss");
const pairingCode = randomDigits(6);
let device = state.devices.find((item) => item.id === nextDeviceId);
if (!device) {
device = {
id: nextDeviceId,
name: payload.name,
avatar: payload.avatar,
account: payload.account,
source: "production",
status: "offline",
projects: payload.projects,
quota5h: 100,
quota7d: 100,
lastSeenAt: nowIso(),
endpoint: payload.endpoint,
token,
note: payload.note,
};
state.devices.push(device);
} else {
device.name = payload.name;
device.avatar = payload.avatar;
device.account = payload.account;
device.source = "production";
device.projects = payload.projects;
device.endpoint = payload.endpoint;
device.note = payload.note;
device.token = token;
}
const enrollment: DeviceEnrollment = {
enrollmentId: randomToken("enroll"),
deviceId: nextDeviceId,
label: payload.name,
pairingCode,
token,
status: "ready",
note: payload.note ?? "等待本地 agent 首次心跳 claim",
createdAt: nowIso(),
expiresAt: new Date(Date.now() + 8 * 60 * 60_000).toISOString(),
};
state.deviceEnrollments = [
enrollment,
...state.deviceEnrollments.filter((item) => item.deviceId !== nextDeviceId),
];
return { device, enrollment, deviceId: nextDeviceId };
});
publishBossEvent("devices.updated", { deviceId });
return { device, enrollment };
}
export async function updateDevice(deviceId: string, payload: Partial<Device>) {
const device = await mutateState((state) => {
const nextDevice = state.devices.find((item) => item.id === deviceId);
if (!nextDevice) throw new Error("DEVICE_NOT_FOUND");
if (payload.name) nextDevice.name = payload.name.trim();
if (payload.avatar) nextDevice.avatar = payload.avatar.trim().slice(0, 2) || nextDevice.avatar;
if (payload.account) nextDevice.account = payload.account.trim();
if (payload.status) nextDevice.status = payload.status;
if (payload.endpoint !== undefined) nextDevice.endpoint = payload.endpoint;
if (payload.note !== undefined) nextDevice.note = payload.note;
if (payload.projects) {
nextDevice.projects = payload.projects.filter(Boolean);
}
nextDevice.lastSeenAt = nowIso();
return nextDevice;
});
publishBossEvent("devices.updated", { deviceId });
return device;
}
function claimEnrollment(
state: BossState,
deviceId: string,
pairingCode?: string,
token?: string,
) {
const enrollment = state.deviceEnrollments.find((item) => item.deviceId === deviceId);
if (!enrollment) return null;
const expired = new Date(enrollment.expiresAt).getTime() < Date.now();
if (expired) {
enrollment.status = "expired";
return null;
}
if (token && token === enrollment.token) {
enrollment.status = "claimed";
enrollment.claimedAt = nowIso();
enrollment.claimedDeviceId = deviceId;
return enrollment;
}
if (pairingCode && pairingCode === enrollment.pairingCode) {
enrollment.status = "claimed";
enrollment.claimedAt = nowIso();
enrollment.claimedDeviceId = deviceId;
return enrollment;
}
return null;
}
function hasAuthorizedDeviceToken(
state: BossState,
deviceId: string,
token?: string,
) {
if (!token) return false;
const device = state.devices.find((item) => item.id === deviceId);
if (device?.token && device.token === token) {
return true;
}
const enrollment = state.deviceEnrollments.find((item) => item.deviceId === deviceId);
if (!enrollment || enrollment.token !== token) {
return false;
}
return new Date(enrollment.expiresAt).getTime() > Date.now();
}
export async function verifyDeviceToken(deviceId: string, token?: string) {
if (!token) return false;
const state = await readState();
return hasAuthorizedDeviceToken(state, deviceId, token);
}
function upsertDeviceImportDraftFromHeartbeat(
state: BossState,
payload: {
deviceId: string;
enrollmentId?: string;
candidates: DeviceImportCandidate[];
},
) {
const existing = state.deviceImportDrafts.find((item) => item.deviceId === payload.deviceId);
if (payload.candidates.length === 0) {
if (existing?.status === "applied" && existing.appliedProjectNames.length > 0) {
return existing;
}
const waitingDraft = normalizeDeviceImportDraft({
draftId: existing?.draftId ?? randomToken("import-draft"),
deviceId: payload.deviceId,
enrollmentId: payload.enrollmentId ?? existing?.enrollmentId,
status: "pending_candidates",
candidates: [],
selectedCandidateIds: [],
appliedProjectNames: [],
createdAt: existing?.createdAt ?? nowIso(),
updatedAt: nowIso(),
}, existing);
waitingDraft.reviewedAt = undefined;
waitingDraft.reviewedBy = undefined;
waitingDraft.resolutionId = undefined;
state.deviceImportResolutions = state.deviceImportResolutions.filter(
(item) => item.draftId !== waitingDraft.draftId,
);
state.deviceImportDrafts = [
waitingDraft,
...state.deviceImportDrafts.filter((item) => item.draftId !== waitingDraft.draftId),
];
return waitingDraft;
}
const selectedCandidateIds = dedupeStrings(
(existing?.selectedCandidateIds ?? []).filter((candidateId) =>
payload.candidates.some((candidate) => candidate.candidateId === candidateId),
),
);
const previousCandidateIds = existing?.candidates.map((candidate) => candidate.candidateId) ?? [];
const nextCandidateIds = payload.candidates.map((candidate) => candidate.candidateId);
const selectionChanged =
!sameStringSet(existing?.selectedCandidateIds ?? [], selectedCandidateIds) ||
!sameStringSet(previousCandidateIds, nextCandidateIds);
const keepAppliedState =
!selectionChanged &&
existing?.status === "applied" &&
Boolean(existing.resolutionId) &&
selectedCandidateIds.length > 0;
const keepResolvedState =
!selectionChanged &&
selectedCandidateIds.length > 0 &&
Boolean(existing?.resolutionId);
const nextDraft = normalizeDeviceImportDraft({
draftId: existing?.draftId ?? randomToken("import-draft"),
deviceId: payload.deviceId,
enrollmentId: payload.enrollmentId ?? existing?.enrollmentId,
status:
keepAppliedState
? "applied"
: selectedCandidateIds.length > 0
? keepResolvedState
? "resolved"
: "pending_resolution"
: "pending_selection",
candidates: payload.candidates,
selectedCandidateIds,
appliedProjectNames:
keepAppliedState
? existing.appliedProjectNames
: [],
createdAt: existing?.createdAt ?? nowIso(),
updatedAt: nowIso(),
reviewedAt: keepResolvedState || keepAppliedState ? existing?.reviewedAt : undefined,
reviewedBy: keepResolvedState || keepAppliedState ? existing?.reviewedBy : undefined,
resolutionId: keepResolvedState || keepAppliedState ? existing?.resolutionId : undefined,
}, existing);
if (!keepResolvedState && !keepAppliedState) {
state.deviceImportResolutions = state.deviceImportResolutions.filter(
(item) => item.draftId !== nextDraft.draftId,
);
}
state.deviceImportDrafts = [
nextDraft,
...state.deviceImportDrafts.filter((item) => item.draftId !== nextDraft.draftId),
];
return nextDraft;
}
export async function upsertDeviceHeartbeat(payload: {
deviceId: string;
token?: string;
pairingCode?: string;
name: string;
avatar: string;
account: string;
status: DeviceStatus;
quota5h: number;
quota7d: number;
projects: string[];
endpoint?: string;
projectCandidates?: Array<{
folderName: string;
folderRef?: string;
threadId: string;
threadDisplayName: string;
codexFolderRef?: string;
codexThreadRef?: string;
lastActiveAt?: string;
suggestedImport?: boolean;
}>;
}) {
const result = await mutateState((state) => {
const projectUnderstandingSyncRequests: Array<{
projectId: string;
observedActivityAt: string;
reason: "heartbeat_activity";
}> = [];
const existingDevice = state.devices.find((item) => item.id === payload.deviceId) ?? null;
const claimedEnrollment = claimEnrollment(
state,
payload.deviceId,
payload.pairingCode,
payload.token,
);
const normalizedCandidates = ensureArray(payload.projectCandidates, []).map((candidate) =>
normalizeDeviceImportCandidate({
deviceId: payload.deviceId,
folderName: candidate.folderName,
folderRef: candidate.folderRef,
threadId: candidate.threadId,
threadDisplayName: candidate.threadDisplayName,
codexFolderRef: candidate.codexFolderRef,
codexThreadRef: candidate.codexThreadRef,
lastActiveAt: candidate.lastActiveAt ?? nowIso(),
suggestedImport: candidate.suggestedImport ?? true,
}),
);
const reportedProjectCandidates = Array.isArray(payload.projectCandidates);
const shouldAutoImportLegacyProjects = !reportedProjectCandidates && normalizedCandidates.length === 0;
let device = existingDevice;
if (!device) {
device = {
id: payload.deviceId,
name: payload.name,
avatar: payload.avatar,
account: payload.account,
source: "production",
status: payload.status,
projects: payload.projects,
quota5h: payload.quota5h,
quota7d: payload.quota7d,
lastSeenAt: nowIso(),
endpoint: payload.endpoint,
token: claimedEnrollment?.token ?? payload.token ?? randomToken("boss"),
note: claimedEnrollment?.note,
};
state.devices.push(device);
} else {
if (device.token && payload.token && device.token !== payload.token && !claimedEnrollment) {
throw new Error("DEVICE_TOKEN_MISMATCH");
}
device.name = payload.name;
device.avatar = payload.avatar;
device.account = payload.account;
device.source = "production";
device.status = payload.status;
device.projects = payload.projects;
device.quota5h = payload.quota5h;
device.quota7d = payload.quota7d;
device.lastSeenAt = nowIso();
device.endpoint = payload.endpoint ?? device.endpoint;
device.token = claimedEnrollment?.token ?? payload.token ?? device.token;
}
if (shouldAutoImportLegacyProjects) {
for (const projectName of payload.projects) {
const existing = state.projects.find((item) => item.name === projectName);
if (!existing) {
state.projects.push(
normalizeProject({
id: slugify(projectName),
name: projectName,
pinned: false,
deviceIds: [payload.deviceId],
preview: `${payload.name} 已自动上报项目文件夹`,
updatedAt: nowIso(),
lastMessageAt: nowIso(),
isGroup: false,
unreadCount: 0,
riskLevel: "low",
contextBudgetPct: 80,
contextBudgetLabel: "80%",
messages: [
{
id: randomToken("auto"),
sender: "device",
senderLabel: payload.name,
body: `本机发现新的项目目录:${projectName}`,
sentAt: nowIso(),
kind: "text",
},
],
goals: [],
versions: [],
}),
);
} else if (!existing.deviceIds.includes(payload.deviceId)) {
existing.deviceIds.push(payload.deviceId);
existing.isGroup = existing.deviceIds.length > 1;
}
}
}
let draft = upsertDeviceImportDraftFromHeartbeat(state, {
deviceId: payload.deviceId,
enrollmentId: claimedEnrollment?.enrollmentId,
candidates: normalizedCandidates,
});
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 (hasNewObservedActivity) {
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}`,
});
}
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;
}
}
return {
device,
token: claimedEnrollment?.token ?? device.token,
pairingStatus: claimedEnrollment?.status,
importDraft: draft,
projectUnderstandingSyncRequests,
};
});
for (const request of result.projectUnderstandingSyncRequests ?? []) {
await queueProjectUnderstandingSyncTask(request);
}
publishBossEvent("devices.updated", { deviceId: payload.deviceId });
publishBossEvent("conversation.updated", { deviceId: payload.deviceId });
return result;
}
function resolveDeviceImportAction(
state: BossState,
deviceId: string,
candidate: DeviceImportCandidate,
): DeviceImportResolutionItem {
const directMatch = state.projects.find(
(project) =>
!project.isGroup &&
((candidate.codexThreadRef && project.threadMeta.codexThreadRef === candidate.codexThreadRef) ||
project.threadMeta.threadId === candidate.threadId),
);
if (directMatch) {
return {
candidateId: candidate.candidateId,
action: "attach_existing",
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
targetProjectId: directMatch.id,
reason: `已匹配到现有会话《${directMatch.name}》,直接补充设备与线程映射。`,
};
}
const similarByFolder = state.projects.find(
(project) =>
!project.isGroup &&
project.deviceIds.includes(deviceId) &&
project.threadMeta.folderName === candidate.folderName &&
project.threadMeta.threadDisplayName === candidate.threadDisplayName,
);
if (similarByFolder) {
return {
candidateId: candidate.candidateId,
action: "attach_existing",
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
targetProjectId: similarByFolder.id,
reason: `同设备下已有同名线程《${similarByFolder.name}》,避免重复导入。`,
};
}
return {
candidateId: candidate.candidateId,
action: "create_thread_conversation",
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
reason: `建议把 ${candidate.threadDisplayName} 作为独立聊天窗口导入。`,
};
}
function summarizeDeviceImportResolution(
deviceName: string,
items: DeviceImportResolutionItem[],
) {
const createCount = items.filter((item) => item.action === "create_thread_conversation").length;
const attachCount = items.filter((item) => item.action === "attach_existing").length;
const skipCount = items.filter((item) => item.action === "skip").length;
return `${deviceName} 导入建议:新建 ${createCount} 个会话,关联 ${attachCount} 个现有会话${skipCount > 0 ? `,跳过 ${skipCount}` : ""}`;
}
function resolveAutoSyncCandidateIds(draft: DeviceImportDraft) {
const suggestedCandidateIds = draft.candidates
.filter((candidate) => candidate.suggestedImport !== false)
.map((candidate) => candidate.candidateId);
return dedupeStrings(
suggestedCandidateIds.length > 0
? suggestedCandidateIds
: draft.candidates.map((candidate) => candidate.candidateId),
);
}
function shouldAutoSyncHeartbeatCandidates(input: {
wasExistingDevice: boolean;
device: Device;
claimedEnrollment: DeviceEnrollment | null;
draft: DeviceImportDraft | null;
}) {
if (!input.wasExistingDevice) return false;
if (input.device.source !== "production") return false;
if (!input.draft || input.draft.candidates.length === 0) return false;
if (
input.claimedEnrollment?.enrollmentId &&
input.draft.enrollmentId === input.claimedEnrollment.enrollmentId
) {
return false;
}
return true;
}
export async function getLatestDeviceImportDraft(deviceId: string) {
const state = await readState();
const draft = state.deviceImportDrafts.find((item) => item.deviceId === deviceId) ?? null;
const resolution = draft?.resolutionId
? state.deviceImportResolutions.find((item) => item.resolutionId === draft.resolutionId) ?? null
: state.deviceImportResolutions.find((item) => item.deviceId === deviceId) ?? null;
const reviewTask = draft
? state.masterAgentTasks.find(
(item) =>
item.taskType === "device_import_resolution" &&
item.deviceImportDraftId === draft.draftId,
) ?? null
: null;
const understandingTasks = draft
? listDeviceImportUnderstandingTasks(state, draft.draftId)
: [];
const projectUnderstandings = draft
? deriveDeviceImportProjectUnderstandings(state, draft.draftId)
: [];
return { draft, resolution, reviewTask, understandingTasks, projectUnderstandings };
}
function listDeviceImportUnderstandingTasks(state: BossState, draftId: string) {
const latestByCandidate = new Map<string, MasterAgentTask>();
for (const task of state.masterAgentTasks) {
if (
task.taskType !== "conversation_reply" ||
task.deviceImportDraftId !== draftId ||
!task.deviceImportCandidateId
) {
continue;
}
const existing = latestByCandidate.get(task.deviceImportCandidateId);
if (!existing || existing.requestedAt < task.requestedAt) {
latestByCandidate.set(task.deviceImportCandidateId, task);
}
}
return [...latestByCandidate.values()].map((task) => ({
taskId: task.taskId,
candidateId: task.deviceImportCandidateId ?? "",
threadDisplayName: task.targetThreadDisplayName ?? "",
folderName: task.deviceImportCandidateFolderName ?? "",
status: task.status,
updatedAt: task.completedAt ?? task.claimedAt ?? task.requestedAt,
}));
}
function parseDeviceImportUnderstandingReply(
task: Pick<MasterAgentTask, "replyBody" | "deviceImportCandidateId" | "targetThreadDisplayName" | "deviceImportCandidateFolderName" | "taskId" | "completedAt" | "requestedAt">,
): DeviceImportProjectUnderstanding | null {
const understanding = parseStructuredProjectUnderstandingReply(task);
const candidateId = task.deviceImportCandidateId?.trim();
if (!candidateId || !understanding) {
return null;
}
return {
candidateId,
threadDisplayName: task.targetThreadDisplayName?.trim() || "未命名线程",
folderName: task.deviceImportCandidateFolderName?.trim() || "",
...understanding,
};
}
function parseStructuredProjectUnderstandingReply(
task: Pick<MasterAgentTask, "replyBody" | "taskId" | "completedAt" | "requestedAt">,
): ProjectUnderstandingSnapshot | null {
const replyBody = task.replyBody?.trim();
if (!replyBody) {
return null;
}
const fencedMatch = replyBody.match(/```(?:json)?\s*([\s\S]*?)```/i);
const jsonCandidate = fencedMatch?.[1]?.trim() ?? replyBody;
let parsed:
| {
projectGoal?: string;
currentProgress?: string;
technicalArchitecture?: string;
currentBlockers?: string;
recommendedNextStep?: string;
}
| null = null;
try {
parsed = JSON.parse(jsonCandidate);
} catch {
return null;
}
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() ?? "";
if (!projectGoal && !currentProgress && !technicalArchitecture && !currentBlockers && !recommendedNextStep) {
return null;
}
return {
projectGoal,
currentProgress,
technicalArchitecture,
currentBlockers,
recommendedNextStep,
sourceTaskId: task.taskId,
updatedAt: task.completedAt ?? task.requestedAt,
sourceKind: "thread_sync",
};
}
function deriveDeviceImportProjectUnderstandings(state: BossState, draftId: string) {
const latestByCandidate = new Map<string, DeviceImportProjectUnderstanding>();
for (const task of state.masterAgentTasks) {
if (
task.taskType !== "conversation_reply" ||
task.deviceImportDraftId !== draftId ||
task.status !== "completed" ||
!task.deviceImportCandidateId
) {
continue;
}
const understanding = parseDeviceImportUnderstandingReply(task);
if (!understanding) {
continue;
}
const existing = latestByCandidate.get(understanding.candidateId);
if (!existing || existing.updatedAt < understanding.updatedAt) {
latestByCandidate.set(understanding.candidateId, understanding);
}
}
return [...latestByCandidate.values()];
}
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("LOCAL_AGENT_CODEX_THREAD_READ_ONLY")) {
return "线程当前处于只读环境,无法继续执行,请切换到可写线程后再试。";
}
return message;
}
function shouldQueueProjectUnderstandingSync(
project: Project,
observedActivityAt: string,
state: BossState,
reason: "heartbeat_activity" | "thread_reply" = "heartbeat_activity",
) {
// 主 Agent 自动向线程发隐藏理解对话当前整体关闭。
// 保留现有数据模型,后续如果需要恢复,可在明确产品决策后重新开启。
void project;
void observedActivityAt;
void state;
void reason;
return false;
/*
if (!isDispatchableThreadProject(project)) {
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" ? "检测到线程有新活动" : "线程刚刚产生了新的执行结果"}`,
"",
"只输出 JSON不要输出解释性文字或 Markdown。",
"JSON 结构固定为:",
'{ "projectGoal": "一句中文目标", "currentProgress": "一句中文进度", "technicalArchitecture": "一句中文架构说明", "currentBlockers": "一句中文阻塞说明", "recommendedNextStep": "一句中文建议动作" }',
"",
"要求:",
"1. 只写当前项目最重要、对主 Agent 接手有帮助的事实。",
"2. 不要重复内部字段、线程编号、目录路径、设备 ID。",
"3. 如果某个字段暂时不清楚,填空字符串。",
].join("\n");
}
async function queueProjectUnderstandingSyncTask(input: {
projectId: string;
observedActivityAt: string;
reason: "heartbeat_activity" | "thread_reply";
}) {
const state = await readState();
const project = state.projects.find((item) => item.id === input.projectId);
if (!project || !shouldQueueProjectUnderstandingSync(project, input.observedActivityAt, state, input.reason)) {
return null;
}
const requestedByAccount = state.user.account || project.deviceIds[0] || "17600003315";
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,
});
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 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<string>();
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();
return normalizeProject({
id: projectId,
name: candidate.threadDisplayName,
pinned: false,
systemPinned: false,
deviceIds: [device.id],
preview: `已从 ${device.name} 导入线程 ${candidate.threadDisplayName}`,
updatedAt: now,
lastMessageAt: now,
isGroup: false,
unreadCount: 0,
riskLevel: "low",
threadMeta: {
projectId,
threadId: candidate.threadId,
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
activityIconCount: 1,
updatedAt: candidate.lastActiveAt || now,
codexFolderRef: candidate.codexFolderRef ?? candidate.folderRef,
codexThreadRef: candidate.codexThreadRef,
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
messages: [
{
id: randomToken("msg"),
sender: "master",
senderLabel: "主 Agent",
body: `已从设备 ${device.name} 导入线程《${candidate.threadDisplayName}》。`,
sentAt: now,
kind: "text",
},
],
goals: [],
versions: [],
});
}
function candidateThreadSignature(candidate: DeviceImportCandidate) {
return (
trimToDefined(candidate.codexThreadRef) ??
trimToDefined(candidate.threadId) ??
`${candidate.folderName}:${candidate.threadDisplayName}`
);
}
function projectThreadSignature(project: Project) {
return (
trimToDefined(project.threadMeta.codexThreadRef) ??
trimToDefined(project.threadMeta.threadId) ??
`${project.threadMeta.folderName}:${project.threadMeta.threadDisplayName}`
);
}
function pruneStaleAutoImportedProjectsForDevice(
state: BossState,
device: Device,
selectedCandidates: DeviceImportCandidate[],
) {
const activeSignatures = new Set(selectedCandidates.map((candidate) => candidateThreadSignature(candidate)));
const reservedProjectIds = new Set(["master-agent", "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 understandingsByCandidate = new Map(
deriveDeviceImportProjectUnderstandings(state, draft.draftId).map((item) => [item.candidateId, item] as const),
);
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.preview = `已导入 ${candidate.threadDisplayName}`;
targetProject.updatedAt = nowIso();
targetProject.lastMessageAt = targetProject.updatedAt;
const understanding = understandingsByCandidate.get(candidate.candidateId);
if (understanding) {
applyProjectUnderstandingSnapshotInState(state, {
projectId: targetProject.id,
account: device.account,
snapshot: {
projectGoal: understanding.projectGoal,
currentProgress: understanding.currentProgress,
technicalArchitecture: understanding.technicalArchitecture,
currentBlockers: understanding.currentBlockers,
recommendedNextStep: understanding.recommendedNextStep,
sourceTaskId: understanding.sourceTaskId,
updatedAt: understanding.updatedAt,
sourceKind: "device_import",
},
sourceMessageId: understanding.sourceTaskId,
sourceKind: "device_import",
});
}
importedProjects.push({ ...targetProject });
}
if (input.pruneMissingCandidates) {
pruneStaleAutoImportedProjectsForDevice(state, device, selectedCandidates);
}
device.projects = dedupeStrings(
selectedCandidates.map((candidate) => candidate.folderName),
);
resolution.status = "applied";
resolution.appliedAt = nowIso();
resolution.appliedBy = input.appliedBy;
draft.status = "applied";
draft.appliedProjectNames = importedProjects.map((project) => project.name);
draft.updatedAt = nowIso();
return {
draft: { ...draft },
resolution: { ...resolution },
importedProjects,
};
}
export async function applyDeviceImportResolution(input: {
deviceId: string;
appliedBy: string;
}) {
const result = await mutateState((state) =>
applyDeviceImportResolutionInState(state, {
deviceId: input.deviceId,
appliedBy: input.appliedBy,
}),
);
publishBossEvent("devices.updated", { deviceId: input.deviceId });
publishBossEvent("conversation.updated", { deviceId: input.deviceId });
return result;
}
export async function upsertDeviceSkills(payload: {
deviceId: string;
skills: Array<{
name: string;
description?: string;
path: string;
invocation?: string;
category?: string;
}>;
}) {
const nextSkills = await mutateState((state) => {
const device = state.devices.find((item) => item.id === payload.deviceId);
if (!device) throw new Error("DEVICE_NOT_FOUND");
const syncedAt = nowIso();
const skills = payload.skills.map((skill) => ({
skillId: `${payload.deviceId}:${slugify(skill.name)}`,
deviceId: payload.deviceId,
name: skill.name,
description: skill.description?.trim() || "未提供说明",
path: skill.path,
invocation: skill.invocation?.trim() || `[$${skill.name}](${skill.path})`,
category: skill.category?.trim() || device.name,
updatedAt: syncedAt,
}));
state.deviceSkills = [
...skills,
...state.deviceSkills.filter((item) => item.deviceId !== payload.deviceId),
];
device.lastSeenAt = syncedAt;
return skills;
});
publishBossEvent("devices.skills.updated", {
deviceId: payload.deviceId,
note: `${nextSkills.length}`,
});
return nextSkills;
}
export async function appendAppLog(payload: {
deviceId: string;
projectId?: string;
level: AppLogLevel;
source: "app_client" | "local_agent";
category: string;
message: string;
detail?: string;
mirrorToMaster?: boolean;
}) {
const { entry, mirroredProjectId } = await mutateState((state) => {
const entry: AppLogEntry = {
logId: randomToken("applog"),
deviceId: payload.deviceId,
projectId: payload.projectId,
level: payload.level,
source: payload.source,
category: payload.category,
message: payload.message.trim(),
detail: payload.detail?.trim(),
mirroredToProject: Boolean(payload.mirrorToMaster),
createdAt: nowIso(),
};
state.appLogs.unshift(entry);
let mirroredProjectId: string | undefined;
if (payload.mirrorToMaster) {
const device = state.devices.find((item) => item.id === payload.deviceId);
pushProjectLedgerMessage(state, "master-agent", {
sender: payload.level === "error" ? "ops" : "device",
senderLabel: `${device?.name ?? payload.deviceId} · APP 日志`,
body: `[${payload.category}] ${payload.message}${payload.detail ? `\n${payload.detail}` : ""}`,
kind: "text",
});
if (shouldAutoReplyToMirroredLog(entry)) {
pushProjectLedgerMessage(state, "master-agent", {
sender: "master",
senderLabel: "主 Agent",
body: buildMasterAgentLogReply(state, entry),
kind: "text",
});
}
mirroredProjectId = "master-agent";
}
return { entry, mirroredProjectId };
});
publishBossEvent("app.logs.updated", {
deviceId: payload.deviceId,
projectId: payload.projectId,
note: payload.category,
});
if (mirroredProjectId) {
publishBossEvent("project.messages.updated", {
projectId: mirroredProjectId,
deviceId: payload.deviceId,
});
publishBossEvent("conversation.updated", {
projectId: mirroredProjectId,
deviceId: payload.deviceId,
});
}
return entry;
}
export async function updateConversationAction(
projectId: string,
action: "toggle_pin" | "mark_read",
) {
const project = await mutateState((state) => {
const nextProject = state.projects.find((item) => item.id === projectId);
if (!nextProject) throw new Error("PROJECT_NOT_FOUND");
if (action === "toggle_pin") {
if (nextProject.systemPinned) {
throw new Error("MASTER_PROJECT_PIN_LOCKED");
}
nextProject.pinned = !nextProject.pinned;
}
if (action === "mark_read") {
nextProject.unreadCount = 0;
}
return nextProject;
});
publishBossEvent("conversation.updated", { projectId });
return project;
}
export async function renameProjectThread(input: {
projectId: string;
threadDisplayName: string;
requestedBy: string;
}) {
const threadDisplayName = input.threadDisplayName.trim();
if (!threadDisplayName) {
throw new Error("THREAD_DISPLAY_NAME_REQUIRED");
}
const project = await mutateState((state) => {
const nextProject = state.projects.find((item) => item.id === input.projectId);
if (!nextProject) throw new Error("PROJECT_NOT_FOUND");
if (nextProject.isGroup) throw new Error("PROJECT_IS_GROUP_CHAT");
const updatedAt = nowIso();
nextProject.name = threadDisplayName;
nextProject.threadMeta.threadDisplayName = threadDisplayName;
nextProject.threadMeta.updatedAt = updatedAt;
nextProject.updatedAt = updatedAt;
return nextProject;
});
publishBossEvent("conversation.updated", {
projectId: input.projectId,
note: `renamed by ${input.requestedBy}`,
});
return project;
}
export async function renameGroupChat(input: {
projectId: string;
name: string;
requestedBy: string;
}) {
const name = input.name.trim();
if (!name) {
throw new Error("GROUP_CHAT_NAME_REQUIRED");
}
const project = await mutateState((state) => {
const nextProject = state.projects.find((item) => item.id === input.projectId);
if (!nextProject) throw new Error("PROJECT_NOT_FOUND");
if (!nextProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT");
const updatedAt = nowIso();
nextProject.name = name;
nextProject.threadMeta.threadDisplayName = name;
nextProject.threadMeta.updatedAt = updatedAt;
nextProject.updatedAt = updatedAt;
return nextProject;
});
publishBossEvent("conversation.updated", {
projectId: input.projectId,
note: `renamed by ${input.requestedBy}`,
});
return project;
}
export async function createProjectGroupChat(input: {
sourceProjectId: string;
memberProjectIds: string[];
createdBy: string;
}) {
const project = await mutateState((state) => {
const source = state.projects.find((item) => item.id === input.sourceProjectId);
if (!source) throw new Error("GROUP_CHAT_SOURCE_NOT_FOUND");
return createGroupChatFromProjectIds(state, {
requestedProjectIds: [input.sourceProjectId, ...input.memberProjectIds],
createdBy: input.createdBy,
defaultRiskLevel: source.riskLevel,
});
});
publishBossEvent("project.messages.updated", { projectId: project.id });
publishBossEvent("conversation.updated", { projectId: project.id });
return project;
}
export async function createIndependentGroupChat(input: {
memberProjectIds: string[];
createdBy: string;
}) {
const project = await mutateState((state) =>
createGroupChatFromProjectIds(state, {
requestedProjectIds: input.memberProjectIds,
createdBy: input.createdBy,
}),
);
publishBossEvent("project.messages.updated", { projectId: project.id });
publishBossEvent("conversation.updated", { projectId: project.id });
return project;
}
function resolveGroupChatThreadProjects(
state: BossState,
requestedProjectIds: string[],
) {
const memberProjects: Project[] = [];
const seenProjectIds = new Set<string>();
for (const projectId of requestedProjectIds) {
if (!projectId || seenProjectIds.has(projectId)) {
continue;
}
seenProjectIds.add(projectId);
const memberProject = state.projects.find((item) => item.id === projectId);
if (!memberProject) {
throw new Error("GROUP_CHAT_MEMBER_NOT_FOUND");
}
if (!isDispatchableThreadProject(memberProject)) {
throw new Error("GROUP_CHAT_MEMBER_NOT_THREAD");
}
memberProjects.push(memberProject);
}
return memberProjects;
}
export async function replaceGroupChatMembers(input: {
projectId: string;
memberProjectIds: string[];
requestedBy: string;
}) {
const result = await mutateState((state) => {
const groupProject = state.projects.find((item) => item.id === input.projectId);
if (!groupProject) {
throw new Error("PROJECT_NOT_FOUND");
}
if (!groupProject.isGroup) {
throw new Error("PROJECT_NOT_GROUP_CHAT");
}
const memberProjects = resolveGroupChatThreadProjects(state, input.memberProjectIds);
if (memberProjects.length < 2) {
throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS");
}
const now = nowIso();
groupProject.groupMembers = memberProjects.map((memberProject) => ({
projectId: memberProject.id,
deviceId: memberProject.deviceIds[0] ?? memberProject.id,
threadId: memberProject.threadMeta.threadId,
threadDisplayName: memberProject.threadMeta.threadDisplayName,
folderName: memberProject.threadMeta.folderName,
}));
groupProject.deviceIds = dedupeStrings(groupProject.groupMembers.map((member) => member.deviceId));
groupProject.threadMeta.activityIconCount = Math.max(1, groupProject.groupMembers.length);
groupProject.threadMeta.folderName = "群聊";
groupProject.threadMeta.updatedAt = now;
groupProject.updatedAt = now;
groupProject.lastMessageAt = now;
groupProject.approvalState = "not_required";
const memberLabel = memberProjects
.map((project) => project.threadMeta.threadDisplayName || project.name)
.join("、");
pushProjectLedgerMessage(state, groupProject.id, {
sender: "master",
senderLabel: "主 Agent",
body: `已更新群成员:${memberLabel}`,
kind: "system_notice",
sentAt: now,
});
return {
project: { ...groupProject },
groupMembers: groupProject.groupMembers.map((member) => ({ ...member })),
};
});
publishBossEvent("project.messages.updated", { projectId: input.projectId });
publishBossEvent("conversation.updated", {
projectId: input.projectId,
note: `group members updated by ${input.requestedBy}`,
});
return result;
}
function createGroupChatFromProjectIds(
state: BossState,
input: {
requestedProjectIds: string[];
createdBy: string;
defaultRiskLevel?: Project["riskLevel"];
},
) {
const memberProjects = resolveGroupChatThreadProjects(state, input.requestedProjectIds);
if (memberProjects.length < 2) {
throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS");
}
const now = nowIso();
const projectId = randomToken("project");
const threadId = randomToken("thread");
const threadDisplayName = buildAutoGroupChatName(memberProjects);
const folderName = "群聊";
const groupMembers = memberProjects.map((memberProject) => ({
projectId: memberProject.id,
deviceId: memberProject.deviceIds[0] ?? memberProject.id,
threadId: memberProject.threadMeta.threadId,
threadDisplayName: memberProject.threadMeta.threadDisplayName,
folderName: memberProject.threadMeta.folderName,
}));
const seedProject = memberProjects[0];
const nextProject = normalizeProject({
id: projectId,
name: threadDisplayName,
pinned: false,
systemPinned: false,
deviceIds: dedupeStrings(groupMembers.map((member) => member.deviceId)),
preview: `已创建群聊《${threadDisplayName}`,
updatedAt: now,
lastMessageAt: now,
isGroup: true,
unreadCount: 0,
riskLevel: input.defaultRiskLevel ?? seedProject?.riskLevel ?? "normal",
threadMeta: {
projectId,
threadId,
threadDisplayName,
folderName,
activityIconCount: Math.max(1, memberProjects.length),
updatedAt: now,
},
groupMembers,
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
messages: [
{
id: randomToken("msg"),
sender: "master",
senderLabel: input.createdBy || "群聊创建",
body: `已由 ${input.createdBy || "系统"} 创建群聊《${threadDisplayName}》。`,
sentAt: now,
kind: "text",
},
],
goals: [],
versions: [],
});
state.projects.unshift(nextProject);
return nextProject;
}
function buildAutoGroupChatName(memberProjects: Project[]) {
const titles = memberProjects
.map((project) => project.threadMeta.threadDisplayName || project.name)
.filter((title) => typeof title === "string" && title.trim().length > 0);
if (titles.length === 0) {
return "新群聊";
}
if (titles.length === 1) {
return titles[0];
}
if (titles.length === 2) {
return `${titles[0]}${titles[1]}`;
}
return `${titles[0]}${titles[1]}${titles.length}个线程`;
}
export async function appendProjectMessage(payload: {
projectId: string;
sender?: MessageSender;
senderLabel?: string;
body?: string;
kind?: MessageKind;
attachments?: MessageAttachment[];
}) {
const result = await mutateState((state) => {
const project = state.projects.find((item) => item.id === payload.projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
const body = payload.body?.trim();
if (!body && payload.kind === "text") {
throw new Error("MESSAGE_BODY_REQUIRED");
}
if (payload.kind === "attachment" && (!payload.attachments || payload.attachments.length === 0)) {
throw new Error("ATTACHMENT_REQUIRED");
}
const firstAttachment = payload.attachments?.[0];
const message: Message = {
id: randomToken("msg"),
sender: payload.sender ?? "user",
senderLabel: payload.senderLabel ?? "你",
body:
body ??
(payload.kind === "attachment"
? buildAttachmentMessageBody(
firstAttachment ?? {
attachmentId: randomToken("att"),
fileName: "附件",
mimeType: "application/octet-stream",
fileSizeBytes: 0,
attachmentKind: "binary",
storageBackend: "server_file",
storagePath: "",
previewAvailable: false,
uploadedAt: nowIso(),
uploadedBy: payload.senderLabel ?? "你",
analysisState: "not_applicable",
},
)
: payload.kind === "voice_intent"
? "已提交语音转文字请求,等待主 Agent 记录语音摘要。"
: payload.kind === "image_intent"
? "已登记图片证据上传请求,等待对象存储通道接入。"
: payload.kind === "video_intent"
? "已登记视频证据上传请求,等待对象存储通道接入。"
: "已提交消息。"),
sentAt: nowIso(),
kind: payload.kind ?? "text",
attachments: payload.attachments?.map((attachment) => normalizeMessageAttachment(attachment)),
};
project.messages.push(message);
project.unreadCount = 0;
project.lastMessageAt = message.sentAt;
project.preview = message.body;
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"),
};
});
if (result.shouldQueueUnderstandingSync) {
await queueProjectUnderstandingSyncTask({
projectId: payload.projectId,
observedActivityAt: result.message.sentAt,
reason: "thread_reply",
});
}
publishBossEvent("project.messages.updated", { projectId: payload.projectId });
publishBossEvent("conversation.updated", { projectId: payload.projectId });
return result.message;
}
export async function appendAttachmentMessage(payload: {
projectId: string;
sender?: MessageSender;
senderLabel?: string;
attachment: MessageAttachment;
body?: string;
}) {
return appendProjectMessage({
projectId: payload.projectId,
sender: payload.sender ?? "user",
senderLabel: payload.senderLabel ?? "你",
body: payload.body ?? buildAttachmentMessageBody(payload.attachment),
kind: "attachment",
attachments: [payload.attachment],
});
}
function findProjectMessage(project: Project, messageId: string) {
return project.messages.find((message) => message.id === messageId) ?? null;
}
export function findProjectAttachment(
project: Project,
attachmentId: string,
): { message: Message; attachment: MessageAttachment } | null {
for (const message of project.messages) {
const attachment = message.attachments?.find((item) => item.attachmentId === attachmentId);
if (attachment) {
return { message, attachment };
}
}
return null;
}
export async function getProjectAttachment(projectId: string, attachmentId: string) {
const state = await readState();
const project = state.projects.find((item) => item.id === projectId);
if (!project) {
return null;
}
const match = findProjectAttachment(project, attachmentId);
if (!match) {
return null;
}
return {
project,
message: match.message,
attachment: match.attachment,
};
}
export async function getAttachmentById(attachmentId: string) {
const state = await readState();
for (const project of state.projects) {
const match = findProjectAttachment(project, attachmentId);
if (match) {
return {
project,
message: match.message,
attachment: match.attachment,
};
}
}
return null;
}
function summarizeAttachmentAnalysis(body: string) {
const compact = body.replace(/\s+/g, " ").trim();
if (!compact) {
return "附件分析已完成。";
}
return compact.length <= 120 ? compact : `${compact.slice(0, 117)}...`;
}
export async function updateAttachmentAnalysisResult(payload: {
projectId: string;
attachmentId: string;
status: Exclude<AttachmentAnalysisState, "not_applicable" | "queued_auto" | "ready_manual">;
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<UserSettings>) {
const nextSettings = await mutateState((state) => {
state.user.settings = {
...state.user.settings,
...settings,
};
return state.user.settings;
});
publishBossEvent("conversation.updated", { deviceId: PRIMARY_CODEX_NODE_ID });
return nextSettings;
}
export async function getOtaStatus() {
const state = await readState();
const available = firstAvailableOta(state);
const asset = await getPublishedOtaAsset();
const availableRelease = available
? {
...available,
packageFileName: asset?.fileName,
packageSizeBytes: asset?.sizeBytes,
packageSha256: asset?.sha256,
downloadUrl: asset?.downloadUrl,
assetUpdatedAt: asset?.updatedAt,
}
: null;
return {
currentVersion: state.user.version,
hasOta: state.user.hasOta,
availableRelease,
logs: state.otaUpdateLogs,
canApply: state.user.role === "highest_admin",
boundCodexNodeLabel: state.user.boundCodexNodeLabel,
roleLabel: state.user.roleLabel,
};
}
export async function checkForOta() {
const result = await mutateState((state) => {
const available = firstAvailableOta(state);
state.otaUpdateLogs.unshift({
logId: randomToken("otalog"),
releaseId: available?.releaseId ?? "ota_check_empty",
version: available?.version ?? state.user.version,
status: "checked",
triggeredBy: state.user.name,
triggeredAt: nowIso(),
note: available
? `检查到可用 OTA${available.version},目标范围:${available.targetScope}`
: "检查完成,当前已是最新版本。",
});
return {
currentVersion: state.user.version,
availableRelease: available,
hasOta: Boolean(available),
deviceId: state.user.boundDeviceId,
note: available?.version ?? state.user.version,
};
});
const asset = await getPublishedOtaAsset();
publishBossEvent("ota.updated", {
deviceId: result.deviceId,
note: result.note,
});
return {
...result,
availableRelease: result.availableRelease
? {
...result.availableRelease,
packageFileName: asset?.fileName,
packageSizeBytes: asset?.sizeBytes,
packageSha256: asset?.sha256,
downloadUrl: asset?.downloadUrl,
assetUpdatedAt: asset?.updatedAt,
}
: null,
};
}
export async function performOta() {
const result = await mutateState((state) => {
const available = firstAvailableOta(state);
if (!available) {
throw new Error("NO_OTA_AVAILABLE");
}
if (state.user.role !== "highest_admin") {
throw new Error("ADMIN_REQUIRED_FOR_OTA");
}
const nextVersion = available.version;
const otaSummary = [...available.summary];
state.user.version = nextVersion;
available.status = "applied";
state.otaUpdateLogs.unshift({
logId: randomToken("otalog"),
releaseId: available.releaseId,
version: available.version,
status: "applied",
triggeredBy: state.user.name,
triggeredAt: nowIso(),
completedAt: nowIso(),
note: `${state.user.roleLabel}${state.user.boundCodexNodeLabel ?? PRIMARY_CODEX_NODE_LABEL} 发起 OTA。`,
});
const project = state.projects.find((item) => item.id === "master-agent");
if (project) {
project.messages.push({
id: randomToken("ota"),
sender: "ops",
senderLabel: "OTA 控制面",
body: `已完成 OTA 升级到 ${nextVersion},变更:${otaSummary.join("") || "无"}`,
sentAt: nowIso(),
kind: "text",
});
project.lastMessageAt = nowIso();
if (!project.versions.some((entry) => entry.version === nextVersion)) {
project.versions.unshift({
version: nextVersion,
summary: `通过 OTA 发布:${otaSummary.join("") || "无"}`,
createdAt: nowIso(),
});
}
}
return {
version: state.user.version,
summary: otaSummary,
deviceId: state.user.boundDeviceId,
nextVersion,
};
});
const asset = await getPublishedOtaAsset();
publishBossEvent("ota.updated", {
deviceId: result.deviceId,
projectId: "master-agent",
note: result.nextVersion,
});
publishBossEvent("project.messages.updated", { projectId: "master-agent" });
publishBossEvent("conversation.updated", { projectId: "master-agent" });
return {
...result,
downloadUrl: asset?.downloadUrl,
packageFileName: asset?.fileName,
packageSizeBytes: asset?.sizeBytes,
packageSha256: asset?.sha256,
};
}
export async function approveRepairTicket(ticketId: string) {
return mutateState((state) => {
const ticket = state.opsRepairTickets.find((item) => item.ticketId === ticketId);
if (!ticket) throw new Error("TICKET_NOT_FOUND");
ticket.approvalStatus = "approved";
ticket.executionStatus = "running";
ticket.approvedBy = "主 Agent";
ticket.updatedAt = nowIso();
return ticket;
});
}
export async function verifyRepairTicket(ticketId: string) {
return mutateState((state) => {
const ticket = state.opsRepairTickets.find((item) => item.ticketId === ticketId);
if (!ticket) throw new Error("TICKET_NOT_FOUND");
ticket.executionStatus = "verified";
ticket.updatedAt = nowIso();
const verification = state.opsRepairVerifications.find((item) => item.ticketId === ticketId);
if (verification) {
verification.status = "passed";
verification.summary = "最新一轮修复已通过复验,可关闭工单。";
verification.verifiedAt = nowIso();
}
const fault = state.opsFaults.find((item) => item.faultId === ticket.faultId);
if (fault) {
fault.status = "resolved";
fault.lastSeenAt = nowIso();
}
return ticket;
});
}