feat: add attachment storage config model

This commit is contained in:
kris
2026-03-29 15:11:17 +08:00
parent e4ff24a18f
commit 4262c8fb5c

View File

@@ -20,7 +20,34 @@ export type MessageKind =
| "video_intent"
| "forward_notice"
| "forward_single"
| "forward_bundle";
| "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;
previewAvailable: boolean;
uploadedAt: string;
uploadedBy: string;
analysisState: AttachmentAnalysisState;
analysisSummary?: string;
analysisCardId?: string;
}
export interface ForwardSource {
sourceProjectId: string;
@@ -150,6 +177,23 @@ export interface Message {
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 GoalItem {
id: string;
text: string;
@@ -609,6 +653,7 @@ export interface BossState {
otaUpdateLogs: OtaUpdateLog[];
deviceSkills: DeviceSkill[];
appLogs: AppLogEntry[];
userAttachmentStorageConfigs: UserAttachmentStorageConfig[];
threadContextSnapshots: ThreadContextSnapshot[];
threadHandoffPackages: ThreadHandoffPackage[];
threadContextAlerts: ThreadContextAlert[];
@@ -1016,6 +1061,13 @@ const initialState: BossState = {
reason: "初始化默认主控身份",
},
],
userAttachmentStorageConfigs: [
{
account: PRIMARY_ADMIN_ACCOUNT,
mode: "server_file",
updatedAt: "2026-03-29T00:00:00+08:00",
},
],
masterAgentTasks: [],
otaUpdates: [
{
@@ -1847,6 +1899,33 @@ function normalizeMessage(raw: Partial<Message>): Message {
};
}
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 normalizeProject(raw: Partial<Project>, fallback?: Project): Project {
const base = fallback ?? cloneInitialState().projects[0];
const projectId = raw.id ?? base.id;
@@ -2034,6 +2113,15 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
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],
),
),
threadContextSnapshots: ensureArray(raw.threadContextSnapshots, base.threadContextSnapshots).map(
(snapshot, index) => ({
...base.threadContextSnapshots[index % base.threadContextSnapshots.length],
@@ -2573,7 +2661,12 @@ export async function readState(): Promise<BossState> {
try {
const state = normalizeState(JSON.parse(raw) as Partial<BossState>);
lastPersistedStateText = JSON.stringify(state, null, 2);
const normalizedText = JSON.stringify(state, null, 2);
lastPersistedStateText = normalizedText;
if (normalizedText !== raw) {
await fs.writeFile(dataFile, normalizedText, "utf8");
await fs.writeFile(backupFile, normalizedText, "utf8");
}
return state;
} catch {
const fallbackText =
@@ -2582,7 +2675,12 @@ export async function readState(): Promise<BossState> {
JSON.stringify(syncDerivedState(cloneInitialState()), null, 2);
await fs.writeFile(dataFile, fallbackText, "utf8");
const state = normalizeState(JSON.parse(fallbackText) as Partial<BossState>);
lastPersistedStateText = JSON.stringify(state, null, 2);
const normalizedText = JSON.stringify(state, null, 2);
lastPersistedStateText = normalizedText;
if (normalizedText !== fallbackText) {
await fs.writeFile(dataFile, normalizedText, "utf8");
await fs.writeFile(backupFile, normalizedText, "utf8");
}
return state;
}
}
@@ -2627,6 +2725,31 @@ export async function getDevice(deviceId: string) {
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;
});
}
function preferredDeviceForAccount(
state: BossState,
account: string,