Skip to content

Latest commit

 

History

History
2437 lines (1858 loc) · 195 KB

File metadata and controls

2437 lines (1858 loc) · 195 KB

Open Design 插件与 Marketplace 规范(v1)

一句话总结: Open Design 插件把可移植的 SKILL.md 能力包装成 marketplace 可发现、一键可用的设计工作流,同时保留对现有 agent skill 生态、headless CLI 使用方式和自托管部署的兼容性。

父文档: spec.md · 同级文档: skills-protocol.md · architecture.md · agent-adapters.md · modes.md

Plugin(插件) 是 Open Design 的分发单元。Skill 描述的是 agent 可以执行的一项能力,而 Plugin 是围绕这项能力形成的可发布包:一个或多个 skills、可选 design system 引用、可选 craft 规则、可选 Claude-plugin 资产、预览、use-case query、资产文件夹,以及一个用于驱动 OD marketplace 表面的轻量机器可读 sidecar。插件始终以可移植的 SKILL.md 为锚点,因此可以不经修改地发布到现有 agent skill 生态。

兼容性承诺(扩展 skills-protocol.md): 任何包含 SKILL.md 的插件文件夹,都可以作为普通 agent skill 在 Claude Code、Cursor、Codex、Gemini CLI、OpenClaw、Hermes 等工具中运行。添加 open-design.json 只是纯增量能力:它会解锁 OD 的 marketplace 卡片、预览、一键「使用」流程、类型化 context-chip strip,但不会改变底层 skill 的运行方式。一个 repo,两种消费模式。

给读者的全局梳理

这是一套 agent 时代的插件系统,不是 Figma 时代的 UI extension system。插件不会直接挂载到 canvas 里,不拥有本地面板生命周期,也不通过一套专用 postMessage / RPC 协议和宿主应用通信。插件本质上是一组可打包的意图与上下文:用户、UI、CLI 或另一个 agent 选中它之后,OD 把它解析成 query、context chips、assets、design-system 引用、MCP/tool capabilities 和 run metadata;之后由选定的 code agent 通过 OD 统一的 project/run pipeline 消费这些信息。

最短心智模型:

  1. 插件作者发布可移植能力。 SKILL.md 仍然是可执行的 agent contract;open-design.json 增加 OD marketplace 元数据、输入字段、默认值、预览和上下文 wiring。
  2. 用户或 agent 选择一个工作流。 选择入口可以是 marketplace、首页输入框、已有 project chat、CLI,或者 CI。
  3. OD apply 插件,但插件不是 UI 进程。 Apply 返回 hydrated brief、类型化 context chips、assets 和 capability requirements;它不会启动隐藏的插件 runtime。
  4. agent 驱动生成。 daemon 创建或更新 project,启动 run,通过 SSE / CLI ND-JSON streaming events 输出过程,并记录 artifacts。
  5. UI 是协作表面。 Web/desktop UI 可以展示表单、预览、direction picker、critique panel 和 live artifacts,但同一个流程必须可以通过 od headless 完成。

Figma 时代与 agent 时代的边界

问题 Figma 时代插件假设 Open Design v1 的答案
谁消费插件? 宿主 UI runtime。 code agent 通过 OD project/run pipeline 消费。
插件需要 live UI lifecycle 吗? 通常需要:挂载 panel、监听消息、修改 document。 不需要。插件是静态文件加 manifest;活跃进程是 agent run。
是否需要插件到 app 的 RPC 协议? 经常需要。 不是 primary contract。OD 内部用 HTTP,agent 用 CLI,合适的场景用 MCP,run events 用 SSE/ND-JSON。
「Use plugin」会发生什么? 打开或运行一个 UI extension。 Hydrate query、context chips、assets、inputs 和 capability gates,然后创建或继续一次 agent run。
什么会被持久化? 宿主 document mutations。 OD project metadata、artifacts、conversation/run history 和 plugin provenance。

核心交互时序

sequenceDiagram
  participant U as 用户或 Agent
  participant S as OD 表面<br/>(Web, Desktop, CLI, CI)
  participant D as OD Daemon
  participant P as Plugin Manifest<br/>(SKILL.md + open-design.json)
  participant A as Code Agent
  participant R as Project Runtime<br/>(files + artifacts)

  U->>S: 选择插件并填写 inputs
  S->>D: apply plugin(pluginId, inputs, optional projectId)
  D->>P: 解析 skill、query template、context、assets、capabilities
  P-->>D: ApplyResult(query, contextItems, assets, requiredCapabilities)
  D-->>S: hydrated brief + chips + trust/capability prompts
  U->>S: 发送 / 确认 run
  S->>D: create project or run(pluginId, query, contextItems)
  D->>A: 用 skill + project context 启动 agent
  A->>R: 读写文件并生成 artifacts
  R-->>D: artifact 和 file updates
  D-->>S: SSE 或 ND-JSON run events
  S-->>U: live preview、questions、critique、final artifacts
Loading

不同模式下的 query 用例

模式 入口 示例 query 插件贡献什么
Marketplace 详情页 用户在插件页点击「Use」 Make a 12-slide investor deck for a Series A SaaS startup targeting enterprise design teams. Deck skill、slide craft rules、示例 assets、必填 inputs、preview samples。
首页 inline input 用户输入 brief 后选择推荐插件 Create a landing page for a new AI browser extension, use a dark neon visual direction. Landing-page skill、推荐 design system、prompt rewrite、starter assets。
Project chat follow-up 用户已经在生成好的 project 里 Turn this landing page into a launch announcement deck. 现有 project context、选中的 artifact refs、deck conversion skill、保留 brand tokens。
Headless CLI / code agent Claude Code、Cursor、Codex、CI 或脚本调用 od od run create --plugin make-a-deck --input audience=investors --input topic='AI design ops' 不打开 desktop,也得到同一套 manifest resolution、context chips 和 run events。
自托管 marketplace 团队运行私有目录 Create an internal QBR deck using the Acme design system and sales metrics CSV. 私有 plugin index、可信内部 design system、asset attachments、受限数据策略。

关键产品变化是:插件不是本地 UI addon,而是可复用的 agent workflow。 UI 组件可以和这些 workflow 协作,但消费与处理主体已经变成 agent run。

长程任务一键交付、原子管线与 devloop

OD 的核心不是「一次 prompt 一次输出」,而是 long-running design agent 任务:单个 run 通常经过 discovery → 方向选择 → 生成 → critique → 二次调优等多步,可能跨越数十分钟到数小时。插件的本质是把这条长程任务切成可发布的单元,让用户、UI、CLI 或另一个 agent 一键启动。

围绕这一点,spec 把已有的「一方 atoms」从扁平的 capability 列表升级为可被插件组装的原子管线

  • Atom(§10):OD daemon 与 first-party tools 暴露的具名能力(discovery-question-form、direction-picker、todo-write、file-read/write、research-search、media-image、live-artifact、critique-theater 等)。
  • Pipeline(§5 / §10.1):插件通过 od.pipeline 把若干 atoms 组装成有序 stages;spec 默认提供一条「discovery → plan → generate → critique」的 reference pipeline,插件可以增删、重排或循环其中任何一步。
  • Devloop(§10.2):当一条 stage 标记 repeat: true 并附带 until 终止条件(critique score、用户确认、preview 加载成功等)时,agent 基于上一轮 artifact 自动进入下一轮,直到条件满足或显式取消。
  • Generative UI(§10.3):pipeline 的某个 stage 需要人类介入(提供信息、授权、方向选择、优化确认)时,agent 触发插件预先在 manifest od.genui.surfaces[]声明过的 surface;daemon 通过 OD 原生事件流广播给所有协作面(web / desktop / CLI / 其他 code agent),并可投影成 AG-UI canonical events 供外部 client 使用。用户回应后 daemon 把答案写回 project,run 继续。Surface 的 persist 字段决定答案在 run / conversation / project 三个层级中哪个层级被记住,让多轮对话不会反复打扰用户。

一句话:插件描述「这次长程任务的 pipeline 该长什么样、需要哪些 GenUI surface 与用户协作」,daemon 提供 atoms 与 surface 总线,agent 在 pipeline 上跑 devloop,artifact 带 provenance(§11.5)记录这条长程任务跑过谁。

当前实现澄清: discovery -> plan -> generate -> critique 是 reference pipeline 形态,不是一套写死的 wizard。插件 snapshot 可以携带 od.pipeline.stages[].atoms[];daemon 解析 snapshot 后,把 active plugin block 与 active stage atom blocks 注入 system prompt,同时发出 stage events,让 agent 按 pipeline 推进。如果用户没有显式选择插件,OD 也不是启动一个通用裸 agent:Open Design 基础 designer prompt 与 discovery 规则始终存在。产品入口会在此基础上绑定合理默认值:Home 自由输入走内置隐藏的 od-default scenario,按类型创建新 project 时走对应 project kind 的 bundled scenario。od-default 是 router / task shaper;它的职责是把请求导回正常设计 pipeline,不应被理解成一个独立的「美化生成器」。

四类产品场景

场景 (od.taskKind) 用户起点 插件贡献 典型 atom 序列
new-generation 一句话 brief 或 marketplace 选品 流程 + design system 推荐 + craft + starter assets discovery → direction-picker → generate → critique
code-migration 已有 repo / 本地路径 源代码摄取 atom + design tokens 抽取 + 重写策略 + diff preview code-import → design-extract → rewrite-plan → generate → diff-review
figma-migration Figma file URL / 截图 figma-extract atom + token 映射 + 高保真 web 实现策略 figma-extract → token-map → generate → critique
tune-collab 已有 OD project 与 artifact 在已有 artifact 上做 critique-tune、品牌切换、A/B、stakeholder review direction-picker → patch-edit → critique → handoff

四类场景共享同一份 ApplyResult、同一 run pipeline、同一 artifact provenance 契约(§11.5);区别只在 inputs 形态、initial assets 与 pipeline 起点。


目录

  1. 状态
  2. 愿景
  3. 目标与非目标
  4. 兼容性矩阵
  5. 插件文件夹形态
  6. open-design.json schema
  7. open-design-marketplace.json schema
  8. 发现与安装
  9. Apply pipeline
  10. 信任与能力
  11. 一方 atoms
  12. 架构:现有 repo 要改什么
  13. CLI 表面
  14. 公网 Web 表面
  15. 发布与目录分发
  16. 部署与可移植性:Docker,任意云
  17. 分阶段实现计划
  18. 示例
  19. 风险与开放问题
  20. 为什么这是 Open Design 的重要一步
  21. Post-v1 可扩展性:artifact taxonomy、evaluators 与 production handoff
  22. 场景覆盖矩阵与交付路线图
  23. 作者扩展点:基于 v1 substrate 实现未交付场景
  24. 自举:把 OD 自己的硬流程做成一方 plugin

0. 状态

  • 写作阶段:草稿,等待评审。
  • 规划阶段锁定的默认选择(评审时可覆盖):
    • 兼容性 = wrap-then-extend。 现有 SKILL.md.claude-plugin/plugin.json repo 原样可运行;open-design.json 是增量 sidecar。
    • 信任 = 分层且来源敏感。 内置插件与官方 marketplace 插件默认 trusted;用户添加的第三方 marketplace、任意 GitHub / URL / local 插件默认 restricted,除非 marketplace 或插件被显式 trust。

1. 愿景

Open Design 变成一套 server + CLI + atomic core engine + plugin/marketplace system。产品表面发生反转:不再是「点一个按钮,填一个表单」,而是用户打开 marketplace,点击某个插件,输入框自动填入 query,并在上方注入类型化 context chips。相同的插件文件夹也同时是 Claude Code、Cursor、Codex、Gemini CLI、OpenClaw、Hermes 可消费的 agent skill,并且可以作为独立 GitHub repo 发布到:

不同目录的收录格式不同,但它们都索引 SKILL.md 形态的文件夹。只要保持 SKILL.md 作为 canonical,open-design.json 作为严格 sidecar,一个 repo 就可以不做目标目录专用改写而进入所有生态目录。

同一愿景的第二条轴线:CLI 是 Open Design 面向 agent 的 canonical API。 代码 agent(Claude Code、Cursor、Codex、OpenClaw、Hermes、企业内部 orchestrator)通过 shell 调用 od … 驱动 OD,而不是直接请求 /api/*。CLI 用稳定的子命令 contract 包装所有 server 能力:project 创建、conversation/run 生命周期、plugin apply、project 文件系统操作、design library introspection、daemon control。HTTP server 是 desktop UI 与 CLI 自身的实现细节;agent 如果直接访问 HTTP,就绕过了 contract。

第三条轴线来自第二条:OD 可以完全 headless 运行;UI 是效率层,而不是运行时依赖。 用户只有 Claude Code(或 Cursor、Codex、Gemini CLI)和已安装的 od,也能浏览 marketplace、安装插件、创建 project、拉起任务、消费产物,全流程不需要启动 desktop app。OD desktop UI 的价值类似 Cursor IDE 相对于 cursor-agent CLI:更快发现、实时 artifact preview、chat/canvas 并排、marketplace 浏览、direction-picker GUI、critique-theater 面板。这些都是同一批 primitives 之上的体验增强。每个 UI 功能都必须先能表达为 CLI 子命令或 streaming event;UI 消费这些 primitives 并添加呈现层。这个解耦由架构规则强制(§11.7)。

第四条轴线是生态覆盖与商业价值的基础:OD 是一个 Docker image,可以部署到任意云。 因为第三条轴线里的 headless mode 没有 electron、没有 GUI 依赖,一个 multi-arch container image(linux/amd64 + linux/arm64)就能在 AWS、Google Cloud、Azure、阿里云、腾讯云、华为云,或任何自托管 Kubernetes / docker-compose / k3s 环境里启动完整 daemon + CLI + web UI,不需要针对云厂商重写。自托管企业可以运行私有 marketplace;合作伙伴可以把 OD 嵌入自己的 stack;CI pipeline 可以拉起临时 OD container 来完成「为日报生成 slides」这类任务。技术 contract 见 §15。

第五条轴线是与 agent 共演进的产品形态:UI 由 agent 请求,但由产品内的受控组件渲染 (Generative UI),而不是任意 agent-authored frontend code。 长程 design agent 在跑 pipeline 的过程中,会随时需要向用户索取信息(例:figma OAuth、确认目标受众)、寻求授权(例:批准一次 high-cost media 生成)、收敛方向(例:从 3 个 critique 选项里选一个)、补内容(例:缺失的品牌资产);这些 UI 不是预制的 marketplace chip strip,而是 plugin 在 manifest 中声明、agent 在 run 中触发、daemon 用 OD 原生事件发布给前端 / CLI 渲染,并通过 AG-UI adapter 供外部 client 消费的 surface(详见 §10.3)。OD v1 提供 4 类内置 surface(form / choice / confirmation / oauth-prompt)作为最小集;plugin 自带 React 组件必须经过 genui:custom-component capability gate 与 sandbox。对应这条轴线,project 表多记录一组 GenUI surface 的 persisted state:用户做过一次的授权与确认在同一 project 的多轮对话、多次 run 之间复用,不会被反复打扰;这是「插件 = 长程任务封装」与「project = 长期工作产物」的自然落点。

2. 目标与非目标

目标

  1. 每个可运行、可分发的 OD 插件都是合法 agent skill(以 SKILL.md.claude-plugin/plugin.json 为锚点)。不 fork skill spec。
  2. 一个普通 skill 或 claude-plugin repo,只要添加可选 open-design.json sidecar,就成为 OD 插件;不改名、不改 body。
  3. 支持四类安装源:本地文件夹、GitHub repo(可带 ref/subpath)、任意 HTTPS archive,以及联邦 open-design-marketplace.json index。
  4. 一键「使用」会自动填充 brief 输入框,并在上方填充 ContextItem chips(skills、design-system、craft、assets、MCP、claude-plugin、atom)。
  5. 默认分层信任;能力 scope 是声明式、可选的。
  6. OD core engine、atomic capabilities、plugin runtime 全部可以通过 CLI 访问,因此任何 code agent 都能 headless 地驱动 Open Design。
  7. 插件即长程任务封装:每个插件覆盖四类产品场景之一(new-generation / code-migration / figma-migration / tune-collab),通过 od.pipeline 把 OD 一方 atoms 组装成有序 stages + 可选 devloop(§10)。
  8. 可复现 + 可审计:每次 apply 落一份不可变 AppliedPluginSnapshot(§8.2.1),run / artifact 通过 snapshot id 反查 plugin source;插件升级不破坏历史 run 的 prompt 还原。
  9. 同一 artifact 跨协作面流转:artifact manifest(§11.5.1)记录 plugin provenance + 各下游协作面(cli / 其他 code agent / 云 / 桌面端)的 export 与 deploy 历史,让后续二次调优、迁移、协作围绕同一 artifact 接续。
  10. Generative UI 是 plugin 一等输出:插件在 manifest 中声明 od.genui.surfaces[](§10.3),agent 运行时通过 OD 受控事件流发布 form / choice / confirmation / oauth-prompt 请求,产品 renderer 负责视觉样式;用户的回应按 persist 等级(run / conversation / project)落到 project metadata,被同 project 后续多轮对话、多次 run 复用。外部 AG-UI client 通过 adapter 消费同一条 run,而不是替换 OD 内部 renderer。

非目标(v1)

  • 替代 SKILL.md / claude-plugin spec:OD 永不 fork。
  • 托管插件二进制:OD 指向 GitHub / CDN URL;存储由发布者负责。
  • 签名/PKI 生态:能力 gating 依赖用户授权,而不是签名。
  • 一个由 Open Design SaaS 代表用户运行 agent 的 web-hosted marketplace:v1 只做 local-first / self-hostable。

3. 兼容性矩阵:什么样的文件夹对哪些系统是合法插件

存在的文件 OD 可安装 Claude Code / Cursor / Codex / Gemini CLI OpenClaw / Hermes awesome-agent-skills clawhub skills.sh
SKILL.md yes yes yes yes yes yes
.claude-plugin/plugin.json yes yes (claude) partial listable listable listable
open-design.json metadata-only no no no no no
SKILL.md + open-design.json enriched yes yes yes yes yes
.claude-plugin/... + open-design.json enriched yes (claude) partial listable listable listable
SKILL.md + .claude-plugin/... + open-design.json fully enriched yes yes yes yes yes

结论:SKILL.md 是最低共同分母。任何推荐用于分发的插件都应该包含 SKILL.md,这样它可以进入所有主流目录;再添加 open-design.json 来获得 OD 的产品表面。

仅包含 open-design.json 的文件夹在 v1 中不是可运行插件,而是 metadata-only preset:OD 可以读取它来展示市场卡片、聚合远端引用或作为未来 install stub,但它不能触发 agent run,也不能进入跨 agent catalog。od plugin doctor 必须把这种形态标记为 metadata-only,并提示作者补充 SKILL.md.claude-plugin/plugin.json 才能发布为 runnable plugin。

4. 插件文件夹形态

my-plugin/
├── SKILL.md                          # required for portability; anchors agent behavior
├── .claude-plugin/                   # optional: claude-plugin compat (commands/agents/hooks/.mcp.json)
│   └── plugin.json
├── open-design.json                  # optional sidecar — unlocks OD product surface
├── README.md                         # standard catalog readme
├── preview/                          # OD preview assets
│   ├── index.html
│   ├── poster.png
│   └── demo.mp4
├── examples/                         # sample outputs (rendered on detail page)
│   └── b2b-saas/index.html
├── assets/                           # files staged into project cwd at run time
├── skills/                           # nested skills (bundle plugins)
├── design-systems/                   # nested DESIGN.md(s)
├── craft/                            # nested craft .md(s)
└── plugins/                          # nested claude-plugins (bundle plugins)

作者规则:

  • SKILL.md body 不承载 OD 专属元数据;它保持干净、可移植。
  • open-design.json指向 SKILL.md / DESIGN.md / craft 文件;永不复制它们的正文。
  • 当前 SKILL.md frontmatter 上已有的 OD 专属 od: namespace(已在 skills-protocol.md 中描述,并在 skills/blog-post/SKILL.md 中使用)继续作为没有 open-design.json 的插件的 fallback。我们不废弃它,只在其上叠加。
  • v1 的 runnable plugin 必须至少包含 SKILL.md.claude-plugin/plugin.json 之一。open-design.json 本身不定义 agent 行为,只定义 OD 如何展示、解析和应用这些行为。

5. open-design.json — schema v1

{
  "$schema": "https://open-design.ai/schemas/plugin.v1.json",
  "specVersion": "1.0.0",
  "name": "make-a-deck",
  "title": "Make a deck",
  "title_i18n": { "en": "Make a deck", "zh-CN": "制作 Deck" },
  "version": "1.0.0",
  "description": "Generate a 12-slide investor deck from a one-line brief.",
  "description_i18n": {
    "en": "Generate a 12-slide investor deck from a one-line brief.",
    "zh-CN": "根据一句 brief 生成 12 页投资人 deck。"
  },
  "author":   { "name": "Open Design", "url": "https://open-design.ai" },
  "license":  "MIT",
  "homepage": "https://github.com/open-design/plugins/make-a-deck",
  "icon":     "./icon.svg",
  "tags":     ["deck", "marketing", "investor"],

  "compat": {
    "agentSkills":   [{ "path": "./SKILL.md" }],
    "claudePlugins": [{ "path": "./.claude-plugin/plugin.json" }]
  },

  "od": {
    "kind": "skill",
    "taskKind": "new-generation",
    "mode": "deck",
    "platform": "desktop",
    "scenario": "marketing",
    "engineRequirements": { "od": ">=0.4.0" },

    "preview": {
      "type": "html",
      "entry":  "./preview/index.html",
      "poster": "./preview/poster.png",
      "video":  "./preview/demo.mp4",
      "gif":    "./preview/demo.gif"
    },

    "useCase": {
      "query": {
        "en": "Make a 12-slide investor deck for a Series A SaaS startup targeting {{audience}} on {{topic}}.",
        "zh-CN": "为一家面向 {{audience}}、主题是 {{topic}} 的 A 轮 SaaS 初创公司制作一份 12 页投资人 deck。"
      },
      "exampleOutputs": [
        { "path": "./examples/b2b-saas/", "title": "B2B SaaS deck" }
      ]
    },

    "context": {
      "skills":         [{ "ref": "./skills/deck-skeleton" }],
      "designSystem":   { "ref": "linear-clone" },
      "craft":          ["typography", "deck-pacing"],
      "assets":         ["./assets/sample-data.csv"],
      "claudePlugins":  [{ "ref": "./plugins/code-review" }],
      "mcp": [
        { "name": "tavily", "command": "npx", "args": ["-y", "@tavily/mcp"] }
      ],
      "atoms": ["discovery-question-form", "todo-write", "research-search"]
    },

    "pipeline": {
      "stages": [
        { "id": "discovery",  "atoms": ["discovery-question-form"] },
        { "id": "plan",       "atoms": ["direction-picker", "todo-write"] },
        { "id": "generate",   "atoms": ["file-write", "live-artifact"] },
        { "id": "critique",   "atoms": ["critique-theater"], "repeat": true,
          "until": "critique.score>=4 || iterations>=3" }
      ]
    },

    "genui": {
      "surfaces": [
        {
          "id": "audience-clarify",
          "kind": "form",
          "persist": "conversation",
          "trigger": { "stageId": "discovery", "atom": "discovery-question-form" },
          "schema": {
            "type": "object",
            "required": ["audience"],
            "properties": {
              "audience": { "type": "string", "enum": ["VC", "Customer", "Internal"] },
              "tone":     { "type": "string" }
            }
          }
        },
        {
          "id": "direction-pick",
          "kind": "choice",
          "persist": "conversation",
          "trigger": { "stageId": "plan", "atom": "direction-picker" },
          "schema": {
            "type": "object",
            "required": ["direction"],
            "properties": { "direction": { "type": "string" } }
          }
        },
        {
          "id": "media-spend-approval",
          "kind": "confirmation",
          "persist": "run",
          "trigger": { "atom": "media-image" },
          "capabilitiesRequired": ["mcp"]
        }
      ]
    },

    "connectors": {
      "required": [
        { "id": "slack",  "tools": ["channels.list", "messages.search"] },
        { "id": "notion", "tools": ["pages.create", "blocks.append"] }
      ],
      "optional": [
        { "id": "google_drive", "tools": ["files.list"] }
      ]
    },

    "inputs": [
      { "name": "topic",    "label": "Topic",    "type": "string", "required": true },
      { "name": "audience", "label": "Audience", "type": "select",
        "options": ["VC", "Customer", "Internal"] }
    ],

    "capabilities": ["prompt:inject", "fs:read", "connector:slack", "connector:notion"]
  }
}

5.1 字段说明

  • compat.*:指向继承格式文件的相对路径。loader 会把它们的内容合并进 composeSystemPrompt() 组装出的 OD prompt stack。
  • specVersion:解释此 manifest 时使用的 Open Design 插件规范版本。它独立于插件 version,并会冻结到 apply snapshot,便于 replay。
  • version:插件包自身版本。只要行为、元数据、pipeline、inputs 或随包 assets 出现用户需要审计的变化,就应该 bump。
  • title_i18n / description_i18n:可选本地化展示元数据。titledescription 保持英文 fallback;UI 会按请求 locale、基础语言、英文、首个可用值的顺序解析。
  • od.kind:registry 里的分类(skill / scenario / atom / bundle)。
  • od.taskKind:四类产品场景之一(new-generation / code-migration / figma-migration / tune-collab,§1「四类产品场景」)。决定 marketplace filter、初始 inputs 模板、推荐 pipeline 起点。
  • od.preview:驱动 marketplace 卡片和详情页。entry 通过 daemon 以 sandboxed 方式服务(扩展现有 /api/skills/:id/example plumbing)。
  • od.useCase.query:一键使用时进入 brief 字段的精确文本。它可以是兼容旧 manifest 的字符串,也可以是按 BCP-47 风格 locale key 组织的文本映射(例如 { "en": "...", "zh-CN": "..." })。apply 时会依次尝试请求的 locale、基础语言、en,最后回退到第一个可用值。{{var}} placeholder 绑定到 od.inputs
  • od.context.*:用于填充输入框上方 ContextChipStrip 的类型化 chips。每一项都会编译成一个 ContextItem(§5.2)。
  • od.context.atoms无序集合——声明插件需要的 atoms。daemon 仅以默认顺序使用它们;用于不需要自定义流程的简单插件。
  • od.pipeline有序管线——插件作者显式编排 atoms 的 stages、循环、终止条件(§10.1)。当 od.pipelineod.context.atoms 同时出现时,pipeline 优先;context.atoms 仅作为 chip strip 展示。
  • od.genui.surfaces[]Generative UI 声明——agent 在 run 中可能触发的 surface 集合(§10.3)。每条 entry 的 kind 是 v1 内置的 form / choice / confirmation / oauth-prompt 之一;persist 决定回答的留存层级(run / conversation / project);trigger 把 surface 绑定到具体 stage / atom,避免 agent 滥用;schema 是 JSON Schema,daemon 据此渲染默认表单并校验回答。未在 manifest 中声明的 surface kind 不可在 run 中触发——doctor 校验保证 plugin 不会动态产生未知 UI。
  • od.connectorsConnector 依赖声明——required[] 列出 plugin 必须的 daemon-内置 connector(apps/daemon/src/connectors/;当前由 Composio 驱动),每条 { id, tools[] } 对应 catalog 里的 ConnectorCatalogDefinition.idallowedToolNames 子集;optional[] 是「有就用、没有降级」。od plugin doctor 在 install / apply 时校验:(a) 每个 idconnectorService.listAll() 里存在;(b) 每个 tools[] 都在该 connector 的 allowedToolNames 内;(c) required[].id 必须有匹配的 connector:<id> capability(§5.3 / §9)。required 中的 connector 在 apply 时若未 connect,会自动派生一条 oauth-prompt GenUI surface(§10.3.1,route: 'connector');optional 不会,但 agent 可以在 run 中显式触发。Plugin 不直接持有 OAuth token——token 仍归 <dataDir>/connectors/credentials.json 管理,plugin 只声明依赖。
  • od.inputs:详情页与 inline composer 中展示的表单字段;其值会 template 到 useCase.query 以及任何 string-valued context entry。
  • od.capabilities:声明式能力;如果一个 restricted 插件省略此字段,默认是 ['prompt:inject']

5.2 ContextItem union(TypeScript)

export type ContextItem =
  | { kind: 'skill';         id: string; label: string }
  | { kind: 'design-system'; id: string; label: string; primary?: boolean }
  | { kind: 'craft';         id: string; label: string }
  | { kind: 'asset';         path: string; label: string; mime?: string }
  | { kind: 'mcp';           name: string; label: string; command?: string }
  | { kind: 'claude-plugin'; id: string; label: string }
  | { kind: 'atom';          id: string; label: string }
  | { kind: 'plugin';        id: string; label: string };

位于新的 packages/contracts/src/plugins/context.ts(仅 TypeScript、无 runtime deps,符合根 AGENTS.md 的 contracts 边界)。

5.3 能力词表

Capability 授权后的效果
prompt:inject 把 SKILL/DESIGN/craft 内容注入 system prompt(永远允许)
fs:read 在 project create / run start 阶段把插件 assets stage 到 project cwd
fs:write 插件拥有的 post-run 写入(例如发布 artifacts)
mcp daemon 在 run start 阶段写 .mcp.json,使插件里的 MCP servers 启动
subprocess / bash 执行 Claude-plugin hooks,开启 agent-callable bash tools
network 插件脚本可以发起出站 HTTP
connector 笼统能力:允许 plugin 通过 daemon 内置 connector 子系统(apps/daemon/src/connectors/)调用任意已 connected 的 Composio tool
connector:<id> 细粒度版(推荐):仅允许调用 id 指向的单个 connector(如 connector:slack)。当 plugin 同时声明多个 connector:<id> 时,只有列出来的那些会被 tool-tokens 颁发;connector:<id> 优先级 > connector 笼统能力

能力不是孤立字符串,resolver 必须计算 隐含 capability

  • 声明 mcp 且 MCP command 不是 OD 内置 stdio tool 时,隐含需要 subprocess
  • MCP command 通过 npxuvxpipx、remote URL 或 package-manager 下载代码时,隐含需要 network
  • 声明 .claude-plugin hooks 时,隐含需要 subprocess;hook 如果读取 bundled assets,还需要 fs:read
  • bashsubprocess 是高危能力:一旦授予,插件理论上可以绕过细粒度 fs:* / network 限制。UI 和 CLI 必须把它们作为 elevated capabilities 单独确认,不得被 "Grant all low-risk" 默认勾选。
  • 声明 od.connectors.required[] 时,每个 required[].id 隐含需要 connector:<id>;resolver 在 apply 阶段把缺失的 connector:<id> 加进 capabilitiesRequired,并在 restricted plugin 上拒绝 run(exit 66 / §9.1)。
  • connector:<id> 不隐含 network:connector 调用全程经 daemon HTTP,不让 plugin 自己起网络请求。但 connector 笼统能力会被审视为 elevated(任意已 connected provider 都可触达),UI 和 CLI 必须显式确认。
  • provider credentials 不是 v1 capability;插件不能直接声明访问 ANTHROPIC_API_KEY、media provider keys 或 connector secrets。需要 credentials 的能力必须通过 OD 自己的 first-party atom / tool 间接调用。

5.4 SKILL.md frontmatter 到 PluginManifest 的映射

当插件没有 open-design.json,但 SKILL.md 已经包含 skills-protocol.md 中定义的 od: frontmatter 时,adapters/agent-skill.ts 会合成一个最小 PluginManifest。映射规则必须稳定,避免旧 skill 与新 plugin schema 出现两套语义:

SKILL.md 字段 Plugin manifest 字段 规则
name name、fallback title title 缺失时从 name humanize
description description 原样保留
od.mode od.mode 原样保留;缺失时沿用 skills-protocol.md 的 keyword inference
od.preview od.preview type / entry / reload 中仅 typeentry 进入 v1 manifest;reload 保留为 adapter metadata,不进入公共 contract
od.design_system.requires od.context.designSystem true 表示运行时使用 active project design system;sections 作为 prompt-pruning hint 保留在 resolved context 中
od.craft.requires od.context.craft slug 数组原样映射
od.inputs od.inputs stringstringintegernumberenumselectvaluesoptionsmin / max 保留为 future metadata,v1 UI 可以忽略但不得丢失
od.parameters adapter metadata v1 plugin apply 不渲染 live sliders;字段保留给 Phase 4,不进入 ApplyResult.inputs
od.outputs projectMetadata hints 用于 artifact bookkeeping 和 preview default,不作为用户可编辑 input
od.capabilities_required od.capabilities 只映射能表达的能力;未知 capability 保留为 compatWarnings[]od plugin doctor 必须提示

如果 open-design.jsonSKILL.md frontmatter 同时存在,open-design.json 优先,但 loader 必须保留 adapter 产生的 warnings。这样作者可以渐进迁移:先让旧 skill 原样可用,再逐步增加 OD marketplace 信息。

6. open-design-marketplace.json — 联邦目录

它镜像 anthropics/skills/.claude-plugin/marketplace.json 的形态,因此现有社区 catalog 只需要重命名即可复用。

{
  "$schema": "https://open-design.ai/schemas/marketplace.v1.json",
  "specVersion": "1.0.0",
  "name": "open-design-official",
  "version": "1.0.0",
  "owner":    { "name": "Open Design", "url": "https://open-design.ai" },
  "metadata": { "description": "First-party plugins", "version": "1.0.0" },
  "plugins": [
    { "name": "make-a-deck", "version": "1.0.0", "source": "github:open-design/plugins/make-a-deck", "tags": ["deck"] },
    { "name": "tweet-card",  "version": "1.0.0", "source": "https://files.../tweet-card-1.0.0.tgz",  "tags": ["marketing"] }
  ]
}

Marketplace 顶层 version 是 catalog snapshot 版本;每个 plugins[] entry 也声明被列入的插件版本。Installer 抓取后仍会校验目标文件夹自己的 open-design.json,但 registry search、审计日志和 marketplace refresh events 可以在安装前就理解 catalog 与插件版本。

可以同时存在多个 marketplaces。用户通过 od marketplace add <url> 注册额外 index(Vercel 的、OpenClaw 的 clawhub、企业私有 catalog)。默认情况下,用户添加的 marketplace 只是 discovery source,它里面的插件仍然以 restricted 安装;只有官方内置 marketplace 或用户显式执行 od marketplace add <url> --trust / od marketplace trust <id> 后,来自该 marketplace 的插件才可以默认继承 trusted

7. 发现与安装

7.1 发现层级(统一 legacy skill locations 与 plugin locations)

Priority 路径 资源形态 来源
1 <projectCwd>/.open-design/plugins/<id>/ plugin bundle 新增,与用户代码一起提交;必须显式安装到 project
2 <projectCwd>/.claude/skills/<id>/ legacy SKILL.md 沿用 skills-protocol.md 的 project-private skill 兼容路径
3 <daemonDataDir>/plugins/<id>/ plugin bundle 新增,由 od plugin install 写入 daemon data root
4 ~/.open-design/skills/<id>/ legacy SKILL.md OD canonical skill install path;可 symlink 到其它 agent
5 ~/.claude/skills/<id>/ legacy SKILL.md 外部 Claude Code / skills 工具写入的兼容路径,只读扫描
6 repo root skills/, design-systems/, craft/ bundled resources 现有一方资源,不变

冲突解决:按 normalized name / plugin id;数字越小优先级越高。legacy SKILL.md location 被 adapter 合成为 plugin record,但不会被复制到 <daemonDataDir>/plugins/,除非用户显式执行 od plugin install。这样现有 Claude skills 继续零配置可用,新 plugin bundle 也有清晰的安装根。

7.2 安装源

od plugin install ./folder
od plugin install github:owner/repo
od plugin install github:owner/repo@v1.2.0
od plugin install github:owner/repo/path/to/subfolder
od plugin install https://example.com/plugin.tar.gz
od plugin install make-a-deck                   # via configured marketplaces
od marketplace add https://.../open-design-marketplace.json

GitHub install 使用 https://codeload.github.com/owner/repo/tar.gz/<ref>,不需要 git binary,同时包含 path-traversal guard 和可配置 size cap。

8. Apply pipeline

插件系统暴露两个 apply 表面;二者调用相同 daemon endpoint,并接收相同 ApplyResult。v1 中 apply 默认是纯解析动作:它只解析 manifest、模板化 query、返回 context chips、inputs、asset refs 与 MCP specs;不会写 project cwd、不会复制 assets、不会写 .mcp.json、不会启动任何进程。真正的副作用发生在后续 POST /api/projectsPOST /api/runs,并由同一套 trust/capability gate 检查。

  • 详情页 apply(deep):用户进入 /marketplace/:id,查看 preview 和 capability checklist,然后点击「Use this plugin」。composer 被 hydrate,用户回到 Home 或 project;如果用户随后取消发送,daemon 不需要清理任何 staged 文件。
  • Inline apply(shallow,核心产品表面):Home 输入框与现有 project 内的输入框(ChatComposer)都在下方渲染一个 inline plugins rail。点击 rail 中的插件卡片会在原地应用插件,不跳转、不丢上下文。brief input 预填,输入框上方 chip strip 填充,插件的 od.inputs 表单空项显示在 input 与 Send 按钮之间。用户填几个空、微调 brief、点击 Send。这个自然语言 + 填空的交互同时驱动 project 创建和已有 project 内的任务。

两个表面共享相同 primitives(InlinePluginsRailContextChipStripPluginInputsForm)和同一个 applyPlugin() state helper。唯一差别是最终 endpoint:Home 调 POST /api/projects 创建新 project;ChatComposer 调 POST /api/runs 在当前 project 内创建新任务。

8.1 Sequence diagrams

Mode A — 详情页 apply(深入口,浏览 marketplace 后):

sequenceDiagram
  participant U as User
  participant W as Web /marketplace/:id
  participant D as Daemon
  participant FS as PluginFS
  participant H as Home /
  participant A as Agent run

  U->>W: open detail
  W->>D: GET /api/plugins/:id
  D->>FS: read manifest + preview + capability state
  D-->>W: PluginDetail
  U->>W: click "Use this plugin"
  W->>D: POST /api/plugins/:id/apply
  D->>FS: resolve od.context refs<br/>(skills, DS, craft, atoms, assets, mcp)
  D-->>W: ApplyResult { query, contextItems, assets, mcpServers, projectMetadata }
  W->>H: navigate, hydrate NewProjectPanel
  Note over H: brief = useCase.query<br/>chip strip = contextItems<br/>inputs form = od.inputs<br/>attachments = asset refs
  U->>H: fill blanks, tweak, Send
  H->>D: POST /api/projects + POST /api/runs
  D->>D: stage assets / write .mcp.json<br/>only after capability gate
  D->>A: composeSystemPrompt() with merged context
  A-->>U: streamed design output
Loading

Mode B — Inline apply(浅入口,主流程;运行在 Home 和 ChatComposer 内):

sequenceDiagram
  participant U as User
  participant Rail as InlinePluginsRail<br/>(below input)
  participant C as Composer<br/>(NewProjectPanel or ChatComposer)
  participant D as Daemon
  participant A as Agent run

  Note over C,Rail: User is staring at an input box;<br/>plugin cards listed below it
  U->>Rail: click plugin card (no navigation)
  Rail->>D: POST /api/plugins/:id/apply<br/>{ projectId? }
  D-->>Rail: ApplyResult
  Rail->>C: hydrate in place
  Note over C: brief = useCase.query<br/>chip strip = contextItems<br/>inputs form = od.inputs (visible blanks)<br/>attachments = asset refs
  U->>C: fill required blanks, edit brief, Send
  alt Home (no project yet)
    C->>D: POST /api/projects + POST /api/runs
  else Existing project
    C->>D: POST /api/runs { projectId, pluginId }
  end
  D->>D: stage assets / write .mcp.json<br/>only after capability gate
  D->>A: composeSystemPrompt() with merged context
  A-->>U: streamed design output
Loading

8.2 ApplyResult shape(新 contract)

export interface ApplyResult {
  query: string;
  contextItems: ContextItem[];
  inputs: InputFieldSpec[];
  assets: PluginAssetRef[];
  mcpServers: McpServerSpec[];
  pipeline?: PluginPipeline;
  genuiSurfaces?: GenUISurfaceSpec[];
  projectMetadata: Partial<ProjectMetadata>;
  trust: 'trusted' | 'restricted';
  capabilitiesGranted: string[];
  capabilitiesRequired: string[];
  appliedPlugin: AppliedPluginSnapshot;
}

export interface AppliedPluginSnapshot {
  snapshotId: string;
  pluginId: string;
  pluginSpecVersion: string;
  pluginVersion: string;
  manifestSourceDigest: string;
  sourceMarketplaceId?: string;
  pinnedRef?: string;
  inputs: Record<string, string | number | boolean>;
  resolvedContext: ResolvedContext;
  capabilitiesGranted: string[];
  assetsStaged: PluginAssetRef[];
  taskKind: 'new-generation' | 'code-migration' | 'figma-migration' | 'tune-collab';
  appliedAt: number;

  // §11.4 SQLite 同名列;snapshot 即使 plugin 升级后也保留 apply 时的视图,保证 replay 可复现
  connectorsRequired: PluginConnectorRef[];     // 来自 manifest od.connectors.required[]
  connectorsResolved: PluginConnectorBinding[]; // apply 时实际 connected 的 (id, accountLabel)
  mcpServers: McpServerSpec[];                  // apply 时启用的 MCP server set;与 mcpServers 字段同义但冻结到 snapshot
}

export interface PluginConnectorRef {
  id: string;          // 必须存在于 connector catalog
  tools: string[];     // 必须是 connector 的 allowedToolNames 子集
  required: boolean;   // false = 来自 od.connectors.optional[]
}

export interface PluginConnectorBinding extends PluginConnectorRef {
  accountLabel?: string;       // 取自 ConnectorDetail.accountLabel
  status: 'connected' | 'pending' | 'unavailable';
}

export interface PluginAssetRef {
  path: string;
  src: string;
  mime?: string;
  stageAt: 'project-create' | 'run-start';
}

export interface InputFieldSpec {
  name: string;
  label: string;
  type: 'string' | 'text' | 'select' | 'number' | 'boolean';
  required?: boolean;
  options?: string[];
  placeholder?: string;
  default?: string | number | boolean;
}

位于 packages/contracts/src/plugins/apply.ts,并从 packages/contracts/src/index.ts re-export。

8.2.1 Snapshot 持久化与可复现 run

appliedPlugin 不是装饰字段;它是「插件 → run」之间的契约。仅传 pluginId 不够,因为:

  • 插件可能在两次 run 之间被 od plugin update 升级。
  • 同一 pluginId 在不同 marketplace 上可能解析到不同 git SHA。
  • od.pipeline / od.context.* 中的 ref 可能指向移动的 default branch。
  • assets staging 计划与 capabilitiesGranted 必须与生成 prompt 时的视图一致。

因此 daemon 必须:

  1. Apply 时:把 hydrated manifest 与 inputs 一起 hash 成 manifestSourceDigest,连同 pluginSpecVersionpluginVersionpinnedRefsourceMarketplaceIdresolvedContextcapabilitiesGrantedassetsStagedconnectorsRequired / connectorsResolved(参考 connector 子系统当前 statusmcpServers(apply 时启用的 MCP server set) 写入 appliedPlugin,返回给 caller。
  2. Project create / run start 时:把客户端提交的 appliedPlugin(或 daemon 在 server-side 重新解析得到的 snapshot)写入 SQLite applied_plugin_snapshots 表(§11.4),并在 runs / conversations 表中以 FK 指向。
  3. Replayod run replay <runId>od plugin export <runId> 必须从 snapshot 而非 live manifest 还原 prompt 与 assets,使老 run 在插件升级后仍可复现。
  4. Audit:UI 的 ProjectView 顶端展示 snapshot id + version + digest;artifact provenance(§11.5 ArtifactManifest)通过 snapshot id 反查 plugin source。

AppliedPluginSnapshot 只在 daemon side 写入;CLI / UI 客户端只读。Plugin 升级或 marketplace ref 漂移触发 od plugin doctor 把受影响的历史 snapshot 标为 stale,但永不重写:reproducibility 优先于 freshness。

8.3 Inline od.inputs form

当 applied plugin 声明 od.inputs 时,composer 在 brief textarea 与 Send button 之间渲染 PluginInputsForm。行为如下:

  • required fields 会 gate Send(按钮 disabled,并通过 tooltip 列出缺失字段)。
  • 用户输入时,useCase.query 和任何 string-valued context entry 内的 {{var}} placeholders 会实时 re-render,用户在发送前看到最终 brief 和最终 chip labels。
  • 默认表单紧凑:短字段 inline 呈现,像搜索栏;长文本字段折叠到「Add details」expander。
  • 发送时,input values 会随 run request 一起传给 daemon;daemon 也会在 prompt 中加入一个小的 ## Plugin inputs block,让 agent 看到用户提供的 literal values,而不只是 template 后的 brief。
  • inputs 会保存在组件 state 中,直到用户清除 chip strip;在同一个 composer 里重新 apply 同一插件时,预填上次值。

8.4 在已有 project 内 apply(新 chat task)

ChatComposer(每个 project 的 conversation 输入组件,apps/web/src/components/ChatComposer.tsx)在输入框下方渲染相同 InlinePluginsRail。rail 展示「下一步做什么?」类型插件,并按当前 project 的 kind / scenario / 当前 artifacts 过滤。点击卡片时,带当前 projectIdPOST /api/plugins/:id/apply;daemon 基于 project 解析 context(跳过 project creation 时已经 pinned 的 context,例如 design system),返回 ApplyResult,其中 query 和 chips 原地 hydrate composer。Send 通过已有 POST /api/runs 创建新 run,新增可选 pluginId,因此 agent runtime 会带 plugin context 组装 prompt。

结果:同一个 project 可以被多个 plugin-driven tasks 连续引导——「先 apply make-a-deck,迭代;再 apply tweet-card,把相同 brief 包装成社交内容;再 apply critique-theater 给整套作品评分」——全程不离开 project。每个 task 是一个 chat turn;project history 就是 audit log。

8.5 Generative UI 运行时契约

apply 阶段已经把 plugin 声明的 genuiSurfaces 写进 AppliedPluginSnapshot,但真正的 surface lifecycle 发生在 run 中。完整 wire 与持久化协议见 §10.3,这里只钉住与 apply pipeline 的 4 条衔接:

  1. Apply 阶段不渲染任何 surface。 apply 仍然纯粹解析;UI 表面、CLI 表面在此时只是把 genuiSurfaces 转成「这个长程任务可能会问你这些问题」的告示卡。一旦 plugin 在 manifest 里声明了 oauth-prompt,详情页 capability checklist 上会增加一行「This plugin will ask you to authorize 」,让用户在发送前知道 run 中可能弹出的 surface 类型。
  2. Run 中 agent 通过 atom 触发 surface。 每条 surface 的 trigger.atom(与可选 trigger.stageId)是一组 allowlist:daemon 拒绝任何来自未声明 atom 的 genui_surface_request 事件——这是「未声明的 UI 不会被动态产生」的强制点(doctor + runtime 双重检查)。
  3. 同 project 已存在的答复直接复用。 当 surface 的 persistprojectconversation,daemon 在触发前先查 genui_surfaces 表(§11.4);若已有 valid 状态(未过期、未失效),直接以 stored value 通过,不向用户广播请求。这正是「插件创建项目后多轮交互、多个会话也会用到」的具体落地。
  4. 不响应 surface ≠ 阻塞 run。 每个 surface 必须声明 timeout(默认 5 分钟)与 onTimeoutabort / default / skip);CLI 在 ND-JSON 流中以 genui_surface_request event 暴露同一份 surface 描述,使 headless 自动化可以 od ui respond --surface-id … 在另一个进程中作答,或在不需要时优雅 skip(详见 §10.3)。

ApplyResult.genuiSurfacesappliedPlugin.snapshotId 一起构成 plugin 与 project 之间的 GenUI 契约:snapshot 不可变;surface answer 落进 genui_surfaces 后归 project 拥有,被同 project 后续 plugin(甚至换插件、换 conversation)按 surface.id 查找复用,前提是当前 plugin 也声明了相同 surface id 与兼容的 schema

9. 信任与能力

flowchart LR
  M[Plugin manifest] --> C{trust + capabilities}
  C -->|"prompt:inject only<br/>(restricted default)"| P1[inject SKILL.md / DESIGN.md / craft]
  C -->|"+ fs:read"| P2[stage assets into cwd]
  C -->|"+ mcp"| P3[write .mcp.json]
  C -->|"+ subprocess / bash"| P4[register claude plugin commands/hooks]
  C -->|"+ connector:&lt;id&gt;"| P5[issue scoped tool token<br/>for /api/tools/connectors/execute]
  P1 & P2 & P3 & P4 & P5 --> R[run agent]
Loading

restricted 插件永远不能进入 P3/P4/P5,除非用户授权该能力——通过 od plugin trust <id> 或详情页上的「Grant capabilities」。默认 trusted 的来源只有两类:repo bundled first-party 插件,以及 OD 官方 marketplace。用户添加的第三方 marketplace 只提供 discovery;其中插件默认仍是 restricted,除非该 marketplace 本身被显式 trust,或某个插件按 id + version + capability 单独授权。

Connector capability gate: plugin 调用 connector tool 走 daemon HTTP(/api/tools/connectors/execute,由 apps/daemon/src/tool-tokens.ts 颁发 scoped token),与 MCP 是两条不同路径。restricted plugin 即使获得 mcp capability,也不会自动获得 connector 调用能力——必须通过 connector 笼统能力或具体 connector:<id> 显式授权。daemon 在颁发 tool token 时把 applied_plugin_snapshot_id + 当前 capabilitiesGranted 写入 token;执行 /api/tools/connectors/execute 时校验 connector_id 在 granted 列表里(trusted plugin 隐含 connector:*),否则返回 403。这条 gate 的 daemon 实现见 §11.3 的 apps/daemon/src/plugins/connector-gate.ts

信任记录必须绑定 provenance,而不是只绑定名称:

  • pluginId
  • specVersion
  • version 或 resolved git SHA / archive digest
  • source marketplace id(如果有)
  • granted capabilities
  • granted at / updated at

当插件升级、resolved ref 变化或 source marketplace 变化时,高危能力(mcpsubprocessbashnetworkconnectorconnector:<id>)需要重新确认。

9.1 Headless capability grant flow(CLI / 自动化场景)

UI 上的 capability gate 是 modal + checklist;headless / CI / 第三方 code agent 则通过下面三种方式完成同一动作,其行为在 spec 里钉死,不依赖 interactive prompt:

  1. 预先 trust(推荐 hosted / CI)。

    od plugin trust make-a-deck --caps fs:read,mcp,subprocess
    od plugin trust make-a-digest --caps fs:read,connector:slack,connector:notion
    od plugin trust make-a-deck --caps all          # 等价于 manifest 中声明的全部 capability

    写入 SQLite installed_plugins.capabilities_granted,对所有后续 apply / run 生效,直到 plugin 升级或 source marketplace 变化(见 §9 provenance 规则)触发重新确认。connector:<id>--caps 里写完整 id(connector:slackconnector:notion),不接受 glob。

  2. Per-call 临时 grant

    od plugin apply make-a-deck   --project p_abc --grant-caps fs:read,mcp --json
    od plugin apply make-a-digest --project p_abc --grant-caps fs:read,connector:slack --json
    od plugin run   make-a-deck   --project p_abc --grant-caps fs:read --follow

    仅作用于当前 apply 调用产生的 AppliedPluginSnapshot,写入 snapshot.capabilitiesGranted回写到 installed_plugins

  3. 不带授权直接调用 → recoverable error。当 plugin 需要的 capability 未被任何上述路径授予时,daemon 不静默降级、不弹 prompt、不挂起:CLI 立即以 exit code 66 退出,并在 stderr 输出结构化 JSON:

    {
      "error": {
        "code": "capabilities-required",
        "message": "Plugin make-a-deck requires capabilities not yet granted.",
        "data": {
          "pluginId": "make-a-deck",
          "pluginVersion": "1.0.0",
          "required": ["mcp", "subprocess"],
          "granted": ["prompt:inject", "fs:read"],
          "remediation": [
            "od plugin trust make-a-deck --caps mcp,subprocess",
            "or pass --grant-caps mcp,subprocess to this command"
          ]
        }
      }
    }

    Code agent 读到 exit 66 时可以选择重试(带 --grant-caps)、降级、或把 remediation 文本回报给上游用户。HTTP 等价响应是 409 Conflict,body 为同样 shape;用于 desktop UI 自动构建 capability checklist。

bash / subprocess / network / connector(笼统形态,不带 :<id>)等 elevated capabilities 绝不支持 --grant-caps all shorthand 单独覆盖:CLI 强制要求显式列出每一项,避免脚本误授权。connector:<id> 是 elevated 的 scoped 形态,可以列在 --caps all 内,但 hosted operator 通常更偏好显式枚举每个 connector id 以审计。

9.2 Preview sandbox

插件 preview 可能来自未信任 GitHub / archive,因此不能与 OD app 共用同一执行权限。od.preview.entry 的 HTML 预览必须满足以下约束:

  • preview iframe 使用 sandbox="allow-scripts" 起步;默认不允许 allow-same-originallow-formsallow-popupsallow-downloads。如果某个 first-party preview 需要额外 flag,必须在 manifest 中声明并通过 od plugin doctor 标记为 elevated preview。
  • preview 通过 daemon 的只读 preview endpoint 服务,不能读取 /api/*、不能携带 Authorization header、不能访问 provider credentials、不能访问 project 文件系统。
  • preview 响应带独立 CSP:default-src 'none'; img-src 'self' data: blob:; media-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'none'; frame-ancestors 'self'。如需远程字体、图片或 analytics,v1 一律拒绝。
  • preview asset 路径必须经过 normalized relative path 检查:拒绝 absolute path、.. traversal、symlink escape、hidden credential files、超过 size cap 的资源。
  • restricted 插件的 preview 即使可渲染,也不代表插件能力已授权。预览只展示静态/前端演示;MCP、bash、network、asset staging 仍然只在 run start capability gate 后发生。

10. 一方 atoms:可被插件组装的原子管线

apps/daemon/src/prompts/system.ts 与 daemon-backed bash tools 里已有的能力提升为具名、可声明、可被插件组装的 atoms。Atom 不是孤立的 capability tag——它是 OD 长程 design agent pipeline 的一个节点,可以被插件拼接成有序 stages,并通过 devloop 在 stage 内循环。v1 仍然是声明式:daemon 已经知道如何为每个 atom 发出 prompt fragment 与必要的 tool gating,插件只是声明 pipeline 拓扑。

Atom id 当前来源 作用 taskKind 适用
discovery-question-form system.ts 中的 DISCOVERY_AND_PHILOSOPHY 面向模糊 brief 的首轮 question form new-generation, tune-collab
direction-picker 同上 final 前的 3–5 个方向选择 new-generation, tune-collab
todo-write 同上 TodoWrite 驱动的计划 all
file-read / file-write / file-edit code-agent native 文件操作 all
research-search od research searchapps/daemon/src/cli.ts Tavily web research new-generation
media-image / media-video / media-audio od media generate 带 provider config 的媒体生成 new-generation, tune-collab
live-artifact MCP mcp__live-artifacts__* 创建/刷新 live artifacts all
connector MCP mcp__connectors__* Composio connectors all
critique-theater system.ts critique addendum 5 维 panel critique;devloop 收敛信号 all
code-import (planned) tbd: repo handle ingestion clone / read 已有 repo,抽取设计相关结构 code-migration
design-extract (planned) tbd 从源代码 / Figma / 截图抽取 design tokens code-migration, figma-migration
figma-extract (planned) tbd: Figma REST + 节点遍历 抽取 Figma 节点树 + tokens + assets figma-migration
token-map (planned) tbd 把抽取出来的 tokens 映射到 active design system code-migration, figma-migration
rewrite-plan / patch-edit (planned) tbd 长程多文件改写规划与小步 patch code-migration, tune-collab
diff-review (planned) tbd 把改写以 diff 形式呈现给用户/agent 评审 code-migration, tune-collab
handoff (planned) tbd 把 artifact 推到 cli / 其他 code agent / 云 / 桌面端协作面 tune-collab

(planned) 标记代表 v1 不实现,但在 §10 的 ID namespace 与 §5 schema 里先把名字钉住,避免后续二次拆分。GET /api/atoms v1 只返回已实现 atom;planned atom 在 doctor 检查时给出明确「not yet implemented」warning 而非 unknown error。

10.1 od.pipeline:插件组装的有序原子管线

export interface PluginPipeline {
  stages: PipelineStage[];
}

export interface PipelineStage {
  id: string;
  atoms: string[];
  repeat?: boolean;
  until?: string;
  onFailure?: 'abort' | 'skip' | 'retry';
}

约束:

  • stages[*].id 在一个 pipeline 内唯一;同名 atom 可以出现在多个 stages(典型例子:critique 在 generate 之后,又在最终 handoff 之前再跑一次)。
  • 默认顺序就是数组顺序;v1 不支持 DAG 分叉(branching)——若插件需要分支,作者应拆分成多 plugin 串联。
  • until 是 daemon 解析的轻量 expression(仅支持比较运算与已知信号变量:critique.scoreiterationsuser.confirmedpreview.ok),不是任意 JS。od plugin doctor 会校验语法。
  • od.pipeline 缺省时,daemon 按 od.taskKind 选择 reference pipeline(§1「四类产品场景」中各场景列出的典型序列)。

Pipeline 是声明式的;agent 不直接读 pipeline JSON。daemon 把每个 stage 编译成 system prompt 中一个有锚点 ID 的 block,并在 stage 进入/退出时发出 pipeline_stage_started / pipeline_stage_completed SSE/ND-JSON event(与 PersistedAgentEvent discriminated union 对齐),UI 与 CLI 可以渲染进度条 / stage timeline / devloop iteration 计数。

10.2 Devloop:基于 artifact 的迭代收敛

Stage 的 repeat: true 标志将单步执行升级为循环

  1. agent 完成 stage 一次。
  2. daemon 评估 stage 的 until 条件——读取最近一轮 critique-theater 输出、live-artifact preview 状态、用户回复,或一个内置 iterations >= N 计数器。
  3. 条件未满足 → 回到 stage 起点,把上一轮 artifact 作为 input 再跑一次;条件满足 → 进入下一 stage。
  4. 用户通过 od run respond <runId> --json '{"action":"break-loop"}' 或 UI 中的「Stop refining」按钮可以随时打断。

Devloop 的两条硬约束:

  • 必须有 untilrepeat: true 但缺 until 的 pipeline 在 od plugin doctor 中报错;daemon 拒绝执行。
  • iterations 上限:daemon 强制 iterations <= 10(可通过 OD_MAX_DEVLOOP_ITERATIONS 调整),防止 plugin bug 导致无限循环烧 provider quota。

每一轮 devloop 都把当轮 artifact diff、critique 输出、消耗 tokens 写入 runs.devloop_iterations(§11.4 SQLite 扩展),用于审计与按 iteration 计费的未来商业模型。

GET /api/atoms 返回 atoms 与已知 reference pipelines。当前实现已经开始自举:一方 atom plugins 位于 plugins/_official/atoms/**,bundled scenario plugins 位于 plugins/_official/scenarios/**renderActiveStageBlock(stageId, bodies) 会把 active stage 的 atom bodies 注入 prompt。因此 system prompt 现在已经 pipeline-aware,但还不是完全 data-driven:Open Design 基础 designer prompt、discovery philosophy 和部分入口默认逻辑仍在 daemon / product code 中。这足以支撑"插件组装核心管线"这条主张,但不假装所有行为字节都已经迁到插件。

10.3 Generative UI:AG-UI–inspired surfaces

OD 接受 CopilotKit / AG-UI 协议 中有价值的部分:agent 可以在 run 中请求交互 UI。OD 允许 agent 在主产品表面自由发明 app UI 或视觉样式。v1 提供自己的 GenUISurface* discriminated union,跟现有 PersistedAgentEvent、SSE / ND-JSON 流共用通道;@open-design/agui-adapter 会把这些事件投影成 AG-UI canonical events 供外部 client 使用。

产品规则是:agent / plugin 输出的是数据,OD 掌握 renderer。 插件可以声明 formchoiceconfirmationoauth-prompt surface,带 schema 与 prompt data;web / desktop / CLI renderer 决定 layout、typography、controls、validation、accessibility 和 persistence UX。这让插件 UI 能覆盖未来场景,同时保持产品系统一致。任意视觉或代码输出属于生成的 artifact,或者必须走独立 custom-component sandbox 与 genui:custom-component capability gate;它不能替换核心协作 UI 的内置 renderer。

10.3.1 4 类内置 surface(v1)

kind 用途 默认渲染 触发方 atom(可选) 默认 persist
form 收集结构化信息(受众、品牌、目标、分辨率等) schema 渲染 JSON-Schema 驱动的表单 discovery-question-formmedia-image 等需要参数的 atom conversation
choice 让用户在 N 个选项里挑一个(方向、命题、版本) 卡片网格或单选列表 direction-pickercritique-theater conversation
confirmation 二选一确认(继续 / 取消、批准 / 拒绝) 行内 Yes/No 按钮 任何高代价 atom,例如 media-imagesubprocess 类 hook run
oauth-prompt 拉起第三方 OAuth(Figma、Notion、Slack 等) 弹窗 + 引导文案 connector / MCP 的鉴权需要 project

每个 surface 有以下 v1 字段:

export interface GenUISurfaceSpec {
  id: string;                                  // 同一 plugin 内唯一
  kind: 'form' | 'choice' | 'confirmation' | 'oauth-prompt';
  persist: 'run' | 'conversation' | 'project';
  trigger?: { stageId?: string; atom?: string };
  schema?: object;                             // JSON Schema (form / choice 必填)
  prompt?: string;                             // 默认渲染时显示的 question / instruction
  capabilitiesRequired?: string[];             // 例:oauth-prompt with route='connector' 需要 'connector:<id>'
  timeout?: number;                            // ms,默认 300_000
  onTimeout?: 'abort' | 'default' | 'skip';    // 默认 'abort'
  default?: unknown;                           // onTimeout='default' 时的回退值

  // oauth-prompt only:决定 daemon 把回调路由到哪个现有子系统
  oauth?: {
    route: 'connector' | 'mcp';                // v1 仅这两条;'plugin' 留给 Phase 4
    connectorId?: string;                      // route='connector' 时必填,引用 od.connectors.required[].id
    mcpServerId?: string;                      // route='mcp' 时必填,引用 plugin 自带 MCP server 的 name
  };
}

oauth-prompt 路由规则(v1 钉死):

oauth.route daemon 行为 UI 行为 持久化
connector 复用现有 apps/daemon/src/connectors/ flow:调 POST /api/connectors/:connectorId/connect/start 拿 redirect URL,OAuth 完成后 token 落到 <dataDir>/connectors/credentials.json 弹窗或抽屉里展示 connector 卡片(沿用 apps/web/src/components/ConnectorsBrowser.tsx 的视觉),用户点击触发 connector 标准 OAuth genui_surfaces.value_json = { connectorId, accountLabel };token 不进 SQLite
mcp 复用 POST /api/mcp/oauth/start flow,token 落到 <dataDir>/mcp-tokens.json 沿用 Settings → MCP servers 的 OAuth 视觉 genui_surfaces.value_json = { mcpServerId };token 不进 SQLite
plugin(Phase 4) plugin 提供任意 third-party OAuth metadata;daemon 走通用 PKCE adapter TBD TBD

od plugin doctor 在 install / apply 阶段强制:(1) oauth.route === 'connector' 时,oauth.connectorId 必须出现在同 plugin 的 od.connectors.required[]od.connectors.optional[] 中;(2) oauth.route === 'mcp' 时,oauth.mcpServerId 必须匹配 plugin 自带的某个 MCP server name。

od.connectors.required[] 的自动派生: 如果 plugin 声明了 od.connectors.required[]没有显式 oauth-prompt surface,daemon 在 apply 阶段会自动为每个未 connected 的 required connector 派生一条 kind: 'oauth-prompt'persist: 'project'oauth.route: 'connector' 的 implicit surface(id__auto_connector_<connectorId>)。这条 implicit surface 同样被记入 AppliedPluginSnapshot.genuiSurfaces,享受 §10.3.3 的跨 conversation 复用——用户对同一个 connector 一次授权,整个 project 后续 run 不再被打扰。Plugin 作者也可以显式声明同名 surface 覆盖 implicit 行为(提供自定义 prompt 或 schema)。

10.3.2 运行时事件(与 SSE / ND-JSON PersistedAgentEvent 联合)

export type GenUIEvent =
  | { kind: 'genui_surface_request';   surfaceId: string; runId: string; payload: GenUIPayload; requestedAt: number }
  | { kind: 'genui_surface_response';  surfaceId: string; runId: string; value: unknown;          respondedAt: number; respondedBy: 'user' | 'agent' | 'auto' | 'cache' }
  | { kind: 'genui_surface_timeout';   surfaceId: string; runId: string; resolution: 'abort' | 'default' | 'skip' }
  | { kind: 'genui_state_synced';      surfaceId: string; runId: string; persistTier: 'run' | 'conversation' | 'project' };

事件流中 genui_* 事件与 pipeline_stage_*message_chunk 共用同一 SSE 通道,但带专门的 schema 标记,便于 desktop / web / CLI / 其他 code agent 选择性订阅。

10.3.3 跨 conversation / 跨 run 的 persisted state

-- §11.4 完整迁移见架构章节,这里仅展示外形
CREATE TABLE genui_surfaces (
  id                    TEXT PRIMARY KEY,         -- composite of (project_id, conversation_id?, surface_id) for project tier
  project_id            TEXT NOT NULL,
  conversation_id       TEXT,                     -- null when persist='project'
  run_id                TEXT,                     -- null when persist!='run'
  plugin_snapshot_id    TEXT NOT NULL,            -- §8.2.1
  surface_id            TEXT NOT NULL,            -- plugin-declared id
  kind                  TEXT NOT NULL,
  persist               TEXT NOT NULL,            -- run | conversation | project
  schema_digest         TEXT,                     -- JSON Schema 的 digest,schema 漂移时 invalidate
  value_json            TEXT,                     -- 用户 / agent 给出的回答;null 表示 pending
  status                TEXT NOT NULL,            -- pending | resolved | timeout | invalidated
  requested_at          INTEGER NOT NULL,
  responded_at          INTEGER,
  expires_at            INTEGER,                  -- 例:oauth token 的到期
  FOREIGN KEY (project_id)         REFERENCES projects(id)                  ON DELETE CASCADE,
  FOREIGN KEY (conversation_id)    REFERENCES conversations(id)             ON DELETE SET NULL,
  FOREIGN KEY (run_id)             REFERENCES runs(id)                      ON DELETE SET NULL,
  FOREIGN KEY (plugin_snapshot_id) REFERENCES applied_plugin_snapshots(id)  ON DELETE SET NULL
);

CREATE INDEX idx_genui_proj_surface ON genui_surfaces(project_id, surface_id);
CREATE INDEX idx_genui_conv_surface ON genui_surfaces(conversation_id, surface_id);

查找规则:

  1. persist='project':daemon 用 (project_id, surface_id) 找最新 resolved;命中且 schema_digest 匹配且未 expires_at 则直接复用,不广播。
  2. persist='conversation':用 (conversation_id, surface_id) 同样查;命中即复用。换会话则失效。
  3. persist='run':仅本 run 内有效。
  4. schema 漂移降级为 invalidated plugin 升级且 manifest 上 surface 的 schema 变了,旧 row 自动失效;新 surface 重新向用户问。
  5. 用户主动撤销: UI / CLI 提供 od ui revoke <surface-id>,把 row 翻到 invalidated。常用于 OAuth token 退出。

这条规则正面对接用户提出的需求:"同一个 project 用户授过权、做过确认,下一次(多轮 / 多会话)不要再问。"

10.3.4 Headless / CLI 行为

od run watch / od run start --follow 的 ND-JSON 流中包含 genui_surface_request event。第三方 code agent 有三种应对方式:

# Inspect pending surfaces on a run
od ui list --run <runId> --json

# Read a single surface (kind / schema / prompt) for rendering or auto-fill
od ui show <runId> <surface-id> --json

# Respond from any process; daemon writes to genui_surfaces, run continues
od ui respond <runId> <surface-id> --value-json '{"audience":"VC"}'
od ui respond <runId> <surface-id> --skip          # 触发 onTimeout='skip'
od ui revoke  <projectId> <surface-id>             # 跨 conversation 撤销

如果 CLI 调用方完全不响应,run 在 surface.timeout 后按 onTimeout 收敛,不会无限期挂起。Code agent 也可以为整个 plugin 预先回答

od ui prefill <projectId> --plugin <pluginId> --json '{"figma-oauth":"<token>","direction-pick":"editorial"}'

prefill 只把 row 写成 resolved,等待 plugin 触发时按 cache 读出(事件流上仍会发 genui_surface_response { respondedBy: 'cache' },便于 audit)。

10.3.5 与 AG-UI 协议的对齐路线

维度 v1(OD 原生) AG-UI adapter / 外部兼容
Wire format OD 自有 SSE / ND-JSON PersistedAgentEvent 同时输出 AG-UI canonical events(包括 agent.messagetool_callstate_updateui.surface_requestedui.surface_responded
Surface kinds 4 类内置 + plugin 在 manifest 中声明 OD 内置 surface 仍是产品 source of truth;plugin React 组件路径必须经过 genui:custom-component gate 与 sandbox
Shared state genui_surfaces 表 + genui_state_synced 事件 把 OD 持久化状态映射到 AG-UI 的 state channel,供外部消费者使用
Frontend SDK 兼容 OD desktop / web 自带 renderer @open-design/agui-adapter 让 CopilotKit / 其他 AG-UI client 直接消费 OD run

Adapter 是互操作表面,不是内部 UI 的 source of truth。除非另有外部 embed / demo / client 明确需要,否则 OD 不应把 CopilotKit 加成主产品依赖。v1 plugin 不需要改就可以在 AG-UI ecosystem 内被消费,因为 adapter 是 OD 自有事件的一层投影。

11. 架构:现有 repo 要改什么

11.1 新 package:packages/plugin-runtime

纯 TypeScript,不依赖 Next/Express/SQLite/browser:

  • parsers/manifest.ts:读取 open-design.jsonPluginManifest(Zod-validated)。
  • adapters/agent-skill.ts:读取 SKILL.md → 基于 skills-protocol.md 里的 od: frontmatter 合成 PluginManifest
  • adapters/claude-plugin.ts:读取 .claude-plugin/plugin.json → 合成 PluginManifest
  • merge.ts:合并 sidecar + adapters,open-design.json 优先;foreign content 落到 compat.*
  • resolve.ts:针对 registry 解析 od.context.* refs → ResolvedContext
  • validate.ts:JSON Schema(同时驱动 runtime checks 和 od plugin doctor)。

11.2 新 contracts:packages/contracts/src/plugins/

manifest.tscontext.tsapply.tsmarketplace.tsinstalled.ts。从 packages/contracts/src/index.ts re-export。只包含纯 TypeScript,遵守根 AGENTS.md 的边界规则。

11.3 Daemon changes

文件 变更
apps/daemon/src/skills.tsdesign-systems.tscraft.ts 重构为委托给统一的 apps/daemon/src/plugins/registry.ts。现有 endpoints 继续兼容。
apps/daemon/src/plugins/registry.ts 三层扫描、冲突解决、hot-reload watcher。
apps/daemon/src/plugins/installer.ts github / https / local / marketplace install paths;tar/zip 解压;SQLite 写入。
apps/daemon/src/plugins/apply.ts 实现 ApplyResult 组装:解析 refs、返回 asset refs / MCP specs / capability requirements / appliedPlugin snapshot;不执行写入。实际 staging 与 .mcp.json 写入由 project create / run start 在 capability gate 之后执行。
apps/daemon/src/plugins/snapshots.ts §8.2.1 不可变 snapshot 写入与读取;status='stale' 标记由 od plugin doctor 触发;提供 replay helper 给 POST /api/runs/:runId/replay
apps/daemon/src/plugins/pipeline.ts 解析 od.pipeline(含 until expression 求值器)、调度 stages、控制 §10.2 devloop(含 OD_MAX_DEVLOOP_ITERATIONS 上限与 break 信号)。
apps/daemon/src/genui/{registry,events,store}.ts §10.3 GenUI surface 注册(来自 od.genui.surfaces[])、genui_surface_* event 发布、cross-conversation persisted state 读写、AG-UI–inspired event union 序列化。
apps/daemon/src/plugins/connector-gate.ts §9 connector capability gate:(a) apply.ts 通过它解析 od.connectors.required[]connectorService.listAll(),标记 connectorsResolved 与 implicit oauth-prompt GenUI surface(§10.3.1);(b) 在 apps/daemon/src/tool-tokens.ts 颁发 connector tool token 之前注入 plugin trust × connector:<id> capability 校验(trusted 隐含 connector:*,restricted 必须显式列出);(c) /api/tools/connectors/execute 执行前再校验一次(防止 token 颁发后被替换)。这条 gate 是 §9 中 P5 决策路径的实际落点。
apps/daemon/src/prompts/system.ts composeSystemPrompt() 组装 OD 基础 designer/discovery prompt、可选 design system/craft/skill blocks、snapshot 派生的 renderPluginBlock(snapshot),以及来自 renderActiveStageBlock(stageId, bodies) 的 active-stage atom blocks。现有层级顺序保持有意设计;plugin-block renderer 已在 contracts 中,但 plugin-driven fallback mode 仍按 §11.8 拒绝。
新 SQLite migration installed_pluginsplugin_marketplacesapplied_plugin_snapshotsrun_devloop_iterations,以及 runs / conversations / projects 上的 applied_plugin_snapshot_id ALTER(§11.4)。
apps/daemon/src/server.ts 挂载新 endpoints(§11.5);POST /api/projectsPOST /api/runs 接受可选 pluginId / pluginInputs / appliedPluginSnapshotId;新增 GET /api/applied-plugins/:snapshotIdPOST /api/runs/:runId/replayGET /api/runs/:runId/devloop-iterations
apps/daemon/src/cli.ts 新增 pluginmarketplaceprojectrunfiles subcommand routers(Phase 1 含 plugin verbs + headless MVP project/run/files;§16)。

11.4 SQLite migrations

新增三张表(installed catalog、marketplace catalog、apply 时刻的不可变 snapshot),并在已有 runs / conversations 表上加可选 FK。

CREATE TABLE installed_plugins (
  id                   TEXT PRIMARY KEY,
  title                TEXT NOT NULL,
  version              TEXT NOT NULL,
  source_kind          TEXT NOT NULL,    -- bundled | user | project | marketplace | github | url | local
  source               TEXT NOT NULL,
  pinned_ref           TEXT,
  source_digest        TEXT,
  trust                TEXT NOT NULL,    -- trusted | restricted
  capabilities_granted TEXT NOT NULL,    -- JSON array
  manifest_json        TEXT NOT NULL,    -- cached open-design.json (or synthesized)
  fs_path              TEXT NOT NULL,
  installed_at         INTEGER NOT NULL,
  updated_at           INTEGER NOT NULL
);

CREATE TABLE plugin_marketplaces (
  id            TEXT PRIMARY KEY,
  url           TEXT NOT NULL,
  trust         TEXT NOT NULL,           -- official | trusted | untrusted
  manifest_json TEXT NOT NULL,
  added_at      INTEGER NOT NULL,
  refreshed_at  INTEGER NOT NULL
);

-- §8.2.1: 不可变 apply-time snapshot;run / artifact 通过 snapshot_id 反查
CREATE TABLE applied_plugin_snapshots (
  id                      TEXT PRIMARY KEY,            -- snapshot_id
  project_id              TEXT NOT NULL,
  conversation_id         TEXT,
  run_id                  TEXT,                        -- 可空:apply 后用户取消 send 时 run 不存在
  plugin_id               TEXT NOT NULL,
  plugin_version          TEXT NOT NULL,
  manifest_source_digest  TEXT NOT NULL,               -- 见 §8.2.1
  source_marketplace_id   TEXT,
  pinned_ref              TEXT,
  task_kind               TEXT NOT NULL,               -- §1: new-generation | code-migration | figma-migration | tune-collab
  inputs_json             TEXT NOT NULL,
  resolved_context_json   TEXT NOT NULL,
  pipeline_json           TEXT,                        -- materialized PluginPipeline,缺省时记录 reference pipeline
  capabilities_granted    TEXT NOT NULL,               -- JSON array
  assets_staged_json      TEXT NOT NULL,               -- 实际 stage 到 cwd 的资产清单
  connectors_required_json TEXT NOT NULL DEFAULT '[]', -- §5 od.connectors.required + optional 冻结视图:PluginConnectorRef[]
  connectors_resolved_json TEXT NOT NULL DEFAULT '[]', -- apply 时实际 connected 的 PluginConnectorBinding[]({id, accountLabel, status})
  mcp_servers_json        TEXT NOT NULL DEFAULT '[]',  -- apply 时启用的 MCP server set 冻结视图(McpServerSpec[])
  status                  TEXT NOT NULL DEFAULT 'fresh', -- fresh | stale (plugin 升级后 doctor 标记)
  applied_at              INTEGER NOT NULL,
  FOREIGN KEY (project_id)      REFERENCES projects(id)      ON DELETE CASCADE,
  FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE SET NULL,
  FOREIGN KEY (run_id)          REFERENCES runs(id)          ON DELETE SET NULL
);

CREATE INDEX idx_snapshots_project ON applied_plugin_snapshots(project_id);
CREATE INDEX idx_snapshots_run     ON applied_plugin_snapshots(run_id);
CREATE INDEX idx_snapshots_plugin  ON applied_plugin_snapshots(plugin_id, plugin_version);

-- runs / conversations / projects 上加可选指针;OD 主键不变,向后兼容
ALTER TABLE runs          ADD COLUMN applied_plugin_snapshot_id TEXT REFERENCES applied_plugin_snapshots(id);
ALTER TABLE conversations ADD COLUMN applied_plugin_snapshot_id TEXT REFERENCES applied_plugin_snapshots(id);
ALTER TABLE projects      ADD COLUMN applied_plugin_snapshot_id TEXT REFERENCES applied_plugin_snapshots(id);

-- §10.2 devloop 审计与按 iteration 的未来计费
CREATE TABLE run_devloop_iterations (
  id                      TEXT PRIMARY KEY,
  run_id                  TEXT NOT NULL,
  stage_id                TEXT NOT NULL,
  iteration               INTEGER NOT NULL,
  artifact_diff_summary   TEXT,
  critique_summary        TEXT,
  tokens_used             INTEGER,
  ended_at                INTEGER NOT NULL,
  FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE CASCADE
);

-- §10.3 GenUI surface persisted state;run / conversation / project 三档复用
CREATE TABLE genui_surfaces (
  id                    TEXT PRIMARY KEY,
  project_id            TEXT NOT NULL,
  conversation_id       TEXT,
  run_id                TEXT,
  plugin_snapshot_id    TEXT NOT NULL,
  surface_id            TEXT NOT NULL,            -- plugin-declared id
  kind                  TEXT NOT NULL,            -- form | choice | confirmation | oauth-prompt
  persist               TEXT NOT NULL,            -- run | conversation | project
  schema_digest         TEXT,
  value_json            TEXT,
  status                TEXT NOT NULL,            -- pending | resolved | timeout | invalidated
  responded_by          TEXT,                     -- user | agent | auto | cache
  requested_at          INTEGER NOT NULL,
  responded_at          INTEGER,
  expires_at            INTEGER,
  FOREIGN KEY (project_id)         REFERENCES projects(id)                  ON DELETE CASCADE,
  FOREIGN KEY (conversation_id)    REFERENCES conversations(id)             ON DELETE SET NULL,
  FOREIGN KEY (run_id)             REFERENCES runs(id)                      ON DELETE SET NULL,
  FOREIGN KEY (plugin_snapshot_id) REFERENCES applied_plugin_snapshots(id)  ON DELETE SET NULL
);

CREATE INDEX idx_genui_proj_surface ON genui_surfaces(project_id, surface_id);
CREATE INDEX idx_genui_conv_surface ON genui_surfaces(conversation_id, surface_id);
CREATE INDEX idx_genui_run          ON genui_surfaces(run_id);

Migration 仅新增;未触碰现有 projects / runs / conversations 列语义。daemon 在 install / apply / run start / stage 完成时按上面 schema 写入。od plugin doctor 在插件升级后比较 manifest_source_digest 把过期 snapshot 的 status 翻到 stale,但永不删除或重写 snapshot 行——历史 run 的可复现性优先于存储成本。

11.5 新 HTTP endpoints

Method Path Purpose
GET /api/plugins 列出已安装插件(filters: kind, scenario, mode, trust)
GET /api/plugins/:id 详情(manifest、preview URLs、capability state)
GET /api/plugins/:id/preview 服务 preview HTML / poster / video
GET /api/plugins/:id/example/:name 服务 example output
POST /api/plugins/install body { source };通过 SSE stream 进度
POST /api/plugins/:id/uninstall 删除并清理 db
POST /api/plugins/:id/trust grant capabilities
POST /api/plugins/:id/apply request { projectId? },返回 ApplyResult
GET /api/marketplaces 已配置 marketplaces
POST /api/marketplaces 添加 marketplace
POST /api/marketplaces/:id/trust trust / untrust marketplace(body trust is trusted or untrusted
GET /api/marketplaces/:id/plugins catalog(paginated)
GET /api/atoms 列出一方 atoms

POST /api/projectsPOST /api/runs(当前在 server.ts:2362 / 6009)接受额外可选 pluginIdpluginInputsappliedPluginSnapshotId(任一组合都可以;server 优先使用 appliedPluginSnapshotId,其次 fallback 到重新解析)。新增 endpoint:

Method Path Purpose
GET /api/applied-plugins/:snapshotId 读取一份不可变 snapshot;用于 audit、replay、od plugin export
POST /api/runs/:runId/replay 基于 run 的 snapshot id 重跑长程任务
GET /api/runs/:runId/devloop-iterations 读取 §10.2 devloop iteration 历史
GET /api/runs/:runId/genui 列出本 run 当前 pending / resolved 的 §10.3 surfaces
GET /api/projects/:projectId/genui 列出 project 级 surfaces(跨 conversation 复用)
POST /api/runs/:runId/genui/:surfaceId/respond 写入 surface 答复,触发 genui_surface_response event
POST /api/projects/:projectId/genui/:surfaceId/revoke 把 surface row 翻成 invalidated(OAuth logout 等场景)
POST /api/projects/:projectId/genui/prefill 一次性预填多个 surfaces;body { pluginId, values }

Transport equivalence(经验规则)。 上面每个 endpoint 都同时暴露为 CLI subcommand,并在适合 MCP 语义的地方暴露为 MCP tool。代码 agent 应该使用 CLI;只有 desktop web app 和 od … 自己直接使用 HTTP。完整 CLI 表面见 §12。

11.5.1 ArtifactManifest 扩展:plugin provenance

为支撑「迁移 / 二次调优 / 协作 / 部署」围绕同一 artifact 接续推进,已有 packages/contracts/src/api/registry.tsArtifactManifest 在保持现有字段(sourceSkillId 等)不变的前提下,新增以下 optional provenance 字段:

export interface ArtifactManifest {
  // ... existing fields kept verbatim ...

  /** §8.2.1 不可变 snapshot;读取这条就能 100% 还原产生此 artifact 的 plugin 状态 */
  sourcePluginSnapshotId?: string;

  /** 冗余字段,便于不 join 也能 filter / 在 marketplace 上聚合统计 */
  sourcePluginId?: string;
  sourcePluginVersion?: string;
  sourceTaskKind?: 'new-generation' | 'code-migration' | 'figma-migration' | 'tune-collab';
  sourceRunId?: string;
  sourceProjectId?: string;

  /** Stable output taxonomy used by preview, export, deploy, tuning, and handoff flows.
   *  These fields are optional in v1 but must be preserved by readers and writers. */
  artifactKind?:
    | 'html-prototype'
    | 'deck'
    | 'interactive-video'
    | 'design-system'
    | 'code-diff'
    | 'production-app'
    | 'asset-pack';
  renderKind?: 'html' | 'jsx' | 'pptx' | 'markdown' | 'video' | 'image' | 'diff' | 'repo';
  handoffKind?: 'design-only' | 'implementation-plan' | 'patch' | 'deployable-app';

  /** 该 artifact 已经被推送到哪些下游协作面,用于 §1 中提到的 "分发到 cli / 其他 code agent / 云 / 桌面端" */
  exportTargets?: Array<{
    surface: 'cli' | 'desktop' | 'web' | 'docker' | 'github' | 'figma' | 'code-agent';
    target: string;
    exportedAt: number;
  }>;

  /** 该 artifact 的部署目标(用于 hosted / cloud 场景) */
  deployTargets?: Array<{
    provider: 'aws' | 'gcp' | 'azure' | 'aliyun' | 'tencent' | 'huawei' | 'self-hosted';
    location: string;
    deployedAt: number;
  }>;
}

写入规则:

  • run 完成时,daemon 把当前 appliedPluginSnapshotId + 冗余字段写入新生成的每个 artifact manifest。
  • 当 plugin 声明 output hints 时,daemon 写入 artifactKindrenderKindhandoffKind;否则根据 od.modeod.preview.type 和实际产出文件推断最保守的值。未知 reader 即使暂时不用这些字段,也必须原样 preserve。
  • 每次 od plugin export / od files upload --to <target> / od deploy ... 追加一行 exportTargets / deployTargets修改 sourcePluginSnapshotId
  • 二次调优时(tune-collab)创建的 artifact 同时记录 sourcePluginSnapshotId(本轮 plugin)与 parentArtifactId(被调优的上一版),形成可回溯链。

这条契约让「同一个 artifact 在不同协作面之间流转」成为 first-class 操作:cli 上看到 artifact 一定能查到原 plugin、原 inputs、原 design system;云端协作者一定能复现本地生成;后续代码迁移 / Figma 迁移产出的 artifact 之间通过 parentArtifactId 串成链。

11.6 Web changes

Web 表面获得两个共存入口,并由相同 primitives 支撑:

  • Deep / browsing surface/marketplace/marketplace/:id,用于发现、安装、能力审阅。
  • Shallow / inline surface:Home(NewProjectPanel)输入框下方,以及每个 project 的 chat composer(ChatComposer)下方。这是 §8 描述的日常主流程:用户看着输入框,下面出现插件卡片,点击一个,填空,发送。

二者共享 ContextChipStripPluginInputsFormInlinePluginsRail(Home 上是 strip/grid,ChatComposer 中是 slim row),以及 applyPlugin(pluginId, projectId?) state helper。

当前 discovery surface 状态: 已发布的 web app 里有两个相关但尚未统一的 discovery 投影。Home hero 的 chip rail(PrototypeLive artifactSlide deckImageVideo 等)是一组精选高频场景,目前仍由产品 UI 数据直接维护。Community / Plugins home filters 更接近数据驱动:它们从 plugin manifest 的 mode、task kind、scenario、tags、pipeline atoms 等元数据推导 counts 与 facets,再叠加 curated category taxonomy。目标扩展模型是一份 scenario registry / manifest projection 同时驱动 Home chips、plugin filters、composer tools 与 @search。在这件事落地前,Home rail 应被视为过渡性的产品快捷入口,而不是 plugin extension primitive。

文件 变更
apps/web/src/router.ts 扩展 Route union,加入 marketplacemarketplace-detail
apps/web/src/components/MarketplaceView.tsx 插件卡片 grid(复用 ExamplesTab 卡片样式)。deep browsing surface。
apps/web/src/components/PluginDetailView.tsx Preview、use-case query、context items、sample outputs、capabilities、install/use button。
apps/web/src/components/InlinePluginsRail.tsx 直接显示在 Home 与 ChatComposer 输入框下方的 plugins。可过滤,按 recency / project relevance / featured 排序。点击卡片 → 原地调用 applyPlugin()。同一组件有两种布局:Home 为宽 grid;ChatComposer 为 slim horizontal strip + overflow scroll。
apps/web/src/components/ContextChipStrip.tsx 渲染在 NewProjectPanel 与 ChatComposer brief input 上方的 chip strip。每个 chip 是一个 ContextItem;点击打开 popover;X 删除。
apps/web/src/components/PluginInputsForm.tsx od.inputs 渲染成 input 与 Send 之间的 inline form。required fields gate Send。inputs 变化时,useCase.query 和 chip labels 里的 {{var}} 实时 re-render。
apps/web/src/components/NewProjectPanel.tsx 增加 contextItems + pluginInputs + appliedPluginId state。布局变成:上方 name input,其上 ContextChipStrip,input 与 Send 之间 PluginInputsForm(当插件被 apply),Send 下方 InlinePluginsRail 作为发现表面。Send 创建 project 和 first run,传入 pluginId 与 inputs。
apps/web/src/components/ChatComposer.tsx 与 NewProjectPanel 相同的 composer 改造:上方 chip strip,input 与 Send 之间 inputs form,下方 plugins rail。Apply 带当前 projectIdPOST /api/plugins/:id/apply;Send 带 pluginIdPOST /api/runs,使 run 使用插件 prompt context。rail filters 从 project 的 kind / scenario 派生。
apps/web/src/components/ExamplesTab.tsx 保留。Phase 3 折叠进 Marketplace 的「Local skills」tab。里面的「Use this prompt」也改走 applyPlugin(),保证行为与 inline rail 一致。
apps/web/src/state/projects.ts 新增 applyPlugin(pluginId, projectId?) helper,命中 POST /api/plugins/:id/apply。新增 setPluginInputs()clearAppliedPlugin()
apps/web/src/components/GenUISurfaceRenderer.tsx §10.3 4 类内置 surface 的默认 React renderer(form / choice / confirmation / oauth-prompt);订阅 genui_surface_request event 渲染 inline 卡片或 modal;调 POST /api/runs/:runId/genui/:surfaceId/respond
apps/web/src/components/GenUIInbox.tsx Project 级面板,列出该 project 跨 conversation 持久化的 surface(如 figma OAuth、品牌确认);提供 revoke 入口。
apps/web/src/components/ProjectView.tsx chat stream 中插入 GenUISurfaceRenderer;左侧 / 右侧抽屉挂 GenUIInbox

11.7 Headless 与 UI:干净解耦

OD 运行在三种 operating modes,它们共享同一个 daemon、同一个 CLI、同一个 plugin runtime。差异只是呈现方式。这与 Cursor 的形态一致:cursor-agent(CLI)自身足够运行;IDE 是增强层。

Mode 运行内容 适用场景 入口
Headless 仅 daemon process;无 web bundle、无 electron CI、server、container、Claude-Code-driven flows od daemon start --headless(Phase 2 新 flag)
Web Daemon + local web UI(无 electron) 纯浏览器部署、Linux 无 GUI dependencies od daemon start --serve-web(Phase 2 新 flag)
Desktop Daemon + web bundle + electron shell 完整产品体验(当前默认) od(当前默认,不变)

此拆分由一条规则强制:

Decoupling rule. 每个 UI 功能必须先能表达为 CLI subcommand 或 streaming event。desktop UI 可以把这些 primitives 渲染得更丰富,但不能引入没有 CLI 等价物的能力。任何增加 UI-only behavior 的 PR 都应被拒绝(对应 §12.6)。

实践含义:

  • Marketplace browsing:UI 是 grid + filters + previews。CLI 是 od plugin list/infood marketplace searchod plugin info <id> --json,返回 UI 渲染的同一份 manifest。
  • Plugin apply:UI 是点击卡片、chips 与 inputs 原地 hydrate。CLI 是 od plugin apply <id> --json,返回完全相同的 ApplyResult
  • Run streaming:UI 是 chat bubbles、todo list、progress chrome。CLI 是 od run start --follow,从 UI 消费的同一 PersistedAgentEvent discriminated union 发出 ND-JSON events。
  • Direction picker / question form:UI 渲染为 inline cards。CLI 发出结构化事件到 stdout;agent 或 scripted wrapper 通过 stdin 或 od run respond <runId> --json '{...}' 选择选项。
  • Live artifact preview:UI 是 hot-reloading iframe。CLI 是 od files watch <projectId> --path <relpath>,stream change events;用户用自己的工具打开文件。
  • Critique theater:UI 是 5-panel side-by-side。CLI 发出结构化 critique event,由 agent 或 wrapper 自行渲染。

解锁的能力:

  • 用户只有 Claude Code(或任意 code agent)加 npm i -g @open-design/cli,再启动一个 headless daemon,就能完成 install plugin → create project → run → consume artifacts 全流程。不需要 OD desktop。
  • OD desktop UI 安装相同 daemon 与相同 CLI;它只是加了一个窗口。用户之后安装 desktop 时,会看到 headless 流程创建的同一批 projects、plugins、history。不存在「headless project format」与「desktop project format」之分。都是同一个 .od/projects/<id>/、同一个 SQLite db。
  • CI 是一等公民:GitHub Action 可以 npm i -g @open-design/cli && od daemon start --headless && od plugin install … && od run start --project … --follow。无 display、无 electron、无 UI scripting。
  • 外部产品可以通过启动 headless daemon 并 shell out 嵌入 OD:od 是 public surface,internals 可以自由演进。

代价:少量 od daemon flags,以及一个新的 lifecycle subcommand(od daemon start/stop/status,带 --headless / --serve-web)。实现与 Phase 2 的 CLI parity slice 一起落地。

11.8 Prompt composition:v1 plugin runs 走 daemon,避免分叉

OD 当前有两份 composeSystemPrompt() 实现:

如果把 ## Active plugin block 只加到 daemon composer,API fallback 模式生成的 prompt 会缺失 plugin context;如果两边各加一套,几乎必然漂移。spec 锁定如下规则:

  1. v1:plugin 驱动的 run 仅在 daemon-orchestrated mode 下支持(desktop / web 走 daemon HTTP;headless / Docker 走 daemon directly)。Web API-fallback 模式(浏览器直连 provider,daemon 不在路径上)在 v1 中支持 pluginId:当客户端在 fallback 模式下尝试创建带 pluginId 的 run 时,daemon 缺席使得请求落在 web sidecar 上,sidecar 检测到该 fallback path 必须回 409 plugin-requires-daemon,UI 提示用户启动 daemon 或切换到 desktop/headless。这条限制在 §16 Phase 2A validation 中明确测试。
  2. Phase 2A:把 plugin prompt block renderer 提到 contracts 共享层。 纯函数 renderPluginBlock(snapshot: AppliedPluginSnapshot): string 位于 packages/contracts/src/prompts/plugin-block.ts,并被 apps/daemon/src/prompts/system.tspackages/contracts/src/prompts/system.ts 同时 import。daemon composer 会调用它;contracts composer 保留 import,但在 v1 fallback rejection 规则 (1) 下不调用它。这样 plugin context 的 prompt 表达只有一处定义,不再需要两边 byte-for-byte fixture 防漂移,同时保留 v1 fallback 拒绝策略。
  3. Phase 4:打开 fallback plugin support。 renderer 已在 contracts 中后,支持 fallback-mode plugin run 只需要在 contracts composer 里接线并移除 web sidecar 的 409;不再需要 prompt shape 重构或新的 cross-check guard。(最初计划是 Phase 4 才迁出 block,并在 Phase 1-4 之间用 CI fixture 防漂移;实施计划 PB1 已把迁移提前到 Phase 2A,因此 fixture 不再需要。)

由此,§14.2 列出的「skill-only consumption / headless OD / full OD」三种消费模式中,只有后两者拥有 plugin context;这与 §1 中「插件是 long-task design agent 的封装」一致——长程任务必须有 daemon 或 daemon 等价物(headless)才能被组装。

这也回答「没有选择插件」路径:没有 applied plugin 的 run 仍然会收到 OD 基础 prompt stack(DISCOVERY_AND_PHILOSOPHY、official designer instructions、project metadata、存在时的 active design system/craft,以及入口选择的默认 scenario routing)。Plugin context 是增量项。选择插件会追加 snapshot 派生的 ## Active plugin## Plugin inputs 与 active-stage atom blocks;它不是把一个非设计 agent 从零变成设计 agent 的唯一来源。

12. CLI 表面

CLI(od …)是 Open Design 面向 agent 的 canonical API。Plugin verbs 只是其中一部分;CLI 的其它部分包装 daemon core capabilities:projects、conversations、runs、file operations、design library introspection、daemon control,因此任何 code agent 都能通过 shell calls 端到端驱动 OD。这是「通过 CLI 用自然语言创建 project + task」的路径:code agent 读取用户请求,然后发出一串 od … 调用,而不是直接说 HTTP。

12.1 一个逻辑 API 的三种 transports

Transport Use case Implementation
HTTP (/api/*) Desktop web app、internal tooling、CLI 自身使用 apps/daemon/src/server.ts
CLI (od …) Code agents shelling out、scripts、CI apps/daemon/src/cli.ts
MCP stdio MCP-aware agents(Claude Code、Cursor 等) od mcpod mcp live-artifacts(现有)

当新能力发布时,CLI subcommand 是 primary contract。HTTP route 用于支撑 CLI;MCP server 把一组精选 CLI subcommands 暴露为 tools。版本策略:subcommand names、argument names、--json schemas 由 packages/contracts 管理并在 CI 中测试;breaking changes 跟随 od bin 的 major-version bump。

12.2 Command groups

现有命令(apps/daemon/src/cli.ts)保留;在其下新增以下 groups。

Project lifecycle(新增)

od project create [--name "<title>"] [--skill <id>] [--design-system <id>]
                  [--plugin <id>] [--input k=v ...] [--brief "<text>"]
                  [--metadata-json <path|->] [--json]
od project list   [--json]
od project info   <id> [--json]
od project delete <id>
od project import <path> [--name "<title>"] [--json]   # wraps existing /api/import/folder
od project open   <id>                                 # opens browser at /projects/<id>

od project create --json 的结果:

{ "projectId": "p_abc", "conversationId": "c_xyz", "url": "http://127.0.0.1:17456/projects/p_abc" }

Conversation lifecycle(新增)

od conversation list <projectId> [--json]
od conversation new  <projectId> [--name "<title>"] [--json]
od conversation info <conversationId> [--json]

Run / task lifecycle(新增)

od run start --project <projectId> [--conversation <conversationId>]
             [--message "<text>"] [--plugin <pluginId>] [--input k=v ...]
             [--agent claude|codex|gemini] [--model <id>] [--reasoning <level>]
             [--attachments <relpath,...>] [--follow] [--json]

od run watch  <runId>                # ND-JSON SSE-equivalent events on stdout
od run cancel <runId>
od run list   [--project <id>] [--status running|done|failed] [--json]
od run logs   <runId>                # historical tail; --since for incremental

run start 上的 --followstart && watch 的 shorthand。二者 stream 相同 event schema,定义在 packages/contracts/src/api/chat.ts(现有 PersistedAgentEvent discriminated union,每行一个 event)。

Project 文件系统操作(新增)

daemon 当前已经拥有 .od/projects/<id>/ 下的 project filesystems(或者 imported folder 的 metadata.baseDir)。这些命令以 project 为 scope:agent 不需要知道 project 在磁盘哪里。

od files list   <projectId> [--path <subdir>] [--json]
od files read   <projectId> <relpath>                   # writes to stdout
od files write  <projectId> <relpath> [< stdin]         # reads from stdin
od files upload <projectId> <localpath> [--as <relpath>]
od files delete <projectId> <relpath>
od files diff   <projectId> <relpath>                   # vs. last committed version (when imported from git)

当目标是 OD-managed projects 时,code agent 通常应该用 od files read / od files write,而不是 native file ops,因为 daemon 负责 artifact bookkeeping(packages/contracts/src/api/registry.ts 中的 ArtifactManifest.sourceSkillId 等)。

Plugin verbs

od plugin install   <source>                            # github: | https://… | ./folder | <name from marketplace>
od plugin uninstall <id>
od plugin list      [--kind skill|scenario|atom|bundle] [--trust trusted|restricted] [--json]
od plugin info      <id> [--json]
od plugin update    [<id>]
od plugin trust     <id> [--caps fs:write,mcp,bash,hooks]   # 永久授权,写入 installed_plugins
od plugin apply     <id> --project <projectId> [--input k=v ...] [--grant-caps fs:read,mcp ...] [--json]
                                                            # returns ApplyResult; pure (no run)
                                                            # --grant-caps: 仅作用于本次 apply 产生的 snapshot
od plugin run       <id> --project <projectId> [--input k=v ...] [--grant-caps ...] [--follow] [--json]
                                                            # shorthand: apply + run start --follow
od plugin replay    <runId> [--follow] [--json]             # 基于 run 的 applied_plugin_snapshot 重跑长程任务

Generative UI verbs (§10.3)

od ui list      [--run <runId> | --project <projectId>] [--status pending|resolved|timeout|invalidated] [--json]
od ui show      <runId> <surface-id> [--json]                 # surface kind / schema / prompt
od ui respond   <runId> <surface-id> --value-json '{...}'     # write answer, unblock the run
od ui respond   <runId> <surface-id> --skip                   # trigger onTimeout='skip'
od ui revoke    <projectId> <surface-id>                      # invalidate persisted answer (e.g. OAuth logout)
od ui prefill   <projectId> --plugin <pluginId> --json '{ "<surface-id>": <value>, ... }'
                                                              # bulk pre-answer, used before the run starts
od plugin export    <projectId> --as od|claude-plugin|agent-skill --out <dir>
od plugin doctor    <id>
od plugin scaffold

Marketplace verbs

od marketplace add     <url> [--trust]
od marketplace remove  <id>
od marketplace trust   <id>
od marketplace untrust <id>
od marketplace list    [--json]
od marketplace refresh [<id>]
od marketplace search  "<query>" [--tag <tag>] [--json]   # search across configured catalogs

Design library introspection(新增)

od skills list             [--json] [--scenario <s>] [--mode <m>]
od skills show             <id> [--json]
od design-systems list     [--json]
od design-systems show     <id> [--json]
od craft list              [--json]
od atoms list              [--json]                       # first-party atoms (§10)

Daemon control(新增)

od daemon start  [--headless] [--serve-web] [--port <n>] [--host <h>]
                                                           # explicit lifecycle (§11.7);
                                                           # default `od` (no args) keeps current behavior
od daemon stop   [--daemon-url <url>]
od daemon status [--json]                                   # alias of `od status`
od status        [--json]                                   # daemon up? port? installed plugins count
od doctor                                                   # diagnostics: skills/DS/craft/plugins, providers, MCP
od version       [--json]
od config get|set|list|unset  [--key ...] [--value ...]     # backed by media-config.json + db

od daemon start --headless 是 §11.7 中 headless mode 的入口(无 web bundle、无 electron)。od daemon start --serve-web 增加本地 web UI 但不启用 electron。二者都沿用现有 tools-dev port conventions(OD_PORT, OD_WEB_PORT)。

现有 agent-callable tools(不变)

od research search ...
od media generate  ...
od tools live-artifacts ...
od tools connectors  ...
od mcp                       # stdio MCP server
od mcp live-artifacts        # specialized MCP server

12.3 输出约定

  • 每个命令都支持 --json 结构化输出。默认输出为 human-readable。
  • Streaming commands(run watchrun start --followplugin install)输出 ND-JSON:每行一个 JSON object,以 \n 终止。每个 event 匹配现有 PersistedAgentEvent schema 或一个小的 command-specific superset(如 install progress lines)。
  • Exit codes:
    • 0:成功。
    • 1:通用失败(非结构化 stderr)。
    • 2:bad usage / argument validation。
    • 64–79:agent 可恢复的已知错误,带结构化 stderr(见 §12.4)。
    • >=80:daemon errors(HTTP 5xx 等)。

12.4 可恢复错误码(给 agents)

Exit Meaning Recovery hint Structured stderr data 字段(节选)
64 Daemon not running od status,然后启动 daemon { host, port }
65 Plugin not found / not installed od plugin listod plugin install <source> { pluginId, candidateSources[] }
66 Plugin restricted, capability required od plugin trust <id> --caps … 或重试时加 --grant-caps … { pluginId, pluginVersion, required[], granted[], remediation[] }
67 Required input missing on apply 用每个缺失字段重新运行 --input k=v { pluginId, missing[], schema }
68 Project not found od project list { projectId }
69 Run not found / already terminal od run list --project <id> { runId, status }
70 Provider not configured 为 provider key 执行 od config set ... { provider, requiredKeys[] }
71 Plugin requires daemon mode 启动 daemon 或切换到 desktop / headless { pluginId, mode: 'api-fallback' }(§11.8)
72 Applied plugin snapshot stale od plugin replay <runId>od plugin upgrade <id> 后重新 apply { snapshotId, pluginId, currentVersion, snapshotVersion }
73 GenUI surface awaiting response od ui show <runId> <surface-id> 后用 od ui respond 作答;或在调用前 od ui prefill { runId, surfaceId, kind, schema, prompt, persist, timeoutAt }(§10.3)

当设置 --json 时,结构化错误输出是 stderr 上的 { "error": { "code": "<short-code>", "message": "<human>", "data": { ... } } }。上面的 exit codes 保持稳定;human prose 可以演进。Exit 66 的 data shape 与 §9.1 中的 capability gate JSON 一致;agent 读到 66 后可以选择带 --grant-caps 重试,或回报给上游用户。

12.5 Code agent authoring patterns

一个通过 CLI 驱动 Open Design 的 code agent 通常这样做:

# 1. (Optional) Inspect what's available.
od skills list --json
od plugin list --json

# 2. Create a project bound to a skill or design system.
PID=$(od project create --skill blog-post --design-system linear-clone --json | jq -r .projectId)

# 3. Apply a plugin to preview the brief and context (pure; no run yet).
od plugin apply make-a-deck --project "$PID" --input topic="agentic design" --input audience=VC --json

# 4. Start the run, follow events live (ND-JSON on stdout).
od run start --project "$PID" --plugin make-a-deck \
             --input topic="agentic design" --input audience=VC \
             --message "Make it concise; investor-ready." --follow \
  | jq -r 'select(.kind == "message_chunk") | .text' \
  | tee run.log

# 5. Consume produced artifacts.
od files list "$PID" --json
od files read "$PID" index.html > out.html

这段 sequence 可以在本机、CI、Docker sidecar、另一个 agent loop 内等同运行。不需要 HTTP、不需要端口发现、不需要 auth tokens:CLI 把这些都藏在稳定 subcommand contract 之后。

12.6 对现有 CLI 的含义

上面每个 group 都是对 apps/daemon/src/cli.ts 的增量。当前默认 od(启动 daemon + 打开 web UI)保持不变。现有 od mediaod researchod toolsod mcp 命令保持当前 contract。新增 groups 是对 apps/daemon/src/server.ts 中既有 desktop UI endpoints 的包装(project create/list、run start/watch、file upload/list/read),再加上 §11.5 的新插件/marketplace/atoms endpoints。

Implementation rule: 如果 code agent 能通过 desktop UI 做某件事,它就必须能通过 od … 用相同参数和等价输出完成。不能有 silent UI-only capabilities。

13. 公网 Web 表面(open-design.ai/marketplace)

产品网站已经是 open-design.ai。公网 marketplace 作为同一站点下的路径发布:open-design.ai/marketplace(canonical),并把 open-design.ai/plugins 作为 alias;不是单独域名。它是一个从官方 open-design-marketplace.json index 渲染出的 static-rendered catalog,插件详情页由每个 listed repo 内的同一份 open-design.json 支撑。视觉上它类似 skills.sh 对 skills 的处理,但详情页会渲染 OD 专属 previews(od.preview.entry HTML、sample outputs、use-case query、chip preview)。

这个站点与 in-app marketplace 共享一个 source of truth:

  • 同一组 JSON Schemas(https://open-design.ai/schemas/plugin.v1.jsonhttps://open-design.ai/schemas/marketplace.v1.json)。
  • 同一套联邦 listing format(open-design-marketplace.json)。
  • 同一份 plugin manifests(每个 repo 内的 open-design.json)。

两个消费表面,一个 substrate:

Surface Audience Primary CTA
In-app marketplace(/marketplace,§11.6) 已登录 OD 用户 “Use this plugin” → apply in place
Public marketplace(open-design.ai/marketplace 匿名访客、SEO、分享 Deep-link od://plugins/<id>?apply=1(自动在 desktop app 中安装并 apply),以及 “Copy install command”

Deep-link contract(Phase 4 deliverable,但在这里先锁定 schema 支持):

  • od://plugins/<id>:在 in-app marketplace 打开 plugin detail。
  • od://plugins/<id>?apply=1[&input.k=v...]:如果缺失则安装,然后用 supplied inputs apply。
  • od://marketplace/add?url=<urlencoded>:注册新的联邦 catalog。

desktop app 注册 od:// URL scheme;点击 open-design.ai/marketplace 上的按钮时,如果 desktop 已安装则启动 desktop,否则 fallback 到「How to install Open Design」流程。

状态:不属于 v1 implementation scope。 但这里锁定 JSON shapes 和 URL scheme,使 in-app marketplace 与公网站点可以独立开发而不分叉。

14. 发布与目录分发

每个插件一个 GitHub repo,同时可以被用户提到的所有 catalog 消费:

Catalog 它需要什么 我们在 repo 里发布什么
anthropics/skills name/description frontmatter 的 SKILL.md SKILL.md(canonical anchor)
anthropics/claude-code/plugins .claude-plugin/plugin.json + standard structure .claude-plugin/plugin.json(可选)
VoltAgent/awesome-agent-skills PR 增加一行指向 repo URL repo URL,§14.1 中的 automation 辅助
openclaw/clawhub 通过 clawhub web app 或 PR 提交 repo URL
skills.sh 一旦观察到 npx skills add owner/repo 即可被索引 repo URL
open-design-marketplace.json entry 指向 github:owner/repo open-design.json 丰富 listing

14.1 作者工具

  • od plugin scaffold:写入 starter folder,包含 SKILL.md(行业标准,带 od: frontmatter 以向后兼容)和 open-design.json(OD enrichment,compat.agentSkills 指向 SKILL.md)。
  • od plugin doctor:运行 JSON Schema、SKILL.md frontmatter parser,以及「它看起来是否能被 awesome-agent-skills / clawhub / skills.sh 收录?」lint,检查 README、license file、frontmatter 完整性。
  • od plugin publish --to <catalog>(Phase 4):打开浏览器进入 catalog PR template,预填 row。

14.2 Cross-agent consumption

任何通过 SKILL.md 消费文件夹的 code agent,都可以在没有 OD 的情况下运行。插件是一个 repo,有三种合法消费模式:

  1. Skill-only consumption(完全没有 OD)。 Cursor 用户执行 npx skills add open-design/make-a-deck。Cursor 读取 SKILL.md 并运行 workflow。没有 OD CLI、没有 OD daemon。插件的 marketplace polish(open-design.json)被忽略,Cursor 看到的是普通 skill。
  2. Headless OD(CLI + code agent,无 OD UI)。 高阶用户继续使用自己偏好的 code agent:Claude Code、Cursor、Codex 等,但把 OD 作为 side service 加进来,获得 plugin context resolution、project bookkeeping、design library injection、artifact tracking。无 browser、无 electron。具体 pipeline 见 §14.3。
  3. Full OD(CLI + code agent + OD UI)。 与 (2) 相同,但加上 desktop 或 web UI,用于 live preview、marketplace browsing、chat/canvas split-view 等。

插件作者只写一次 SKILL.md。三种模式都消费它。

14.3 具体 headless pipeline(Claude Code + od CLI,无 OD UI)

这与 cursor-agent + scripts 对 Cursor 的意义相同:code agent 做思考,OD CLI 提供 project / plugin / artifact substrate。

# One-time setup: install the OD CLI as an npm global (publishable as @open-design/cli).
npm install -g @open-design/cli

# Start the daemon in headless mode — no web bundle, no electron, no browser.
od daemon start --headless --port 17456

# Install the OD plugin you want to drive (or an upstream agent skill — both work).
od plugin install github:open-design/plugins/make-a-deck

# Create a project bound to the plugin. Inputs are templated into the brief.
PID=$(od project create \
        --plugin make-a-deck \
        --input topic="agentic design" \
        --input audience=VC \
        --json | jq -r .projectId)

# Drive the run with Claude Code (or any code agent). Two equivalent paths:

# Path A — let `od` orchestrate Claude Code as the run's agent:
od run start --project "$PID" --plugin make-a-deck \
             --agent claude --follow

# Path B — drive Claude Code directly inside the project cwd; OD only provides
# context resolution and artifact bookkeeping. Useful when the user's existing
# code-agent setup is opinionated.
CWD=$(od project info "$PID" --json | jq -r .cwd)
cd "$CWD"
# OD has already staged the merged SKILL.md / DESIGN.md / craft / atoms into
# .od-skills/ inside the cwd, exactly as the desktop run would.
claude code "Read .od-skills/ and produce the deliverables the active plugin describes."

# Consume the produced artifacts.
od files list "$PID" --json
od files read "$PID" slides.html > slides.html
open slides.html      # or however the user wants to view the file

这证明:

  • 完整 marketplace → plugin → apply → run → artifact pipeline 可以在终端里用不到 10 行触达。
  • OD daemon 不需要渲染任何东西;它作为 project + plugin + artifact server 工作。
  • 同一个 project 之后在 OD desktop UI 中打开时,会显示 headless run 产生的完整 conversation history、files、artifacts,因为 storage layer 只有一套(spec.md §4.6:.od/projects/<id>/ + SQLite)。

14.4 类比:Cursor vs cursor-agent,OD desktop vs od CLI

心智模型:

Layer Cursor Open Design
Headless agent CLI cursor-agent(驱动 agent loop) od run start --agent claude --follow + od plugin run
Local services / db Cursor 的 background indexing / state OD daemon、SQLite、.od/projects/<id>/
GUI productivity layer Cursor IDE OD desktop / web UI(apps/web + apps/desktop
Plugin / skill format .cursor/rules/、MCP servers SKILL.md + open-design.json + atoms

两者以相同方式解耦:terminal flow 已经足够;IDE/desktop 是生产力倍增器。插件作者不需要做选择:他们写一个 SKILL.md 加可选 sidecar,就能覆盖三种消费模式。

15. 部署与可移植性:Docker,任意云

OD 以单个 multi-arch Docker image 发布,使完整 plugin/marketplace system 可以一条命令启动,并在每个主流云上不经修改地运行。这是生态与商业故事的 substrate:合作伙伴在自己的 VPC 内自托管;企业运行私有 marketplace;CI job 启动 per-job OD daemon。这个 image 是 §11.7 headless mode 的 ops 包装,并在设置 --serve-web 时可选择服务 §11.6 的 web UI。

15.1 Image shape

  • Tagghcr.io/open-design/od:<version>,以及 moving :latest:edge
  • Architectureslinux/amd64linux/arm64(single manifest list)。
  • Contents
    • Node 24 runtime + daemon dist/ bundle。
    • PATH 上的 od CLI。
    • Web UI bundle(apps/web build),因此同一个 image 同时服务 API 与 UI。
    • OD 支持作为 agent backends 的 bundled code-agent CLIs:Claude Code、Codex CLI、Gemini CLI。每次 run 可选;默认是 OD_AGENT_BACKEND
    • 插件常假设存在的通用 runtime deps:ffmpeggitripgrep
  • Excluded:electron、native macOS/Windows toolchains、dev tooling。

base image 是 node:24-bookworm-slim。container 内用户是 non-root(uid 10001)。

15.2 Persistence

operator 应挂载三个 volumes;它们映射到根 AGENTS.md 中已有的 OD env vars,因此 daemon 不需要改代码。

Mount path Env var Purpose
/data/od OD_DATA_DIR Projects、SQLite、artifacts、installed plugins(<OD_DATA_DIR>/plugins
/data/config OD_MEDIA_CONFIG_DIR Provider credentials(media-config.json
/data/marketplaces (位于 OD_DATA_DIR 下) Cached marketplace indexes

只挂载 /data/od 是最小配置。推荐 hosted-mode 把 /data/config 拆开,使 secrets 与 data 的生命周期不同。

15.3 Configuration

所有配置通过 env vars 和可选预置 config file 传入。最小 hosted env:

OD_PORT=17456
OD_BIND_HOST=0.0.0.0                 # 当前 daemon 已读取的变量名([`apps/daemon/src/server.ts`](../apps/daemon/src/server.ts))
OD_DATA_DIR=/data/od
OD_MEDIA_CONFIG_DIR=/data/config
OD_TRUST_DEFAULT=restricted          # safe default for hosted (§9) — Phase 5 引入
OD_AGENT_BACKEND=claude              # default code agent backend
OD_API_TOKEN=<random>                # required when OD_BIND_HOST != 127.0.0.1 — Phase 5 引入 bearer middleware
ANTHROPIC_API_KEY=...                # provider keys; also storable via `od config set`
TAVILY_API_KEY=...

当前实现 vs. spec 的差异(migration note):

  • OD_BIND_HOST 已经存在于 daemon 代码(apps/daemon/src/server.tsapps/daemon/src/origin-validation.ts)。早期 spec 草稿曾以 OD_HOST 引用同一变量;正确名称是 OD_BIND_HOST,本 spec 全部采用此名称。新增 OD_HOST 别名,避免双名漂移。
  • OD_TRUST_DEFAULTOD_API_TOKEN 与对应 bearer-token middleware 当前尚未实现;它们是 Phase 5「Cloud deployment + pluggable storage」的一部分(§15.7、§16 Phase 5)。在落地前 hosted deployments 仍依赖 reverse proxy / network ACL 做访问控制,hosted-mode security defaults 章节(§15.7)会显式调用此前置条件。

任何能通过 desktop UI 设置的内容,也都能通过 docker exec od od config set ... 设置,或通过把预置 media-config.json 挂载到 /data/config

15.4 一条命令部署

本地 laptop:

docker run --rm -p 17456:17456 ghcr.io/open-design/od:latest
open http://localhost:17456

持久服务器:

docker run -d --name od \
  -p 17456:17456 \
  -v od-data:/data/od \
  -v od-config:/data/config \
  -e OD_DATA_DIR=/data/od \
  -e OD_MEDIA_CONFIG_DIR=/data/config \
  -e OD_BIND_HOST=0.0.0.0 \
  -e OD_API_TOKEN="$(openssl rand -hex 32)" \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  ghcr.io/open-design/od:latest

在 container 内触达相同 surfaces:

docker exec od od plugin install github:open-design/plugins/make-a-deck
docker exec od od project create --plugin make-a-deck --json
docker exec od od status --json

15.5 Multi-cloud portability

image 故意保持 cloud-agnostic。一个 container image 可以运行在每个主流云的 container service 上:

Cloud Container service Persistent storage Secrets
AWS ECS / Fargate / EKS / App Runner EFS (NFS), EBS, S3 adapter Secrets Manager / SSM
Google Cloud Cloud Run / GKE Filestore, GCS-fuse Secret Manager
Azure Container Apps / AKS Azure Files Key Vault
Alibaba Cloud SAE / ACK NAS, OSS adapter KMS
Tencent Cloud CloudRun / TKE CFS, COS adapter SSM
Huawei Cloud CCE / CCI SFS, OBS adapter KMS
Self-hosted docker-compose, Docker Swarm, k3s, k0s Bind mounts, NFS, Longhorn env / SOPS / Vault

OD 随 image 版本发布两个 reference manifests:

  • tools/pack/docker-compose.yml:daemon + optional reverse proxy + optional Postgres for §15.6。
  • tools/pack/helm/:Helm chart,values presets 用于每个云的 volume + secret patterns。chart 保持 generic;cloud-specific bootstrap(CloudFormation / Deployment Manager / ARM / Aliyun ROS / Tencent TIC / Huawei RFS)放在独立 open-design/deploy repo 中,允许单独演进。

15.6 Pluggable storage and database(Phase 5)

daemon 的 filesystem 与 SQLite 使用被封装在两个窄接口后:

  • ProjectStorage:读/写/list project files。
  • DaemonDb:SQLite typed wrapper(今天已经存在)。

v1 只发布 local-disk + SQLite 实现。Phase 5 增加:

  • S3-compatible blob stores 的 ProjectStorage adapter。 适用于 AWS S3、GCS(S3-compatible mode)、Azure Blob(S3-compat shim 或 native)、Aliyun OSS、Tencent COS、Huawei OBS;它们都支持 S3 API 或薄 shim。
  • Postgres 的 DaemonDb adapter,使多个 daemon replicas 可以共享 state。适合负载均衡后面或 serverless containers scale-to-zero 的场景。

adapter 之间保持相同 on-disk layout,因此 single-tenant deployment 迁移到 multi-replica 时不需要重新导入 projects。

15.7 Hosted-mode security defaults

当 daemon 跑在 container 中时,默认行为偏安全:

  • 推荐默认 OD_TRUST_DEFAULT=restricted。能力(mcpsubprocessbashnetwork)需要 operator 通过 od plugin trust <id> 显式 opt-in,或通过 OD_TRUSTED_PLUGINS allow-list env var 授权。
  • image 以 non-root user 运行;plugin sandboxes 继承这一点。
  • HTTP API 监听 OD_BIND_HOST;当设置为 0.0.0.0 时,Phase 5 落地后必须设置 OD_API_TOKEN,并且每个 request 都通过 Authorization: Bearer <token> 检查。若缺失,daemon 拒绝绑定 public interface 并退出。在 Phase 5 之前(即当前实现),hosted deployments 必须依赖 reverse proxy / network ACL 隔离 daemon 端口。
  • 未来 hardening pass(Phase 5)可选地把每个 plugin 的 bash/MCP work 跑在 per-run nested containers(firecracker / gVisor / sysbox)中,使不可信插件不能逃出 run boundary。v1 single-tenant deployments 不要求。
  • Authentication scope(v1): 仅 single-tenant:一个共享 OD_API_TOKENMulti-tenant auth(per-user OAuth、RBAC、project ownership、billing)明确不属于 v1,作为 §18 中的开放问题跟踪。

15.8 解锁的生态动作

  1. Self-hosted enterprise。 企业托管私有 OD instance,注册内部 open-design-marketplace.jsonod marketplace add https://internal/...),限制插件只来自内部审过的集合。设计师和 PM 本地使用 desktop client;CI 使用 docker exec od od …
  2. Partner integrations。 厂商(CMS、设计工具、BI 平台、SaaS dashboards)把 OD 嵌入自己的 stack,增加 design generation。一个 image,无需 per-vendor port。
  3. Cloud-native CI。 「为日报生成 slides」变成 GitHub Action / GitLab pipeline / Tekton task:启动 ephemeral OD container,apply plugin,把 artifacts 放到 S3 / OSS / COS / OBS。
  4. Sovereign-cloud reach。 OD 可以在阿里云 / 腾讯云 / 华为云上不经修改运行,服务受监管区域客户;不需要重写,也不需要单独分发渠道。

16. 分阶段实现计划

Phase 0 — Spec freeze(1–2 天)

  • 本文档落地为 docs/plugins-spec.md(当前)。
  • JSON Schemas:docs/schemas/open-design.plugin.v1.jsonopen-design.marketplace.v1.json
  • Pure-TS contracts:packages/contracts/src/plugins/{manifest,context,apply,marketplace,installed}.ts
  • Migration note:现有 skills/design-systems/craft/ 100% 向后兼容。SKILL.md frontmatter 不变。

Validation:pnpm guardpnpm typecheckpnpm --filter @open-design/contracts test

Phase 1 — Loader, installer, persistence + headless MVP CLI 闭环(5–7 天)

设计原则: Phase 1 完成时,外部 code agent(Claude Code、Cursor、Codex、CI 脚本)必须能完整走完 install plugin → create project → run → consume artifact 闭环,不依赖任何 UI。spec 强调 CLI 是 canonical agent-facing API(§12),所以最小 CLI 闭环必须与 plugin verbs 一起在 Phase 1 落地,不能拖到 Phase 2C。

Phase 1 内容(合并原 Phase 1 + Phase 2C 的最小子集):

  • packages/plugin-runtime,包含 parsers/adapters/merger/resolver/validator。
  • apps/daemon/src/plugins/{registry,installer,apply}.ts;重构现有 skills/DS/craft loaders 以委托给它。
  • SQLite migration:installed_pluginsplugin_marketplacesapplied_plugin_snapshots schema 也在此 phase 落地(即使 §10 pipeline 完整支持要等 Phase 2A)。
  • Endpoints:GET /api/pluginsGET /api/plugins/:idPOST /api/plugins/install(folder + github tarball)、POST /api/plugins/:id/uninstallPOST /api/plugins/:id/applyGET /api/atomsGET /api/applied-plugins/:snapshotId
  • Plugin CLI verbs: od plugin install/list/info/uninstall/apply/doctorod plugin apply --json 是 Phase 2 inline rail 与外部 code agents 的必要能力,必须在此返回包含 appliedPlugin: AppliedPluginSnapshotApplyResult
  • Headless MVP CLI 闭环(新提前): od project create/list/infood run start/watch/cancel(含 --follow 与 ND-JSON streaming)、od files list/read。这些包装 desktop UI 今天已经使用的 endpoints(POST /api/projectsPOST /api/runsGET /api/runs/:id/events、project list/read endpoints),不新增 HTTP surface,只新增 CLI surface。
  • <daemonDataDir>/plugins/<id>/ 写入路径,带 safe extraction(path-traversal guard、size cap、symlink rejection)。

Validation:

  • pnpm --filter @open-design/plugin-runtime test(parser fixtures:pure SKILL.md、pure claude plugin、metadata-only open-design.json、三者组合、SKILL frontmatter mapping)。
  • pnpm --filter @open-design/daemon testpnpm guardpnpm typecheck
  • End-to-end headless smoke(与 §12.5 walkthrough 等价):od plugin install ./fixtures/sample-pluginod project create --plugin <id> --jsonod run start --project <pid> --plugin <id> --followod files read <pid> <artifact>。要求 produced artifact bytes 与同一插件在 Phase 2A UI 流程下产出完全相同
  • Apply 纯净性 smoke:od plugin apply <id> 后取消 send,project cwd 无 staged assets、无 .mcp.jsonapplied_plugin_snapshots 行存在但未被任何 run/project 引用。

Phase 2A — Inline UI + 完整 snapshot 持久化 + Daemon-only plugin runs(4–6 天)

  • 新 components:ContextChipStripInlinePluginsRailPluginInputsForm(§11.6)。
  • Inline rail 先集成进 NewProjectPanelChatComposer integration 留到 Phase 2B。
  • applyPlugin() wiring;POST /api/plugins/:id/apply 返回带 inputs: InputFieldSpec[]appliedPlugin 的完整 ApplyResult
  • POST /api/projectsPOST /api/runs 接受 pluginId / pluginInputs / appliedPluginSnapshotId(§11.5),落库时把 applied_plugin_snapshot_id 关联到 runs / conversations / projects
  • daemon composeSystemPrompt() 接受 ResolvedContext,发出 ## Active plugin — <title>## Plugin inputs block。
  • Web API-fallback mode 拒绝 plugin runs(§11.8):当 web sidecar 检测到 fallback path + pluginId 时,返回 409 plugin-requires-daemon;UI 显示「启动 daemon 或切换到 desktop / headless」。
  • §10.3 GenUI 最小集落地:
    • apps/daemon/src/genui/:4 类内置 surface(form / choice / confirmation / oauth-prompt)的 registry / event publisher / genui_surfaces store。
    • SQLite 迁移:genui_surfaces 表 + 三个索引。
    • HTTP:GET /api/runs/:runId/genuiGET /api/projects/:projectId/genuiPOST /api/runs/:runId/genui/:surfaceId/respondPOST /api/projects/:projectId/genui/:surfaceId/revokePOST /api/projects/:projectId/genui/prefill
    • SSE / ND-JSON 流加 genui_surface_requestgenui_surface_responsegenui_surface_timeoutgenui_state_synced event。
    • Web:GenUISurfaceRenderer 嵌入 ProjectView chat stream;GenUIInbox drawer 列出 project 级 persisted surfaces;revoke 入口可用。
    • CLI:od ui list/show/respond/revoke/prefillod run watch ND-JSON 包含 genui_* event;exit code 73 接通。
    • Persistence 行为验证: 同一 project 完成一次 oauth-prompt 后,第二个 conversation 触发同 surface id 时广播 request,事件流上仍发 genui_surface_response { respondedBy: 'cache' }
  • §9 connector capability gate 落地:
    • apps/daemon/src/plugins/connector-gate.ts(§11.3 表):apply 阶段读 od.connectors.required[]、调 connectorService.listAll() 计算 connectorsResolved、为未 connected 的 required connector 派生 implicit oauth-prompt GenUI surface(§10.3.1,oauth.route='connector')。
    • SQLite 迁移:applied_plugin_snapshotsconnectors_required_json / connectors_resolved_json / mcp_servers_json(§11.4)。
    • apps/daemon/src/tool-tokens.ts 颁发 connector tool token 之前调 connector-gate 校验 plugin trust × connector:<id>/api/tools/connectors/execute 执行前再校验一次。
    • od plugin doctor:(a) od.connectors.required[].id 必须存在于 connectorService catalog;(b) tools[] 是该 connector allowedToolNames 子集;(c) oauth.route='connector'oauth.connectorId 必须出现在同 plugin 的 connector 声明里。
    • exit code 66 / 409 capabilities-requireddata.requiredconnector:<id> 形态。
    • od plugin trust / od plugin apply --grant-caps 接受 connector:<id> 形态(§9.1)。

Validation:e2e in e2e/

(a) install local plugin → Home inline rail → click plugin → home prefilled → run produces design;run 在 SQLite 中携带 applied_plugin_snapshot_idapplied_plugin_snapshots 行包含 manifest_source_digestinputs_json

(b) apply 后取消发送,project cwd 无 staged assets,.mcp.json 不存在。

(c) od plugin replay <runId> 即使在源 plugin 已被 od plugin update 升级后,仍能产生与原 run 完全一致的 prompt(snapshot 不可变)。

(d) Web API-fallback mode(OD daemon 关闭、浏览器直连 provider)下,UI 中 inline rail 渲染 plugin 卡片但点击「Use」时弹出 daemon-required 提示;daemon 启动后再点击恢复正常。

(e) Plugin 声明 oauth-promptconfirmation 两种 surface:在 conversation A 完成后,conversation B 在同 project 内重新 apply 同插件,run 中 oauth-promptpersist=project)走 cache;confirmationpersist=run)重新询问。od ui revoke 后下一次 run 又重新询问 oauth-prompt

(f) Connector trust gate:local plugin 声明 od.connectors.required = [{ id: 'slack', tools: ['channels.list'] }]显式 grant connector:slack

  • apply(无 --grant-caps) → exit 66 / 409,data.requiredconnector:slackdata.remediationod plugin trust <id> --caps connector:slack
  • apply 带 --grant-caps connector:slack,但 connector 尚未 connected → daemon 派生一条 implicit oauth-prompt(surface id __auto_connector_slackpersist=project),用户在 UI 完成现有 connector OAuth flow 后 surface 落 resolvedapplied_plugin_snapshots.connectors_resolved_json 包含 { id:'slack', accountLabel:..., status:'connected' }
  • 同 project 第二次 apply 同插件:connector:slack 已 connected → 不再派生 oauth-prompt;snapshot connectors_resolved_json[0].status='connected' 命中 cache。
  • 第二个 plugin 声明 od.connectors.required = [{ id: 'notion', tools: [...] }]未声明 connector:notion capability → apply 失败 exit 66;额外验证 plugin trust × connector tool 的 token 颁发:手动 curl /api/tools/connectors/execute 模拟绕过尝试,daemon 返回 403 connector-not-granted

Phase 2B — Marketplace deep UI + ChatComposer apply(4–6 天)

  • 新 routes /marketplace/marketplace/:id
  • 新 components:MarketplaceViewPluginDetailView(§11.6)。
  • ChatComposer 集成 inline rail(§8.4)。
  • 通过 /api/plugins/:id/preview 渲染 preview(镜像现有 /api/skills/:id/example),并实现 §9.2 preview sandbox。

Validation:e2e in e2e/:install local plugin → marketplace → detail preview sandbox → "Use" → Home or ChatComposer prefilled → run produces design。restricted preview 不能访问 /api/*

Phase 2C — 剩余 CLI / files write / 高级 watch(2–3 天)

原 Phase 2C 中"最小 project + run + files CLI"已经提前到 Phase 1。此阶段只剩进阶操作。

  • od project delete/importod run list/logs --sinceod files write/upload/delete/diffod conversation list/new/info
  • 这些包装现有 endpoints,不引入新 HTTP surface。

Validation:扩展 §12.5 的 walkthrough:od project import 一个本地文件夹 → od plugin applyod run replay <runId> 在 import 出来的 project 上重跑。

Phase 3 — Federated marketplace + tiered trust(3–5 天)

  • od marketplace add/remove/trust/untrust/list/refreshod plugin install <name> 通过 marketplaces 解析。
  • GET /api/marketplacesPOST /api/marketplacesGET /api/marketplaces/:id/plugins
  • PluginDetailView 上的 Trust UI(capability checklist + "Grant" action)。
  • Apply pipeline 通过 trust + capabilities_granted gate。
  • Bundle plugins(一个 repo 内多个 skills + DS + craft):installer fan out 到 namespaced ids 的 registry。
  • od plugin doctor <id> 执行完整 validation。

Validation:从本地 mock marketplace.json 安装 plugin、rotate ref、uninstall。Restricted plugin 在点击 "Grant" 前不能启动 MCP server。

Phase 4 — Atoms, publish-back, full CLI parity(1–2 周,可拆)

  • docs/atoms.md 中记录 atoms;通过 GET /api/atoms 暴露。
  • od plugin export <projectId> --as od|claude-plugin|agent-skill:从已有 project 生成 publish-ready folder。
  • od plugin run <id> --input k=v --follow:apply + run start + watch 的 shorthand wrapper。
  • od plugin scaffold interactive starter。
  • od plugin publish --to anthropics-skills|awesome-agent-skills|clawhub 打开 PR template。
  • 剩余 CLI parity: od conversation list/new/infood skills/design-systems/craft/atoms list/showod status/doctor/versionod config get/set/listod marketplace search。这些基本都是纯 CLI 工作,endpoints 已存在或很轻量。
  • 可选:把 atoms 提取到 skills/_official/<atom>/SKILL.md。只在 Phases 1–3 稳定后做。
  • §10.3.5 AG-UI 完整对齐:
    • 新 package @open-design/agui-adapter:把 OD 的 PersistedAgentEvent + GenUIEvent 双向映射到 AG-UI canonical events(agent.messagetool_callstate_updateui.surface_requestedui.surface_responded)。
    • daemon 增加可选 /api/runs/:runId/agui SSE 端点,输出 AG-UI canonical events,使 CopilotKit / 其他 AG-UI client 直接消费 OD run。
    • Plugin manifest 升级允许 od.genui.surfaces[].component:相对路径指向 plugin 内 React 组件(capability gate genui:custom-component),由 desktop / web renderer 在 sandbox 中加载。
    • Open-Ended (MCP-Apps / Open-JSON) 模式:让 plugin 通过 MCP server 推送任意 JSON UI tree,desktop / web 以受限 schema 渲染。

Validation:
(a) install a published plugin → export from a real project that used it → diff produced manifest against original。
(b) 「UI vs CLI parity test」:挑 5 个 desktop-UI workflows,仅通过 od … 重放,byte-for-byte 比较 produced artifacts(对应 §12.6 implementation rule)。

Phase 5 — Cloud deployment + pluggable storage(并行,可拆)

此 phase 独立于 Phases 1–4;Phase 1 落地后即可并行,因为 headless mode 与 daemon contract 从 Phase 1 起就稳定。

  • Container image(第 1 周): multi-arch linux/amd64 + linux/arm64 Dockerfile,内容见 §15.1;CI 在每次 main commit 推 :edge,tag 时推 :<version>
  • Reference manifests: tools/pack/docker-compose.ymltools/pack/helm/。compose file 展示 daemon + reverse proxy pattern;Helm chart 参数化任意云的 volume + secret patterns。
  • Bound-API-token guard(Phase 5 新增能力): daemon 在没有 OD_API_TOKEN 时拒绝绑定 OD_BIND_HOST=0.0.0.0/api/* 上 bearer-token middleware(仅 loopback host 跳过)。
  • S3-compatible blob stores 的 ProjectStorage adapter(适用于 AWS S3、GCS S3-compat、Azure Blob via shim、Aliyun OSS、Tencent COS、Huawei OBS)。
  • Postgres 的 DaemonDb adapter(让 multi-replica deployments 共享 state)。
  • Per-cloud one-click templates 放在独立 open-design/deploy repo(CloudFormation、Deployment Manager、ARM、Aliyun ROS、Tencent TIC、Huawei RFS),非阻塞,单独跟踪。

Validation:

  • docker run smoke:image starts、web UI renders、od plugin install works inside the container。
  • Multi-cloud smoke:把 compose file 部署到 AWS Fargate、GCP Cloud Run、Azure Container Apps、Aliyun SAE、Tencent CloudRun、Huawei CCE;运行固定 plugin → produced artifact bytes across clouds identical。
  • Pluggable storage smoke:同一 plugin、同一 project,在 local-disk + SQLite 与 S3 + Postgres adapters 间切换;produced artifacts identical。

17. 示例

17.1 最小可行插件(只有 SKILL.md)

OD 通过 skills-protocol.md 中现有 od: frontmatter loader 把它当作插件读取。不需要 open-design.json:这个插件缺少 marketplace polish,但完全可运行。

my-plugin/
└── SKILL.md
---
name: my-plugin
description: One-paragraph what+when.
od:
  mode: prototype
  scenario: marketing
---
# My Plugin
Workflow steps...

17.2 Enriched plugin(可跨目录发布,完整 OD 产品表面)

my-plugin/
├── SKILL.md
├── README.md
├── open-design.json
├── preview/
│   ├── index.html
│   ├── poster.png
│   └── demo.mp4
└── examples/
    └── b2b-saas/index.html

SKILL.md 保持可移植:Cursor / Codex / OpenClaw 可直接读取。open-design.json 增加 preview、query、chip strip、capabilities。这个 repo 可以不经修改地列入 §14 的所有 catalog。

17.3 Bundle plugin(一个 repo 中包含多个 skills + DS + craft)

my-bundle/
├── SKILL.md                          # bundle-level overview (catalog uses this)
├── open-design.json                  # kind: 'bundle'; lists nested skills
├── skills/
│   ├── deck-skeleton/SKILL.md
│   └── deck-finalize/SKILL.md
├── design-systems/linear-clone/DESIGN.md
└── craft/deck-pacing.md

installer 会把 nested skills/design-systems/craft fan out 到 registry 的 namespaced ids(my-bundle/deck-skeleton 等),因此其它插件可以单独引用其中任一项。

18. 风险与开放问题

Risk Mitigation
OD plugin schema 与更广义 skill spec 发生 drift open-design.json 仅为 sidecar;永不修改 SKILL.md。CI against public anthropics/skills repo。
任意 GitHub install = supply-chain risk 默认 restricted;bash/hooks/MCP 前必须 capability prompt;记录 pinned-ref。
composeSystemPrompt() 已经超过 200 行 ## Active plugin block 追加在现有位置;不重排 layers。
ExamplesTab 与 Marketplace overlap Phase 2 保持 ExamplesTab;Phase 3 折叠为 Marketplace 的「Local skills」tab。
Atoms-as-plugins 范围大 Entry slice 已落地:bundled atom SKILL.md bodies 与 renderActiveStageBlock() 已存在;OD 基础 designer/discovery prompt 仍留在 daemon code,等待 §23 剩余迁移完成。
Project-local plugins 被提交到用户 repo 仅发现 <projectCwd>/.open-design/plugins/;通过 od plugin install --project opt-in。
Trust model 让 community plugins 默认半功能 详情页提供清晰 capability checklist 与一键「Grant all」;restricted 行为显式,不静默。
插件自带 MCP servers 可能无法启动 od plugin doctor dry-launch declared MCP commands;在「Use」前暴露失败。
applied_plugin_snapshots 表无界增长 按 PB2(已决):未被引用的 snapshot 默认 30 天后过期;被 run / conversation / project 引用的 snapshot 永久 pin(reproducibility 优先);GC worker 与 od plugin snapshots prune --before <ts> 提供清理通道。
daemon composeSystemPrompt 与 contracts composeSystemPrompt 漂移 按 PB1(已决):plugin block renderer 从 Phase 2A 起位于 packages/contracts/src/prompts/plugin-block.ts;两份 composer import 同一个函数,不再需要 byte-for-byte fixture。
od.pipeline devloop 无限循环烧 quota until 必填且 syntax 受限;OD_MAX_DEVLOOP_ITERATIONS 上限(默认 10);UI 与 CLI 提供「Stop refining」打断。
OD_HOST / OD_BIND_HOST 命名漂移 Spec 全部使用现有 daemon 已读取的 OD_BIND_HOST;不引入 OD_HOST 别名;§15.3 显式标注与历史草稿的差异。
没有 bound-API-token guard 的 hosted deployments 可能公开泄露 API(Phase 5 之前依赖 reverse proxy) Phase 5 落地后 daemon 在无 OD_API_TOKEN 时拒绝绑定 OD_BIND_HOST=0.0.0.0/api/* enforce bearer-token middleware;§15.3 / §15.7 记录当前 vs. 目标差异。
Sovereign-cloud customers(阿里云 / 腾讯云 / 华为云)需要 provider-specific secret + storage integrations S3-compatible adapter 覆盖三者的 blob storage(Phase 5);env-var secrets 各云都可用;cloud-specific KMS integrations post-v1。
Multi-cloud testing matrix 太大 Phase 5 先发布一个 canonical compose smoke(单云),再逐步加云;per-cloud one-click templates 放在 open-design/deploy 独立演进(§15.5)。
恶意 plugin 通过 GenUI surface 钓鱼用户敏感信息 od.genui.surfaces[] schema 必须列入 manifest 并由 od plugin doctor 校验;运行时拒绝未声明 surface kind / surface id;oauth-promptconfirmation 的 issuer / capability 信息显示「来自 plugin ,由 marketplace 验证」;restricted 插件触发 oauth-prompt 前还需要 network capability 显式 grant(§9)。
AG-UI 协议生态可能演进,OD 自有 wire-format 与 AG-UI canonical 漂移 OD-native GenUIEvent 仍是内部 source of truth;@open-design/agui-adapter 是外部投影层,因此 upstream 协议升级不绑死 daemon 或 web renderer release cadence。
genui_surfaces 表 跨 conversation 复用导致用户「忘记自己授过权」 UI 的 GenUIInbox、CLI 的 od ui list --project <id> 必须列出所有 persist=project 的 resolved row 与 revoke 入口;hosted mode 提供 OD_GENUI_PROJECT_TTL_DAYS 让 operator 设置默认过期;revoke 操作写 audit 日志。

落代码前值得确认的开放问题:

  • Default trust tier:保持 tiered(当前),还是从 day 1 就切到 capability-scoped?
  • Marketplace JSON shape:是否偏离 anthropic 的 marketplace.json shape,还是保持 byte-compatible,从而复用现有 claude-plugin marketplaces?(默认:保持 byte-compatible。)
  • od plugin run headless contract:当前是否足够,还是也为非 CLI agents 暴露 HTTP POST endpoint?(默认:v1 只 CLI;Phase 4 如需要再补 HTTP。)
  • Multi-tenant auth(per-user OAuth、RBAC、project ownership、billing) 明确不属于 v1。Docker image 设计为 single-tenant(一个 OD_API_TOKEN)。Multi-tenancy 是 post-v1 story,需要单独 spec。请确认 first ecosystem release 这个 scope 是否可接受。
  • Hosted mode 的 trust propagation:当前 spec 已锁定 arbitrary GitHub / URL / local 插件默认 restricted,第三方 marketplace 也默认不传递 trust。还需确认 hosted deployments 是否允许 operator 通过 OD_TRUSTED_PLUGINS 直接 trust 单个插件,还是必须先 trust 它所属 marketplace。
  • Discovery-time hot reload:daemon 是否 watch <daemonDataDir>/plugins/(开发体验好),还是只在 od plugin install/update/uninstall 后 reload(稳定性高)?(默认:watch,500ms debounce。)
  • Versioning policy:安装时 pin tag/SHA,还是默认跟踪 default branch 并提供 opt-in pin?(默认:安装时 pin resolved ref;od plugin update 重新 resolve。)
  • Plugin prompt block 提到 contracts 共享层的时机已按 PB1 解决。 Phase 2A 已把 renderPluginBlock(snapshot) 放到 packages/contracts/src/prompts/plugin-block.ts;两份 composer import 同一函数;v1 fallback rejection 规则保留。
  • AppliedPluginSnapshot 留存策略已按 PB2 解决。 被 run / conversation / project 引用的 snapshot 永久 pin;未引用 snapshot 默认 30 天过期;OD_SNAPSHOT_RETENTION_DAYS 是 operator opt-in;od plugin snapshots prune 保留为强制清理出口。
  • Devloop 计费颗粒度:每次 stage 的 iteration 是否单独按 token 计费 / 单独 audit / 单独 cancellable?(默认:单独 audit + 可单独 cancel;计费颗粒度跟随 provider 实际消耗,不在 spec 层定义新颗粒。)
  • od.taskKind 是否成为 marketplace 一等过滤维度:现有 kind / mode / scenario 是否需要为新增的 taskKind 重排 UI filter?(默认:marketplace 顶部增加 taskKind tab;现有 filter 保留为二级。)
  • od.genui.surfaces[].component 是否进 v1已作为 gated extension path 解决。 Manifest schema 接受该字段,od plugin doctor 校验 genui:custom-component 与 path traversal;内置 form / choice / confirmation / oauth-prompt renderer 仍是主产品默认。
  • GenUI persisted state 与 AppliedPluginSnapshot 的耦合:当 plugin 升级且 surface.schema 变了,旧 row 自动 invalidated;但是否要同时强制重新 apply plugin(生成新 AppliedPluginSnapshot),还是允许仅 surface 失效、其余 snapshot 不变?(默认:仅 surface 失效;od plugin doctor 提示 schema drift;replay 仍走旧 snapshot。)
  • AG-UI 协议引入时机已解决。 @open-design/agui-adapterGET /api/runs/:runId/agui 已作为可选互操作表面交付;OD-native GenUI 仍是内部 renderer,CopilotKit 不是主产品必需依赖。

19. 为什么这是 Open Design 的重要一步

  • 继承供给。 anthropics/skillsawesome-agent-skillsclawhubskills.sh 上的每个 public agent skill,只需一个可选 open-design.json 就能成为 OD 插件;反过来,每个 OD 插件也能不经修改发布到这些 catalog。
  • 边界干净。 新代码落在两个 pure-TS packages(packages/plugin-runtimepackages/contracts/src/plugins/*)和一个 daemon module group(apps/daemon/src/plugins/);无跨 app coupling,无 contracts package leaks,无 SKILL.md fork。遵守根 AGENTS.md 的约束。
  • 可逆重构。 现有 loaders(apps/daemon/src/skills.ts 等)与 composeSystemPrompt() 保持 public shape;Phase 1 是 drop-in delegate,Phase 2 只追加 prompt block。
  • CLI 从 day 1 存在。 每个新 endpoint 都有对应 od plugin … subcommand,因此同一 surface 可被任何 code agent 访问,不依赖 desktop app。
  • Marketplace-first 产品叙事。 从 Phase 2 开始,home screen 变成「input + chip strip + deep marketplace」;这正是 brief 中描述的反转:主交互 = 输入框 + 插件社区。

20. Post-v1 可扩展性:artifact taxonomy、evaluators 与 production handoff

本节有意作为 contract reservation,而不是 v1 implementation commitment。Plugin runtime、pipeline 与 marketplace 可以在 evaluator 与 production-handoff stack 完整落地前先发布,但核心数据模型必须为这些能力留下稳定位置,避免后续 preview / export / deploy 逻辑各自分叉。

20.1 Artifact taxonomy

每个 plugin 产出的 artifact 最终都应该携带三组互相独立的标签:

Field 含义 初始取值
artifactKind 这个 artifact 作为产品对象是什么 html-prototype, deck, interactive-video, design-system, code-diff, production-app, asset-pack
renderKind OD 应该如何 preview 或打开它 html, jsx, pptx, markdown, video, image, diff, repo
handoffKind 它对下游交付承诺到什么程度 design-only, implementation-plan, patch, deployable-app

§11.5.1 的 v1 ArtifactManifest 扩展已经预留这些 optional 字段。v1 producer 可以保守推断;v1 consumer 必须 preserve 未知值。这样后续阶段不需要依赖文件名 sniffing(index.htmlslides.jsondiff.patch),也不需要把 export 与 handoff 决策硬塞进 od.mode

20.2 Evaluator atoms

现有 critique-theater 与 devloop stage 是正确入口,但「critique」不能长期停留在纯 LLM 主观评价。后续 evaluator 能力应该作为 first-party atoms 接入 od.pipeline.stages[]

  • visual-diff 对比截图或 Figma capture 与生成后的 HTML。
  • responsive-check 校验常见 viewport breakpoints。
  • accessibility-check 运行自动化 a11y checks,并总结需要人工判断的问题。
  • brand-consistency-check 根据 active DESIGN.md 对比颜色、字体、间距和语气。
  • build-test 为 code outputs 运行 package-scoped build / typecheck / lint commands。
  • screenshot-regression 记录并比较 devloop iterations 之间的 preview snapshots。

Evaluator 结果应该先作为 run events 落库;稳定后,再在 artifact manifest 上汇总为 evaluationSummary 并链接到详细 reports。它们不需要新 plugin primitive:它们就是 atoms,像其他 atom 一样走 capability gate,也能参与现有 repeat / until devloop condition。

20.3 Production handoff

code-migrationfigma-migration 初期可以先产出 html-prototypecode-diffimplementation-plan artifacts。后续 production-handoff phase 会把这条路径升级为可交付业务代码 workflow,但不需要改变 plugin substrate:

  1. Target stack contract. Run 记录 framework、package manager、styling system、component library、routing model 与 test command assumptions。
  2. Design-token mapping. 生成的 tokens 映射到现有 app tokens;如果无法直接映射,先生成 migration plan,再 patch code。
  3. Component mapping. Agent 把 artifact-level components 映射到 repo 内已有组件、shadcn-style primitives,或新建符合 ownership boundary 的 local components。
  4. Patch safety. 输出是可 review 的 diff,并附 build/test evidence,而不是 opaque generated repo overwrite。
  5. Delivery evidence. 最终 artifact 携带 handoffKind: 'patch'handoffKind: 'deployable-app'、evaluator summaries,以及 export/deploy targets。

这让 v1 的承诺保持诚实:plugins 已经可以组织 design-to-code workflows,但完整 production delivery 是建立在 artifactKind、evaluator atoms 与 repo-aware patch orchestration 之上的更严格 contract。

21. 场景覆盖矩阵与交付路线图

本节是对 §1 已声明的四个产品场景("Four product scenarios":new-generation / code-migration / figma-migration / tune-collab)与 v1 实际交付能力之间的诚实对账。它不引入新的设计面,而是把 §10(atoms,部分标 (planned))、§16(分阶段计划)、§18(开放问题)、§20(post-v1 reservation)中已经隐含的 gap 集中到一处,作为未来实施时不需要重读全文就能 reconcile 的单点表格。

为方便阅读,这里把 OD 产品锁定的"四个核心 agent-native 设计问题"重申一次:

  1. 存量 Figma → HTML / artifact 迁移,并基于代码做调优、设计体系一致性、协作。
  2. 存量代码库刷新 —— 让旧仓库变得"更好看"且 patch-safe。
  3. 从 0 到 1 的设计 —— 类 Figma 设计、好看的原型、PPT、交互式视频,从 brief 起步。
  4. 设计 → 可交付业务代码 —— 把生成出来的设计落成可上线的业务级代码。

21.1 一览表:v1 覆盖度

# 场景 taskKind spec 是否覆盖契约 v1 实现状态 阻挡 "v1 一键 native" 的关键缺口
1 Figma 迁移 figma-migration 是(§1,§10 atom 占位) 部分 —— figma-extract / token-map 在 §10 标 (planned);pipeline shape 与 provenance 已就绪 需要补这两个 (planned) atom;其他(DS 注入、GenUI OAuth、devloop、snapshot)v1 已经具备
2 存量代码库刷新 code-migration 是(§1、§10、§20.3) v1 不交付 —— code-import / rewrite-plan / patch-edit / diff-review 全部 (planned)build-test evaluator 在 §20.2 需要 §20.3 整套契约(target stack / token mapping / component mapping / patch safety / build evidence)
3 0→1 设计(原型 / PPT / 交互式视频) new-generation 是(§1 默认 reference pipeline) v1 已交付 —— 所需的 discovery-question-formdirection-pickertodo-writelive-artifactmedia-image/video/audiocritique-theater 全部 implemented 可选:把 §20.2 的 visual-diff / brand-consistency-check 提前到 Phase 2,让 critique 有客观信号
4 设计 → 可交付业务代码 tune-collab(handoff 侧) 部分(§20.3 显式 post-v1) v1 不交付 —— v1 的 handoffKind 上限是 'design-only' | 'implementation-plan' | 'patch''deployable-app' 是 post-v1 要么完整落 §20.3,要么把 §14.3 的 OD ↔ 外部 code-agent 接力正式化为 v1 的"设计→生产代码"路径

阅读规则:只有场景 3 在 v1 完全 native 命中。场景 1、2 的契约和命名 v1 已经预留,但需要补 atom 实现;场景 4 显式 post-v1,过渡期靠 §14.3。

21.2 v1 真正解决的 "agent-native 设计" 问题

v1 在 native 完整性上停在场景 3,但它交付的 substrate 对四个场景都是正确的 substrate。下列 5 条契约是未来场景工作不需要重新设计就能直接继承的部分:

  1. plugin = workflow contract,不是 UI extension(§1,§13)。Figma 时代的 "plugin 拥有宿主的一个 panel" 假设被替换成 "plugin 是 manifest + atoms + assets,agent 通过 ApplyResult 消费"。场景 1、2、4 都直接继承这条;迁移和生产 handoff 都不需要重开这个决策。
  2. od.pipeline + devloop 收敛(§10.1,§10.2)。repeat: true + until 是场景 1、2、3 共用的收敛机制:场景 3 收敛在 critique 评分,场景 1 收敛在 token-map 保真度,场景 2 收敛在 build-test 通过。同一个 loop primitive 跑三个场景,差异仅在 until 信号。
  3. GenUI 跨对话持久化状态(§10.3,§11.4 genui_surfaces 表)。一次性 Figma OAuth、品牌确认、目标技术栈确认、direction pick 都在 project / conversation / run 层级被记住,并在后续多轮对话中复用。场景 1(Figma OAuth)和场景 2(目标栈确认)依赖这条;没有它,每次新对话都重新追问,体验直接崩塌。
  4. AppliedPluginSnapshot 不可变 + 重放(§8.2.1)。跨 plugin 升级的可重放性是场景 1、2 半年后仍可审计的关键 —— "哪个 figma 文件 → 哪个 token map → 哪个生成的 diff" 可以从一行 snapshot 完整复现。场景 3 受益较小,但成本为零。
  5. CLI-first headless 模式(§11.7,§14.3)。这是 v1 阶段场景 4 的 fallback:OD 把 SKILL.md / DESIGN.md / craft / 生成 artifact 都 stage 到 project cwd,由 Cursor / Claude Code / Codex 在那个 cwd 内完成 patch。这条接力在 §14.3 已规定;§21.5 把它升格为 v1 的"设计→生产代码"明确契约。

v1 显式不解决的部分(一次性写明,避免歧义):

  • native 的 figma-extract / token-map atom 对(场景 1)。
  • native 的 code-import / rewrite-plan / patch-edit / diff-review 链路加 build-test evaluator(场景 2,及场景 4 的 patch 端)。
  • §20.3 的 production-handoff 完整契约(场景 4 的 native 路径)。
  • 在 OD 内部做的 repo-aware 多文件 diff 编排 + build evidence(场景 4 的 native 路径)。

21.3 逐场景 gap 分析

21.3.1 场景 1:Figma 迁移(figma-migration

v1 已经免费给到的:

  • figma-migrationtaskKindAppliedPluginSnapshot(§8.2.1)、ArtifactManifest(§11.5.1)和 marketplace filters(§18 关于一级 taskKind filter 的开放问题)中是一等枚举成员。
  • 推荐 pipeline shape 已锁定:figma-extract → token-map → generate → critique(§1)。
  • 现有 composeSystemPrompt() 链路负责把 active design system 注入 prompt;只要 token-map 落地,"与所选 DESIGN.md 的一致性" 就是下游效果,不需要新基础设施。
  • 只要注册 Figma connector,Figma OAuth 就走 GenUI 的 oauth-prompt surface kind + oauth.route='connector'(§10.3.1);跨对话持久化通过 genui_surfaces(§10.3.3)自动生效。
  • parentArtifactId 链(§11.5.1)允许迁移产物直接成为后续 tune-collab run 的种子,无需重新抽取。

v1 native 落地仍缺:

  • figma-extract atom —— 通过 Figma REST 从 Figma file URL 抽出 node tree、tokens、assets。spec 占了 id;实现不在 v1 范围。
  • token-map atom —— 把抽出来的 tokens 映射到 active OD design system(design-systems/<id>/DESIGN.md)。spec 占了 id。
  • 如果 apps/daemon/src/connectors/ 里还没有 Figma connector,则需要补;如果已有,那么仅缺前两个 atom。

为什么这是三个 "缺失" 场景里最容易落地的:

  • 输入边界明确:Figma 文件 URL + OAuth token。不需要应付"任意仓库结构"。
  • 输出边界是 HTML / artifact,不是真实 repo patch;patch-safety 不在关键路径。
  • 第一版 critique 可以保持 LLM 主观;客观 evaluator(如对原 Figma 导出做 visual-diff)可以增量加上。

建议落地路径: 先把 figma-extract + token-map 作为 out-of-tree plugin 发布(这样能独立迭代,不阻塞 daemon 发版节奏);等 Figma REST 表面与 token-mapping 启发式稳定后,再升级为一方 atom。

21.3.2 场景 2:存量代码库刷新(code-migration

v1 已经免费给到的:

  • code-migrationtaskKind 与推荐 pipeline shape code-import → design-extract → rewrite-plan → generate → diff-review 已预留。
  • ArtifactManifest 已支持 artifactKind: 'code-diff'renderKind: 'diff' | 'repo'handoffKind: 'patch'(§11.5.1)。
  • tune-collabparentArtifactId 链是"先生成候选刷新方案,再在同一个 artifact 上迭代"的正确 primitive,v1 已交付。

v1 native 落地仍缺(基本就是 §20.3 全部):

  • code-import atom —— clone 或读取已有仓库,抽出与设计相关的结构(当前组件树、token 使用、routing model)。仅占 id。
  • design-extract atom —— 从源代码 / Figma / 截图抽取 design tokens。仅占 id。
  • rewrite-plan atom —— 长时多文件重写规划,对接 §20.3 的 "Component mapping" 与 "Target stack contract"。仅占 id。
  • patch-edit atom —— 遵循 rewrite plan 做小步 file patch,并正确产出 parentArtifactId。仅占 id。
  • diff-review atom —— 把重写渲染成可 review 的 diff。仅占 id。
  • build-test evaluator atom(§20.2)—— 对代码产物运行 package 范围的 build / typecheck / lint / test,并发出 critique 信号。仅占名字。

为什么是三个 "缺失" 场景里最难的:

  • 输入是"任意 repo + 任意 build/test 配置";agent 不能瞎猜。
  • 输出是带 build evidence 的 repo patch;patch-safety 在关键路径上。
  • devloop 的 until 信号是 "build + test pass + visual-diff 阈值",把三个缺失 atom 耦合在一起。

v1 fallback(同时见 §21.5): 不在 OD 内部端到端跑这条,而是引导用户走 §14.3 的 headless pipeline —— OD 产出 html-prototypeimplementation-plan artifact;Cursor / Claude Code 在用户自己的 repo cwd 里 apply patch 并跑 build/test。v1 不阻挡这条路径,只是不拥有 patch-application 这一步。

21.3.3 场景 3:0→1 设计(new-generation

v1 已经免费给到的(这是 v1 native 命中的场景):

  • 所有依赖 atom 已 implemented:discovery-question-formdirection-pickertodo-writelive-artifactmedia-image / media-video / media-audiocritique-theater。见 §10 atom 表。
  • 默认 reference pipeline discovery → plan → generate → critique 与典型 new-generation 流程一致;plugin 不需要声明 od.pipeline 也能拿到一条工作 pipeline。
  • 四个 GenUI 内置 surface kind(form / choice / confirmation / oauth-prompt)都直接服务这个场景。
  • live-artifact 的 live preview 与 od files watch(§12)合在一起,让 hot reload 和 CLI co-watch 在 v1 都能跑通。

唯一值得提前的优化:

  • §20.2 evaluator atoms —— 特别是 visual-diffresponsive-checkaccessibility-checkbrand-consistency-checkscreenshot-regression。v1 critique 是 critique-theater 的 5 维 LLM panel,主观;until: critique.score>=4 也因此是主观信号。把 visual-diffbrand-consistency-check 提前到 §16 Phase 2 是 ROI 最高的一步:它给 devloop 一个客观收敛信号,让 "好看 + 设计体系一致" 在所有 plugin 之间都能 scale。

结论: v1 已覆盖此场景,无需进一步设计工作。把 §20.2 提前视作可选的 Phase 2 质量提升,不是 blocking gap。

21.3.4 场景 4:设计 → 可交付业务代码(扩展的 tune-collab

事实陈述(§20.3 对此已显式说明):

  • v1 的 handoffKind 上限是 'design-only' | 'implementation-plan' | 'patch''deployable-app'ArtifactManifest(§11.5.1)中预留,但没有 §20.3 契约就在 v1 内不可实现
  • §20.3 的五条要求(target stack contract、design-token mapping、component mapping、patch safety、delivery evidence)整体上预设场景 2 的 atom 链已就位 —— build-testrewrite-planpatch-editdiff-review 缺一不可。没有场景 2,场景 4 的 native 路径无法落地。

v1 契约(锁定,详见 §21.5):

  • "设计 → 生产代码" 在 v1 是一个双产品接力:OD 拥有 design substrate(SKILL.md / DESIGN.md / craft / 生成 artifact 都被 stage 进 project cwd,加上 od files 管理的 artifact 簿记);用户原本的 code agent(Cursor / Claude Code / Codex / Gemini CLI)在用户的 repo cwd 内拥有真实 repo patch。
  • handoff 表面是 §14.3 的 headless pipeline,外加 od files read / od files watch 让 code agent 内联消费 artifacts。

native v1 交付需要:

  • 端到端实现场景 2(即 §21.3.2 全部)。
  • 加上在 OD 内部做的 repo-aware 多文件 patch 编排,并捕获 build evidence(这一步本身比 §20.3 还多迈一步)。
  • 加上"review patch、apply 或 reject"的 UI / CLI surface(v1 不交付)。

因此,本场景是"spec 描述的能力" 与 "v1 native 交付能力" 之间最大的 gap。未来 spec patch 在宣称这个场景为 native 之前,应当先落 §20.3,再补 repo-aware 编排。

21.4 建议交付顺序

§16 的分阶段计划保持不变;本节是只补不改的 delta roadmap,把 §21.3 中的 gap 映射到增量 phase 上,不重构既有计划。

序号 Phase 范围 为什么是这个顺序
1 §16 Phase 1 + Phase 2A(已计划) 端到端落地场景 3,按现有规范 在最干净的场景上验证 plugin-as-workflow-contract thesis
2 §16 Phase 2A++(小幅增补) 把 §20.2 的 visual-diffbrand-consistency-check 提前到 Phase 2A critique-theateruntil 拥有客观信号,使 1、2、3 三个场景都能基于更强的标准收敛
3 §16 Phase 2B + Phase 3(已计划) Marketplace 深页 UI + 联邦 trust 在任何跨产品接力规范化之前先打通三方 plugin 分发
4 新增 "Phase 6 — Figma 迁移 native 路径" 实现 figma-extracttoken-map,稳定后升级为一方 atom;发布官方 figma-migration plugin 三个缺失场景里最容易;输入输出边界清晰;复用 GenUI OAuth + parentArtifactId 链,无需新 primitive
5 新增 "Phase 7 — 生产 handoff 契约(§20.3 §21.3.2)" 实现 code-importdesign-extractrewrite-planpatch-editdiff-reviewbuild-test;冻结目标技术栈契约;冻结 design-token mapping 契约 把场景 2 落成 native,并解锁场景 4 的 native 路径
6 新增 "Phase 8 — 原生生产代码交付" 在 OD 内部做 repo-aware 多文件 patch 编排;提供 native "review and apply" 表面;把 handoffKind: 'deployable-app' 从预留升格为实现 闭环场景 4 native 路径;依赖 Phase 7

Phase 6、7、8 故意排在 §16 既有 Phase 5(云部署)之后,避免与 headless / Docker 稳定线索相互干扰。它们之间的内部顺序固定:7 必须先于 8;6 可与 7 并行,因为两个场景仅共享 parentArtifactId 链,不共享 atom 实现。

21.5 OD ↔ code-agent 接力作为 v1 的生产代码路径

在 Phase 6、7、8 落地之前,生产代码体验对用户而言走的是 §14.3 headless pipeline。本节把这个模式升格为 v1 的契约,使外部 code-agent 集成不需要重读 §14.3 就可以依赖它。

契约锁定四点:

  1. OD 把 design substrate stage 进 project cwd。 按 §14.3,daemon 把 SKILL.md / DESIGN.md / craft 写入 .od-skills/,把生成 artifact 通过 od files 写入 project cwd。cwd 通过 od project info <id> --json | jq -r .cwd 可发现。
  2. 用户的 code agent 在该 cwd 或自己的 repo cwd 内操作。 OD 不在 IDE 里跑;它作为 daemon 与 IDE 并列。Cursor / Claude Code / Codex / Gemini CLI 是 patch-application 的表面。
  3. 簿记留在 OD。 ArtifactManifest(§11.5.1)记录 sourcePluginSnapshotIdsourceTaskKind: 'tune-collab'(Phase 7 落地后还有 'code-migration')、handoffKind: 'patch'od files 记录每一字节的 artifact。哪怕 patch 由 code agent 完成,OD 仍是 audit log 的拥有者。
  4. 重新进入 OD 是 single-step。 用户随时可以通过 inline rail(§8)或 od plugin apply ... --project <id> 在同一个 project 上重新 apply 任意 plugin(或不同 plugin)。parentArtifactId 链(§11.5.1)跨越 OD ↔ code-agent 边界保留 lineage。

这就是用户层面问题"v1 阶段我能不能用这套 plugin 体系交付业务代码?"的回答:可以,但交付方式是 OD substrate + 外部 code-agent 接力,不是 OD native 一键。Phase 8(§21.4)才是 native 一键交付的路径。

21.6 阅读契约

本节是关于"四个场景下哪些已交付、哪些是预留"的唯一信息源。未来 spec patch 在新增 atoms、evaluators、生产 handoff primitive 时,必须在同一次 patch 中更新 §21.1 的对应行与 §21.3 的对应小节。Reviewer 应当拒绝任何"新加 atom 但没更新 §21"的 phase 变更 —— 这是防止 spec 退化回 "看起来都覆盖了,实际上什么都没交付" 的关键护栏。§22(第三方可作者性)与 §23(自举)在各自范围内承担同样的更新义务;三节合在一起构成 spec 的 openness contract。

22. 作者扩展点:基于 v1 substrate 实现未交付场景

§21 记录的是 v1 native 交付到哪。本节记录的是开放性的另一半:即便 v1 只为场景 3(new-generation)交付了完整的 native pipeline,第三方 plugin 作者今天就可以在 v1 substrate 上为场景 1、2、4 写出可运行的 plugin,而不必等 §21.4 的 Phase 6 / 7 / 8 落地。两半合起来 ——OD 一方交付(§21)+ substrate 给第三方的可达性(§22)—— 才能把"插件系统真的开放吗?"这个问题答成具体的"是"。

22.1 substrate 与 implementation

本节正式区分这对概念:

  • substrate(底层基础)= v1 spec 给 plugin 作者的 primitive:manifest 字段、capability 词汇表、atom catalog、pipeline / devloop / GenUI / connector / MCP / od files / parentArtifactId / AppliedPluginSnapshot(§5–§11.5.1)。
  • implementation(实现)= 哪些 atom 已经作为 daemon 内置项可以一行写进 od.pipeline(§10)。

只有 substrate 与 implementation 都到位,场景才算 "v1 native"。当 substrate 到位、缺的 implementation 可以由 plugin 作者用 substrate 提供的逃生通道补上时,场景就属于 "v1 community-buildable"。场景 1、2、4 今天都是 community-buildable —— 缺口在 atom ergonomics,不在 capability gate。

22.2 扩展点 cookbook

plugin 作者实际会用到的填补一方缺失的方式:

plugin 作者的需求 v1 提供的对应 primitive spec 出处
调一个 OD 没有的工具(Figma REST、AST 解析、SVG 转换…) od.context.mcp[] 里捆绑一个 MCP server §5 / §5.3(mcp + subprocess + network
调用第三方 API(Slack / Notion / GitHub / Figma / Drive) od.connectors.required[],借现有 Composio 子系统 §5 / §9 / §10.3.1 oauth.route='connector'
在用户真实仓库上工作 od project import <path> 把 repo 装进 OD 的 project model;之后 od files 与 agent 文件操作就地可用 §12 / §11.7 / §14.3
跑任意 build / test / lint / 自定义脚本 bash / subprocess capability §5.3
拉起一段第三方 OAuth GenUI oauth-prompt surface,route=connectormcp §10.3.1
自定义 HITL 表单 / 选项 / 确认 GenUI form / choice / confirmation surface 声明 §10.3
让答案跨对话 / 跨 run 不再追问 genui_surfaces 表 + persist: 'project' | 'conversation' | 'run' §10.3.3
多阶段 + 迭代收敛 od.pipeline.stages[] + repeat: true + until §10.1 / §10.2
承载 artifact 血缘 ArtifactManifest.parentArtifactId + sourcePluginSnapshotId §11.5.1
半年后仍可重放 AppliedPluginSnapshot 不可变 + od plugin replay §8.2.1 / §12
教 agent 一整套领域工作流 SKILL.md body 注入 prompt + od.context.assets[] 携带例子 §11.3 composeSystemPrompt()

总结成一条规则:OD 一方 atom 缺位 → plugin 作者用 MCP server + bash + SKILL.md 三件套替代。代价是 ergonomics(每个 plugin 各自重新发明命名和 prompt fragment),不是能力。

22.3 真实例子

下面给出三个 "v1 not native" 场景的 plugin 作者落地草图。

22.3.1 不依赖一方 figma-extract / token-map 的 Figma 迁移 plugin

manifest 草图:

{
  "od": {
    "taskKind": "figma-migration",
    "context": {
      "designSystem": { "ref": "linear-clone" },
      "mcp": [
        { "name": "figma-rest", "command": "npx",
          "args": ["-y", "@community/figma-mcp"] }
      ]
    },
    "connectors": { "required": [{ "id": "figma", "tools": ["files.get"] }] },
    "genui": {
      "surfaces": [
        { "id": "figma-oauth", "kind": "oauth-prompt", "persist": "project",
          "oauth": { "route": "connector", "connectorId": "figma" } },
        { "id": "file-pick",   "kind": "form",         "persist": "conversation",
          "schema": { "type": "object",
            "properties": { "fileUrl": { "type": "string" } } } }
      ]
    },
    "pipeline": { "stages": [
      { "id": "discovery", "atoms": ["discovery-question-form"] },
      { "id": "extract",   "atoms": ["todo-write", "file-write"] },
      { "id": "generate",  "atoms": ["file-write", "live-artifact"] },
      { "id": "critique",  "atoms": ["critique-theater"],
        "repeat": true, "until": "critique.score>=4 || iterations>=3" }
    ]},
    "capabilities": ["prompt:inject", "fs:read", "fs:write", "mcp",
                     "subprocess", "network", "connector:figma"]
  }
}

配套 SKILL.md 教 agent:"收到 figma URL → 调 figma-rest.get_file → 遍历 node tree → 读 active design system 的 DESIGN.md → 在 cwd 里写 HTML,保留 figma 布局但用 DESIGN.md 的 token → live-artifact 预览 → critique 循环"。不需要等 OD 实现 figma-extract / token-map;它们只是把 SKILL.md 的指令压成可复用的 prompt fragment。

22.3.2 不依赖一方 code-import / rewrite-plan / build-test 的代码库刷新 plugin

前置:用户先 od project import /path/to/old-repo,project cwd 就是真实仓库。

manifest 声明 bash + subprocess + fs:write;SKILL.md 引导 agent:

  1. 通过 agent-native file-read 扫文件,抽出当前的 tokens、组件惯例、routing model。
  2. GenUI form surface 让用户确认 target stack(Next/Vite/Remix、shadcn/MUI、包管理、test 命令)。
  3. todo-write 写多文件 refactor 计划。
  4. repeat: true 的 devloop stage:每轮 file-edit 一小批 → bash pnpm typecheck && pnpm test → 把 build/test 成败编码进一个 critique-theater 事件,分数反映通过情况(5 = 都通过,1 = build 挂)。devloop 在 critique.score>=4 || iterations>=8 终止。
  5. 最后再做一次 critique-theater,覆盖视觉 + token 一致性。

相对 native(§21.4 Phase 7)的两个 ergonomic gap:

  • build/test 结论被编码进 critique.score 而非自己的 until 信号 —— 能跑通,但读 run log 的人必须知道这个约定。
  • 没有 native diff-review UI;plugin 用一个 confirmation surface 配合 prompt 里的原始 diff 文本。

两个 gap 都被 Phase 7 修复 —— 把 build-testdiff-review 升格为一方 atom,附带它们各自的 pipeline 与 surface 语义;同时 §22.4 limit-1 的 patch 让 until 接受 atom 自带的命名信号。

22.3.3 通过 OD ↔ code-agent 接力实现 设计→生产代码

plugin 作者在 v1 不应该试图把整段 handoff 都在 OD 内部完成。正确的形态是:

  • plugin 在 project cwd 里产出 artifactKind: 'code-diff' + handoffKind: 'patch' 的 artifact。
  • 最后一个 stage 触发一个 confirmation GenUI surface:"准备 apply 吗?把 project cwd 在 Cursor / Claude Code / Codex 中打开,按附带的指令执行。"
  • od files 留下 audit;parentArtifactId 把 patch artifact 接到 design artifact 上。

这就是 §21.5 的契约;plugin 作者写 SKILL.md 引导 agent 产出 handoff 形态的 artifact,到 OD 内部跑 patch 这一步就此打住。

22.4 substrate 级硬限制(plugin 作者绕不开)

下面三条不是 ergonomics gap,而是 v1 substrate 的封闭词汇决策,需要未来 spec patch 才能放开:

  1. until 信号词汇表是封闭的(§10.1)。只接受 critique.scoreiterationsuser.confirmedpreview.ok。plugin 想用 build.passingtests.passingvisual_diff.delta < 0.05 这种自定义信号,必须塞进 critique.score(语义模糊)或塞进 confirmation surface(失去自动化)。Phase 7(§21.4)与 §23.3.1 的 patch 1 联合放开这条 —— 让 atom 可以声明自己的命名信号;在那之前,场景 2 的"build/test 通过即收敛"无法 native 表达。
  2. atom registry 是封闭的od.pipeline.stages[*].atoms[] 必须命中 §10 catalog(implemented + reserved)。plugin 作者无法注册新 atom 名字。变通:把新能力封成 MCP tool,挂到最近的通用 atom(file-write / todo-write / critique-theater)下;daemon 发出的 pipeline_stage_started / pipeline_stage_completed 事件流就丢失这一步的粒度。
  3. 核心 GenUI surface kind 在 v1 是封闭枚举(§10.3.1)。产品自有 renderer 只有 form / choice / confirmation / oauth-prompt 四类内置 surface。plugin 作者想要分屏 diff review、画板涂改、3D 预览或其他高保真 UI,只能在可用时走独立的 gated od.genui.surfaces[].component sandbox path;不能新增内置 surface kind,也不能覆盖主 renderer 的 styling contract。

第 1 条对场景 2 影响最大,第 3 条对场景 4 影响最大,第 2 条所有人都受一点影响、但通过 MCP 最容易绕过去。

22.5 promotion 路径:out-of-tree → 一方

这是把 §21.3.1 的 Figma 例子推广为整张路线图通则的写法:

  1. 先发 out-of-tree plugin。社区作者用 §22.2 的逃生通道发布社区 plugin,迭代节奏不绑 daemon 发版。
  2. 稳定契约。SKILL.md 指令、MCP tool surface、pipeline shape 稳定后,提取成候选一方 atom 契约:atom id、prompt fragment、tool gating、可选的 until 信号、可选的 GenUI surface 默认值。
  3. 晋升一方 atom。把该 atom 加入 §10 catalog(从 (planned) 转 implemented),并在 §23 的 bundled scenario plugin 里使用它。晋升对原 plugin 透明:原 plugin 可以继续走 SKILL.md 路径,也可以改用新的 atom 名。
  4. 更新 §21 与 §23。按 §21.6,coverage matrix 必须在晋升 atom 的同一次 patch 中更新;按 §23.6,任何 kernel 边界的变化(例如扩 until 信号词汇表)也必须同步反映到 §23.3。

这就是 spec 保持诚实的方式:缺一方 atom 永远不是永久 gap,只是社区工作还没被蒸馏的"晋升前"状态。

23. 自举:把 OD 自己的硬流程做成一方 plugin

§22 确立了第三方 plugin 在 v1 substrate 上的可扩展性。本节确立对称性质:OD 自己写死的流程也可以被重新表达为运行在同一份 substrate 上的一方 plugin,没有任何只有 OD 能用、第三方触不到的特权路径。plugin substrate 是真正的"地板",OD-the-product 只是它上面的一种配置。

§10.2 最初为 atom 暗示了这条路径;当前实现已经落下 entry slice。Bundled atom plugins 位于 plugins/_official/atoms/**,bundled scenario plugins 位于 plugins/_official/scenarios/**,active stage blocks 已经可以从 atom bodies 渲染。本节继续明确 kernel/userspace 边界,并记录在 prompt 与入口选择完全自举前还剩什么。

23.1 kernel/userspace 边界

OD 今天做的事可以归到三类:

类别 自举之后住在哪里 今天 daemon 里的例子
A. 用户态(可移到 plugin) 一方 plugin 的 manifest + SKILL.md atom prompt fragment、默认 reference pipeline、atom 自带的 MCP server、critique 评分坐标、discovery 提问措辞
B. 内核(必须留在 daemon) apps/daemon/src/... snapshot SQLite 写表、GenUI 持久化与复用、capability gate + tool-token 颁发、devloop scheduler + until 求值 + ceiling、OAuth token 存储、composeSystemPrompt() 作为 assembler、project metadata block
C. v1 已经是 plugin 驱动 已经在 <daemonDataDir>/plugins/... 或 per-project plugin 文件夹 当前 SKILL.md、DESIGN.md、craft .md、plugin 声明的 MCP server、plugin 声明的 GenUI surface、plugin 声明的 pipeline

B 类的清单是内核边界:不是"难以 plugin 化",而是"出于安全、持久化、运行时状态原因必须留在 daemon"。一个让 plugin 直接写 applied_plugin_snapshots 行或颁发 connector tool token 的 plugin runtime 是坏掉的 runtime。

A 类是 spec 承诺自举之后会移动的部分。今天它们大都是 apps/daemon/src/prompts/system.ts 里的字符串常量。

23.2 v1 已经自举的部分(容易的一半 + 第一批 atom slice)

C 类是 v1 已交付的那一半;A 类的第一批也已经移动。截至本 spec:

  • 当前 SKILL.md、DESIGN.md、craft .md 由 apps/daemon/src/skills.ts / design-systems.ts / craft.ts 加载,§11.3 已把它们重构为委托 apps/daemon/src/plugins/registry.ts。Phase 1 之后,prompt 中所有进入的行为内容都是 plugin-loaded 的,即使内容本身是 bundled。
  • plugin 声明的 MCP server、connector 需求、GenUI surface、od.pipeline.stages[] 全部走的是和第三方 plugin 同一套 registry / resolver 路径。
  • 驱动 §1 产品 brief 里"一致性"的 active design system + craft 注入,已经是 plugin substrate 的读:一方 DESIGN.md 没有比第三方 DESIGN.md 多任何特权路径。
  • plugins/_official/atoms/** 下的一方 atom plugins 已经携带 atom SKILL.md body 与 manifest metadata;packages/contracts/src/prompts/atom-block.ts 可以从这些 bodies 渲染 active stage blocks。
  • plugins/_official/scenarios/** 下的 bundled scenario plugins 已经携带默认 pipeline 形态,其中包括用于 Home 自由输入 routing / task shaping 的 od-defaultpackages/plugin-runtime/src/pipeline-fallback.ts 会在 plugin 省略 od.pipeline 时,通过这些 bundled scenarios 解析 applied pipeline。
  • @open-design/agui-adapter/api/runs/:runId/agui 提供外部 AG-UI event projection,同时不改变 OD 内部 GenUI renderer。

这就是 §22 成立的原因:substrate 已经在 plugin artifacts、snapshots、GenUI declarations、pipeline declarations、bundled scenarios 与第一条 atom-body injection path 上自举。剩余硬编码部分更窄,也更偏产品入口:OD 基础 designer/discovery prompt、部分 stage-entry 选择逻辑、Home curated scenario rail,以及 §22.4 中列出的封闭 signal / surface 词汇表。

23.3 v1 还在硬编码的部分(完成自举要做的工作)

下面这些 patch 继续把 A 类从 daemon 里抽出来。部分已经以 entry slice 形式落地,所以应把它们读成完成清单,而不是“现在还不存在”。做完之后,整套 prompt + pipeline 都由 plugin manifest 驱动;composeSystemPrompt() 变成纯 assembler,自身不再携带任何行为内容。

23.3.1 Patch 1 —— od.kind: 'atom' 的正式契约

§5 的 od.kind 枚举里有 'atom',但从未定义 atom plugin 的形态。这条 patch:

  • atom plugin 是带 open-design.jsonod.kind: 'atom')+ SKILL.md 的目录。
  • SKILL.md 正文就是该 atom 的 prompt fragment;当某个 stage 用 id 引用了该 atom,daemon assembler 把它注入。
  • 可选的 od.context.mcp[] 声明该 atom 用到的 MCP tool(如 live-artifactconnector)。
  • 可选的 od.atom.untilSignals[] 声明该 atom 发出的命名信号变量,贡献到 §10.1 的 until 词汇表。这正是 patch 1 同时放开 §22.4 limit 1 的方式:每个 atom 自带信号(例如 build-test 声明 build.passingtests.passing),until 求值器对照当前 stage 的 atoms 而非硬编码列表来查表。
  • 可选的 od.atom.toolGating[] 声明该 atom 期望可用的 agent 端工具(file-read/write/edit、bash)。

23.3.2 Patch 2 —— 完成把 atom prompt fragment 从 system.ts 迁出

Atom SKILL.md fragments 与 active-stage block renderer 现在已经存在,但迁移尚未完成。apps/daemon/src/prompts/system.ts 中剩余的字符串常量(DISCOVERY_AND_PHILOSOPHY、base official designer prompt、critique addenda、TodoWrite policy、file-ops policy 等)要么迁到 plugins/_official/atoms/<atom>/SKILL.md,要么被显式重新归类为 kernel-level assembly policy。composeSystemPrompt() 解析当前 stage 的 atoms[],逐个 atom plugin 读 SKILL.md fragment,按 stage 顺序拼接,并加稳定 header(## Active stage: <stage-id> 后跟 atom fragment)。

迁移是机械工作:system.ts 里每条 prose 常量都有一对一的目的地 plugins/_official/atoms/<atom>/SKILL.md。Reviewer 应当拒绝 patch 落地后仍把行为类 prose 常量留在 system.ts 里的迁移。

23.3.3 Patch 3 —— 完成把默认 reference pipeline 迁到 bundled scenario plugin

Bundled scenario plugins 与 pipeline fallback resolver 现在已经存在。剩余工作是移除任何仍然手写 default stage list 的产品入口,让它们改为选择 scenario plugin。目标仍然是:当 od.pipeline 缺省时,daemon / product code 根据 taskKind 或显式入口 route 解析一个 bundled scenario plugin,然后使用该 scenario 的 od.pipeline

  • plugins/_official/scenarios/od-new-generation/open-design.json
  • plugins/_official/scenarios/od-figma-migration/open-design.json(Phase 6 后)
  • plugins/_official/scenarios/od-code-migration/open-design.json(Phase 7 后)
  • plugins/_official/scenarios/od-tune-collab/open-design.json

每个 plugin 只 ship 一个 od.pipeline 加(可选)一些 scenario 默认的 od.genui.surfaces[]。daemon 解析变成:"没传 od.pipeline + 有 taskKind → 查匹配 taskKind 的 bundled scenario plugin → 用它的 od.pipeline"。

这条 patch 之后,替换或 fork od-new-generation 是 ship 一个不同 OD 风味产品的标准做法。

23.3.4 Patch 4 —— 新的 bundled trust 等级

§9 今天把 "随 daemon 一起 bundled" 与 "来自官方 marketplace" 都叫 trusted。这条 patch 把它们分开:

trust 等级 来源 安装时是否 capability prompt 是否可被 marketplace upgrade 替换 SQLite source_kind
bundled <repo-root>/plugins/_official/** 否 —— daemon 内部 allowlist 直接授权 否 —— 仅随 daemon 升级一同替换 bundled
trusted 一方 / 显式 trust 的 marketplace 否 —— 安装即自动授权 是 —— od plugin update 可以拉新版本 marketplace
restricted 其他来源(GitHub URL、任意 marketplace、本地文件夹) 是 —— 需 capability checklist github / url / local / marketplace

bundled 等级让 patch 2 与 patch 3 安全:daemon 不会就自己一直拥有的 capability 来给自己提示,恶意 marketplace plugin 也无法靠重用 bundled atom 的 id 来冒充。

23.3.5 Patch 5 —— plugins/_official/ 目录约定 + 首次启动自动安装

daemon 启动多一步:

  1. <repo-root>/plugins/_official/**,把每个 plugin 注册到 installed_plugins 中:source_kind='bundled'trust='bundled'、capabilities = 该 plugin 声明的 od.capabilities
  2. bundled plugin 复制到 <daemonDataDir>/plugins/;它们直接从 repo path 加载并热重载,daemon 升级时与 daemon 代码同步替换。
  3. od plugin uninstall 拒绝 uninstall bundled plugin(会让 daemon 失能);od plugin update 对 bundled 是 no-op。
  4. 用户可以安装一个 id 与 bundled 相同的 trustedrestricted plugin;正常 apply 时用户 copy 胜出,但 daemon 保留 bundled copy 作为 fallback,给那些 pin 了 bundled 版本的旧 AppliedPluginSnapshot replay 用。

23.4 自举之后的 kernel:纯 assembler

5 条 patch 全部落地之后,apps/daemon/src/prompts/system.tscomposeSystemPrompt() 变成纯 assembler,形态如下(只示意,不是规范):

function composeSystemPrompt(input: PromptInput): string {
  const stack: string[] = [];
  stack.push(renderProjectMetadata(input.project));         // kernel 独有
  stack.push(renderResolvedDesignSystem(input.context));    // 已经 plugin 驱动
  stack.push(renderResolvedCraft(input.context));           // 已经 plugin 驱动
  for (const stage of input.pipeline.stages) {
    stack.push(`## Active stage: ${stage.id}`);
    for (const atomId of stage.atoms) {
      const atom = registry.getAtomPlugin(atomId);          // bundled plugin
      stack.push(atom.skillFragment);                       // SKILL.md body
    }
  }
  stack.push(renderResolvedSkillBody(input.context));       // 已经 plugin 驱动
  stack.push(renderPluginInputs(input.appliedPlugin));      // 已经 plugin 驱动
  return stack.join('\n\n');
}

这个函数里没有任何行为内容。所有行为内容都住在 plugins/_official/...(一方)或 user-installed plugin 文件夹(第三方)。B 类 kernel 责任(snapshot、GenUI 持久化、capability gate、devloop、OAuth)不受影响 —— 它们是 runtime 关切,不是 prompt 组装关切。

23.5 这件事在产品上的意义

3 条具体收益足以证明这套 patch 序列值得做:

  1. 审计 surface 收敛。所有进入 agent 系统 prompt 的字节都从 plugin manifest + SKILL.md 可达。读 plugins/_official/... 就能完整回答"OD 今天在指挥 agent 干什么",不需要 grep 代码。
  2. 风味可替换。企业部署、行业版本(地产 / 游戏 / 法律 / 财报)、伙伴集成可以替换自己的 plugins/_official/scenarios/od-<taskKind> 而不需要 fork daemon。产品风味(discovery 提问风格、critique 维度、默认 pipeline 形态)从代码关切变成内容关切。
  3. 一方工作走的是和第三方一样的路径。OD 落 Phase 6 / 7 / 8(§21.4)的时候,那些场景就是住进 plugins/_official/scenarios/ 的一方 plugin,跑的是任何社区 plugin 都在跑的 loader / resolver / snapshot / GenUI runtime。如果一方撞 ergonomics 墙,第三方撞得只会更狠 —— 这是最强的 dogfooding 压力来源。

23.6 与 §21、§22 的关系

三节合在一起回答"插件系统是什么、谁能用、边界在哪":

章节 回答的问题 受众
§21 OD 在 v1 里 native 交付了什么?哪些场景是 covered / partial / post-v1? 计划 §16 分阶段交付的 maintainer
§22 不等 OD 多 ship native atom,第三方 plugin 作者在 v1 substrate 上能走多远? 评估这套平台的 plugin 作者与集成方
§23 OD 自己的硬流程能不能在 plugin substrate 内重新表达?kernel 边界在哪? 确保没有特权后门的架构师与 reviewer

未来 spec patch 在新增 atoms、evaluators、scenarios 或 kernel 责任时,必须在同一次 patch 中更新对应章节。§21 记录已交付的内容,§22 记录从外部可达的内容,§23 记录从内部可达的内容;三节合起来构成 spec 的 openness contract。


规范结束。请 inline review 与 refine。进入任何非 markdown 改动前,请先确认 plan 和 Phase 0。