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

8320 lines
278 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;
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";
orchestrationBackendOverride?: OrchestrationBackendOverride;
agentControls?: ProjectAgentControls;
unreadCount: number;
riskLevel: RiskLevel;
contextBudgetPct?: number;
contextBudgetLabel?: string;
messages: Message[];
goals: GoalItem[];
versions: VersionEntry[];
}
export interface DispatchPlanTarget {
deviceId: string;
projectId: string;
threadId: string;
threadDisplayName: string;
folderName: string;
codexFolderRef?: string;
codexThreadRef?: string;
reason: string;
}
export interface DispatchPlan {
planId: string;
groupProjectId: string;
requestMessageId: string;
requestedBy: string;
status: DispatchPlanStatus;
targets: DispatchPlanTarget[];
summary: string;
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">,
) {
if (!project) {
return {
isGroup: false,
collaborationMode: "development" as const,
requiresMasterAgentApproval: false,
approvalState: "not_required" as const,
};
}
return {
isGroup: project.isGroup,
collaborationMode: project.collaborationMode,
requiresMasterAgentApproval: project.isGroup && project.collaborationMode === "approval_required",
approvalState: project.approvalState,
};
}
export interface ProjectAgentControls {
modelOverride?: string;
reasoningEffortOverride?: ReasoningEffort;
promptOverride?: string;
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 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;
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[];
otaUpdates: OtaUpdate[];
otaUpdateLogs: OtaUpdateLog[];
deviceSkills: DeviceSkill[];
appLogs: AppLogEntry[];
userAttachmentStorageConfigs: UserAttachmentStorageConfig[];
masterAgentPromptPolicy: MasterAgentPromptPolicy | null;
userMasterPrompts: UserMasterPrompt[];
masterAgentMemories: MasterAgentMemory[];
userProjectAgentControls: UserProjectAgentControls[];
threadContextSnapshots: ThreadContextSnapshot[];
threadHandoffPackages: ThreadHandoffPackage[];
threadContextAlerts: ThreadContextAlert[];
deviceEnrollments: DeviceEnrollment[];
opsFaults: OpsFault[];
opsRepairTickets: OpsRepairTicket[];
opsRepairVerifications: OpsRepairVerification[];
auditRequests: AuditTaskRequest[];
auditResults: AuditTaskResult[];
capabilities: Capability[];
}
function detectRuntimeRoot(startDir: string) {
let current = startDir;
while (true) {
if (existsSync(path.join(current, "package.json")) && existsSync(path.join(current, "src", "app"))) {
return current;
}
const parent = path.dirname(current);
if (parent === current) {
return startDir;
}
current = parent;
}
}
function resolveRuntimeRoot() {
if (process.env.BOSS_RUNTIME_ROOT?.trim()) {
return path.resolve(process.env.BOSS_RUNTIME_ROOT);
}
if (process.env.BOSS_STATE_FILE?.trim()) {
return path.dirname(path.dirname(path.resolve(process.env.BOSS_STATE_FILE)));
}
return detectRuntimeRoot(/* turbopackIgnore: true */ process.cwd());
}
const runtimeRoot = resolveRuntimeRoot();
const dataFile = process.env.BOSS_STATE_FILE
? path.resolve(process.env.BOSS_STATE_FILE)
: path.join(runtimeRoot, "data", "boss-state.json");
const dataDir = path.dirname(dataFile);
const backupFile = `${dataFile}.bak`;
const publishedApkPath = path.join(runtimeRoot, "public", "downloads", "boss-android-latest.apk");
const publishedApkMetaPath = path.join(runtimeRoot, "public", "downloads", "boss-android-latest.json");
const defaultSettings: UserSettings = {
liveUpdates: true,
showRiskBadges: true,
confirmDangerousActions: true,
preferredEntryPoint: "conversations",
};
const PRIMARY_ADMIN_ACCOUNT = "17600003315";
const PRIMARY_ADMIN_PASSWORD = "boss123456";
const PRIMARY_ADMIN_VERIFICATION_EMAIL = "verify@boss.hyzq.net";
const PRIMARY_CODEX_NODE_ID = "mac-studio";
const PRIMARY_CODEX_NODE_LABEL = "本机 Codex · Mac Studio";
const VERIFICATION_SEND_COOLDOWN_MS = 60_000;
const VERIFICATION_SEND_WINDOW_MS = 15 * 60_000;
const VERIFICATION_SEND_WINDOW_LIMIT = 5;
export const AUTH_SESSION_TTL_MS = 30 * 24 * 60 * 60_000;
const AUTH_LOGIN_LOCK_THRESHOLD = 5;
const AUTH_LOGIN_LOCK_MS = 10 * 60_000;
const ENV_OPENAI_ACCOUNT_ID = "env-openai-api";
function baseThreadChecklist(labels: string[]) {
return labels;
}
const initialState: BossState = {
user: {
id: "user-boss-admin",
name: "Boss 超级管理员",
avatar: "17",
account: PRIMARY_ADMIN_ACCOUNT,
verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL,
role: "highest_admin",
roleLabel: "最高管理员",
accountType: "最高管理员 · 本机 Codex 已绑定",
qrCodeValue: `boss://user/${PRIMARY_ADMIN_ACCOUNT}`,
boundCodexNodeId: PRIMARY_CODEX_NODE_ID,
boundCodexNodeLabel: PRIMARY_CODEX_NODE_LABEL,
boundDeviceId: PRIMARY_CODEX_NODE_ID,
boundAt: "2026-03-26T09:00:00+08:00",
version: "1.4.0",
otaVersion: "v1.4.1",
otaSummary: ["新增登录会话守卫", "新增验证码防重放", "开放 APK 下载 OTA 包元数据"],
hasOta: true,
settings: defaultSettings,
},
devices: [
{
id: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: PRIMARY_ADMIN_ACCOUNT,
source: "production",
status: "online",
projects: ["Boss 移动控制台", "硬件审计协作"],
quota5h: 68,
quota7d: 81,
lastSeenAt: "2026-03-25T11:52:00+08:00",
endpoint: "mac://kris.local",
token: "boss-mac-studio-token",
note: "本机 Codex 主节点 · 17600003315 已绑定",
},
{
id: "win-gpu-01",
name: "Windows GPU",
avatar: "W",
account: "kris.plus.gpu",
source: "demo",
status: "abnormal",
projects: ["Boss 移动控制台", "硬件审计协作"],
quota5h: 31,
quota7d: 46,
lastSeenAt: "2026-03-25T11:40:00+08:00",
endpoint: "win://gpu.local",
token: "boss-win-gpu-token",
note: "摄像头证据通道偶发抖动",
},
{
id: "cloud-backup",
name: "Cloud Backup",
avatar: "C",
account: "kris.plus.backup",
source: "demo",
status: "offline",
projects: ["Boss 移动控制台"],
quota5h: 92,
quota7d: 95,
lastSeenAt: "2026-03-25T08:15:00+08:00",
endpoint: "cloud://standby",
token: "boss-cloud-backup-token",
note: "standby 节点",
},
],
projects: [
{
id: "master-agent",
name: "主 Agent",
pinned: true,
systemPinned: true,
deviceIds: ["mac-studio"],
preview: "已汇总 3 个项目,优先收尾 Boss 移动控制台里 must_finish_before_compaction 的线程。",
updatedAt: "2026-03-25T12:06:00+08:00",
lastMessageAt: "2026-03-25T12:06:00+08:00",
isGroup: false,
threadMeta: {
projectId: "master-agent",
threadId: "thread-master-main",
threadDisplayName: "主 Agent 汇总",
folderName: "主控线程",
activityIconCount: 1,
updatedAt: "2026-03-25T12:06:00+08:00",
codexThreadRef: "thread-master-main",
codexFolderRef: "master-agent",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "medium",
contextBudgetPct: 71,
contextBudgetLabel: "71%",
messages: [
{
id: "master-summary",
sender: "master",
senderLabel: "主 Agent",
body: "Boss 移动控制台存在 urgent 线程待交接,硬件审计协作还有 1 条摄像头证据待复核。",
sentAt: "2026-03-25T12:06:00+08:00",
kind: "text",
},
],
goals: [],
versions: [],
},
{
id: "boss-console",
name: "Boss 移动控制台",
pinned: false,
deviceIds: ["mac-studio"],
preview: "登录、设备页、线程预算与设备绑定链路正在收口到 v13。",
updatedAt: "2026-03-25T11:52:00+08:00",
lastMessageAt: "2026-03-25T11:52:00+08:00",
isGroup: false,
threadMeta: {
projectId: "boss-console",
threadId: "thread-boss-ui",
threadDisplayName: "北区试产线回归",
folderName: "归档确认",
activityIconCount: 1,
updatedAt: "2026-03-25T11:52:00+08:00",
codexThreadRef: "thread-boss-ui",
codexFolderRef: "boss-console",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 2,
riskLevel: "medium",
contextBudgetPct: 62,
contextBudgetLabel: "62%",
messages: [
{
id: "p1",
sender: "master",
senderLabel: "主 Agent",
body: "项目目标页已切成可完成、可编辑、可记录完成时间的结构。",
sentAt: "2026-03-25T11:40:00+08:00",
kind: "text",
},
{
id: "p2",
sender: "device",
senderLabel: "Mac Studio / Codex",
body: "登录、注册、忘记密码页已经补齐,并带验证码发送状态回显。",
sentAt: "2026-03-25T11:48:00+08:00",
kind: "text",
},
],
goals: [
{
id: "goal-1",
text: "完成北区试产线全链路回归覆盖串口、视觉、OTA 和容灾切换。",
state: "completed",
note: "已完成 · 09:12 由主 Agent 复核",
completedAt: "2026-03-25T09:12:00+08:00",
completedBy: "主 Agent",
},
{
id: "goal-2",
text: "所有关键步骤必须留下可交接证据,禁止仅口头确认。",
state: "pending",
note: "进行中 · 允许用户编辑,主 Agent 会同步重排任务",
},
{
id: "goal-3",
text: "当线程上下文余量进入 urgent 前,必须完成阶段摘要与 handoff。",
state: "pending",
note: "待处理 · 主 Agent 会优先把压缩前必须收尾的任务推到前面",
},
],
versions: [
{
version: "v1.3.0",
summary: "登录页改为账号密码 / 验证码双模式,并新增 OTA 版本中心与本机最高管理员绑定。",
createdAt: "2026-03-26T09:20:00+08:00",
},
{
version: "v1.2.8",
summary: "补齐认证页、线程预算接口、设备绑定草稿与运维审计摘要。",
createdAt: "2026-03-25T11:30:00+08:00",
},
{
version: "v1.2.7",
summary: "会话页、设备页、我的页切到微信式一级导航。",
createdAt: "2026-03-25T09:15:00+08:00",
},
],
},
{
id: "audit-collab",
name: "硬件审计协作",
pinned: false,
deviceIds: ["mac-studio", "win-gpu-01"],
preview: "Windows 端等待摄像头证据回传,总审计 Agent 正在复核。",
updatedAt: "2026-03-25T10:58:00+08:00",
lastMessageAt: "2026-03-25T10:58:00+08:00",
isGroup: true,
threadMeta: {
projectId: "audit-collab",
threadId: "thread-audit-chief",
threadDisplayName: "审计对话",
folderName: "审计群聊",
activityIconCount: 2,
updatedAt: "2026-03-25T10:58:00+08:00",
codexThreadRef: "thread-audit-chief",
codexFolderRef: "audit-collab",
},
groupMembers: [
{
projectId: "audit-collab",
deviceId: "mac-studio",
threadId: "thread-audit-chief",
threadDisplayName: "审计对话",
folderName: "审计群聊",
},
{
projectId: "audit-collab",
deviceId: "win-gpu-01",
threadId: "thread-audit-hardware",
threadDisplayName: "Windows 摄像头证据",
folderName: "审计群聊",
},
],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 1,
riskLevel: "high",
messages: [
{
id: "a1",
sender: "audit",
senderLabel: "审计 Agent",
body: "群聊式协作项目不在首页展示单一线程预算,详情页查看各线程风险。",
sentAt: "2026-03-25T10:58:00+08:00",
kind: "text",
},
],
goals: [
{
id: "goal-a1",
text: "同步 Windows GPU 节点的摄像头证据和串口日志。",
state: "pending",
note: "待处理 · 由审计 Agent 汇总",
},
],
versions: [
{
version: "v0.9.3",
summary: "接入跨设备线程对话、摄像头证据上传和 chief audit 汇总。",
createdAt: "2026-03-24T18:10:00+08:00",
},
],
},
],
verificationCodes: [],
verificationDispatches: [],
authAccounts: [
{
id: "account-17600003315",
account: PRIMARY_ADMIN_ACCOUNT,
passwordHash: hashPassword(PRIMARY_ADMIN_PASSWORD),
displayName: "Boss 超级管理员",
role: "highest_admin",
verificationEmail: PRIMARY_ADMIN_VERIFICATION_EMAIL,
codexNodeId: PRIMARY_CODEX_NODE_ID,
codexNodeLabel: PRIMARY_CODEX_NODE_LABEL,
primaryDeviceId: PRIMARY_CODEX_NODE_ID,
isPrimary: true,
createdAt: "2026-03-25T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
},
],
authSessions: [],
aiAccounts: [
{
accountId: "master-codex-primary",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "17600003315 · Master Codex Node",
accountIdentifier: PRIMARY_ADMIN_ACCOUNT,
nodeId: PRIMARY_CODEX_NODE_ID,
nodeLabel: PRIMARY_CODEX_NODE_LABEL,
enabled: true,
isActive: true,
status: "ready",
loginStatusNote: "已绑定本机 Codex可通过 local-agent relay 执行主 Agent 对话。",
createdAt: "2026-03-26T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
lastSwitchedAt: "2026-03-26T09:00:00+08:00",
switchReason: "默认主控身份预留位",
},
{
accountId: "master-codex-backup",
label: "备用 GPT",
role: "backup",
provider: "master_codex_node",
displayName: "备用 Master Codex Node",
enabled: false,
isActive: false,
status: "disabled",
loginStatusNote: "备用节点未启用。",
createdAt: "2026-03-26T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
},
{
accountId: "openai-api-fallback",
label: "API 容灾",
role: "api_fallback",
provider: "openai_api",
displayName: "OpenAI API",
model: "gpt-5.4",
enabled: true,
isActive: false,
status: "needs_api_key",
loginStatusNote: "配置 OpenAI API Key 后,可直接为主 Agent 生成真实回复。",
createdAt: "2026-03-26T09:00:00+08:00",
updatedAt: "2026-03-26T09:00:00+08:00",
},
],
aiAccountSwitchHistory: [
{
switchId: "aiswitch-seed-primary",
toAccountId: "master-codex-primary",
toLabel: "主 GPT",
role: "primary",
switchedAt: "2026-03-26T09:00:00+08:00",
reason: "初始化默认主控身份",
},
],
userAttachmentStorageConfigs: [
{
account: PRIMARY_ADMIN_ACCOUNT,
mode: "server_file",
updatedAt: nowIso(),
},
],
masterAgentPromptPolicy: null,
userMasterPrompts: [],
masterAgentMemories: [],
userProjectAgentControls: [],
masterAgentTasks: [],
dispatchPlans: [],
dispatchExecutions: [],
deviceImportDrafts: [],
deviceImportResolutions: [],
otaUpdates: [
{
releaseId: "ota_140_to_141",
version: "v2.0.0",
currentVersion: "1.4.0",
channel: "stable",
packageType: "android_shell",
status: "available",
summary: ["切到原生 Android 客户端", "新增原生会话 / 设备 / 我的三栏", "登录页改为原生一键进入"],
targetScope: "Boss Android 原生客户端与 Web 控制台",
requiredRole: "highest_admin",
publishedAt: "2026-03-26T11:20:00+08:00",
packageFileName: "boss-android-latest.apk",
downloadUrl: "/api/v1/user/ota/package",
},
],
otaUpdateLogs: [
{
logId: "otalog_seed_130",
releaseId: "ota_seed_check",
version: "1.4.0",
status: "checked",
triggeredBy: "Boss 超级管理员",
triggeredAt: "2026-03-26T09:05:00+08:00",
note: "本机 Codex 已切到 17600003315等待管理员决定是否升级到 v1.4.1。",
},
],
deviceSkills: [],
appLogs: [],
threadContextSnapshots: [
{
snapshotId: "snapshot-master-main",
projectId: "master-agent",
taskId: "task-master-summary",
threadId: "thread-master-main",
title: "主控汇总线程",
summary: "正在整理 3 个项目状态、2 条未关闭告警和 1 个待交接线程。",
nodeId: "mac-studio",
workerId: "worker-mac-master",
sourceKind: "worker_estimator",
status: "running",
contextBudgetRemainingPct: 71,
contextBudgetLevel: "safe",
compactionExpectedAt: "2026-03-25T13:20:00+08:00",
mustFinishBeforeCompaction: false,
estimatedRemainingTurns: 18,
estimatedRemainingLargeMessages: 6,
lastCompactionAt: "2026-03-25T09:48:00+08:00",
compactionCount: 1,
patchPending: false,
testsPending: false,
evidencePending: false,
checklist: baseThreadChecklist(["持续刷新阶段摘要", "复核风险排序", "回写交接文档"]),
capturedAt: "2026-03-25T12:06:00+08:00",
},
{
snapshotId: "snapshot-boss-ui",
projectId: "boss-console",
taskId: "task-ui-refine",
threadId: "thread-boss-ui",
title: "Boss UI 收口线程",
summary: "会话页、设备页和我的页已稳定,剩设备绑定与运维摘要联调。",
nodeId: "mac-studio",
workerId: "worker-mac-ui",
sourceKind: "worker_estimator",
status: "running",
contextBudgetRemainingPct: 62,
contextBudgetLevel: "watch",
compactionExpectedAt: "2026-03-25T14:05:00+08:00",
mustFinishBeforeCompaction: false,
estimatedRemainingTurns: 10,
estimatedRemainingLargeMessages: 4,
lastCompactionAt: "2026-03-25T10:28:00+08:00",
compactionCount: 1,
patchPending: true,
testsPending: true,
evidencePending: false,
checklist: baseThreadChecklist(["设备绑定页接 API", "回归 lint/build", "回写 README"]),
capturedAt: "2026-03-25T11:52:00+08:00",
},
{
snapshotId: "snapshot-boss-auth",
projectId: "boss-console",
taskId: "task-auth-delivery",
threadId: "thread-boss-auth",
title: "认证链路交接线程",
summary: "登录/注册/忘记密码已完成,但验证码回显和文档收口必须先固化。",
nodeId: "mac-studio",
workerId: "worker-mac-auth",
sourceKind: "worker_estimator",
status: "handoff_pending",
contextBudgetRemainingPct: 34,
contextBudgetLevel: "urgent",
compactionExpectedAt: "2026-03-25T12:24:00+08:00",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 4,
estimatedRemainingLargeMessages: 1,
lastCompactionAt: "2026-03-25T11:14:00+08:00",
compactionCount: 2,
patchPending: true,
testsPending: true,
evidencePending: false,
checklist: baseThreadChecklist(["固化 patch", "记录测试结果", "生成 handoff package"]),
capturedAt: "2026-03-25T11:54:00+08:00",
},
{
snapshotId: "snapshot-audit-chief",
projectId: "audit-collab",
taskId: "task-chief-audit",
threadId: "thread-audit-chief",
title: "Chief Audit 汇总线程",
summary: "总审计正在复核硬件证据、串口日志和运维修复票据。",
nodeId: "mac-studio",
workerId: "worker-chief-audit",
sourceKind: "worker_estimator",
status: "running",
contextBudgetRemainingPct: 58,
contextBudgetLevel: "watch",
compactionExpectedAt: "2026-03-25T13:12:00+08:00",
mustFinishBeforeCompaction: false,
estimatedRemainingTurns: 9,
estimatedRemainingLargeMessages: 3,
lastCompactionAt: "2026-03-25T10:01:00+08:00",
compactionCount: 1,
patchPending: false,
testsPending: false,
evidencePending: true,
checklist: baseThreadChecklist(["等待摄像头证据", "核对串口日志", "给出最终 verdict"]),
capturedAt: "2026-03-25T10:58:00+08:00",
},
{
snapshotId: "snapshot-audit-hw",
projectId: "audit-collab",
taskId: "task-hardware-evidence",
threadId: "thread-audit-hardware",
title: "Windows 摄像头证据线程",
summary: "摄像头关键帧仍缺 1 段,必须先收尾证据再继续扩上下文。",
nodeId: "win-gpu-01",
workerId: "worker-win-audit",
sourceKind: "worker_estimator",
status: "context_urgent",
contextBudgetRemainingPct: 21,
contextBudgetLevel: "critical",
compactionExpectedAt: "2026-03-25T12:12:00+08:00",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 2,
estimatedRemainingLargeMessages: 1,
lastCompactionAt: "2026-03-25T11:36:00+08:00",
compactionCount: 3,
patchPending: false,
testsPending: false,
evidencePending: true,
checklist: baseThreadChecklist(["保存摄像头关键帧", "补录串口摘要", "通知 chief audit"]),
capturedAt: "2026-03-25T11:58:00+08:00",
},
],
threadHandoffPackages: [
{
handoffPackageId: "handoff-boss-auth",
projectId: "boss-console",
taskId: "task-auth-delivery",
fromThreadId: "thread-boss-auth",
toThreadId: "thread-boss-auth-followup",
packageStatus: "ready",
summaryText: "认证页功能已齐,剩余文档、回归和部署验证待在新线程继续。",
openQuestions: ["验证码直回显是否继续保留在公网环境", "是否把帮助页挂到登录页右上角"],
criticalFiles: ["src/app/auth", "src/app/api/auth", "src/components/app-ui.tsx"],
criticalCommands: ["npm run lint", "npm run build", "curl -sS http://127.0.0.1:3000/api/health"],
criticalTests: ["登录验证码发送", "注册后登录", "忘记密码重置"],
criticalArtifacts: ["docs/architecture/api_and_service_inventory_cn.md"],
decisionLinks: ["boss-console:auth-mvp"],
createdAt: "2026-03-25T11:49:00+08:00",
readyAt: "2026-03-25T11:54:00+08:00",
},
],
threadContextAlerts: [
{
alertId: "alert-boss-auth",
threadId: "thread-boss-auth",
projectId: "boss-console",
alertType: "context_urgent",
alertStatus: "opened",
openedAt: "2026-03-25T11:54:00+08:00",
summary: "认证链路线程已进入 urgent必须优先固化 patch、测试结论和 handoff。",
masterActions: ["prepare_handoff", "avoid_large_context_append", "finalize_artifacts"],
},
{
alertId: "alert-audit-hw",
threadId: "thread-audit-hardware",
projectId: "audit-collab",
alertType: "context_critical",
alertStatus: "opened",
openedAt: "2026-03-25T11:58:00+08:00",
summary: "Windows 摄像头证据线程进入 critical任何新增背景信息前先收尾证据。",
masterActions: ["complete_wrapup", "handoff_required", "freeze_context_growth"],
},
],
deviceEnrollments: [
{
enrollmentId: "enroll-cloud-backup",
deviceId: "cloud-backup",
label: "Cloud Backup",
pairingCode: "482913",
token: "boss-cloud-backup-token",
status: "ready",
note: "standby 节点待重新连接",
createdAt: "2026-03-25T10:40:00+08:00",
expiresAt: "2026-03-25T18:40:00+08:00",
},
],
opsFaults: [
{
faultId: "fault-win-camera",
faultKey: "OPS.LOCAL_AGENT.CAMERA_EVIDENCE.DELAYED",
severity: "warning",
status: "repairing",
nodeId: "win-gpu-01",
serviceName: "boss-local-agent",
projectId: "audit-collab",
threadRef: "thread-audit-hardware",
traceId: "trace-audit-001",
runbookId: "runbook-camera-sync",
firstSeenAt: "2026-03-25T11:34:00+08:00",
lastSeenAt: "2026-03-25T11:58:00+08:00",
summary: "Windows 节点摄像头证据回传延迟chief audit 正在等待关键帧。",
suggestedNextAction: "优先重试本地 agent 证据上传,再做审计复验。",
autoRepairable: true,
},
{
faultId: "fault-context-auth",
faultKey: "THREAD.CONTEXT.HANDOFF.REQUIRED",
severity: "critical",
status: "opened",
nodeId: "mac-studio",
serviceName: "boss-web",
projectId: "boss-console",
threadRef: "thread-boss-auth",
traceId: "trace-boss-auth-002",
runbookId: "runbook-thread-handoff",
firstSeenAt: "2026-03-25T11:50:00+08:00",
lastSeenAt: "2026-03-25T11:54:00+08:00",
summary: "认证线程预算降到 urgent未完成 handoff 前不应继续追加长上下文。",
suggestedNextAction: "执行压缩前收尾,写明关键文件、命令和测试结果。",
autoRepairable: false,
},
],
opsRepairTickets: [
{
ticketId: "ticket-win-camera",
faultId: "fault-win-camera",
title: "重试 Windows 摄像头证据上传",
approvalStatus: "approved",
executionStatus: "running",
requestedBy: "运维 Agent",
approvedBy: "主 Agent",
targetNodeId: "win-gpu-01",
actionSummary: "重新触发本地 agent 心跳和摄像头证据上传。",
resultSummary: "等待运维审计 Agent 复验中。",
createdAt: "2026-03-25T11:42:00+08:00",
updatedAt: "2026-03-25T11:57:00+08:00",
},
],
opsRepairVerifications: [
{
verificationId: "verify-win-camera",
ticketId: "ticket-win-camera",
verifier: "运维审计 Agent",
status: "watching",
summary: "主控在线,已允许修复动作,但仍需等待关键帧落库后才能关闭工单。",
verifiedAt: "2026-03-25T11:58:00+08:00",
},
],
auditRequests: [
{
protocolVersion: "1.0",
auditRequestId: "auditreq_001",
projectId: "audit-collab",
projectName: "硬件审计协作",
taskId: "task-hardware-evidence",
sourceThreadRef: "win-gpu-01:thread-audit-hardware",
trigger: "failure_escalation",
auditType: "multimodal",
priority: 75,
objective: "验证设备唤醒流程里的屏幕、LED、串口和摄像头证据是否一致。",
systemContextSummary: "设备端已刷入固件 v0.9.2,当前 chief audit 等待 Windows 节点关键帧。",
acceptanceCriteria: [
"LED 在 2 秒内变蓝",
"屏幕切换到 listening 状态",
"串口输出唤醒完成",
"摄像头观测到机械动作完成",
],
riskFocus: ["摄像头关键帧缺失", "串口时间线不一致"],
evidenceRefs: ["evidence://serial-log/20260325-1"],
artifactRefs: [],
capabilityRequirements: [
{ capabilityType: "camera", mode: "exclusive" },
{ capabilityType: "serial_port", mode: "exclusive" },
],
timeBudgetSeconds: 300,
responseMode: "structured_json",
createdAt: "2026-03-25T11:36:00+08:00",
metadataJson: { requestedBy: "chief-audit" },
},
],
auditResults: [
{
auditRequestId: "auditreq_001",
auditType: "multimodal",
status: "needs_human_review",
decision: "warning",
confidence: 0.72,
summary: "串口链路通过,但摄像头关键帧仍缺最后一段,暂不允许关闭工单。",
findings: ["LED 与串口时间线一致", "摄像头关键帧缺失最后 2 秒"],
requiredActions: ["补录关键帧", "回放 chief audit 证据摘要"],
usedCapabilities: ["camera", "serial_port"],
artifactRefs: ["artifact://camera/keyframe-20260325-1"],
timeline: ["11:36 接单", "11:44 拉串口", "11:58 等待摄像头关键帧"],
durationMs: 1_320_000,
completedAt: "2026-03-25T11:58:00+08:00",
},
],
capabilities: [
{
capabilityId: "cap-camera-win",
providerId: "provider-win-gpu",
nodeId: "win-gpu-01",
capabilityType: "camera",
displayName: "Windows 审计摄像头",
status: "busy",
healthStatus: "warning",
leaseMode: "exclusive",
preemptible: true,
supportedActions: ["snapshot", "record_clip", "start_stream", "stop_stream"],
evidenceModes: ["image", "video_clip"],
},
{
capabilityId: "cap-serial-win",
providerId: "provider-win-gpu",
nodeId: "win-gpu-01",
capabilityType: "serial_port",
displayName: "串口日志采集",
status: "online",
healthStatus: "healthy",
leaseMode: "exclusive",
preemptible: false,
supportedActions: ["open", "read", "write", "capture_log"],
evidenceModes: ["serial_log"],
},
],
};
const levelPriority: Record<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(),
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",
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",
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 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,
status: task.status ?? "queued",
requestedAt: task.requestedAt ?? nowIso(),
claimedAt: task.claimedAt,
completedAt: task.completedAt,
replyBody: task.replyBody,
errorMessage: task.errorMessage,
requestId: task.requestId,
})),
dispatchPlans: ensureArray(raw.dispatchPlans, base.dispatchPlans).map((plan, index) =>
normalizeDispatchPlan(plan, base.dispatchPlans[index % Math.max(1, base.dispatchPlans.length)]),
),
dispatchExecutions: ensureArray(raw.dispatchExecutions, base.dispatchExecutions).map((execution, index) =>
normalizeDispatchExecution(
execution,
base.dispatchExecutions[index % Math.max(1, base.dispatchExecutions.length)],
),
),
deviceImportDrafts: ensureArray(raw.deviceImportDrafts, base.deviceImportDrafts).map((draft, index) =>
normalizeDeviceImportDraft(
draft,
base.deviceImportDrafts[index % Math.max(1, base.deviceImportDrafts.length)],
),
),
deviceImportResolutions: ensureArray(
raw.deviceImportResolutions,
base.deviceImportResolutions,
).map((resolution, index) =>
normalizeDeviceImportResolution(
resolution,
base.deviceImportResolutions[index % Math.max(1, base.deviceImportResolutions.length)],
),
),
otaUpdates: ensureArray(raw.otaUpdates, base.otaUpdates).map((update, index) => ({
...base.otaUpdates[index % base.otaUpdates.length],
...update,
summary: ensureArray(update.summary, base.otaUpdates[index % base.otaUpdates.length].summary),
})),
otaUpdateLogs: ensureArray(raw.otaUpdateLogs, base.otaUpdateLogs).map((log, index) => ({
...base.otaUpdateLogs[index % base.otaUpdateLogs.length],
...log,
})),
deviceSkills: ensureArray(raw.deviceSkills, base.deviceSkills).map((skill) => ({
...skill,
invocation: skill.invocation ?? `$${skill.name}`,
category: skill.category ?? "本机技能",
updatedAt: skill.updatedAt ?? nowIso(),
})),
appLogs: ensureArray(raw.appLogs, base.appLogs).map((log) => ({
...log,
mirroredToProject: log.mirroredToProject ?? false,
createdAt: log.createdAt ?? nowIso(),
})),
userAttachmentStorageConfigs: ensureArray(
raw.userAttachmentStorageConfigs,
base.userAttachmentStorageConfigs,
).map((config, index) =>
normalizeAttachmentStorageConfig(
config,
base.userAttachmentStorageConfigs[index % base.userAttachmentStorageConfigs.length],
),
),
masterAgentPromptPolicy: normalizeMasterAgentPromptPolicy(
raw.masterAgentPromptPolicy,
base.masterAgentPromptPolicy,
),
userMasterPrompts: ensureArray(raw.userMasterPrompts, base.userMasterPrompts).map(
(prompt, index) =>
normalizeUserMasterPrompt(
prompt,
base.userMasterPrompts[index % Math.max(1, base.userMasterPrompts.length)],
),
),
masterAgentMemories: ensureArray(raw.masterAgentMemories, base.masterAgentMemories).map(
(memory, index) =>
normalizeUserMasterMemory(
memory,
base.masterAgentMemories[index % Math.max(1, base.masterAgentMemories.length)],
),
),
userProjectAgentControls: ensureArray(
raw.userProjectAgentControls,
base.userProjectAgentControls,
)
.map((controls, index) =>
normalizeUserProjectAgentControls(
controls,
base.userProjectAgentControls[index % Math.max(1, base.userProjectAgentControls.length)],
),
)
.filter((item): item is UserProjectAgentControls => Boolean(item)),
threadContextSnapshots: ensureArray(raw.threadContextSnapshots, base.threadContextSnapshots).map(
(snapshot, index) => ({
...base.threadContextSnapshots[index % base.threadContextSnapshots.length],
...snapshot,
contextBudgetLevel:
snapshot.contextBudgetLevel ??
deriveLevelFromPercent(snapshot.contextBudgetRemainingPct ?? 100),
checklist: ensureArray(snapshot.checklist, base.threadContextSnapshots[index % base.threadContextSnapshots.length].checklist),
}),
),
threadHandoffPackages: ensureArray(raw.threadHandoffPackages, base.threadHandoffPackages).map(
(item) => ({
...item,
openQuestions: ensureArray(item.openQuestions, []),
criticalFiles: ensureArray(item.criticalFiles, []),
criticalCommands: ensureArray(item.criticalCommands, []),
criticalTests: ensureArray(item.criticalTests, []),
criticalArtifacts: ensureArray(item.criticalArtifacts, []),
decisionLinks: ensureArray(item.decisionLinks, []),
}),
),
threadContextAlerts: ensureArray(raw.threadContextAlerts, base.threadContextAlerts).map((item) => ({
...item,
masterActions: ensureArray(item.masterActions, []),
})),
deviceEnrollments: ensureArray(raw.deviceEnrollments, base.deviceEnrollments),
opsFaults: ensureArray(raw.opsFaults, base.opsFaults),
opsRepairTickets: ensureArray(raw.opsRepairTickets, base.opsRepairTickets),
opsRepairVerifications: ensureArray(raw.opsRepairVerifications, base.opsRepairVerifications),
auditRequests: ensureArray(raw.auditRequests, base.auditRequests).map((item) => ({
...item,
acceptanceCriteria: ensureArray(item.acceptanceCriteria, []),
riskFocus: ensureArray(item.riskFocus, []),
evidenceRefs: ensureArray(item.evidenceRefs, []),
artifactRefs: ensureArray(item.artifactRefs, []),
capabilityRequirements: ensureArray(item.capabilityRequirements, []),
metadataJson: item.metadataJson ?? {},
})),
auditResults: ensureArray(raw.auditResults, base.auditResults).map((item) => ({
...item,
findings: ensureArray(item.findings, []),
requiredActions: ensureArray(item.requiredActions, []),
usedCapabilities: ensureArray(item.usedCapabilities, []),
artifactRefs: ensureArray(item.artifactRefs, []),
timeline: ensureArray(item.timeline, []),
})),
capabilities: ensureArray(raw.capabilities, base.capabilities).map((item) => ({
...item,
supportedActions: ensureArray(item.supportedActions, []),
evidenceModes: ensureArray(item.evidenceModes, []),
})),
};
if (!state.projects.some((project) => project.id === "master-agent")) {
state.projects.unshift(base.projects[0]);
}
return syncDerivedState(state);
}
function latestProjectTimestamp(state: BossState, projectId: string) {
const project = state.projects.find((item) => item.id === projectId);
const messageTimes = (project?.messages ?? []).map((message) => messageTimeValue(message.sentAt));
const snapshotTimes = state.threadContextSnapshots
.filter((snapshot) => snapshot.projectId === projectId)
.map((snapshot) => messageTimeValue(snapshot.capturedAt));
const alertTimes = state.threadContextAlerts
.filter((alert) => alert.projectId === projectId)
.map((alert) => messageTimeValue(alert.openedAt));
const opsTimes = state.opsFaults
.filter((fault) => fault.projectId === projectId)
.map((fault) => messageTimeValue(fault.lastSeenAt));
const auditTimes = state.auditResults
.filter((result) =>
state.auditRequests.some(
(request) =>
request.auditRequestId === result.auditRequestId && request.projectId === projectId,
),
)
.map((result) => messageTimeValue(result.completedAt));
const latest = Math.max(0, ...messageTimes, ...snapshotTimes, ...alertTimes, ...opsTimes, ...auditTimes);
return latest ? new Date(latest).toISOString() : project?.lastMessageAt ?? nowIso();
}
function deriveProjectPreview(state: BossState, project: Project) {
if (project.id === "master-agent") {
return project.preview;
}
const relevantSnapshots = state.threadContextSnapshots
.filter((snapshot) => snapshot.projectId === project.id)
.sort(compareSnapshotsForRisk);
const topSnapshot = relevantSnapshots[0];
if (topSnapshot && (topSnapshot.contextBudgetLevel !== "safe" || topSnapshot.mustFinishBeforeCompaction)) {
return `${topSnapshot.title} · ${topSnapshot.contextBudgetLevel} ${topSnapshot.contextBudgetRemainingPct}% · ${topSnapshot.summary}`;
}
const latestAudit = state.auditResults
.map((result) => ({
result,
request: state.auditRequests.find((request) => request.auditRequestId === result.auditRequestId),
}))
.filter(
(item): item is { result: AuditTaskResult; request: AuditTaskRequest } =>
Boolean(item.request && item.request.projectId === project.id),
)
.sort((a, b) => messageTimeValue(b.result.completedAt) - messageTimeValue(a.result.completedAt))[0];
if (latestAudit) {
return `审计 · ${latestAudit.result.summary}`;
}
const openFault = state.opsFaults
.filter((fault) => fault.projectId === project.id && fault.status !== "resolved")
.sort((a, b) => messageTimeValue(b.lastSeenAt) - messageTimeValue(a.lastSeenAt))[0];
if (openFault) {
return `运维 · ${openFault.summary}`;
}
const lastMessage = [...project.messages].sort(
(a, b) => messageTimeValue(b.sentAt) - messageTimeValue(a.sentAt),
)[0];
if (lastMessage) {
return lastMessage.body;
}
return project.preview;
}
function updateMasterProjectSummary(state: BossState) {
const masterProject = state.projects.find((project) => project.id === "master-agent");
if (!masterProject) return;
const riskThreads = [...state.threadContextSnapshots]
.filter(
(snapshot) =>
snapshot.projectId !== "master-agent" &&
(snapshot.contextBudgetLevel !== "safe" || snapshot.mustFinishBeforeCompaction),
)
.sort(compareSnapshotsForRisk);
const openFaults = state.opsFaults.filter((fault) => fault.status !== "resolved");
const pendingAudits = state.auditRequests.filter(
(request) =>
!state.auditResults.some((result) => result.auditRequestId === request.auditRequestId),
);
const summaryParts = [];
if (riskThreads[0]) {
summaryParts.push(
`${riskThreads[0].title} ${riskThreads[0].contextBudgetLevel} ${riskThreads[0].contextBudgetRemainingPct}%`,
);
}
if (openFaults[0]) {
summaryParts.push(`运维 ${openFaults[0].faultKey}`);
}
if (pendingAudits.length > 0) {
summaryParts.push(`审计待处理 ${pendingAudits.length}`);
}
masterProject.preview =
summaryParts.join(" · ") ||
"当前无高风险线程,主 Agent 正在维护项目总结、运维账本和交接文档。";
masterProject.riskLevel = riskThreads[0]
? deriveRiskFromSnapshots([riskThreads[0]])
: openFaults.some((fault) => fault.severity === "critical")
? "high"
: "low";
const masterSnapshot = state.threadContextSnapshots.find(
(snapshot) => snapshot.projectId === "master-agent",
);
masterProject.contextBudgetPct = masterSnapshot?.contextBudgetRemainingPct;
masterProject.contextBudgetLabel = masterSnapshot
? `${masterSnapshot.contextBudgetRemainingPct}%`
: undefined;
masterProject.lastMessageAt = latestProjectTimestamp(state, "master-agent");
masterProject.updatedAt = masterProject.lastMessageAt;
const summaryMessage = masterProject.messages.find((message) => message.id === "master-summary");
if (summaryMessage) {
summaryMessage.body = masterProject.preview;
summaryMessage.sentAt = masterProject.lastMessageAt;
}
}
function firstAvailableOta(state: BossState) {
return state.otaUpdates.find((item) => item.status === "available" || item.status === "scheduled") ?? null;
}
function readPublishedOtaAsset() {
if (!existsSync(publishedApkPath)) {
return null;
}
const stats = statSync(publishedApkPath);
type PublishedOtaMetadata = {
fileName?: string;
urlPath?: string;
sizeBytes?: number;
updatedAt?: string;
sha256?: string;
versionName?: string;
};
let metadata: PublishedOtaMetadata | null = null;
if (existsSync(publishedApkMetaPath)) {
try {
metadata = JSON.parse(readFileSync(publishedApkMetaPath, "utf8")) as PublishedOtaMetadata;
} catch {
metadata = null;
}
}
return {
fileName: metadata?.fileName ?? path.basename(publishedApkPath),
downloadUrl: metadata?.urlPath ?? "/api/v1/user/ota/package",
sizeBytes: metadata?.sizeBytes ?? stats.size,
assetUpdatedAt: metadata?.updatedAt ?? stats.mtime.toISOString(),
packageSha256: metadata?.sha256,
versionName: metadata?.versionName,
};
}
function syncPublishedOtaAsset(state: BossState) {
const asset = readPublishedOtaAsset();
for (const release of state.otaUpdates) {
if (release.packageType !== "android_shell") continue;
release.currentVersion = state.user.version;
if (!asset) {
release.packageFileName = undefined;
release.packageSizeBytes = undefined;
release.packageSha256 = undefined;
release.downloadUrl = undefined;
release.assetUpdatedAt = undefined;
continue;
}
release.packageFileName = asset.fileName;
release.packageSizeBytes = asset.sizeBytes;
release.packageSha256 = asset.packageSha256;
release.downloadUrl = asset.downloadUrl;
release.assetUpdatedAt = asset.assetUpdatedAt;
if (asset.versionName) {
release.version = asset.versionName.startsWith("v")
? asset.versionName
: `v${asset.versionName}`;
}
}
}
function isProductionDevice(device: Device) {
return device.source === "production";
}
function pushProjectLedgerMessage(
state: BossState,
projectId: string,
message: Omit<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),
);
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 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;
}) {
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,
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 (!groupProject.isGroup) throw new Error("DISPATCH_PLAN_GROUP_PROJECT_INVALID");
const validatedTargets = normalizeDispatchPlanTargetsForCreate(state, input.targets);
const existing = state.dispatchPlans.find(
(plan) => plan.groupProjectId === groupProjectId && plan.requestMessageId === requestMessageId,
);
if (existing) {
const payloadMatches =
existing.requestedBy === requestedBy &&
existing.summary === summary &&
sameDispatchPlanTargets(existing.targets, validatedTargets) &&
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 applyDispatchPlanConfirmationInState(
state: BossState,
input: {
planId: string;
confirmedBy: string;
approvedTargetProjectIds: string[];
},
) {
const plan = state.dispatchPlans.find((item) => item.planId === input.planId);
if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND");
if (plan.status === "rejected") throw new Error("DISPATCH_PLAN_REJECTED");
const confirmedBy = input.confirmedBy.trim();
if (!confirmedBy) throw new Error("DISPATCH_PLAN_CONFIRMED_BY_REQUIRED");
requireDispatchActorSession(state, confirmedBy);
const approvedTargetProjectIds = normalizeStringSet(input.approvedTargetProjectIds);
if (approvedTargetProjectIds.length === 0) {
throw new Error("DISPATCH_PLAN_APPROVED_TARGETS_REQUIRED");
}
const canonicalTargetProjectIds = normalizeStringSet(plan.targets.map((target) => target.projectId));
if (approvedTargetProjectIds.some((projectId) => !canonicalTargetProjectIds.includes(projectId))) {
throw new Error("DISPATCH_PLAN_APPROVED_TARGETS_INVALID");
}
if (plan.confirmedBy && plan.confirmedBy !== confirmedBy) {
throw new Error("DISPATCH_PLAN_CONFIRMED_BY_MISMATCH");
}
if (plan.confirmedTargetProjectIds?.length && !sameStringSet(plan.confirmedTargetProjectIds, approvedTargetProjectIds)) {
throw new Error("DISPATCH_PLAN_APPROVED_TARGETS_MISMATCH");
}
if (plan.status !== "dispatched") {
plan.status = "approved";
}
if (!plan.confirmedAt) {
plan.confirmedAt = nowIso();
}
plan.confirmedBy = confirmedBy;
plan.confirmedTargetProjectIds = approvedTargetProjectIds;
return plan;
}
export async function confirmDispatchPlan(input: {
planId: string;
confirmedBy: string;
approvedTargetProjectIds: string[];
}) {
return mutateState((state) => {
return applyDispatchPlanConfirmationInState(state, input);
});
}
export async function rejectDispatchPlan(input: {
groupProjectId: string;
planId: string;
rejectedBy: string;
}) {
const result = await mutateState((state) => {
const groupProjectId = input.groupProjectId.trim();
if (!groupProjectId) throw new Error("PROJECT_NOT_FOUND");
const groupProject = state.projects.find((item) => item.id === groupProjectId);
if (!groupProject) throw new Error("PROJECT_NOT_FOUND");
if (!groupProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT");
requireDispatchActorSession(state, input.rejectedBy);
const plan = state.dispatchPlans.find((item) => item.planId === input.planId);
if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND");
if (plan.groupProjectId !== groupProjectId) {
throw new Error("DISPATCH_PLAN_PROJECT_MISMATCH");
}
if (plan.status === "dispatched") {
throw new Error("DISPATCH_PLAN_ALREADY_DISPATCHED");
}
if (plan.status !== "rejected") {
plan.status = "rejected";
}
groupProject.approvalState = "rejected";
const notice =
pushProjectLedgerMessage(state, groupProjectId, {
sender: "master",
senderLabel: "主 Agent",
body: "已拒绝主 Agent 推荐,本次不会下发到任何线程。",
kind: "system_notice",
}) ?? null;
return {
plan: { ...plan },
notice: notice ? { ...notice } : null,
collaborationGate: buildCollaborationGate(groupProject),
};
});
publishBossEvent("project.messages.updated", { projectId: input.groupProjectId });
publishBossEvent("conversation.updated", { projectId: input.groupProjectId });
return result;
}
export async function createDispatchExecutionsFromPlan(input: {
planId: string;
confirmedBy: string;
}) {
return mutateState((state) => {
const plan = state.dispatchPlans.find((item) => item.planId === input.planId);
if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND");
if (plan.status === "rejected") throw new Error("DISPATCH_PLAN_REJECTED");
const confirmedBy = input.confirmedBy.trim();
if (!confirmedBy) throw new Error("DISPATCH_PLAN_CONFIRMED_BY_REQUIRED");
requireDispatchActorSession(state, confirmedBy);
if (!plan.confirmedAt || !plan.confirmedBy || !plan.confirmedTargetProjectIds?.length) {
throw new Error("DISPATCH_PLAN_NOT_CONFIRMED");
}
if (plan.confirmedBy !== confirmedBy) {
throw new Error("DISPATCH_PLAN_CONFIRMED_BY_MISMATCH");
}
const groupProject = state.projects.find((item) => item.id === plan.groupProjectId);
if (!groupProject) throw new Error("PROJECT_NOT_FOUND");
const canonicalTargetProjectIds = normalizeStringSet(plan.confirmedTargetProjectIds);
const existingExecutions = state.dispatchExecutions.filter((item) => item.planId === plan.planId);
if (existingExecutions.length > 0) {
const existingTargetIds = normalizeStringSet(existingExecutions.map((execution) => execution.targetProjectId));
if (!sameStringSet(existingTargetIds, canonicalTargetProjectIds)) {
throw new Error("DISPATCH_EXECUTION_SET_MISMATCH");
}
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 代码块,不要输出额外解释。",
`groupProjectId: ${input.groupProject.id}`,
`groupProjectName: ${input.groupProject.name}`,
`threadProjectId: ${input.target.projectId}`,
`threadId: ${input.target.threadId}`,
`threadTitle: ${input.target.threadDisplayName}`,
`folderName: ${input.target.folderName}`,
`requestText: ${requestText}`,
`dispatchSummary: ${input.plan.summary}`,
].join("\n");
}
function ensureDispatchExecutionTaskInState(
state: BossState,
plan: DispatchPlan,
execution: DispatchExecution,
) {
const groupProject = state.projects.find((item) => item.id === execution.groupProjectId);
if (!groupProject) {
throw new Error("DISPATCH_EXECUTION_GROUP_PROJECT_NOT_FOUND");
}
const target = plan.targets.find(
(item) =>
item.projectId === execution.targetProjectId &&
item.threadId === execution.targetThreadId &&
item.deviceId === execution.deviceId,
);
if (!target) {
throw new Error("DISPATCH_EXECUTION_TARGET_NOT_FOUND");
}
const existing = state.masterAgentTasks.find(
(task) =>
task.taskType === "dispatch_execution" &&
(task.dispatchExecutionId === execution.executionId ||
(task.projectId === execution.groupProjectId &&
task.requestMessageId === plan.planId &&
task.targetProjectId === execution.targetProjectId &&
task.targetThreadId === execution.targetThreadId)),
);
if (existing) {
existing.dispatchExecutionId = existing.dispatchExecutionId ?? execution.executionId;
existing.targetProjectId = existing.targetProjectId ?? execution.targetProjectId;
existing.targetThreadId = existing.targetThreadId ?? execution.targetThreadId;
existing.targetThreadDisplayName = existing.targetThreadDisplayName ?? target.threadDisplayName;
existing.targetCodexThreadRef = existing.targetCodexThreadRef ?? target.codexThreadRef;
existing.targetCodexFolderRef = existing.targetCodexFolderRef ?? target.codexFolderRef;
existing.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));
}
export async function confirmDispatchPlanAndCreateExecutions(input: {
groupProjectId: string;
planId: string;
confirmedBy: string;
approvedTargetProjectIds: string[];
}) {
const result = await mutateState((state) => {
const groupProjectId = input.groupProjectId.trim();
if (!groupProjectId) throw new Error("PROJECT_NOT_FOUND");
const groupProject = state.projects.find((item) => item.id === groupProjectId);
if (!groupProject) throw new Error("PROJECT_NOT_FOUND");
if (!groupProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT");
const plan = applyDispatchPlanConfirmationInState(state, {
planId: input.planId,
confirmedBy: input.confirmedBy,
approvedTargetProjectIds: input.approvedTargetProjectIds,
});
if (plan.groupProjectId !== groupProjectId) {
throw new Error("DISPATCH_PLAN_PROJECT_MISMATCH");
}
const canonicalTargetProjectIds = normalizeStringSet(plan.confirmedTargetProjectIds ?? []);
const existingExecutions = state.dispatchExecutions.filter((item) => item.planId === plan.planId);
let executions: DispatchExecution[];
let createdNotice: Message | null = null;
if (existingExecutions.length > 0) {
const existingTargetIds = normalizeStringSet(existingExecutions.map((execution) => execution.targetProjectId));
if (!sameStringSet(existingTargetIds, canonicalTargetProjectIds)) {
throw new Error("DISPATCH_EXECUTION_SET_MISMATCH");
}
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");
}
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;
},
) {
const execution = state.dispatchExecutions.find(
(item) => item.executionId === payload.dispatchExecutionId,
);
if (!execution) throw new Error("DISPATCH_EXECUTION_NOT_FOUND");
if (execution.groupProjectId !== payload.groupProjectId) {
throw new Error("DISPATCH_EXECUTION_GROUP_PROJECT_MISMATCH");
}
if (execution.targetProjectId !== payload.targetProjectId) {
throw new Error("DISPATCH_EXECUTION_TARGET_PROJECT_MISMATCH");
}
if (execution.targetThreadId !== payload.targetThreadId) {
throw new Error("DISPATCH_EXECUTION_TARGET_THREAD_MISMATCH");
}
if (execution.deviceId !== payload.completedByDeviceId) {
throw new Error("DISPATCH_EXECUTION_DEVICE_MISMATCH");
}
const groupProject = state.projects.find((item) => item.id === payload.groupProjectId);
if (!groupProject) {
throw new Error("PROJECT_NOT_FOUND");
}
const device = state.devices.find((item) => item.id === payload.completedByDeviceId);
const threadTitle =
payload.targetThreadDisplayName?.trim() ||
state.projects.find((item) => item.id === payload.targetProjectId)?.threadMeta.threadDisplayName ||
payload.targetThreadId;
if (execution.status === "completed" || execution.status === "failed") {
if (execution.status !== payload.status) {
throw new Error("DISPATCH_EXECUTION_COMPLETION_MISMATCH");
}
const existingMirroredResult = execution.resultMessageId
? findProjectMessage(groupProject, execution.resultMessageId)
: null;
if (
payload.status === "completed" &&
payload.rawThreadReply?.trim() &&
existingMirroredResult &&
existingMirroredResult.body !== payload.rawThreadReply.trim()
) {
throw new Error("DISPATCH_EXECUTION_COMPLETION_MISMATCH");
}
return {
execution: { ...execution },
mirroredResult: existingMirroredResult,
masterSummary: null,
};
}
let mirroredResult: Message | null = null;
let masterSummary: Message | null = null;
if (payload.status === "completed") {
if (!payload.rawThreadReply?.trim()) {
throw new Error("DISPATCH_EXECUTION_RAW_REPLY_REQUIRED");
}
mirroredResult = pushProjectLedgerMessage(state, payload.groupProjectId, {
sender: "device",
senderLabel: `${threadTitle} · ${device?.name ?? payload.completedByDeviceId}`,
body: payload.rawThreadReply.trim(),
kind: "text",
});
masterSummary = pushProjectLedgerMessage(state, payload.groupProjectId, {
sender: "master",
senderLabel: "主 Agent",
body:
payload.masterSummary?.trim() ||
summarizeDispatchExecutionReply(payload.rawThreadReply, threadTitle),
kind: "text",
});
} else {
masterSummary = pushProjectLedgerMessage(state, payload.groupProjectId, {
sender: "ops",
senderLabel: "主 Agent Relay",
body: `${threadTitle} 执行失败,请稍后重试。`,
kind: "text",
});
}
execution.status = payload.status;
execution.completedAt = nowIso();
execution.completedByDeviceId = payload.completedByDeviceId;
execution.resultMessageId = mirroredResult?.id ?? execution.resultMessageId;
return {
execution: { ...execution },
mirroredResult,
masterSummary,
};
}
export async function appendDispatchExecutionResult(payload: {
dispatchExecutionId: string;
completedByDeviceId: string;
status: "completed" | "failed";
groupProjectId: string;
targetProjectId: string;
targetThreadId: string;
targetThreadDisplayName?: string;
rawThreadReply?: string;
masterSummary?: string;
}) {
const result = await mutateState((state) =>
appendDispatchExecutionResultInState(state, payload),
);
publishBossEvent("project.messages.updated", { projectId: payload.groupProjectId });
publishBossEvent("conversation.updated", { projectId: payload.groupProjectId });
return result;
}
export async function getMasterAgentTask(taskId: string) {
const state = await readState();
return state.masterAgentTasks.find((item) => item.taskId === taskId) ?? null;
}
export async function reassignMasterAgentTaskExecution(payload: {
taskId: string;
deviceId: string;
accountId?: string;
accountLabel?: string;
executionPrompt?: string;
}) {
const task = await mutateState((state) => {
const next = state.masterAgentTasks.find((item) => item.taskId === payload.taskId);
if (!next) {
throw new Error("MASTER_AGENT_TASK_NOT_FOUND");
}
if (next.status !== "queued") {
return { ...next };
}
next.deviceId = payload.deviceId;
next.accountId = payload.accountId;
next.accountLabel = payload.accountLabel;
if (payload.executionPrompt?.trim()) {
next.executionPrompt = payload.executionPrompt.trim();
}
return { ...next };
});
publishBossEvent("master_agent.task.updated", {
taskId: task.taskId,
deviceId: task.deviceId,
status: task.status,
});
return task;
}
export async function claimNextMasterAgentTask(deviceId: string) {
let attachmentProjectId: string | undefined;
let dispatchExecutionProjectId: string | undefined;
const task = await mutateState((state) => {
const next = state.masterAgentTasks.find(
(item) => item.deviceId === deviceId && item.status === "queued",
);
if (!next) return null;
next.status = "running";
next.claimedAt = nowIso();
if (next.taskType === "attachment_analysis" && next.attachmentId) {
const project = state.projects.find((item) => item.id === next.projectId);
const match = project ? findProjectAttachment(project, next.attachmentId) : null;
if (match) {
match.attachment.analysisState = "processing";
match.attachment.analysisSummary = undefined;
match.attachment.analysisCardId = undefined;
attachmentProjectId = next.projectId;
}
}
if (next.taskType === "dispatch_execution" && next.dispatchExecutionId) {
const execution = state.dispatchExecutions.find(
(item) => item.executionId === next.dispatchExecutionId,
);
if (execution && execution.status === "queued") {
execution.status = "running";
dispatchExecutionProjectId = execution.groupProjectId;
}
}
return { ...next };
});
if (task) {
publishBossEvent("master_agent.task.updated", {
taskId: task.taskId,
deviceId: task.deviceId,
status: task.status,
});
if (attachmentProjectId) {
publishBossEvent("project.messages.updated", { projectId: attachmentProjectId });
publishBossEvent("conversation.updated", { projectId: attachmentProjectId });
}
if (dispatchExecutionProjectId) {
publishBossEvent("conversation.updated", { projectId: dispatchExecutionProjectId });
}
}
return task;
}
export async function completeMasterAgentTask(payload: {
taskId: string;
deviceId: string;
status: "completed" | "failed";
replyBody?: string;
errorMessage?: string;
requestId?: string;
dispatchExecutionId?: string;
targetProjectId?: string;
targetThreadId?: string;
rawThreadReply?: string;
dispatchPlan?: {
summary?: string;
targets: DispatchPlanTarget[];
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(),
});
} else if (!attachmentProjectId && payload.status === "completed" && task.replyBody) {
const isThreadConversationReply =
task.taskType === "conversation_reply" &&
task.projectId !== "master-agent" &&
Boolean(task.targetProjectId && task.targetThreadId);
if (isThreadConversationReply) {
const threadProject = state.projects.find(
(item) => item.id === (task.targetProjectId ?? task.projectId),
);
const device = state.devices.find((item) => item.id === payload.deviceId);
pushProjectLedgerMessage(state, threadProject?.id ?? task.projectId, {
sender: "device",
senderLabel:
task.targetThreadDisplayName?.trim() ||
threadProject?.threadMeta.threadDisplayName ||
device?.name ||
"线程",
body: task.replyBody,
kind: "text",
});
} else {
pushProjectLedgerMessage(state, task.projectId, {
sender: "master",
senderLabel: task.accountLabel ? `主 Agent · ${task.accountLabel}` : "主 Agent",
body: task.replyBody,
kind: "text",
});
autoCaptureMasterAgentMemoriesInState(state, {
account: task.requestedByAccount,
requestText: task.requestText,
replyText: task.replyBody,
sourceMessageId: task.requestMessageId,
});
}
} else if (!attachmentProjectId && payload.status === "failed") {
const isThreadConversationReply =
task.taskType === "conversation_reply" &&
task.projectId !== "master-agent" &&
Boolean(task.targetProjectId && task.targetThreadId);
pushProjectLedgerMessage(state, task.projectId, {
sender: "ops",
senderLabel: isThreadConversationReply
? "线程执行失败"
: task.accountLabel
? `主 Agent Relay · ${task.accountLabel}`
: "主 Agent Relay",
body: isThreadConversationReply
? `${task.targetThreadDisplayName ?? "当前线程"} 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`
: `Master Codex Node 执行失败:${task.errorMessage ?? "UNKNOWN_ERROR"}`,
kind: "text",
});
}
return {
...task,
dispatchPlan: createdDispatchPlan ? { ...createdDispatchPlan } : undefined,
dispatchExecution: dispatchExecutionResult?.execution,
};
});
publishBossEvent("master_agent.task.updated", {
taskId: result.taskId,
deviceId: result.deviceId,
status: result.status,
});
publishBossEvent("project.messages.updated", { projectId: result.projectId });
publishBossEvent("conversation.updated", { projectId: result.projectId });
return result;
}
export async function recordVerificationDelivery(
account: string,
purpose: VerificationCode["purpose"],
status: VerificationDispatch["status"],
note: string,
) {
return mutateState((state) => {
recordVerificationDispatch(state, account, purpose, getVerificationDeliveryMode(), status, note);
});
}
export async function registerAccount(account: string, password: string, code: string) {
await mutateState((state) => {
if (!consumeVerificationCode(state, account, "register", code)) {
throw new Error("INVALID_VERIFICATION_CODE");
}
if (state.authAccounts.some((item) => item.account === account)) {
throw new Error("ACCOUNT_ALREADY_EXISTS");
}
state.authAccounts.push({
id: `account-${slugify(account)}`,
account,
passwordHash: hashPassword(password),
displayName: account,
role: "member",
verificationEmail: isLikelyEmailAccount(account) ? account : undefined,
createdAt: nowIso(),
updatedAt: nowIso(),
});
});
}
export async function loginAccount(params: {
account: string;
password?: string;
code?: string;
method?: LoginMethod;
}) {
return mutateState((state) => {
const method = params.method ?? (params.password?.trim() ? "password" : "code");
const existing = state.authAccounts.find((item) => item.account === params.account);
if (!existing) {
throw new Error("ACCOUNT_NOT_FOUND");
}
if (existing.lockedUntil && new Date(existing.lockedUntil).getTime() > Date.now()) {
throw new Error("LOGIN_TEMPORARILY_LOCKED");
}
if (method === "password") {
if (!params.password?.trim()) {
throw new Error("PASSWORD_REQUIRED");
}
if (!verifyPasswordHash(params.password, existing.passwordHash)) {
registerLoginFailure(existing);
throw new Error("INVALID_ACCOUNT_OR_PASSWORD");
}
if (!existing.passwordHash.startsWith("scrypt$")) {
existing.passwordHash = hashPassword(params.password);
}
} else {
if (!params.code?.trim()) {
throw new Error("VERIFICATION_CODE_REQUIRED");
}
const directFixedCode = shouldAcceptDirectFixedVerificationCode("login", params.code);
if (!directFixedCode && !consumeVerificationCode(state, params.account, "login", params.code)) {
registerLoginFailure(existing);
throw new Error("INVALID_VERIFICATION_CODE");
}
}
clearLoginFailure(existing);
existing.updatedAt = nowIso();
existing.lastLoginAt = nowIso();
existing.lastLoginMethod = method;
const session = {
sessionId: randomToken("session"),
sessionToken: randomBytes(24).toString("hex"),
restoreToken: randomBytes(24).toString("hex"),
account: existing.account,
role: existing.role,
displayName: existing.displayName,
loginMethod: method,
createdAt: nowIso(),
expiresAt: new Date(Date.now() + AUTH_SESSION_TTL_MS).toISOString(),
lastSeenAt: nowIso(),
} satisfies AuthSession;
state.authSessions = [
session,
...state.authSessions.filter((item) => item.account !== existing.account),
].slice(0, 20);
return {
account: existing.account,
role: existing.role,
displayName: existing.displayName,
loginMethod: method,
sessionToken: session.sessionToken,
restoreToken: session.restoreToken,
sessionExpiresAt: session.expiresAt,
};
});
}
export async function resetAccountPassword(account: string, password: string, code: string) {
await mutateState((state) => {
if (!consumeVerificationCode(state, account, "forgot-password", code)) {
throw new Error("INVALID_VERIFICATION_CODE");
}
const existing = state.authAccounts.find((item) => item.account === account);
if (!existing) {
throw new Error("ACCOUNT_NOT_FOUND");
}
existing.passwordHash = hashPassword(password);
clearLoginFailure(existing);
existing.updatedAt = nowIso();
state.authSessions = state.authSessions.filter((item) => item.account !== account);
});
}
function masterActionsForSnapshot(snapshot: ThreadContextSnapshot) {
const actions = new Set<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 existingDevice = state.devices.find((item) => item.id === payload.deviceId) ?? null;
const claimedEnrollment = claimEnrollment(
state,
payload.deviceId,
payload.pairingCode,
payload.token,
);
const normalizedCandidates = ensureArray(payload.projectCandidates, []).map((candidate) =>
normalizeDeviceImportCandidate({
deviceId: payload.deviceId,
folderName: candidate.folderName,
folderRef: candidate.folderRef,
threadId: candidate.threadId,
threadDisplayName: candidate.threadDisplayName,
codexFolderRef: candidate.codexFolderRef,
codexThreadRef: candidate.codexThreadRef,
lastActiveAt: candidate.lastActiveAt ?? nowIso(),
suggestedImport: candidate.suggestedImport ?? true,
}),
);
const reportedProjectCandidates = Array.isArray(payload.projectCandidates);
const shouldAutoImportLegacyProjects = !reportedProjectCandidates && normalizedCandidates.length === 0;
let device = existingDevice;
if (!device) {
device = {
id: payload.deviceId,
name: payload.name,
avatar: payload.avatar,
account: payload.account,
source: "production",
status: payload.status,
projects: payload.projects,
quota5h: payload.quota5h,
quota7d: payload.quota7d,
lastSeenAt: nowIso(),
endpoint: payload.endpoint,
token: claimedEnrollment?.token ?? payload.token ?? randomToken("boss"),
note: claimedEnrollment?.note,
};
state.devices.push(device);
} else {
if (device.token && payload.token && device.token !== payload.token && !claimedEnrollment) {
throw new Error("DEVICE_TOKEN_MISMATCH");
}
device.name = payload.name;
device.avatar = payload.avatar;
device.account = payload.account;
device.source = "production";
device.status = payload.status;
device.projects = payload.projects;
device.quota5h = payload.quota5h;
device.quota7d = payload.quota7d;
device.lastSeenAt = nowIso();
device.endpoint = payload.endpoint ?? device.endpoint;
device.token = claimedEnrollment?.token ?? payload.token ?? device.token;
}
if (shouldAutoImportLegacyProjects) {
for (const projectName of payload.projects) {
const existing = state.projects.find((item) => item.name === projectName);
if (!existing) {
state.projects.push(
normalizeProject({
id: slugify(projectName),
name: projectName,
pinned: false,
deviceIds: [payload.deviceId],
preview: `${payload.name} 已自动上报项目文件夹`,
updatedAt: nowIso(),
lastMessageAt: nowIso(),
isGroup: false,
unreadCount: 0,
riskLevel: "low",
contextBudgetPct: 80,
contextBudgetLabel: "80%",
messages: [
{
id: randomToken("auto"),
sender: "device",
senderLabel: payload.name,
body: `本机发现新的项目目录:${projectName}`,
sentAt: nowIso(),
kind: "text",
},
],
goals: [],
versions: [],
}),
);
} else if (!existing.deviceIds.includes(payload.deviceId)) {
existing.deviceIds.push(payload.deviceId);
existing.isGroup = existing.deviceIds.length > 1;
}
}
}
let draft = upsertDeviceImportDraftFromHeartbeat(state, {
deviceId: payload.deviceId,
enrollmentId: claimedEnrollment?.enrollmentId,
candidates: normalizedCandidates,
});
if (
draft &&
shouldAutoSyncHeartbeatCandidates({
wasExistingDevice: Boolean(existingDevice),
device,
claimedEnrollment,
draft,
})
) {
const autoSyncDraft = draft;
const selectedCandidateIds = resolveAutoSyncCandidateIds(autoSyncDraft);
if (selectedCandidateIds.length > 0) {
autoSyncDraft.selectedCandidateIds = selectedCandidateIds;
autoSyncDraft.status = "pending_resolution";
autoSyncDraft.updatedAt = nowIso();
autoSyncDraft.reviewedAt = undefined;
autoSyncDraft.reviewedBy = undefined;
autoSyncDraft.resolutionId = undefined;
state.deviceImportResolutions = state.deviceImportResolutions.filter(
(item) => item.draftId !== autoSyncDraft.draftId,
);
const selectedCandidates = autoSyncDraft.candidates.filter((candidate) =>
autoSyncDraft.selectedCandidateIds.includes(candidate.candidateId),
);
const items = selectedCandidates.map((candidate) =>
resolveDeviceImportAction(state, payload.deviceId, candidate),
);
upsertDeviceImportResolutionInState(state, {
deviceId: payload.deviceId,
reviewedBy: "system:auto_sync",
summary: summarizeDeviceImportResolution(device.name, items),
items,
draftId: autoSyncDraft.draftId,
});
const applied = applyDeviceImportResolutionInState(state, {
deviceId: payload.deviceId,
appliedBy: "system:auto_sync",
draftId: autoSyncDraft.draftId,
pruneMissingCandidates: true,
});
draft = applied.draft;
}
}
return {
device,
token: claimedEnrollment?.token ?? device.token,
pairingStatus: claimedEnrollment?.status,
importDraft: draft,
};
});
publishBossEvent("devices.updated", { deviceId: payload.deviceId });
publishBossEvent("conversation.updated", { deviceId: payload.deviceId });
return result;
}
function resolveDeviceImportAction(
state: BossState,
deviceId: string,
candidate: DeviceImportCandidate,
): DeviceImportResolutionItem {
const directMatch = state.projects.find(
(project) =>
!project.isGroup &&
((candidate.codexThreadRef && project.threadMeta.codexThreadRef === candidate.codexThreadRef) ||
project.threadMeta.threadId === candidate.threadId),
);
if (directMatch) {
return {
candidateId: candidate.candidateId,
action: "attach_existing",
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
targetProjectId: directMatch.id,
reason: `已匹配到现有会话《${directMatch.name}》,直接补充设备与线程映射。`,
};
}
const similarByFolder = state.projects.find(
(project) =>
!project.isGroup &&
project.deviceIds.includes(deviceId) &&
project.threadMeta.folderName === candidate.folderName &&
project.threadMeta.threadDisplayName === candidate.threadDisplayName,
);
if (similarByFolder) {
return {
candidateId: candidate.candidateId,
action: "attach_existing",
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
targetProjectId: similarByFolder.id,
reason: `同设备下已有同名线程《${similarByFolder.name}》,避免重复导入。`,
};
}
return {
candidateId: candidate.candidateId,
action: "create_thread_conversation",
threadDisplayName: candidate.threadDisplayName,
folderName: candidate.folderName,
reason: `建议把 ${candidate.threadDisplayName} 作为独立聊天窗口导入。`,
};
}
function summarizeDeviceImportResolution(
deviceName: string,
items: DeviceImportResolutionItem[],
) {
const createCount = items.filter((item) => item.action === "create_thread_conversation").length;
const attachCount = items.filter((item) => item.action === "attach_existing").length;
const skipCount = items.filter((item) => item.action === "skip").length;
return `${deviceName} 导入建议:新建 ${createCount} 个会话,关联 ${attachCount} 个现有会话${skipCount > 0 ? `,跳过 ${skipCount}` : ""}`;
}
function resolveAutoSyncCandidateIds(draft: DeviceImportDraft) {
const suggestedCandidateIds = draft.candidates
.filter((candidate) => candidate.suggestedImport !== false)
.map((candidate) => candidate.candidateId);
return dedupeStrings(
suggestedCandidateIds.length > 0
? suggestedCandidateIds
: draft.candidates.map((candidate) => candidate.candidateId),
);
}
function shouldAutoSyncHeartbeatCandidates(input: {
wasExistingDevice: boolean;
device: Device;
claimedEnrollment: DeviceEnrollment | null;
draft: DeviceImportDraft | null;
}) {
if (!input.wasExistingDevice) return false;
if (input.device.source !== "production") return false;
if (!input.draft || input.draft.candidates.length === 0) return false;
if (
input.claimedEnrollment?.enrollmentId &&
input.draft.enrollmentId === input.claimedEnrollment.enrollmentId
) {
return false;
}
return true;
}
export async function getLatestDeviceImportDraft(deviceId: string) {
const state = await readState();
const draft = state.deviceImportDrafts.find((item) => item.deviceId === deviceId) ?? null;
const resolution = draft?.resolutionId
? state.deviceImportResolutions.find((item) => item.resolutionId === draft.resolutionId) ?? null
: state.deviceImportResolutions.find((item) => item.deviceId === deviceId) ?? null;
const reviewTask = draft
? state.masterAgentTasks.find(
(item) =>
item.taskType === "device_import_resolution" &&
item.deviceImportDraftId === draft.draftId,
) ?? null
: null;
return { draft, resolution, reviewTask };
}
export async function previewDeviceImportResolution(input: { deviceId: string }) {
const state = await readState();
const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId);
if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND");
if (draft.selectedCandidateIds.length === 0) {
throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED");
}
const device = state.devices.find((item) => item.id === input.deviceId);
if (!device) throw new Error("DEVICE_NOT_FOUND");
const selectedCandidates = draft.candidates.filter((candidate) =>
draft.selectedCandidateIds.includes(candidate.candidateId),
);
const items = selectedCandidates.map((candidate) =>
resolveDeviceImportAction(state, input.deviceId, candidate),
);
return {
draft: { ...draft },
device: { ...device },
items,
summary: summarizeDeviceImportResolution(device.name, items),
};
}
function upsertDeviceImportResolutionInState(
state: BossState,
input: {
deviceId: string;
reviewedBy: string;
summary: string;
items: DeviceImportResolutionItem[];
draftId?: string;
},
) {
const draft =
state.deviceImportDrafts.find(
(item) => item.draftId === input.draftId || item.deviceId === input.deviceId,
) ?? null;
if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND");
if (draft.selectedCandidateIds.length === 0) {
throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED");
}
const existingResolution = state.deviceImportResolutions.find((item) => item.draftId === draft.draftId);
const resolution = normalizeDeviceImportResolution({
resolutionId: existingResolution?.resolutionId ?? draft.resolutionId ?? randomToken("import-resolution"),
draftId: draft.draftId,
deviceId: input.deviceId,
status: "ready",
summary: input.summary,
items: input.items,
createdAt: existingResolution?.createdAt ?? nowIso(),
});
draft.status = "resolved";
draft.updatedAt = nowIso();
draft.reviewedAt = nowIso();
draft.reviewedBy = input.reviewedBy;
draft.resolutionId = resolution.resolutionId;
draft.appliedProjectNames = [];
state.deviceImportResolutions = [
resolution,
...state.deviceImportResolutions.filter((item) => item.draftId !== draft.draftId),
];
return { draft: { ...draft }, resolution };
}
export async function selectDeviceImportCandidates(input: {
deviceId: string;
selectedCandidateIds: string[];
selectedBy: string;
}) {
const draft = await mutateState((state) => {
const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId);
if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND");
const availableCandidateIds = new Set(draft.candidates.map((item) => item.candidateId));
const nextSelected = dedupeStrings(input.selectedCandidateIds).filter((candidateId) =>
availableCandidateIds.has(candidateId),
);
draft.selectedCandidateIds = nextSelected;
draft.status = nextSelected.length > 0 ? "pending_resolution" : "pending_selection";
draft.appliedProjectNames = [];
draft.updatedAt = nowIso();
draft.reviewedBy = input.selectedBy;
draft.reviewedAt = undefined;
draft.resolutionId = undefined;
state.deviceImportResolutions = state.deviceImportResolutions.filter(
(item) => item.draftId !== draft.draftId,
);
return { ...draft };
});
publishBossEvent("devices.updated", { deviceId: input.deviceId });
return draft;
}
export async function resolveDeviceImportDraft(input: {
deviceId: string;
reviewedBy: string;
}) {
const result = await mutateState((state) => {
const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId);
if (!draft) throw new Error("DEVICE_IMPORT_DRAFT_NOT_FOUND");
if (draft.selectedCandidateIds.length === 0) {
throw new Error("DEVICE_IMPORT_SELECTION_REQUIRED");
}
const device = state.devices.find((item) => item.id === input.deviceId);
if (!device) throw new Error("DEVICE_NOT_FOUND");
const selectedCandidates = draft.candidates.filter((candidate) =>
draft.selectedCandidateIds.includes(candidate.candidateId),
);
const items = selectedCandidates.map((candidate) =>
resolveDeviceImportAction(state, input.deviceId, candidate),
);
return upsertDeviceImportResolutionInState(state, {
deviceId: input.deviceId,
reviewedBy: input.reviewedBy,
summary: summarizeDeviceImportResolution(device.name, items),
items,
draftId: draft.draftId,
});
});
publishBossEvent("devices.updated", { deviceId: input.deviceId });
publishBossEvent("conversation.updated", { deviceId: input.deviceId });
return result;
}
function parseDeviceImportResolutionReply(
state: BossState,
draft: DeviceImportDraft,
replyBody: string,
) {
const trimmed = replyBody.trim();
if (!trimmed) {
throw new Error("DEVICE_IMPORT_RESOLUTION_REPLY_REQUIRED");
}
const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
const jsonCandidate = fencedMatch?.[1]?.trim() ?? trimmed;
let parsed:
| {
summary?: string;
items?: Array<{
candidateId?: string;
action?: DeviceImportResolutionItem["action"];
targetProjectId?: string;
reason?: string;
}>;
}
| null = null;
try {
parsed = JSON.parse(jsonCandidate);
} catch {
throw new Error("DEVICE_IMPORT_RESOLUTION_JSON_INVALID");
}
const selectedCandidates = draft.candidates.filter((candidate) =>
draft.selectedCandidateIds.includes(candidate.candidateId),
);
const candidateMap = new Map(selectedCandidates.map((candidate) => [candidate.candidateId, candidate]));
const seenCandidateIds = new Set<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", "boss-console", "audit-collab"]);
state.projects = state.projects.filter((project) => {
if (reservedProjectIds.has(project.id)) return true;
if (project.isGroup) return true;
if (!project.createdByAgent) return true;
if (!project.deviceIds.includes(device.id)) return true;
if (project.deviceIds.length !== 1) return true;
if (!trimToDefined(project.threadMeta.codexFolderRef) && !trimToDefined(project.threadMeta.folderName)) {
return true;
}
return activeSignatures.has(projectThreadSignature(project));
});
}
function applyDeviceImportResolutionInState(
state: BossState,
input: {
deviceId: string;
appliedBy: string;
draftId?: string;
pruneMissingCandidates?: boolean;
},
) {
const draft =
state.deviceImportDrafts.find(
(item) => item.draftId === input.draftId || item.deviceId === input.deviceId,
) ?? null;
if (!draft || !draft.resolutionId) throw new Error("DEVICE_IMPORT_RESOLUTION_NOT_FOUND");
const resolution = state.deviceImportResolutions.find(
(item) => item.resolutionId === draft.resolutionId,
);
if (!resolution) throw new Error("DEVICE_IMPORT_RESOLUTION_NOT_FOUND");
const device = state.devices.find((item) => item.id === input.deviceId);
if (!device) throw new Error("DEVICE_NOT_FOUND");
if (draft.status === "applied" && resolution.status === "applied") {
const importedProjects = state.projects.filter(
(project) =>
!project.isGroup &&
project.deviceIds.includes(device.id) &&
draft.appliedProjectNames.includes(project.name),
);
return {
draft: { ...draft },
resolution: { ...resolution },
importedProjects: importedProjects.map((project) => ({ ...project })),
};
}
if (draft.status !== "resolved") {
throw new Error("DEVICE_IMPORT_RESOLUTION_STALE");
}
const selectedCandidates = draft.candidates.filter((candidate) =>
draft.selectedCandidateIds.includes(candidate.candidateId),
);
const importedProjects: Project[] = [];
for (const item of resolution.items) {
const candidate = draft.candidates.find((entry) => entry.candidateId === item.candidateId);
if (!candidate || item.action === "skip") {
continue;
}
let targetProject = item.targetProjectId
? state.projects.find((project) => project.id === item.targetProjectId)
: undefined;
if (item.action === "create_thread_conversation" && !targetProject) {
const draftProject = buildImportedThreadProject(device, candidate);
targetProject =
state.projects.find((project) => project.id === draftProject.id) ??
state.projects.find(
(project) =>
!project.isGroup &&
project.deviceIds.includes(device.id) &&
((candidate.codexThreadRef &&
project.threadMeta.codexThreadRef === candidate.codexThreadRef) ||
project.threadMeta.threadId === candidate.threadId),
);
if (!targetProject) {
targetProject = draftProject;
state.projects.unshift(targetProject);
}
} else if (item.action === "attach_existing" && !targetProject) {
continue;
}
if (!targetProject) continue;
if (!targetProject.deviceIds.includes(device.id)) {
targetProject.deviceIds.push(device.id);
}
targetProject.threadMeta.threadDisplayName = candidate.threadDisplayName;
targetProject.threadMeta.folderName = candidate.folderName;
targetProject.threadMeta.threadId = candidate.threadId;
targetProject.threadMeta.codexFolderRef = candidate.codexFolderRef ?? candidate.folderRef;
targetProject.threadMeta.codexThreadRef = candidate.codexThreadRef;
targetProject.threadMeta.updatedAt = candidate.lastActiveAt;
targetProject.preview = `已导入 ${candidate.threadDisplayName}`;
targetProject.updatedAt = nowIso();
targetProject.lastMessageAt = targetProject.updatedAt;
importedProjects.push({ ...targetProject });
}
if (input.pruneMissingCandidates) {
pruneStaleAutoImportedProjectsForDevice(state, device, selectedCandidates);
}
device.projects = dedupeStrings(
selectedCandidates.map((candidate) => candidate.folderName),
);
resolution.status = "applied";
resolution.appliedAt = nowIso();
resolution.appliedBy = input.appliedBy;
draft.status = "applied";
draft.appliedProjectNames = importedProjects.map((project) => project.name);
draft.updatedAt = nowIso();
return {
draft: { ...draft },
resolution: { ...resolution },
importedProjects,
};
}
export async function applyDeviceImportResolution(input: {
deviceId: string;
appliedBy: string;
}) {
const result = await mutateState((state) =>
applyDeviceImportResolutionInState(state, {
deviceId: input.deviceId,
appliedBy: input.appliedBy,
}),
);
publishBossEvent("devices.updated", { deviceId: input.deviceId });
publishBossEvent("conversation.updated", { deviceId: input.deviceId });
return result;
}
export async function upsertDeviceSkills(payload: {
deviceId: string;
skills: Array<{
name: string;
description?: string;
path: string;
invocation?: string;
category?: string;
}>;
}) {
const nextSkills = await mutateState((state) => {
const device = state.devices.find((item) => item.id === payload.deviceId);
if (!device) throw new Error("DEVICE_NOT_FOUND");
const syncedAt = nowIso();
const skills = payload.skills.map((skill) => ({
skillId: `${payload.deviceId}:${slugify(skill.name)}`,
deviceId: payload.deviceId,
name: skill.name,
description: skill.description?.trim() || "未提供说明",
path: skill.path,
invocation: skill.invocation?.trim() || `[$${skill.name}](${skill.path})`,
category: skill.category?.trim() || device.name,
updatedAt: syncedAt,
}));
state.deviceSkills = [
...skills,
...state.deviceSkills.filter((item) => item.deviceId !== payload.deviceId),
];
device.lastSeenAt = syncedAt;
return skills;
});
publishBossEvent("devices.skills.updated", {
deviceId: payload.deviceId,
note: `${nextSkills.length}`,
});
return nextSkills;
}
export async function appendAppLog(payload: {
deviceId: string;
projectId?: string;
level: AppLogLevel;
source: "app_client" | "local_agent";
category: string;
message: string;
detail?: string;
mirrorToMaster?: boolean;
}) {
const { entry, mirroredProjectId } = await mutateState((state) => {
const entry: AppLogEntry = {
logId: randomToken("applog"),
deviceId: payload.deviceId,
projectId: payload.projectId,
level: payload.level,
source: payload.source,
category: payload.category,
message: payload.message.trim(),
detail: payload.detail?.trim(),
mirroredToProject: Boolean(payload.mirrorToMaster),
createdAt: nowIso(),
};
state.appLogs.unshift(entry);
let mirroredProjectId: string | undefined;
if (payload.mirrorToMaster) {
const device = state.devices.find((item) => item.id === payload.deviceId);
pushProjectLedgerMessage(state, "master-agent", {
sender: payload.level === "error" ? "ops" : "device",
senderLabel: `${device?.name ?? payload.deviceId} · APP 日志`,
body: `[${payload.category}] ${payload.message}${payload.detail ? `\n${payload.detail}` : ""}`,
kind: "text",
});
if (shouldAutoReplyToMirroredLog(entry)) {
pushProjectLedgerMessage(state, "master-agent", {
sender: "master",
senderLabel: "主 Agent",
body: buildMasterAgentLogReply(state, entry),
kind: "text",
});
}
mirroredProjectId = "master-agent";
}
return { entry, mirroredProjectId };
});
publishBossEvent("app.logs.updated", {
deviceId: payload.deviceId,
projectId: payload.projectId,
note: payload.category,
});
if (mirroredProjectId) {
publishBossEvent("project.messages.updated", {
projectId: mirroredProjectId,
deviceId: payload.deviceId,
});
publishBossEvent("conversation.updated", {
projectId: mirroredProjectId,
deviceId: payload.deviceId,
});
}
return entry;
}
export async function updateConversationAction(
projectId: string,
action: "toggle_pin" | "mark_read",
) {
const project = await mutateState((state) => {
const nextProject = state.projects.find((item) => item.id === projectId);
if (!nextProject) throw new Error("PROJECT_NOT_FOUND");
if (action === "toggle_pin") {
if (nextProject.systemPinned) {
throw new Error("MASTER_PROJECT_PIN_LOCKED");
}
nextProject.pinned = !nextProject.pinned;
}
if (action === "mark_read") {
nextProject.unreadCount = 0;
}
return nextProject;
});
publishBossEvent("conversation.updated", { projectId });
return project;
}
export async function renameProjectThread(input: {
projectId: string;
threadDisplayName: string;
requestedBy: string;
}) {
const threadDisplayName = input.threadDisplayName.trim();
if (!threadDisplayName) {
throw new Error("THREAD_DISPLAY_NAME_REQUIRED");
}
const project = await mutateState((state) => {
const nextProject = state.projects.find((item) => item.id === input.projectId);
if (!nextProject) throw new Error("PROJECT_NOT_FOUND");
if (nextProject.isGroup) throw new Error("PROJECT_IS_GROUP_CHAT");
const updatedAt = nowIso();
nextProject.name = threadDisplayName;
nextProject.threadMeta.threadDisplayName = threadDisplayName;
nextProject.threadMeta.updatedAt = updatedAt;
nextProject.updatedAt = updatedAt;
return nextProject;
});
publishBossEvent("conversation.updated", {
projectId: input.projectId,
note: `renamed by ${input.requestedBy}`,
});
return project;
}
export async function renameGroupChat(input: {
projectId: string;
name: string;
requestedBy: string;
}) {
const name = input.name.trim();
if (!name) {
throw new Error("GROUP_CHAT_NAME_REQUIRED");
}
const project = await mutateState((state) => {
const nextProject = state.projects.find((item) => item.id === input.projectId);
if (!nextProject) throw new Error("PROJECT_NOT_FOUND");
if (!nextProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT");
const updatedAt = nowIso();
nextProject.name = name;
nextProject.threadMeta.threadDisplayName = name;
nextProject.threadMeta.updatedAt = updatedAt;
nextProject.updatedAt = updatedAt;
return nextProject;
});
publishBossEvent("conversation.updated", {
projectId: input.projectId,
note: `renamed by ${input.requestedBy}`,
});
return project;
}
export async function createProjectGroupChat(input: {
sourceProjectId: string;
memberProjectIds: string[];
createdBy: string;
}) {
const project = await mutateState((state) => {
const source = state.projects.find((item) => item.id === input.sourceProjectId);
if (!source) throw new Error("GROUP_CHAT_SOURCE_NOT_FOUND");
return createGroupChatFromProjectIds(state, {
requestedProjectIds: [input.sourceProjectId, ...input.memberProjectIds],
createdBy: input.createdBy,
defaultRiskLevel: source.riskLevel,
});
});
publishBossEvent("project.messages.updated", { projectId: project.id });
publishBossEvent("conversation.updated", { projectId: project.id });
return project;
}
export async function createIndependentGroupChat(input: {
memberProjectIds: string[];
createdBy: string;
}) {
const project = await mutateState((state) =>
createGroupChatFromProjectIds(state, {
requestedProjectIds: input.memberProjectIds,
createdBy: input.createdBy,
}),
);
publishBossEvent("project.messages.updated", { projectId: project.id });
publishBossEvent("conversation.updated", { projectId: project.id });
return project;
}
function resolveGroupChatThreadProjects(
state: BossState,
requestedProjectIds: string[],
) {
const memberProjects: Project[] = [];
const seenProjectIds = new Set<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 message = await mutateState((state) => {
const project = state.projects.find((item) => item.id === payload.projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
const body = payload.body?.trim();
if (!body && payload.kind === "text") {
throw new Error("MESSAGE_BODY_REQUIRED");
}
if (payload.kind === "attachment" && (!payload.attachments || payload.attachments.length === 0)) {
throw new Error("ATTACHMENT_REQUIRED");
}
const firstAttachment = payload.attachments?.[0];
const message: Message = {
id: randomToken("msg"),
sender: payload.sender ?? "user",
senderLabel: payload.senderLabel ?? "你",
body:
body ??
(payload.kind === "attachment"
? buildAttachmentMessageBody(
firstAttachment ?? {
attachmentId: randomToken("att"),
fileName: "附件",
mimeType: "application/octet-stream",
fileSizeBytes: 0,
attachmentKind: "binary",
storageBackend: "server_file",
storagePath: "",
previewAvailable: false,
uploadedAt: nowIso(),
uploadedBy: payload.senderLabel ?? "你",
analysisState: "not_applicable",
},
)
: payload.kind === "voice_intent"
? "已提交语音转文字请求,等待主 Agent 记录语音摘要。"
: payload.kind === "image_intent"
? "已登记图片证据上传请求,等待对象存储通道接入。"
: payload.kind === "video_intent"
? "已登记视频证据上传请求,等待对象存储通道接入。"
: "已提交消息。"),
sentAt: nowIso(),
kind: payload.kind ?? "text",
attachments: payload.attachments?.map((attachment) => normalizeMessageAttachment(attachment)),
};
project.messages.push(message);
project.unreadCount = 0;
project.lastMessageAt = message.sentAt;
project.preview = message.body;
return message;
});
publishBossEvent("project.messages.updated", { projectId: payload.projectId });
publishBossEvent("conversation.updated", { projectId: payload.projectId });
return message;
}
export async function appendAttachmentMessage(payload: {
projectId: string;
sender?: MessageSender;
senderLabel?: string;
attachment: MessageAttachment;
body?: string;
}) {
return appendProjectMessage({
projectId: payload.projectId,
sender: payload.sender ?? "user",
senderLabel: payload.senderLabel ?? "你",
body: payload.body ?? buildAttachmentMessageBody(payload.attachment),
kind: "attachment",
attachments: [payload.attachment],
});
}
function findProjectMessage(project: Project, messageId: string) {
return project.messages.find((message) => message.id === messageId) ?? null;
}
export function findProjectAttachment(
project: Project,
attachmentId: string,
): { message: Message; attachment: MessageAttachment } | null {
for (const message of project.messages) {
const attachment = message.attachments?.find((item) => item.attachmentId === attachmentId);
if (attachment) {
return { message, attachment };
}
}
return null;
}
export async function getProjectAttachment(projectId: string, attachmentId: string) {
const state = await readState();
const project = state.projects.find((item) => item.id === projectId);
if (!project) {
return null;
}
const match = findProjectAttachment(project, attachmentId);
if (!match) {
return null;
}
return {
project,
message: match.message,
attachment: match.attachment,
};
}
export async function getAttachmentById(attachmentId: string) {
const state = await readState();
for (const project of state.projects) {
const match = findProjectAttachment(project, attachmentId);
if (match) {
return {
project,
message: match.message,
attachment: match.attachment,
};
}
}
return null;
}
function summarizeAttachmentAnalysis(body: string) {
const compact = body.replace(/\s+/g, " ").trim();
if (!compact) {
return "附件分析已完成。";
}
return compact.length <= 120 ? compact : `${compact.slice(0, 117)}...`;
}
export async function updateAttachmentAnalysisResult(payload: {
projectId: string;
attachmentId: string;
status: Exclude<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;
});
}