 # 抖音 / 小红书云端 Zip 项目可插拔改造方案

  ## Summary

  基于当前仓库事实，这条主链路已经成立：

  1. 客户端初始化时调用 POST https://spb-bp1wv2oe0hvfvi98.supabase.opentrust.net/functions/v1/openclaw-employee-config
  2. 从返回的 skills[].skill.download_url 发现 zip 项目包
  3. ProjectBundleService 下载、解压、物化到本地 workspace/projects/<projectId>
  4. ProjectStoreService 把项目写入本地 inventory
  5. 专家页按 project 展示，进入项目后再使用该项目 skills
  6. ProjectExecutionRouter 在 workspace-entry / skill / chat-fallback 之间路由

  当前阻塞点不是“云端 zip 能不能下发”，而是“项目包要不要满足客户端现有项目契约”。
  按你给的真实抖音包事实，后续小红书同结构时，推荐方案是：

  - 两个项目包都补标准 project.json
  - 客户端继续沿用现有 openclaw-employee-config -> zip bundle -> project isolation 主链路
  - 客户端补少量校验和 UI/展示收口，不做“扫描 skills 自动反推整项目”的大改
  - 最终目标是：新电脑安装客户端，初始化成功后自动拉项目包，本地物化后即可在专家页选项目并跑起来

  ## Key Changes

  ### 1. 云端协议继续沿用现状，不改接口形态

  继续使用现有接口和字段，不新加 projects[]：

  - 接口：/functions/v1/openclaw-employee-config
  - 载荷来源：skills[]
  - zip 地址：skills[i].skill.download_url
  - 一个 zip 对应一个 project
  - 多项目通过多个 skills[] 项表达

  服务端交付要求固定为：

  - 抖音包和小红书包都从这个接口下发
  - 同一项目升级时，保持稳定 projectId
  - zip 变更时，保证 URL、etag、lastModified、content-length 或 configVersion 至少有一项能触发客户端重拉
  - 不要求服务端生成本地运行态文件，运行态仍由客户端本地维护

  ### 2. 项目包交付契约改为“标准 project bundle”

  后续抖音、小红书 zip 都按统一项目根结构交付：

  <project-root>/
    project.json
    AGENTS.md
    SOUL.md
    USER.md
    README.md
    skills/
    cron/            # 可选
    memory/          # 可选，客户端也会补
    output/          # 可选

  当前你给的抖音包已满足大部分条件：

  - 根目录就是项目根
  - 有 skills/
  - 有 AGENTS.md / SOUL.md / USER.md
  - 缺的是 project.json

  因此项目包侧最小改造就是：

  - 抖音包补 project.json
  - 小红书包补 project.json
  - boundSkillIds 与真实 skills/<skillId> 子目录名一致
  - defaultEntry.type 固定为 "workspace-entry"

  推荐项目包模板约束：

  - id 稳定，抖音固定 douyin，小红书固定 xhs
  - name、platform、projectType、description 填全
  - entries 至少包含一条 workspace-entry
  - 不把真实 token、账号密钥、长效凭证打进 zip
  - 所有可变运行态统一写到 memory/，避免 bundle 更新时丢失

  ### 3. 客户端主链路保留，但补 4 处必要改造

  #### 3.1 project-bundle.ts

  这里是本次最关键的改造点。

  当前事实：

  - resolveBundleMetadata() 在没有 project.json 时会自动用 zip 名推导 projectId/projectName
  - stageProjectDirectory() 会在物化时自动写一个 project.json
  - 所以今天“缺 project.json”不会彻底拦住安装，但会把项目识别退化成“按 zip 名猜”，这不适合长期可插拔交付

  改造要求：

  - 安装后显式校验项目根必须存在 project.json
  - 根目录模式和 projects/<id>/ 模式都支持，但都必须最终拿到标准 project.json
  - project.json 缺失时：
      - bundle sync 标记为 error
      - startup log 记录缺失原因
      - 不把该 bundle 写入 ready project
  - 保留现有 shared skills/cron 物化逻辑
  - 保留现有 bundle replace / rollback / preserve local state 逻辑

  这样可以保证以后“所有云端项目都按一个标准接入”，而不是靠客户端猜。

  #### 3.2 project-store.ts

  这里需要把“项目识别”和“项目技能隔离”收紧。

  当前事实：

  - syncBundleProject() 已支持按 project 写入 inventory
  - listProjectSkills(projectId) 会按 boundSkillIds 过滤工作区 skills
  - 但当前还会混入 curated generic skills，这和“进入项目后只看该项目自己的 skills”不完全一致

  改造要求：

  - syncBundleProject() 读取 bundle 物化后的 project.json，把项目级字段完整写入本地记录
  - boundSkillIds 必须和实际 workspace/skills/<skillId> 交叉校验
  - 若 boundSkillIds 声明了不存在 skill：
      - 过滤掉无效项
      - 写诊断日志
      - 若过滤后为空，则项目标记为 error 或 not ready
  - 增加一个明确策略：
      - 专家项目页默认只展示本项目 boundSkillIds
      - curated generic skills 不自动混入专家项目 skills 列表
      - 如需保留通用技能，单独作为 home/global skills 展示，不算项目私有 skills

  这是满足“抖音项目里只出现抖音技能，小红书里只出现小红书技能”的关键。

  #### 3.3 project-execution-router.ts

  当前这里已经基本满足要求。

  当前事实：

  - 若 project.json.defaultEntry.type === "workspace-entry"，优先走 workspace-entry
  - 没有 project.json 时，也会因为 AGENTS.md 命中 workspace marker
  - 旧版 plugin marker 仍保留兼容

  改造要求：

  - 保持 workspace-entry 作为专家项目默认入口
  - 保持 skill 显式选择时仍可走 skill
  - 继续保留旧版 xhs plugin marker 兼容，作为迁移兜底
  - 但对新标准 bundle，不再依赖 AGENTS.md 来“猜项目入口”，而是以 project.json 为准
  - 若项目 defaultEntry 缺失或不合法：
      - 给出明确诊断
      - 不静默退化成普通 chat

  #### 3.4 UI / IPC / 专家页

  当前 UI 已有 projects、currentProjectId、setActive(projectId) 链路，改造重点是收口展示策略。

  改造要求：

  - 专家页一级列表显示 projects，不显示全局混合 skills
  - 点击专家卡片后调用 projects.setActive(projectId)
  - 进入项目后：
      - 会话隔离在 project:<projectId>:<uuid>
      - 技能面板只显示该项目 boundSkillIds
      - 上下文只注入该项目的 AGENTS.md / SOUL.md / USER.md / README.md
  - fresh install 初始化成功后：
      - 首次云端 sync 完成即刷新专家页
      - 如至少有一个 ready project，可自动把第一个项目设为 active，或保留显式选择页，但规则要固定
  - 项目状态要可见：
      - downloading
      - syncing
      - ready
      - error
      - 缺 project.json
      - skill 缺失
      - bundle 更新失败

  ### 4. 新电脑初始化必须打通的完整流程

  目标不是“开发机能跑”，而是“新电脑安装客户端后初始化即可跑项目”。这条链必须定义成标准流程：

  1. 安装客户端
  2. 用户输入或绑定 employee api_key
  3. Main 进程调用 openclaw-employee-config，action=init
  4. 从返回的 skills[].skill.download_url 中识别 zip bundle
  5. ProjectBundleService 下载并安装所有合法项目包
  6. ProjectStoreService 建立本地 project inventory、active project、默认 session
  7. 专家页显示抖音 / 小红书项目
  8. 用户进入对应专家页发自然语言请求
  9. 请求优先走该项目 workspace-entry
  10. 项目在本地隔离工作区中执行

  要把这条链在新电脑上稳定跑起来，还要补这些交付前约束：

  - bundled runtime 必须覆盖项目依赖
  - 如果抖音/小红书项目需要新 Python 依赖，不能只改 zip，必须同步扩展客户端 runtime
  - 项目首次运行所需浏览器态、登录态、调试输出、缓存全部写入 memory/
  - bundle 升级时保留 sessions.json、session-messages/、memory/

  ## Public Interfaces

  需要锁定的公开契约只有这几项：

  ### 1. 云端返回契约

  继续使用现有 openclaw-employee-config：

  - skills[].skill.file_name
  - skills[].skill.file_size
  - skills[].skill.download_url
  不新增 projects[]。
  ### 2. 项目包契约

  每个 zip 必须包含 project.json，至少包含：

  - id
  - name
  - platform
  - projectType
  - description
  - defaultEntry
  - entries
  - boundSkillIds
  - ready

  其中：

  - defaultEntry.type = "workspace-entry"
  - entries 至少包含一条 workspace-entry

  ### 3. 项目技能契约

  skills/<skillId>/SKILL.md 继续只承担 skill 级职责：

  - 展示名
  - 描述
  - 元数据
  - skill 路由辅助信息

  不承担项目级默认入口声明，不承担 project identity。

  ## Test Plan

  必须覆盖这些场景，才能算“新电脑初始化后可插拔可运行”：

  1. openclaw-employee-config 同时下发 douyin.zip 和 xhs.zip，客户端成功识别两个 bundle。
  2. 两个 zip 都含标准 project.json 时，客户端在本地生成两个独立 project。
  3. 专家页显示两个项目，切换项目只影响当前 active project，不串会话。
  4. 进入 douyin 项目后，只显示 douyin 的 boundSkillIds，不混入 xhs skills，也不混入 curated generic skills。
  5. 进入 xhs 项目后，只显示 xhs 的 boundSkillIds。
  6. 在 douyin 项目内发送“生成一个苹果抖音视频文案”，默认命中 workspace-entry。
  7. 在 xhs 项目内发送“小红书笔记文案”，默认命中 workspace-entry。
  8. zip 缺少 project.json 时，bundle sync 明确报错，专家页不显示为 ready。
  9. boundSkillIds 写错时，客户端记录诊断并标记项目异常，不崩溃。
  10. bundle 更新后，项目目录被替换，但 memory/、sessions.json、session-messages/ 保留。
  11. 云端配置移除 douyin.zip 时，本地 douyin project 和共享资源被清理，不影响 xhs。
  12. fresh install + 真正 employee key + 真正云端接口下，初始化完成后专家页可直接使用项目。

  ## Assumptions

  - 后续抖音和小红书 zip 都保持与你当前抖音包相同的根目录形态。
  - 两个项目都按“一包一个项目”交付，不把多个项目塞进一个 zip。
  - 你接受“项目识别靠 project.json，项目内能力靠 skills/*/SKILL.md”这套双层契约。
  - 这次不做“客户端扫描全部 skills 自动反推项目”的重构。
  - 若项目依赖超出当前 bundled runtime，runtime 扩容是单独任务，不纳入 zip 插件化本身