Files
boss/docs/superpowers/plans/2026-03-29-chat-attachments-storage-and-ai-processing.md

29 KiB
Raw Permalink Blame History

Boss 聊天附件、双存储与 AI 处理 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 为 Boss 原生聊天链路补齐图片/视频/文件发送、默认服务器文件存储、可选阿里 OSS、统一附件消息模型、主 Agent 附件分析,以及 Web 端 我的 > 附件与存储 简化配置页。

Architecture: 保持现有 boss-state.json + Next API + BossApiClient + 原生 Android 路线,不引入新的数据库或消息队列。服务端新增统一附件存储抽象层,默认走服务器文件存储,可按用户切到阿里 OSS原生端只与统一上传/下载接口交互AI 分析统一走主 Agent 任务链。

Tech Stack: Next.js App Router、TypeScript、Node.js fs、阿里云 OSS Node SDK、原生 Android AppCompatActivity + ActivityResultContracts + HttpURLConnection、现有 boss-master-agent 队列、文件型持久化 data/boss-state.json


File Structure

需要新增的主要文件

  • src/lib/boss-attachments.ts
    • 统一附件类型推断、大小阈值判断、文件名清洗、下载响应帮助函数。
  • src/lib/boss-storage.ts
    • 定义 AttachmentStorageProvider 接口、按用户配置选择 server_file / aliyun_oss
  • src/lib/boss-storage-server-file.ts
    • 服务器本地文件存储实现。
  • src/lib/boss-storage-aliyun-oss.ts
    • 阿里 OSS 上传、签名 URL、配置校验。
  • src/app/api/v1/storage/config/route.ts
    • 当前登录用户的附件与存储配置读取/更新。
  • src/app/api/v1/storage/config/validate/route.ts
    • 阿里 OSS 配置有效性验证。
  • src/app/api/v1/projects/[projectId]/attachments/route.ts
    • 统一附件上传入口。
  • src/app/api/v1/attachments/[attachmentId]/download/route.ts
    • 统一预览/下载入口。
  • src/app/api/v1/projects/[projectId]/attachments/[attachmentId]/analyze/route.ts
    • 手动触发附件分析。
  • src/app/me/storage/page.tsx
    • Web 端 我的 > 附件与存储 页面。
  • android/app/src/test/java/com/hyzq/boss/AttachmentComposerStateTest.java
    • 原生附件入口与确认状态单测。
  • android/app/src/test/java/com/hyzq/boss/BossApiClientAttachmentTest.java
    • 原生上传/下载/手动分析 API 客户端测试。

需要修改的主要文件

  • src/lib/boss-data.ts
    • 扩展用户级存储配置、附件消息模型、分析状态、主 Agent 附件任务数据。
  • src/lib/boss-master-agent.ts
    • attachment_analysis 任务类型、附件摘要结果回写。
  • src/app/api/v1/projects/[projectId]/messages/route.ts
    • 保持文本消息主链,但允许附件分析结果回写卡片。
  • src/lib/boss-projections.ts
    • 把附件消息和分析状态投影给 Web。
  • src/components/app-ui.tsx
    • Web 会话页补附件消息展示和 我的 > 附件与存储 入口。
  • src/app/me/page.tsx
    • 增加 附件与存储 菜单入口。
  • android/app/src/main/java/com/hyzq/boss/BossApiClient.java
    • 增加附件上传、下载、手动分析调用。
  • android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java
    • 增加 + 按钮底部抽屉、图片/视频确认、文件发送、附件消息渲染与动作。
  • android/app/src/main/java/com/hyzq/boss/BossUi.java
    • 增加附件气泡 / 卡片 UI 构造。
  • android/app/src/main/res/layout/activity_project_chat.xml
    • 在输入区增加 + 和附件抽屉入口挂点。
  • android/app/src/main/AndroidManifest.xml
    • 如需文件打开/下载支持,补 provider 和系统选择权限声明。
  • android/app/build.gradle
    • 升版本号,必要时引入 Android 附件测试依赖。
  • README.md
  • docs/architecture/current_runtime_and_deploy_status_cn.md
  • docs/architecture/api_and_service_inventory_cn.md
  • docs/architecture/ai_handoff_index_cn.md

Task 1: 扩展服务端数据模型与用户级存储配置

Files:

  • Modify: src/lib/boss-data.ts

  • Test: src/lib/boss-data.ts(先用 Node 侧最小读写回归;当前仓库没有单独 Vitest/Jest先用 API 与状态读写验证)

  • Step 1: 先写 failing test 思路并用最小状态回归脚本表达预期

在终端先确认当前状态模型还没有 attachment 和用户级存储配置。先准备一个最小断言脚本草稿,后面实现完成后执行:

node - <<'EOF'
const fs = require('node:fs');
const state = JSON.parse(fs.readFileSync('data/boss-state.json', 'utf8'));
if (!state.userAttachmentStorageConfigs) {
  console.error('MISSING:userAttachmentStorageConfigs');
  process.exit(1);
}
const accountConfig = state.userAttachmentStorageConfigs.find((item) => item.account === '17600003315');
if (!accountConfig) {
  console.error('MISSING:account storage config');
  process.exit(1);
}
console.log('OK');
EOF
  • Step 2: 运行脚本,确认当前会失败

Run:

node - <<'EOF'
const fs = require('node:fs');
const state = JSON.parse(fs.readFileSync('data/boss-state.json', 'utf8'));
if (!state.userAttachmentStorageConfigs) {
  console.error('MISSING:userAttachmentStorageConfigs');
  process.exit(1);
}
EOF

Expected: FAIL提示 MISSING:userAttachmentStorageConfigs

  • Step 3: 在 boss-data.ts 增加附件类型和用户级存储配置模型

MessageKindMessage、用户配置和分析状态补成如下结构:

export type MessageKind =
  | "text"
  | "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;
  previewAvailable: boolean;
  uploadedAt: string;
  uploadedBy: string;
  analysisState: AttachmentAnalysisState;
  analysisSummary?: string;
  analysisCardId?: string;
}

export interface UserAttachmentStorageConfig {
  account: string;
  mode: "server_file" | "oss";
  ossProvider?: "aliyun_oss";
  aliyunOss?: {
    enabled: boolean;
    accessKeyId: string;
    accessKeySecretEncrypted: string;
    bucket: string;
    endpoint: string;
    region: string;
    prefix?: string;
  };
  updatedAt: string;
  validatedAt?: string;
}
  • Step 4: 给默认状态补上 server_file 配置和读写 helper

在默认状态初始化处加入:

userAttachmentStorageConfigs: [
  {
    account: "17600003315",
    mode: "server_file",
    updatedAt: nowIso(),
  },
],

并增加 helper

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;
  });
}
  • Step 5: 运行回归脚本,确认模型已经存在

Run:

node - <<'EOF'
const fs = require('node:fs');
const state = JSON.parse(fs.readFileSync('data/boss-state.json', 'utf8'));
console.log(Array.isArray(state.userAttachmentStorageConfigs) ? 'OK' : 'FAIL');
EOF

Expected: 输出 OK

  • Step 6: Commit
git add src/lib/boss-data.ts
git commit -m "feat: add attachment storage config model"

Task 2: 先把统一附件工具和服务器文件存储跑通

Files:

  • Create: src/lib/boss-attachments.ts

  • Create: src/lib/boss-storage.ts

  • Create: src/lib/boss-storage-server-file.ts

  • Create: src/app/api/v1/projects/[projectId]/attachments/route.ts

  • Create: src/app/api/v1/attachments/[attachmentId]/download/route.ts

  • Step 1: 先写 failing API 验证脚本,确认上传接口还不存在

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -o /dev/null -w '%{http_code}\n' -c "$cookie" -b "$cookie" -F "file=@README.md" http://127.0.0.1:3000/api/v1/projects/boss-console/attachments
  • Step 2: 运行脚本,确认当前是 404 或未实现失败

Run:

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -o /dev/null -w '%{http_code}\n' -c "$cookie" -b "$cookie" -F "file=@README.md" http://127.0.0.1:3000/api/v1/projects/boss-console/attachments

Expected: 不是 200,说明接口尚未实现。

  • Step 3: 在 boss-attachments.ts 实现类型归类和自动/手动分析判定
export function detectAttachmentKind(fileName: string, mimeType: string): AttachmentKind {
  if (mimeType.startsWith("image/")) return "image";
  if (mimeType.startsWith("video/")) return "video";
  if (mimeType === "application/pdf") return "pdf";
  if (mimeType.startsWith("text/")) return "text";
  if (
    mimeType.includes("officedocument") ||
    mimeType.includes("msword") ||
    mimeType.includes("spreadsheet") ||
    mimeType.includes("presentation")
  ) {
    return "office";
  }
  return "binary";
}

export function resolveAttachmentAnalysisState(kind: AttachmentKind, fileSizeBytes: number): AttachmentAnalysisState {
  const isLarge = fileSizeBytes > 20 * 1024 * 1024;
  if (isLarge) return "ready_manual";
  if (kind === "image" || kind === "pdf" || kind === "text") return "queued_auto";
  return "ready_manual";
}
  • Step 4: 在 boss-storage-server-file.ts 实现本地文件上传与下载定位
export async function storeServerFileAttachment(params: {
  account: string;
  messageId: string;
  fileName: string;
  buffer: Buffer;
}) {
  const now = new Date();
  const relativePath = path.join(
    "data",
    "uploads",
    params.account,
    String(now.getUTCFullYear()),
    String(now.getUTCMonth() + 1).padStart(2, "0"),
    `${params.messageId}-${sanitizeFileName(params.fileName)}`,
  );
  const absolutePath = path.join(resolveRuntimeRoot(), relativePath);
  await fs.mkdir(path.dirname(absolutePath), { recursive: true });
  await fs.writeFile(absolutePath, params.buffer);
  return {
    storageBackend: "server_file" as const,
    storagePath: relativePath,
  };
}
  • Step 5: 在上传 route 里先实现 server_file 主链

核心逻辑最小实现:

const form = await request.formData();
const file = form.get("file");
if (!(file instanceof File)) {
  return NextResponse.json({ ok: false, message: "FILE_REQUIRED" }, { status: 400 });
}
const bytes = Buffer.from(await file.arrayBuffer());
const attachmentId = randomToken("att");
const messageId = randomToken("msg");
const attachmentKind = detectAttachmentKind(file.name, file.type || "application/octet-stream");
const analysisState = resolveAttachmentAnalysisState(attachmentKind, bytes.byteLength);
const stored = await provider.storeAttachment(...);
const message = await appendAttachmentMessage(...);
  • Step 6: 在下载 route 里实现 server_file 流式返回
if (attachment.storageBackend === "server_file") {
  const absolutePath = path.join(resolveRuntimeRoot(), attachment.storagePath);
  const stream = createReadStream(absolutePath);
  return new NextResponse(Readable.toWeb(stream) as ReadableStream, {
    headers: {
      "Content-Type": attachment.mimeType,
      "Content-Disposition": `inline; filename="${attachment.fileName}"`,
    },
  });
}
  • Step 7: 启动本地服务并验证上传/下载通过

Run:

npm run build
npm start

再执行:

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -c "$cookie" -b "$cookie" -F "file=@README.md;type=text/plain" http://127.0.0.1:3000/api/v1/projects/boss-console/attachments

Expected: 返回 ok:true 且消息 kind=attachment

  • Step 8: Commit
git add src/lib/boss-attachments.ts src/lib/boss-storage.ts src/lib/boss-storage-server-file.ts src/app/api/v1/projects/[projectId]/attachments/route.ts src/app/api/v1/attachments/[attachmentId]/download/route.ts
git commit -m "feat: add server file attachment pipeline"

Task 3: 接入阿里 OSS 私有桶与配置校验

Files:

  • Create: src/lib/boss-storage-aliyun-oss.ts

  • Create: src/app/api/v1/storage/config/route.ts

  • Create: src/app/api/v1/storage/config/validate/route.ts

  • Modify: src/lib/boss-storage.ts

  • Modify: package.json

  • Step 1: 先写 failing config 路由验证

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -o /dev/null -w '%{http_code}\n' -c "$cookie" -b "$cookie" http://127.0.0.1:3000/api/v1/storage/config
  • Step 2: 运行,确认当前是 404

Run 同上。
Expected: 404

  • Step 3: 安装阿里 OSS SDK 并实现 provider
npm install ali-oss

boss-storage-aliyun-oss.ts 最小实现:

import OSS from "ali-oss";

export function createAliyunOssClient(config: UserAttachmentStorageConfig["aliyunOss"]) {
  if (!config?.enabled) throw new Error("ALIYUN_OSS_NOT_ENABLED");
  return new OSS({
    accessKeyId: config.accessKeyId,
    accessKeySecret: decryptStorageSecret(config.accessKeySecretEncrypted),
    bucket: config.bucket,
    endpoint: config.endpoint,
    region: config.region,
  });
}
  • Step 4: 实现 GET/PATCH /api/v1/storage/config

要求:

  • GET 返回当前用户配置
  • PATCH 接受 modeossProvideraliyunOss
  • PATCH 时对 AccessKey Secret 做加密,不明文落库

最小返回结构:

return NextResponse.json({
  ok: true,
  config: sanitizeStorageConfig(savedConfig),
});
  • Step 5: 实现 POST /api/v1/storage/config/validate

使用 OSS SDK 执行最小探针:

await client.getBucketInfo();
return NextResponse.json({ ok: true, provider: "aliyun_oss" });

失败时返回:

return NextResponse.json({ ok: false, message: normalizeStorageError(error) }, { status: 400 });
  • Step 6: 在 boss-storage.ts 中按用户配置分流到 server_file / aliyun_oss
export async function resolveAttachmentStorageProvider(account: string): Promise<AttachmentStorageProvider> {
  const config = await getAttachmentStorageConfig(account);
  if (config.mode === "oss" && config.ossProvider === "aliyun_oss") {
    return createAliyunOssStorageProvider(config);
  }
  return createServerFileStorageProvider();
}
  • Step 7: 运行接口验证

Run:

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -c "$cookie" -b "$cookie" http://127.0.0.1:3000/api/v1/storage/config

Expected: 返回默认 mode=server_file

  • Step 8: Commit
git add package.json package-lock.json src/lib/boss-storage.ts src/lib/boss-storage-aliyun-oss.ts src/app/api/v1/storage/config/route.ts src/app/api/v1/storage/config/validate/route.ts
git commit -m "feat: add aliyun oss storage config"

Task 4: 打通附件消息创建、下载元数据和主 Agent 分析任务

Files:

  • Modify: src/lib/boss-data.ts

  • Modify: src/lib/boss-master-agent.ts

  • Create: src/app/api/v1/projects/[projectId]/attachments/[attachmentId]/analyze/route.ts

  • Modify: src/app/api/v1/projects/[projectId]/attachments/route.ts

  • Step 1: 先写 failing API 行为验证,确认分析接口未实现

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -o /dev/null -w '%{http_code}\n' -c "$cookie" -b "$cookie" -X POST http://127.0.0.1:3000/api/v1/projects/boss-console/attachments/att-missing/analyze
  • Step 2: 运行,确认不是成功状态

Expected: 404/400。

  • Step 3: 在 boss-data.ts 中增加附件消息 helper

新增:

export async function appendAttachmentMessage(payload: {
  projectId: string;
  senderLabel: string;
  body: string;
  attachment: MessageAttachment;
}) {
  return appendProjectMessage({
    projectId: payload.projectId,
    senderLabel: payload.senderLabel,
    body: payload.body,
    kind: "attachment",
    attachment: payload.attachment,
  });
}

并把 appendProjectMessage 扩展为支持:

attachment?: MessageAttachment;
  • Step 4: 在 boss-master-agent.ts 增加 attachment_analysis 任务类型

最小接口:

export async function queueAttachmentAnalysisTask(params: AttachmentAnalysisTaskPayload) {
  return queueMasterAgentTask({
    taskType: "attachment_analysis",
    projectId: params.projectId,
    requestText: `请分析附件:${params.fileName}`,
    payload: params,
  });
}

并在任务完成回写时,新增:

await appendProjectMessage({
  projectId: task.projectId,
  sender: "master",
  senderLabel: "主 Agent",
  body: shortSummary,
  kind: "text",
});

await appendProjectMessage({
  projectId: task.projectId,
  sender: "master",
  senderLabel: "主 Agent",
  body: cardTitle,
  kind: "analysis_card",
});
  • Step 5: 在上传 route 中自动创建分析任务

规则:

if (attachment.analysisState === "queued_auto") {
  await queueAttachmentAnalysisTask(...);
}
  • Step 6: 实现手动分析接口
export async function POST(...) {
  const attachment = await findProjectAttachment(projectId, attachmentId);
  if (!attachment) return NextResponse.json({ ok: false, message: "ATTACHMENT_NOT_FOUND" }, { status: 404 });
  if (attachment.analysisState !== "ready_manual" && attachment.analysisState !== "failed") {
    return NextResponse.json({ ok: false, message: "ATTACHMENT_ANALYZE_NOT_ALLOWED" }, { status: 400 });
  }
  const task = await queueAttachmentAnalysisTask(...);
  return NextResponse.json({ ok: true, taskId: task.taskId });
}
  • Step 7: 验证自动/手动状态判定

Run:

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -c "$cookie" -b "$cookie" -F "file=@README.md;type=text/plain" http://127.0.0.1:3000/api/v1/projects/boss-console/attachments

Expected: 返回的附件消息 analysisState=queued_auto

  • Step 8: Commit
git add src/lib/boss-data.ts src/lib/boss-master-agent.ts src/app/api/v1/projects/[projectId]/attachments/route.ts src/app/api/v1/projects/[projectId]/attachments/[attachmentId]/analyze/route.ts
git commit -m "feat: add attachment analysis task flow"

Task 5: Web 端补 我的 > 附件与存储

Files:

  • Create: src/app/me/storage/page.tsx

  • Modify: src/app/me/page.tsx

  • Modify: src/components/app-ui.tsx

  • Modify: src/app/api/v1/settings only if existing menu projection needs an extra field (otherwise keep scope local)

  • Step 1: 先写 failing 路由访问验证

tmpdir=$(mktemp -d)
cookie="$tmpdir/cookies.txt"
curl -sS -c "$cookie" -b "$cookie" -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login >/dev/null
curl -sS -o /dev/null -w '%{http_code}\n' -c "$cookie" -b "$cookie" http://127.0.0.1:3000/me/storage
  • Step 2: 运行,确认当前不是 200

Expected: 404

  • Step 3: 创建页面并只做两层交互

page.tsx 结构最小如下:

export default async function StoragePage() {
  const config = await getAttachmentStorageConfigForSession();
  return (
    <main className="space-y-4">
      <section>
        <h1>附件与存储</h1>
        <p>默认使用服务器文件存储,也可以切到阿里 OSS</p>
      </section>
      <StorageModeCard config={config} />
      {config.mode === "oss" ? <AliyunOssForm config={config} /> : null}
    </main>
  );
}
  • Step 4: 在 我的 根页加菜单入口
<Link href="/me/storage">附件与存储</Link>

保持它与 账号与安全 / AI 账号 / 技能 / 关于 同级,继续微信式简单列表,不引入大面板。

  • Step 5: 用最小表单接 GET/PATCH/validate

至少支持:

  • 选择 服务器文件存储 / OSS

  • OSS 后只显示 阿里 OSS

  • AK / SK / Bucket / Endpoint / Region / Prefix

  • 测试并保存

  • 切回服务器文件存储

  • Step 6: 验证页面和配置链

Run:

curl -sS http://127.0.0.1:3000/api/health
curl -sS -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:3000/api/auth/login

并在浏览器打开:

http://127.0.0.1:3000/me/storage

Expected: 页面可访问,能显示默认 server_file

  • Step 7: Commit
git add src/app/me/storage/page.tsx src/app/me/page.tsx src/components/app-ui.tsx
git commit -m "feat: add attachment storage settings page"

Task 6: 原生 Android 接入附件选择、上传和消息渲染

Files:

  • Modify: android/app/src/main/java/com/hyzq/boss/BossApiClient.java

  • Modify: android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java

  • Modify: android/app/src/main/java/com/hyzq/boss/BossUi.java

  • Modify: android/app/src/main/res/layout/activity_project_chat.xml

  • Create: android/app/src/test/java/com/hyzq/boss/AttachmentComposerStateTest.java

  • Create: android/app/src/test/java/com/hyzq/boss/BossApiClientAttachmentTest.java

  • Step 1: 先写 failing 原生状态测试

AttachmentComposerStateTest.java 先写:

@Test
public void imageAndVideoRequireConfirmationButFileDoesNot() {
    assertTrue(ProjectChatUiState.requiresAttachmentConfirmation("image"));
    assertTrue(ProjectChatUiState.requiresAttachmentConfirmation("video"));
    assertFalse(ProjectChatUiState.requiresAttachmentConfirmation("file"));
}
  • Step 2: 运行测试,确认当前失败

Run:

cd android
./gradlew testDebugUnitTest --tests com.hyzq.boss.AttachmentComposerStateTest --no-daemon

Expected: FAIL提示 helper 未实现。

  • Step 3: 在 ProjectChatUiState / ProjectDetailActivity 实现附件入口状态

最小 helper

static boolean requiresAttachmentConfirmation(String sourceType) {
    return "image".equals(sourceType) || "video".equals(sourceType);
}

并在 ProjectDetailActivity 中增加:

  • 左侧 + 按钮

  • 底部抽屉容器

  • 三个入口:图片 / 视频 / 文件

  • ActivityResultLauncher<String>OpenDocument 注册器

  • Step 4: 在 BossApiClient 增加 multipart 上传

实现最小接口:

public ApiResponse uploadAttachment(String projectId, String fileName, String mimeType, byte[] bytes, String sourceType) throws Exception

以及:

public ApiResponse analyzeAttachment(String projectId, String attachmentId) throws Exception
  • Step 5: 在 BossUi 补附件消息卡片

新增:

buildAttachmentMessageCard(...)
buildAttachmentAnalysisStateChip(...)

要求:

  • 图片:缩略图占位 + 文件名 + 状态

  • 视频:封面占位 + 文件名 + 状态

  • 文件:文件图标 + 文件名 + 大小 + 状态

  • Step 6: 运行原生测试和 debug 构建

Run:

cd android
./gradlew testDebugUnitTest --tests com.hyzq.boss.AttachmentComposerStateTest --tests com.hyzq.boss.BossApiClientAttachmentTest :app:compileDebugJavaWithJavac assembleDebug --no-daemon

Expected: BUILD SUCCESSFUL。

  • Step 7: Commit
git add android/app/src/main/java/com/hyzq/boss/BossApiClient.java android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java android/app/src/main/java/com/hyzq/boss/BossUi.java android/app/src/main/res/layout/activity_project_chat.xml android/app/src/test/java/com/hyzq/boss/AttachmentComposerStateTest.java android/app/src/test/java/com/hyzq/boss/BossApiClientAttachmentTest.java
git commit -m "feat: add native chat attachment flow"

Task 7: 原生端补附件动作、分析状态和下载/预览

Files:

  • Modify: android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java

  • Modify: android/app/src/main/java/com/hyzq/boss/BossUi.java

  • Modify: android/app/src/main/AndroidManifest.xml

  • Test: android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java

  • Step 1: 先写 failing UI 测试,验证附件状态动作

ProjectDetailActivityUiTest.java 增补:

@Test
public void manualAnalysisAttachmentShowsActionChip() {
    // render 一个 analysisState=ready_manual 的 attachment message
    // 断言 UI 中出现“让 AI 分析”
}
  • Step 2: 运行测试,确认当前失败

Run:

cd android
./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityUiTest --no-daemon

Expected: FAIL。

  • Step 3: 在消息卡片中增加动作

规则:

  • queued_auto:显示“自动分析排队中”

  • processing显示“AI 分析中”

  • ready_manual:显示按钮“让 AI 分析”

  • completed:显示摘要

  • failed:显示“重试分析”

  • Step 4: 接通下载/预览行为

最小行为:

  • 图片:打开下载 URL
  • 视频:打开下载 URL
  • 文件:打开下载 URL

先用系统浏览器或下载器打开,不在这轮强做自定义预览器。

  • Step 5: 运行 UI 测试与编译

Run:

cd android
./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityUiTest :app:compileDebugJavaWithJavac assembleDebug --no-daemon

Expected: BUILD SUCCESSFUL。

  • Step 6: Commit
git add android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java android/app/src/main/java/com/hyzq/boss/BossUi.java android/app/src/main/AndroidManifest.xml android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java
git commit -m "feat: add attachment analysis states to native chat"

Task 8: 文档、联调、发包、部署

Files:

  • Modify: README.md

  • Modify: docs/architecture/current_runtime_and_deploy_status_cn.md

  • Modify: docs/architecture/api_and_service_inventory_cn.md

  • Modify: docs/architecture/ai_handoff_index_cn.md

  • Modify: android/app/build.gradle

  • Modify: public/downloads/*(如果发 release

  • Step 1: 本地完整验证

Run:

cd /Users/kris/code/boss
npm run lint
npm run build
curl -sS http://127.0.0.1:3000/api/health
curl -sS http://127.0.0.1:4317/health

Expected:

  • lint 通过

  • build 通过

  • 两个 health 都返回 ok:true

  • Step 2: Android 验证和打包

Run:

cd /Users/kris/code/boss/android
./gradlew testDebugUnitTest :app:compileDebugJavaWithJavac assembleDebug --no-daemon
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) npm run apk:release
JAVA_HOME=$(/usr/libexec/java_home) npm run aab:release

Expected:

  • Android 测试和构建成功

  • 产出新版本 APK / AAB

  • Step 3: 部署服务器并验证

Run:

cd /Users/kris/code/boss
./scripts/deploy-server.sh
"$HOME/.codex/skills/boss-server-debug/scripts/server_ssh.sh" exec "curl -sS http://127.0.0.1:3000/api/health"
curl -sS https://boss.hyzq.net/api/health

Expected:

  • 远端本机 health 正常

  • 公网 health 正常

  • Step 4: 文档同步

把以下事实写回文档:

  • 默认服务器文件存储已可用

  • 我的 > 附件与存储 已上线

  • 阿里 OSS 私有桶已接入

  • 图片 / PDF / 文本自动分析

  • 视频 / Office / 大文件手动分析

  • 原生聊天附件入口和分析状态说明

  • Step 5: Commit

git add README.md docs/architecture/current_runtime_and_deploy_status_cn.md docs/architecture/api_and_service_inventory_cn.md docs/architecture/ai_handoff_index_cn.md android/app/build.gradle public/downloads
git commit -m "chore: publish attachment storage release"

Self-Review

Spec coverage

  • 原生附件入口Task 6、Task 7 覆盖
  • 默认服务器文件存储Task 2 覆盖
  • 阿里 OSSTask 3 覆盖
  • 用户级存储配置Task 1、Task 3、Task 5 覆盖
  • 统一下载入口Task 2 覆盖
  • 自动/手动分析Task 4 覆盖
  • 主 Agent 分析回写Task 4 覆盖
  • Web 简化配置页Task 5 覆盖
  • 文档、部署、发包Task 8 覆盖

Placeholder scan

  • 没有 TODO / TBD / implement later / similar to task N
  • 每个任务都给出了具体文件、命令和最小代码形状

Type consistency

  • AttachmentStorageMode 统一使用 server_file | oss
  • OssProvider 统一使用 aliyun_oss
  • MessageKind 统一新增 attachment / analysis_card
  • AttachmentAnalysisState 统一使用 queued_auto / ready_manual / processing / completed / failed